Skip to content

Commit bcb583a

Browse files
authored
Merge pull request #2 from opensource-observer/feat/static-notebook-export
Turn DDP into a static web app
2 parents 79ea111 + f77c6c3 commit bcb583a

34 files changed

Lines changed: 4709 additions & 2064 deletions

.github/workflows/deploy.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Build and deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
workflow_call:
8+
9+
jobs:
10+
build-deploy:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
fetch-depth: 2
16+
17+
- uses: astral-sh/setup-uv@v4
18+
- run: uv python install 3.13
19+
20+
- name: Install Python deps
21+
run: uv sync
22+
23+
- name: Check if notebooks changed
24+
id: changed
25+
run: |
26+
git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^notebooks/' \
27+
&& echo "notebooks=true" >> $GITHUB_OUTPUT \
28+
|| echo "notebooks=false" >> $GITHUB_OUTPUT
29+
30+
- name: Export notebooks
31+
if: steps.changed.outputs.notebooks == 'true' || github.event_name != 'push'
32+
env:
33+
OSO_API_KEY: ${{ secrets.OSO_API_KEY }}
34+
run: uv run python scripts/export_notebooks.py
35+
36+
- uses: pnpm/action-setup@v4
37+
with:
38+
version: 10
39+
40+
- uses: actions/setup-node@v4
41+
with:
42+
node-version: '20'
43+
cache: 'pnpm'
44+
cache-dependency-path: app/pnpm-lock.yaml
45+
46+
- name: Install Node deps
47+
run: cd app && pnpm install
48+
49+
- name: Build Next.js
50+
run: cd app && pnpm build

.github/workflows/refresh-data.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Weekly data refresh
2+
3+
on:
4+
schedule:
5+
- cron: '0 3 * * 0' # Sunday 3am UTC
6+
workflow_dispatch:
7+
8+
jobs:
9+
refresh:
10+
uses: ./.github/workflows/deploy.yml
11+
secrets: inherit

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ __pycache__/
1414
# --- Environment variables ---
1515
.env
1616
.env.local
17+
app/.env.*
1718

1819
# --- Next.js (app/) ---
1920
app/.next/
2021
app/out/
2122
app/node_modules/
23+
app/public/notebooks/
24+
app/public/data/
2225

2326
# --- OS ---
2427
.DS_Store

README.md

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ cp .env.example .env
1313
uv sync
1414
cd app && pnpm install && cd ..
1515

16-
# 3. Start
17-
cd app && pnpm dev:all
16+
# 3. Export notebooks to static HTML
17+
cd app && pnpm export:notebooks && cd ..
18+
19+
# 4. Start the dev server
20+
cd app && pnpm dev
1821
```
1922

2023
Open http://localhost:3000.
@@ -30,7 +33,6 @@ Open http://localhost:3000.
3033
```
3134
ddp/
3235
├── notebooks/ # Marimo notebooks (.py)
33-
│ ├── home.py
3436
│ ├── quick-start.py
3537
│ ├── publications.py
3638
│ ├── agent-guide.py
@@ -39,17 +41,20 @@ ddp/
3941
│ │ ├── models/ # Ecosystems, Repositories, Developers, Commits, Events, Timeseries Metrics
4042
│ │ └── metric-definitions/ # Activity, Alignment, Lifecycle, Retention
4143
│ └── insights/ # 2025 Developer Trends, Lifecycle, Speedrun Ethereum, DeFi Journeys, Retention
44+
├── scripts/
45+
│ └── export_notebooks.py # Exports notebooks to static HTML
4246
├── app/ # Next.js app (UI shell)
4347
│ ├── app/ # Pages (App Router)
44-
── components/ # Sidebar, MarimoIframe
45-
├── serve_notebooks.py # Marimo ASGI server (port 8000)
48+
── components/ # Sidebar, MarimoIframe
49+
│ └── public/notebooks/ # Exported HTML (generated, gitignored)
4650
└── pyproject.toml
4751
```
4852

4953
## How it works
5054

51-
- **Marimo server** (`localhost:8000`) — runs notebook kernels via `serve_notebooks.py`
52-
- **Next.js app** (`localhost:3000`) — navigation shell that embeds notebooks in iframes
55+
- **Export step**`scripts/export_notebooks.py` runs `marimo export html` on each notebook, writing static HTML to `app/public/notebooks/`
56+
- **Next.js app** (`localhost:3000`) — navigation shell that serves the exported HTML via `MarimoIframe`
57+
- **CI** — the [deploy workflow](.github/workflows/deploy.yml) exports notebooks and builds the site on push to `main`. A [weekly refresh](.github/workflows/refresh-data.yml) re-exports to pick up fresh data.
5358

5459
Each page in the Next.js app is a thin wrapper around a `MarimoIframe` component:
5560

@@ -62,6 +67,7 @@ Each page in the Next.js app is a thin wrapper around a `MarimoIframe` component
6267
1. Create `notebooks/<category>/<name>.py` using the standard template below
6368
2. Add a page at `app/app/<category>/<name>/page.tsx`
6469
3. Add a nav entry to `app/components/Sidebar.tsx`
70+
4. Run `cd app && pnpm export:notebooks` to generate the HTML
6571

6672
### Notebook template
6773

@@ -88,22 +94,8 @@ if __name__ == "__main__":
8894

8995
For detailed notebook conventions, see [`notebooks/claude.md`](notebooks/claude.md).
9096

91-
## Running servers separately
92-
93-
```bash
94-
# Terminal 1 — Marimo
95-
uv run python serve_notebooks.py
96-
97-
# Terminal 2 — Next.js
98-
cd app && pnpm dev
99-
```
100-
101-
Notebooks are served at `http://localhost:8000/notebooks/<name>` — Marimo must be running for iframes to render.
102-
10397
## Configuration
10498

10599
| Variable | Description |
106100
|----------|-------------|
107101
| `OSO_API_KEY` | Required. OSO data warehouse access key. |
108-
109-
The Marimo port defaults to `8000`. To change it, update `serve_notebooks.py` and the `marimoPort` prop on `MarimoIframe` in any relevant pages.
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1+
'use client';
12
import MarimoIframe from '@/components/MarimoIframe';
2-
33
export default function DefiDeveloperJourneys() {
4-
return (
5-
<div className="h-full w-full">
6-
<MarimoIframe notebookName="notebooks/insights/defi-developer-journeys" />
7-
</div>
8-
);
4+
return <MarimoIframe notebookName="notebooks/insights/defi-developer-journeys" />;
95
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1+
'use client';
12
import MarimoIframe from '@/components/MarimoIframe';
2-
33
export default function DeveloperLifecycle() {
4-
return (
5-
<div className="h-full w-full">
6-
<MarimoIframe notebookName="notebooks/insights/developer-lifecycle" />
7-
</div>
8-
);
4+
return <MarimoIframe notebookName="notebooks/insights/developer-lifecycle" />;
95
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1+
'use client';
12
import MarimoIframe from '@/components/MarimoIframe';
2-
33
export default function DeveloperReport2025() {
4-
return (
5-
<div className="h-full w-full">
6-
<MarimoIframe notebookName="notebooks/insights/developer-report-2025" />
7-
</div>
8-
);
4+
return <MarimoIframe notebookName="notebooks/insights/developer-report-2025" />;
95
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1+
'use client';
12
import MarimoIframe from '@/components/MarimoIframe';
2-
33
export default function DeveloperRetention() {
4-
return (
5-
<div className="h-full w-full">
6-
<MarimoIframe notebookName="notebooks/insights/developer-retention" />
7-
</div>
8-
);
4+
return <MarimoIframe notebookName="notebooks/insights/developer-retention" />;
95
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1+
'use client';
12
import MarimoIframe from '@/components/MarimoIframe';
2-
33
export default function SpeedrunEthereum() {
4-
return (
5-
<div className="h-full w-full">
6-
<MarimoIframe notebookName="notebooks/insights/speedrun-ethereum" />
7-
</div>
8-
);
4+
return <MarimoIframe notebookName="notebooks/insights/speedrun-ethereum" />;
95
}

app/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default function RootLayout({
1717
<body className="bg-white">
1818
<div className="flex h-screen overflow-hidden">
1919
<Sidebar />
20-
<main className="flex-1 flex flex-col overflow-hidden bg-white">
20+
<main className="flex-1 flex flex-col overflow-y-auto bg-white">
2121
{children}
2222
</main>
2323
</div>

0 commit comments

Comments
 (0)