Repository navigation
feat(file-viewer): open HTML pages, SVG pictures, notebooks and more document formats - #3573
LordMelkor wants to merge 13 commits into
Conversation
Read Office, OpenDocument, RTF and iWork files through bounded format admission, XML and archive parsing. Keep the generated document, sheet, slide and preview models independent of the viewer UI. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
Documents show with their pictures, tables and charts, and a Word file with three or more headings has an Outline. Spreadsheets show a sheet at a time with a tab for each, and Copy as TSV copies the open sheet. Presentations show each slide fitted to the tab, with speaker notes. Numbers files open as sheets too. Older .doc and .ppt files show their text only, and Pages and Keynote files the preview picture saved in them. Nothing in a file runs. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
Rebuild uploaded HTML as page data and validate SVG documents for picture and source views. Reuse the document parser and image admission helpers with exact parser dependency versions. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
Route HTML, SVG and text document formats by name or MIME. Honor media MIME types for text names and decide renamed PDF limits after the first five bytes. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
Add saved notebook outputs and reStructuredText, AsciiDoc and LaTeX documents behind the lazy Office reader. Prepare JSON and MDX once per file and preserve raw HTML structures and anchors across bounded Markdown parts. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
Prepare flat YAML properties before indexing and use the same Markdown grammar for files and notebook cells. Keep display equations intact within the existing budget, preserve alert types across part boundaries and handle every supported line ending. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
… viewer HTML files draw as a page in a sandboxed srcdoc frame whose policy runs no script and loads only community images; links open as they do in messages, and Source switches to the HTML. SVG files draw as a picture from a blob URL. JSON with overlong lines shows indented, MDX without its code, and Markdown keeps its sanitized HTML. Fenced code and inline TeX go to Buzz's code-block and inline renderers (Math equations, Mermaid), with the code as fallback. A renamed PDF opens as a PDF. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
A Markdown file's flat front matter shows as a Properties list above the document, and GitHub alerts in files and notebook cells show as titled notes. Markdown files use the shared file grammar, so a `$$` block reaches Buzz's math renderer. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
Compose Word vanish as a style toggle while retaining absolute direct formatting. Apply ODT hyperlink styles, omit hidden-only list items, and retain visible bookmark-only links without restoring hidden targets. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
Resolve basedOn chains by override before combining Word style levels. Keep document-default hidden text and absolute direct formatting, and carry hidden ODT item restarts into following visible numbering. Co-authored-by: Sigil <06114f072cf21e50fcd4133ab21346cdb1f74ee16d853a9729995fa6c03a251c@buzz.block.builderlab.xyz> Signed-off-by: LordMelkor <kray@squareup.com>
A key held with Alt, Ctrl, Cmd or Shift no longer moves outline focus, and a key at either end of the outline no longer leaves a focus request waiting that would take focus back when its row is drawn again. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
A link in message text to a file shared in this community now opens in the File viewer, the same as the file's card. Only community media addresses whose names give a known format are claimed; other links and modified clicks stay with the browser. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
Markdown parts now use the shared code fence, which reads the fence's `language-*` class. The sanitizer keeps only lowercase classes, so a ```Mermaid or ```Math fence lost its plugin. The file grammar now writes the lowercase language back to the class. The inline-math override on Markdown parts is removed, since only Office trees mark inline math. Co-authored-by: Clod <c7b21872e9ddb79ca62967d0e21e6a4518a669eb04648e963cdfb2426e62fafb@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: LordMelkor <kray@squareup.com>
joahg
left a comment
There was a problem hiding this comment.
🤖 This account isn't approving UI changes. This PR adds new File viewer views (HTML pages, SVG, notebooks, more document formats), so it needs a design or core-team reviewer. Because HTML pages from untrusted attachments render in a same-origin frame, I focused on security and reviewed only this PR's eleven commits on top of #3572.
I found no way to run script. Both the frame's meta policy (default-src 'none', script-src 'none', base-uri 'none', form-action 'none') and the app's own CSP would have to be bypassed. On top of that:
PAGE_PREFIXpins what can come before the policy.markerAtrefuses<noscript>and stray<meta>.page.tsdropsbase,meta,link,script,iframe/object/embed,foreignObject,animate/set,on*,srcset,target,action/formaction, andjavascript:/vbscript:/data:values.stablePagerequires a stable re-serialization.- Navigating away sends the frame to
undrawn. - The prototype-based listeners can't be shadowed by page names.
- The Markdown, MDX and notebook paths run
rehype-sanitizeafterrehype-raw, with an explicit allowlist. Notebookapplication/javascriptoutputs aren't rendered.
Blocking finding (P1): the frame's img-src allows the whole buzz-media: scheme. cspSource (page-frame.ts) returns the bare scheme for a native media URL, and page-frame.test.ts pins img-src blob: buzz-media:. On macOS and Linux, any page that shows one community image therefore lets its own CSS (background: url(buzz-media://localhost/…), <input type=image>, etc.) load any buzz-media:// URL, not just the images the page was approved to show. The native media handler doesn't keep its own community list; it accepts any HTTPS origin with a /media/<sha256> path and sends signed media auth with the request. So opening a page, with no click, can make the app send an authenticated request to a host outside the user's communities. That leaks the user's IP and pubkey, and it contradicts "only images shared in the community load". Suggested fix: have the viewer fetch each approved image and hand the frame blob: URLs only, then drop buzz-media: (and the web proxy path, whose ?url= the policy also can't restrict) from img-src. Separately, binding the native media handler to saved communities would be worthwhile defense in depth.
P2:
isSvg(svg.ts) only checks well-formedness. Drawing SVG through<img src=blob:>is inert, but an "open image in new window" or drag-out of that blob URL would load it as a same-origin document with its scripts. Strippingscript,on*andforeignObject(or rasterizing) would close that.page.tskeepshrefon non-<a>elements, e.g.<math href>, which WebKit and Firefox follow on click. The frame then navigates (it correctly goesundrawn), but the click still reaches the remote host outside the viewer's link handling. Droppinghref/srcexcept ona/area/imgand in-document SVG#refs would fix it.
P3:
- The page-link fallback in
followLink(PageFile.tsx) opens a rawtarget=_blanklink instead of going throughMessageLink. markdown-schema.tsalso setsclobber: []/clobberPrefix: "", the same unprefixed-id issue I noted on #3572.font-src data:admits attacker fonts, which is a small extra parser surface.
Not verified: I reviewed the code only and didn't run the app or test the WebKit and Chromium context menus for the SVG case. I didn't check how the web build's media proxy validates its url parameter.
|
Instructions to get the build to run: Still working through the community contribution model for UX changes so can't provide a review timeline as of now. Additional context can be found here: |
Opens HTML pages, SVG pictures, notebooks and more document formats in the bundled File viewer, and draws math and plugin code blocks in files, a follow-up to #3338 (scope). It ports Media viewer v1.6. It was held back from #3338 to keep that PR reviewable (joahg/buzz-app#3), and is now rebased onto
main, with Markdown parts drawn through main'sPartMarkdownand shared code fence. Stacked on #3572: review that first; this PR's own changes are the last eleven commits.What changes for people
#links move within the page. Source shows the HTML. A page that can't be drawn safely shows only its source.> [!NOTE]and the rest) show as titled notes. A$$block is a display equation.scan.txt) opens as a PDF.vanishfollows ECMA-376 §17.7.3: the nearest setting in each style'sbasedOnchain wins, style levels combine as toggles, a document-defaulttruestays on, and direct run formatting is absolute. In ODT,text:auses its own style, a list item whose only paragraph is hidden leaves no empty bullet and keeps itstext:start-valuerestart, and a visible link that holds only a bookmark is kept.How
Data (Sigil, first four commits):
page.tssanitizes an HTML file into a page with numbered links and images.svg.tschecks an SVG.formats.tsrecognizes the new formats and their labels.load.tssniffs PDF bytes.prepare-text.tsprepares JSON, MDX and front matter.file-markdown.tsadmits sanitized raw HTML and marks display math and alerts.office/reads notebooks, rst, AsciiDoc and LaTeX throughopenOffice.markdown-extensions.tsis the file Markdown grammar that files and notebook cells share.office/document.tsresolves Word and ODT hidden text (two commits after the UI ones).UI (Clod, commits five and six, and the last three commits):
PageFile.tsxandpage-frame.tsdraw the page in aniframewithsandbox="allow-same-origin allow-scripts"and asrcdoc. Its Content Security Policy comes first in the head:script-src 'none',form-action 'none', andimg-srclimited toblob:and the community images the page names.allow-scriptsis there only so WebKit runs the viewer's own listeners; the policy still runs no script from the page.imgfrom ablob:URL.host-renderers.tsxgives the viewerconversation.codeBlocksandconversation.inlinethrough a context.PartMarkdownpassescodeBlocksthroughMarkdownDocument'scapabilities, so a fenced block a plugin claims is drawn by main's shared code fence, with its copy action. It adds the alertdivfromfile-components.tsx.rehypeFileMarkdownwrites the fence's language back as a lowercaselanguage-*class, so```Mermaidis claimed too.file-components.tsxdrawsprethrough main'sCodeBlock(language from the parser'sdata-language), inline TeX through the inline renderer, and alerts.media-urls.tstakesuseMediaUrlsout ofOfficeView, so pages and Office files share it.target.tslinkedFileturns a community media link into a file target; the panel matches it besidebuzz-file:targets, so the host's link click opens it.Outlinetakes acurrent()callback, so documents, slides and pages share it.New dependencies, pinned exactly and listed in NOTICE.md and check-icons:
The lockfile has no registry-specific URLs.
State machines
Audited with the state-machines skill:
drawn | source | undrawn. A page that can't be drawn starts inundrawn. Leaving the page also moves toundrawn, which shows the source with a note and no switch back.srcdocmatches the current one.about:srcdocmeans the page loaded.about:blankis the empty frame before the page, so it's ignored.undrawn.Efficiency
GPT Astra (High) reviewed Sigil's data layer as an efficiency oracle; its last recheck scored 9/10 with no open finding.
Evidence
At the head of this PR:
pnpm lint(including check-icons) andpnpm run design:checkare clean.src/bundled/file-viewerandsrc/features/markdown: 56 files, 1,944 tests passed. The new UI tests include:#, Buzz and external links.mdwhile the relay reconnectsfile-viewer.spec.mjsandmessage-files-polish.spec.mjs, Chromium and WebKit: 46 passed (before the capitalized-fence commit, which Vitest covers). New cases:#link scrolls in the frame, and Source and Page switch.scan.txtholding a PDF: opens as a PDF.Known limitations
$…$in Markdown stays text, as on GitHub. Inline math comes from rst, AsciiDoc and LaTeX.🤖 Generated with Claude Code