Skip to content

slides: the embed element - #466

Merged
nyblnet merged 5 commits into
mainfrom
pr424-land
Sep 13, 2026
Merged

slides: the embed element#466
nyblnet merged 5 commits into
mainfrom
pr424-land

Conversation

@nyblnet

@nyblnet nyblnet commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Carries #424 — Johan Høgåsen-Hallesby's embed element, as his own commit (cherry-picked; authorship intact) — plus the four follow-ups that were waiting for it on pr424-embed-conditions, on top of today's main:

  1. The asset prune from slides: a save drops the assets nothing refers to, so a deck can shrink #447 learns the embed reference form (without it a save emptied every embed's view and source — measured).
  2. An embedded document leaves the shape gate without its envelope; the naming settles; the panel says what a live frame costs.
  3. One envelope rule at both boundaries: the export strip walks embedded documents too.
  4. The embed element's twelve strings reach the 22 language packs (the coverage floor).

Verified at this head: test-embed bundled as CI runs it — 64/64 (browser half included); test-export-secrets — 74/74, negative-controlled; typecheck, build-i18n --check, build-modelkeys --check, test-slides-assets 28/28, test-ci-registered 314/314, language coverage 90.9%.

Merge-tree measurement against refs/pull/424/head: NOT clean — a three-way merge from #424's fork point conflicts on the files where the follow-ups edited lines his commit added (untrusted.ts's embedDoc, the "Beta build" comments, the panel copy, the catalogs). That is by construction: a follow-up that changes a contributor's line can never leave the original PR a no-op merge. So #424 should be closed as landed here once this merges — his commit is on main under his name, which is the credit that lasts.

johanhal and others added 5 commits September 13, 2026 23:07
An `embed` element { type:'embed', app, view, doc?, url?, live? } per the
2026-08-19 decision: the static `view` (svg markup or an asset) always
paints through the same sanitiser the svg element uses; `doc` is the
source; a sandboxed live iframe (no allow-same-origin, no top navigation,
no referrer) is opt-in and exists only when `app` is `web`, `url` is
http(s), remoteSrcBlocked(url) is false and navigator.onLine is true, and
drops back to the view on error. validate() reports a missing view as an
error and a remote view or live-without-url as warnings; untrusted.ts
gates paste and CRDT shape; the editor gets an Embed panel with a
"Capture view" picker. Strings in every catalog. Rig: scripts/test-embed.ts.

Motivation: today an unknown element type loads, survives a round trip
and renders as an empty positioned box with no finding, so a deck from a
build that has this element degrades silently in this one.
…ies on save

#447 landed this morning: a save now drops every doc.assets entry nothing
refers to, and referencedAssetKeys enumerates the forms that existed then.
This branch adds two more — an embed's `view` and `doc` are both interned by
"Capture view…" as asset refs — and on the merged tree a save deleted them.
Measured before the fix:

  {"assetsAfterSave":[],"EMBED_VIEW_LOST":true}

and after:

  {"assetsAfterSave":["view-1","src-1"],"EMBED_VIEW_LOST":false}

Silent, and neither rig could see it: #447's pins the eight forms it knew,
this branch's never saves. It is the failure the prune's own comment names —
a new field that references an asset must be added there — arriving one day
after the comment was written. The rig case is added so the next one cannot.

Also merges main (7e8c0d1 -> 401cade), clean.

(cherry picked from commit b3636ce)
…pe; the naming settles; the panel says what a live frame costs

Three things on top of #424's head, for the contributor to cherry-pick:

1. An embed's `doc` is another deck's JSON, and a deck's envelope carries
   its collaboration secrets (`collab`, including the saved sync state)
   and its identity (`docId`). The shape gate checked shape only, so both
   would pass, survive every save by additivity and travel with every
   export — while the export-secrets rig reads the top-level block alone.
   `embedDoc` now returns an object source without them, copying rather
   than mutating the caller's. Pinned in test-embed.ts (the gate keeps
   content, drops both keys, no private half survives anywhere, caller
   untouched; 64 checks, 2 red with the strip removed) and in
   test-export-secrets.ts by shape (67).

2. "Beta build" (thirteen places) becomes the element's own name, the
   render.ts comment points at the rig that exists (scripts/test-embed.ts),
   and build-modelkeys no longer cites a docs section that never existed.

3. The embed panel gains one hint: a clicked frame keeps the arrow keys
   while presenting until you click outside it, and everyone who presents
   the deck loads the page from its site. In all eight catalogs.

Also carried from `pr424-asset-prune`, as a proper commit this time: #447
prunes assets nothing refers to and did not know the embed element's
view/doc form, so on the merged tree a save deleted them (measured). Plus
the changelog entry the feature was missing, and main merged in.
…lks embedded documents

An embed's doc is another deck's JSON; that deck's envelope (collab,
docId) must never ride inside this one. The shape gate stripped it on
the way in for pasted content; a file already authored with it kept the
inner block in every read-only and invite copy, because the export strip
only saw the top level. Both now apply the same rule — slides/src/
envelope.ts, node-importable — the gate through stripEnvelope, and
stripCollabSecrets through stripEmbeddedEnvelopes (slides and layouts),
before its own early return so a copy that drops its whole collab block
still loses the embedded ones.

test-export-secrets runs both rather than grepping: a deck with an
embedded ownerPriv, writerPriv, invite.priv, room key, room id and docId
exports none of the six with the content intact; security's mutation
(behaviour killed, text kept) now turns it red (72/74). 74 checks.
The packs sat at 89.4% with the embed strings landed in the core catalogs
only; the floor is 90%. Machine-drafted by the packs' convention; tk's
header records its twelve as awaiting the named translator.
@github-actions

Copy link
Copy Markdown

Build size

main (b3ac101) → pr424-land (c2746ec)

app base PR change
bento/slides 695.9 KiB 701.6 KiB +5.8 KiB (+0.83%)
bento/spaces 273.0 KiB 273.0 KiB 0.0 KiB (0.00%)
bento/dash 424.4 KiB 424.4 KiB 0.0 KiB (0.00%)

Updated: 2026-09-13T22:13:36Z

@nyblnet
nyblnet merged commit df7c4e4 into main Sep 13, 2026
2 checks passed
nyblnet added a commit that referenced this pull request Sep 13, 2026
nyblnet added a commit that referenced this pull request Sep 13, 2026
…temporary merge of the fix branch is now redundant
nyblnet added a commit that referenced this pull request Sep 13, 2026
#466 landed an isWebUrl beside the embed element (scheme only); this
branch had a stricter one (scheme, length, no quote or angle bracket).
One definition now, the stricter, where the embed's was; every caller
(embed url gate, live-frame gate, element link, text anchor, markdown)
asks it. The embed rig passes against it unchanged (64/64).
nyblnet added a commit that referenced this pull request Sep 14, 2026
…hortcut list (#465)

* slides: clickable web links, * and indented bullets, and a complete shortcut list

Three things from the issue and discussion scan, each small enough for
this release.

Links (#421; discussions #373, #374). An element's `link` may be an
http(s) URL as well as a slide id (Presenting → Web link), and
[caption](https://…) in a text box becomes an <a href>. One scheme test,
isWebUrl in model.ts, is asked by every surface: the shape gate, the text
sanitizer (an <a> keeps its href only when it passes and is unwrapped to
text otherwise), both markdown converters, and the show before it opens
anything — always a NEW tab with noopener,noreferrer, never a navigation
of the deck, target/rel decided at click time and never stored. In the
editor a link click edits text. The offline switch is honoured with a
toast. Measured in Chrome: text and element links open via window.open
with those flags and the deck stays; a javascript: anchor is unwrapped;
slide links still jump; the editor never navigates; with offline mode on
nothing opens. test-sanitize gains the anchor checks (143 in the browser);
scripts/test-slides-links.ts (27) pins isWebUrl, the converters, and by
shape the four callers.

Bullets (#255, #368). "* " makes a bullet like "- ", and two or more
leading spaces make an indented sub-bullet — while typing (autoformat,
undoable with ⌘Z like before) and when pasting markdown (indent kept as
NBSPs).

Shortcuts (#269). The ? overlay names B, G, [ ], ⌘B/I/U, the zoom keys,
and the arrow keys' two jobs.

Sixteen strings in all eight catalogs and all 22 packs (four of them
pre-existing gaps from the code-snippet work); changelog entries;
DECISIONS entry for the link decisions.

* slides: a middle-click on a link goes through the same door as a click

auxclick is not click: the browser's default for it on an anchor is
'open in a new tab', straight past the offline gate and the noreferrer
flag. Intercepted on the show's slide host; button 1 opens through
openWeb like a click, anything else is swallowed. Pinned by shape.

* slides: sanitizeHtml walks an element's children before unwrapping it

The walk visited a snapshot of a node's children; an unknown element's
children were lifted into place after the snapshot and not visited on
that pass. Walk first, then lift, so lifted children are held to the
same rule as their siblings. One case in test-sanitize: nested markup
is walked like top-level markup, rendered and exercised in the browser.

* slides: show anchors carry rel at mount; a refused anchor's contents are walked before it is unwrapped

rel='noopener noreferrer' is set on the show's anchors when a section is
built — never stored, never in render.ts — so the browser's own routes to
an anchor (context menu, drag), which do not pass through the click
handler, send no referrer on a hosted deck either. The refused-anchor
unwrap now walks its contents first, like the general unwrap. The
nested-markup sanitizer case gains the anchor shape.

* slides: one isWebUrl — the stricter test, at the embed element's site

#466 landed an isWebUrl beside the embed element (scheme only); this
branch had a stricter one (scheme, length, no quote or angle bracket).
One definition now, the stricter, where the embed's was; every caller
(embed url gate, live-frame gate, element link, text anchor, markdown)
asks it. The embed rig passes against it unchanged (64/64).

* ci: re-run on this head (no run was recorded for 511dc29)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants