feat: support serving a gallery of notebooks#8056
feat: support serving a gallery of notebooks#8056mscolnick merged 12 commits intomarimo-team:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
for more information, see https://pre-commit.ci
|
cc @dmadisetti, its still in draft but can you check out the Sandbox semantics here |
dmadisetti
left a comment
There was a problem hiding this comment.
I think this is a great start. Followups that might be useful:
- return to home from an app page
- thumbnails
As is, gallery sandbox falls back to ipc sandbox, which I think is fine. It provides consistency in experience.
We do need to fail fast if pyzmq is not installed like in sandboxed home.
But as is, even with pyzmq- this doesn't seem to work for --sandbox (at least locally, I'm getting blank screens for things that should otherwise work) and also --check.
I'd be OK raising an error for these flags, and following up in another PR. I think marimo run --sandbox falls under some of my wheelhouse so I can grab it if you'd like
| # correctness check - don't start the server if we can't import the module | ||
| for path in validated_paths: | ||
| if Path(path).is_file(): | ||
| check_app_correctness(path) |
There was a problem hiding this comment.
We maybe want to do this on launch and not stop the server itself from spawning?
There was a problem hiding this comment.
I think about this is more of a UX tradeoff than a purely technical one. Based on #3257 my understanding is that in gallery mode we're aiming at potentially non‑technical end users, so I lean toward fail‑fast at launch: if an app check fails, I'd rather surface that to the developer than risk an end user launching a broken app. Happy to discuss alternatives ofc. Maybe we add a CLI flag to toggle between lazy and eager checks?
| ] | ||
| file_count = len(marimo_files) | ||
| has_more = file_count >= MAX_FILES | ||
| return WorkspaceFilesResponse( |
There was a problem hiding this comment.
Not sure if we should dynamically load more apps? Torn on this (see comment above)
There was a problem hiding this comment.
I see value in loading more apps dynamically but also see the associated concerns. Maybe let's consider adding a CLI flag to control this?
5fe2b13 to
f8566b8
Compare
|
Thanks for the feedback @dmadisetti
For now I added a clickable marimo logo to go back. Alternatively, we could go with a back arrow + "Gallery" text maybe or "Gallery / " breadcrumbs with Gallery clickable. I am not convinced though that everyone who deploys a multi-app env will want to call it "Gallery" (hence I just went with the logo for now)
That's exactly what we started to discuss with Myles. We'd want to generate OpenGraph content on the fly, (allowing users to overwrite it e.g. as PEP 723 metadata and add
That would be great. This way I can focus in this PR on better aesthetics through thumbnail (+ |
fb5521d to
5f19848
Compare
5f19848 to
f31df50
Compare
|
This CI error does not seem to be related to changes made in this PR: https://github.com/marimo-team/marimo/actions/runs/21514574296/job/61989661450?pr=8056 |
mscolnick
left a comment
There was a problem hiding this comment.
amazing stuff! great work
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.19.8-dev1 |
## 📝 Summary Adds notebook-level OpenGraph metadata (PEP 723 + [Next.js-inspired](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) optional generator hook) with thumbnail generation plumbing. As a first use case, wired it up to improve gallery cards in `marimo run <dir>`. Follow-up of #8056. ## 🔍 Description of Changes - Introduced `OpenGraphMetadata` for notebooks via PEP 723: `[tool.marimo.opengraph]` with `title`, `description`, `image`, and optional `generator` - `image` supports either an HTTPS URL or a notebook-relative path - Added Next.js-style merge semantics for generators: static PEP 723 fields are the base; generator can override only the fields it returns - Added a canonical thumbnail endpoint: `GET /api/home/thumbnail?file=...` - redirects to HTTPS images - serves notebook-relative images only from the notebook’s `__marimo__/` directory - falls back to a branded placeholder SVG when no image exists - Inject OG tags into notebook pages (`og:title`, `og:description`, `og:image`) - Gallery cards now prefer `FileInfo.opengraph` (title/description/image) and fall back to the existing title-casing behavior - Added `marimo tools thumbnails generate ...` to write thumbnails to `__marimo__/assets/<stem>/opengraph.png` - default is `--no-execute` (fast; no cell outputs) - opt-in `--execute` to include outputs - opt-in `--sandbox` (only applies with `--execute`) ## 📸 Thumbnails > in the examples below `marimo-team/learn` is a local clone of https://github.com/marimo-team/learn ### Auto-generated thumbnails without `playwright` No file gets written to disk. The `/thumbnail` API endpoint calls `DEFAULT_OPENGRAPH_PLACEHOLDER_IMAGE_GENERATOR` to construct an SVG dynamically and serves it. <img width="1111" height="1314" alt="Screenshot 2026-02-03 at 14 36 25" src="https://github.com/user-attachments/assets/73cdaa8c-ba4a-4c4e-a27d-cb48163b17b5" /> ### Auto-generated thumbnails with `playwright`, but without notebook execution Renders code blocks without outputs, similar to Observable, as suggested by @manzt. ``` marimo tools thumbnails generate marimo-team/learn marimo run marimo-team/learn ``` <img width="1111" height="1314" alt="Screenshot 2026-02-03 at 14 40 50" src="https://github.com/user-attachments/assets/d02611bd-e094-4031-ac7b-fec82330960a" /> ### Auto-generated thumbnails with `playwright`, with sandboxed notebook execution for cell outputs > If you have generated thumbnails, pass ` --overwrite` to `marimo tools thumbnails` to ensure they get replaced. ``` marimo tools thumbnails generate marimo-team/learn --execute --sandbox marimo run marimo-team/learn ``` <img width="1111" height="1314" alt="Screenshot 2026-02-03 at 14 49 52" src="https://github.com/user-attachments/assets/9d6e0ea4-3845-436f-9ab5-9defd274cdc6" /> ## ⚡️ Generators Users can define custom functions to return OG metadata based on bespoke logic. Below an example of a notebook (1) defining `generator = "generate_opengraph"` in PEP 723 then (2) implementing `def generate_opengraph(context, parent):` as `@app.function` to return the metadata dynamically, yielding a custom card from with image from https://placehold.co/1200x630/png. <img width="374" height="345" alt="Screenshot 2026-02-03 at 14 58 42" src="https://github.com/user-attachments/assets/478af1c1-e6f3-4fc7-a79f-e604f39b591d" /> ```python # /// script # dependencies = [ # "marimo>=0.19.0", # "pyzmq>=27.1.0", # ] # [tool.marimo.opengraph] # description = "The description is static, but the title and image have been computed dynamically using a generator function defined within the notebook." # generator = "generate_opengraph" # /// import marimo __generated_with = "0.19.7" app = marimo.App(width="medium") @app.cell(hide_code=True) def _(mo): mo.md(""" # Dynamic OpenGraph Image """) return @app.function def generate_opengraph(context, parent): import datetime as dt from pathlib import Path from urllib.parse import quote_plus # Merge behavior: we return `title` and `image`, so static PEP 723 description # remains intact, as `description` is already present in `parent` label = quote_plus(dt.datetime.now().isoformat()) return { "title": f"Dynamic OpenGraph", "image": f"https://placehold.co/1200x630/png?text={label}" } @app.cell(hide_code=True) def _(): import marimo as mo return (mo,) if __name__ == "__main__": app.run() ```

📝 Summary
Enables running multiple marimo notebook files as a gallery of apps in the below style:
When pointed to a local clone of https://github.com/marimo-team/learn this gives us:
Closes #3257
🔍 Description of Changes
I based this work on #4961, borrowing the idea of reusing the existing home page infrastructure used in
marimo editmode and addinggalleryas a new frontend view inmarimo runmode./?file=<encoded>to avoid introducing new routing / mounting logic/?file=<encoded>valuesmarimo._server.file_router.ListOfFilesAppFileRouterto behave like an allowlist routermarimo run app.py -- --arg valuebehavior working while enablingmarimo run file_a.py file_b.py folderand still allowing notebook args to be explicitly separatedmarimo._cli.cli.py._resolve_root_dir)uv run marimo run /Users/petergy/Projects/opensource/marimo-team/learn. thelearnrepo is recognized as gallery root, so opening/Users/petergy/Projects/opensource/marimo-team/learn/functional_programming/05_functors.pywill be available underhttp://localhost:2720/?file=functional_programming%2F05_functors.pytitleandsubtitlefor now (notebook dir and file name piped throughtitleCase)TODO before undrafting this
--sandboxsemantics📋 Checklist