spaces: a page becomes a bento/slides deck - #434
Open
nyblnet wants to merge 1 commit into
Open
Conversation
Save -> Export page as slides... turns one page into a bento/slides presentation. Headings and dividers start slides, lists stay lists, a table stays a table, a board becomes a table of the rows it stands for, and a canvas becomes a slide with each card where the author put it. The page's title, icon and cover make the title slide; the space theme becomes the deck theme. What it hands over is the deck's DOCUMENT JSON, pasted into Bento Slides through its own "Replace from JSON...". A self-contained .bento.html deck is a document spliced into a slides shell, and this app has no way to get one that does not either bundle half a megabyte of another app into every space or fetch it on open. The Markdown exporter is the precedent. Nothing is fetched and nothing dangles: every picture is resolved through the asset table to its bytes and re-interned in the deck's own assets, and anything that would still reach the network - including bytes hidden one asset: indirection away - is left out and reported. The report is shown in the dialog before the download and written into the deck's speaker notes. Speaker notes are otherwise not invented; review comments stay behind. New file spaces/src/todeck.ts; the editor gains one menu entry and one dialog. The deck's format belongs to another zone and nothing under slides/ is edited: the guards are a type-only import of slides/src/model.ts and rig coverage that runs the emitted document through slides' own parseDoc and checks every key it writes against its generated key list. Behaviour is not covered on that side - see docs/DECISIONS.md. 24 new UI strings in all eight catalogs; packed catalogs regenerated. Compressed shell 277,769 -> 294,133 B.
Build size
Updated: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Save → Export page as slides… turns one
bento/spacespage into abento/slidespresentation. No hosted notes app can do this; we can, becauseboth apps are the same repository.
The coupling, stated plainly
This writes another app's format while being forbidden to edit
slides/.Every file changed here is under
spaces/src/, plus the shared rig, thechangelog and the decision log. What holds the two sides together:
slides/src/model.ts— erased at build time, so itcosts the shell nothing, and a field renamed over there is a compile error
here. It caught a missing required
modifiedon the first run.parseDocand checks every key it writes againstslides/src/modelkeys.generated.ts.Neither guard covers behaviour, and nothing on the slides side knows this
exporter exists. A renderer that stopped honouring
valignwould pass both.That is a real cost, knowingly accepted, not a solved problem — it is written
into
docs/DECISIONS.mdand into the file's own header so the next sessiondoes not find it as a surprise. Three defects the node rig could not see were
found by loading the output into a built slides shell and running its
validate(); they are listed below.Shape: the document, not the file
It emits the deck's document JSON, handed to slides through the
"Replace from JSON…" round trip it already documents (or
window.bento.loadDoc()). A self-contained.bento.htmldeck is a documentspliced into a slides shell, and this app can obtain one only by bundling
~560KB of another app into every space, by fetching it (PLATFORM §1 forbids
it), or by a joint shell built in two zones this one may not edit. The Markdown
exporter is the precedent: write the other format faithfully and hand it over.
If a joint shell ever exists, that is the one decision to revisit — the
document this produces is already the whole payload.
The mapping
h1,h2dividerh3p,quote,propbullet/number/todo<ul>/<ol>, nesting kept,☐/☑for a to-docodecodeelement hangs its highlighting offgrammarAssetId, which this exporter has no grammar to fillcallout,toggletableview(board / list)canvasimage,medialink,pagelinkContent taller than a slide continues on the next one under the same title
rather than sitting off the canvas.
What is dropped, and how loudly
Fourteen machine codes (
DeckNote), aggregated per heading, shown in the dialogbefore anything is downloaded and written into the affected slide's
speaker notes — a toast is gone in four seconds, and the presenter who opens
the deck next week is the person who needs to know the page had a video on it.
Codes cover: a picture or clip that would have to be fetched, an embedded clip
that makes the deck large, a flattened link, a pagelink, an unfolded toggle, a
plain-panel callout, a flattened canvas, a split table, a derived board, an
unknown block type, an empty block, an RTL space, review comments, and a page
icon that is one of this app's own glyphs rather than an emoji.
Speaker notes are not invented. The tempting mapping is review comments →
slide.notes. It was rejected: a comment is workspace, addressed to a namedperson, and a deck's notes travel in every copy — the mapping would disclose a
remark its author never put in the document.
Rules honoured
assetValueto its bytes and isre-interned in the deck's asset table; anything still remote — including
bytes hidden one
asset:indirection away, the hole closed on the readingside in spaces: ask the remote question about the URL that will actually be fetched #396 — is left out and reported.
filtered out of the input: every text run is escaped, only
strong/em/u/s/code/br/ul/ol/liare emitted, no attributes. Map lookups ondocument data use
Object.hasOwn. Noeval, nonew Function.switchoft()calls rather thant(MAP[code]); all eight catalogs at 623/623, 100%. The English copywritten into the document stays English on purpose — a saved artefact's
words are its author's, not its next reader's browser's.
portable.tsand the Markdown export are untouched.Verification
Rig coverage went into
scripts/test-spaces-model.ts— an ops-surface file,flagged as a cross-zone touch, claimed under
spaceson the board. It iswhere this zone's model rigs already live and it avoids adding a CI step while
the queue is contended.
Every assertion is behavioural — build a page, run the exporter, assert on
the result. Nine sabotages, nine caught, green restored after each:
no element src reaches the network once the deck's own asset table has had its sayformatthe emitted document loads through bento/slides own parseDocevery key the exporter writes exists in bento/slides modelh3start a slideh3 does NOT start a slideevery tag in the deck's html is one bento/slides rendersevery body row travels across the split (54/60)all three would-be-fetched pictures … are reporteda NAMED icon does not — it would read as the word "image"the table box is tall enough for every row it holdsTwo of those assertions were rewritten because the sabotage exposed them as
vacuous, which is the point of running it: the src check originally asked
about the string on the element (the exact
asset:indirection hole), and themarkup check was a substring test on the serialized JSON that passed with
escaping removed entirely.
End-to-end: it renders
Four pages of the starter space exported and loaded into a real built
slides/dist-single/shell, served overhttp://127.0.0.1:8611from auniquely-named copy carrying an
e2e-markermeta tag (asserted in the pagebefore anything else).
validate()The one remaining
infoon each is slides' owncollab-secrets-present, fromcredentials slides mints on load — the emitted document carries no
collabkey at all (verified in the page).
That run is what found the three defects the node rig could not, all fixed
here: every element flagged
past-margin(the margin is slides' own 96 now),two text boxes overflowing by 10px and 15px (the no-DOM width estimate was too
generous for lists, tuned against the real measurement), and a table clipped
to three of its five rows because rows were sized at 36px when one draws at
about 45. Only the third has a rig assertion, and it is a derived bound rather
than a measurement.
Known, and deliberately not fixed here
about.ts toMarkdown'srowsOfderives aviewblock's rows fromissuesOfand ignores
source, so a view with ahas/undersource exports the wrongrows to Markdown.
todeck.tsusesviewRows(doc, b.source), which is correct.Fixing the Markdown path is a change to a shipped exporter and does not belong
in this PR; raising it so it is not lost.
Cost
Compressed shell 277,769 → 294,133 B (+16,364, +5.9%), measured by building
origin/mainside by side. Roughly half is the exporter and half the 25 strings× 8 locales.