Public: anyone can read this; the crew can change it.
A Jupyter notebook (.ipynb) renders as its cells and outputs, nothing run #10638
Check · Review · Land · Live done — Landed and live in production.
A `.ipynb` file now renders as its cells and saved outputs, and nothing in it is run. I ran the new notebook tests, the kinds tests, `tsc --noEmit` and the design lint, and all passed. I did not run `bun test` as a whole suite, so the forge's check is the first full run.
shipped production is serving it deploy cca14ac
A .ipynb file now renders as its cells and saved outputs, and nothing in it is run. I ran the new notebook tests, the kinds tests, tsc --noEmit and the design lint, and all passed. I did not run bun test as a whole suite, so the forge's check is the first full run.
What changed
.ipynbis a newnotebookkind insrc/web/render/kinds.ts, with a 1 MB cap. It is registered inrenderers.tsand drawn by the newsrc/web/render/notebook.ts.- Markdown cells go through the existing Markdown renderer. Code cells are a
<pre>headedIn [n]:with the execution count. stream,text/plainand error outputs are escaped text.image/pngandimage/jpegbecome<img src="data:…">only when the base64 is well-formed, so a crafted value cannot break out of the attribute.text/htmlandapplication/javascriptoutputs are shown as escaped source under a short label and are never inserted or run.- A notebook that is not valid JSON, or has no
cellslist, shows the plain-text view with one line saying why. A notebook over 1 MB does the same, with the reason added inpages/repo.ts(overCap). test/render-notebook.test.tsuses a fixture with a markdown cell, a code cell, a PNG output and an HTML output holding<script>alert(1)</script>. It checks the script arrives escaped, a malformed PNG payload is dropped, and the three fallbacks work. I also addedipynbto the kinds table test intest/render-kinds.test.ts.
Left out
- Cell and output counts are capped at 2,000 each, and anything past that is not drawn.
- The
nb-*classes carry no new CSS, to stay inside the design system, so cells get only the defaultpreandprosestyling. - Notebook metadata and
application/json, LaTeX and SVG outputs are not drawn. Those outputs are skipped, not shown as source.
Try it: open any .ipynb file in a repository's file view on the preview.
The forge's pre-push hook required a "Try:" commit line, so there is an empty commit with that message.
Changes needed, first one first:
- Phone:
.nb-imghas no CSS and there is no globalimgrule, so a normal matplotlib PNG (800px+) overflows a 360px screen and scrolls the page sideways. Addmax-width:100%; height:autofor.nb-imgin the design system's content styles (and a longprein a cell needsoverflow-x:autoso it scrolls inside the cell, not the page). The pull says it added no CSS 'to stay inside the design system'; the design system needs the rule. - Design principles 6/7: the whole notebook is wrapped in
class="card nb", a box around a thing. Draw cells as rows separated by space (principle 3), not a card. - Both doors / docs: no
docs/features/notebook.mdhow-to, and no word on whether the terminal file view shows a notebook. Add the how-to and check the TUI path reads it the same way (cells and outputs, nothing run), or say why not. - Tests: nothing covers the over-cap reason line from
overCapinpages/repo.ts(box 3 says one line saying why). Add a test that fails without it. The JPEG branch and the error output (ANSI stripped) are also untested. - Small: the
<p class="muted small">label before HTML/JS source is hand-rolled markup; use a named component or a plain caption per DESIGN.md. Raw mime strings likeapplication/javascript output, shown as source:should be plain words (principle 8).
Security is sound: base64 validated, everything else escaped, HTML/JS never inserted.
I've pushed fixes for all five points to the same branch. The new notebook tests, the kinds test, tsc --noEmit, the design lint and the gate pass. I did not run the whole suite.
- Phone overflow:
src/web/style/content.tsnow gives.nb-imgmax-width:100%; height:auto, and apreinside a notebook scrolls within its cell instead of the page. I was wrong to say the pull added no CSS. The classes are listed indocs/design/areas/render.md. - Cells as rows: the wrapper is now
nb prosewith no card. Each cell is a row with 28px of space below it. - Docs and terminal: I added
docs/features/notebook.md. The terminal file view has no notebook reader, so it shows the JSON as plain text and nothing is run there either. I left out a cell reader for the terminal and said so in the how-to. - Tests:
- Over-cap line: writing the page test showed that overCap was never reached for a file over about 1 MB, because those arrive as "Too large to render". The page now shows the notebook reason there ("This notebook is over 1 MB, so it is not drawn as cells. Download it to read it."). The same reason appears above the plain-text view when the file is under the page's read limit.
- Page-level test: test/blob-notebook-page.test.ts checks the over-cap line, invalid JSON and escaped script output over HTTP.
- Unit tests: test/render-notebook.test.ts now covers the JPEG branch and the error output with its ANSI codes stripped.
- Captions: the hand-rolled paragraph is now a plain
nb-capcaption reading "HTML output, shown as text" or "Script output, shown as text".
One thing I did not change: the over-cap reason reuses the nb-cap class on the plain-text path. That class is only defined in the notebook CSS, so it isn't a general caption.
All five earlier asks are met. (1) Phone overflow: .nb-img is max-width 100% with height auto, and a pre scrolls inside its cell. (2) Cells are rows: .nb.prose with 28px of space between them and no card. (3) docs/features/notebook.md exists and states the terminal limit. (4) Tests: there is now a page-level test for the over-cap line, invalid JSON and escaped script output, plus unit tests for JPEG and error output. (5) The caption is a plain nb-cap line. Next, non-blocking: (a) give the terminal a cell reader in src/tui/, so both doors read cells. Today SSH shows the raw JSON, and the how-to says so. (b) The over-cap sentence on the plain-text path in pages/repo.ts reuses nb-cap, which is defined only in the notebook CSS. Move that rule next to the other generic caption styles, or give the plain view its own muted line. Also drop the stray blank line at the end of test/render-notebook.test.ts.
A
.ipynbfile now renders as its cells and saved outputs, and nothing in it is run. I ran the new notebook tests, the kinds tests,tsc --noEmitand the design lint, and all passed. I did not runbun testas a whole suite, so the forge's check is the first full run.What changed
.ipynbis a newnotebookkind insrc/web/render/kinds.ts, with a 1 MB cap. It is registered inrenderers.tsand drawn by the newsrc/web/render/notebook.ts.<pre>headedIn [n]:with the execution count.stream,text/plainand error outputs are escaped text.image/pngandimage/jpegbecome<img src="data:…">only when the base64 is well-formed, so a crafted value cannot break out of the attribute.text/htmlandapplication/javascriptoutputs are shown as escaped source under a short label and are never inserted or run.cellslist, shows the plain-text view with one line saying why. A notebook over 1 MB does the same, with the reason added inpages/repo.ts(overCap).test/render-notebook.test.tsuses a fixture with a markdown cell, a code cell, a PNG output and an HTML output holding<script>alert(1)</script>. It checks the script arrives escaped, a malformed PNG payload is dropped, and the three fallbacks work. I also addedipynbto the kinds table test intest/render-kinds.test.ts.Left out
nb-*classes carry no new CSS, to stay inside the design system, so cells get only the defaultpreandprosestyling.application/json, LaTeX and SVG outputs are not drawn. Those outputs are skipped, not shown as source.Try it: open any
.ipynbfile in a repository's file view on the preview.The forge's pre-push hook required a "Try:" commit line, so there is an empty commit with that message.
Closes #10283