Skip to content

feat(store): a way to get the file out of the store - #24

Merged
johanhal merged 2 commits into
mainfrom
feat/save-to-disk
Sep 10, 2026
Merged

feat(store): a way to get the file out of the store#24
johanhal merged 2 commits into
mainfrom
feat/save-to-disk

Conversation

@johanhal

@johanhal johanhal commented Sep 10, 2026

Copy link
Copy Markdown

A deck made at slides.betamobility.ai could not be saved to the person's own computer or Drive.

The gap

installStoreHost polyfills showSaveFilePicker, so every kernel save purpose resolves to a store object: bento-doc (⌘S) PUTs, bento-copy and bento-share POST new decks. Each of those is right on its own, and together they left no door out. The only download on the store origin was bento-backup, the rollback copy an update leaves behind.

That is exactly the on-ramp the Drive workflow needs (#23, docs/beta-drive-workflow.md): decks belong beside a project's material, and there was no way to put one there.

The change

Save as… → "Save to your computer…", shown only on the store origin, where it is the mirror of "Save to Beta…" on a file.

  • It does not adopt the handle. saveFile adopts what a picker returns and writes ⌘S through it from then on, which would quietly move the deck off the store. saveToDisk writes the handle directly and leaves the ⌘S target alone — same reasoning as writeUpdatedFileAs's keepHandle for share exports.
  • The file keeps its collab, so it is the same deck, live-synced with the stored one. "Duplicate as new deck…" is still the fork.
  • serializeAuto, never serializeFile, so a password-protected deck lands on disk encrypted.
  • No File System Access API → a download. Safari's answer, and still the file.
  • Its own bento-disk picker id, so the browser remembers that folder rather than wherever an unrelated export last went.

Verification

Run in a browser against the built shell served at the store origin, so installStoreHost actually ran (Playwright route + addInitScript so the host captures a stub picker the way real Chrome would):

  • picker called with Beta_Slides.bento.html, id bento-disk, and the compound .bento.html extension
  • 1,034,826 bytes reached the writable, which was closed
  • zero /api/decks requests during the save
  • a ⌘S immediately afterwards still PUT /api/decks/<id> and wrote 0 further bytes to the picked file — the handle was not hijacked
  • off the store origin the item is absent and no host is installed

Gates: tsc -b, kernel tsc, build:single, shell-gate, test-export-secrets 58/58, i18n coverage with all 8 core catalogues complete (the four new strings are in every one, not just English).

Ships with

Needs a shell release to reach people; the worker change in #25 does not. Stacked on neither — both branch from main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KhNJ6no7FeLu15siczqD3C

A deck made at slides.betamobility.ai could not be put on the person's own
disk. `installStoreHost` polyfills the picker, so every kernel save purpose —
Cmd-S, "Save a copy", every share export — resolves to a store object. That is
right for all of them, and together they left no door: the only download on the
store origin was the rollback copy an update leaves behind.

That is the on-ramp the Drive workflow needs (docs/beta-drive-workflow.md), so
"Save to your computer…" is added to Save as…, shown only on the store origin,
where it is the mirror of "Save to Beta…" on a file.

It does NOT adopt the handle. The kernel's saveFile adopts what a picker
returns and writes Cmd-S through it afterwards, which would quietly move the
deck off the store; `saveToDisk` writes the handle directly and leaves the
Cmd-S target alone, the same reason writeUpdatedFileAs grew `keepHandle`. The
file keeps its collab, so it is the same deck live-synced with the stored one,
not a fork — "Duplicate as new deck…" is still the fork. `serializeAuto`, so a
password-protected deck lands on disk encrypted. Without the File System Access
API there is no folder to choose and it is a download, which is Safari's
answer and still the file.

Verified in a browser against the built shell served AT the store origin, so
installStoreHost really ran: the picker is called with the deck's name, its own
`bento-disk` id (the browser then remembers that folder) and the compound
extension; 1,034,826 bytes reach the writable; NO /api/decks request is made
during the save; and a Cmd-S immediately afterwards still PUTs to
/api/decks/<id> and writes nothing to the picked file. Off the store origin the
item is absent and no host is installed.

Gates: tsc -b, kernel tsc, build:single, shell-gate, test-export-secrets 58/58,
i18n coverage with all 8 core catalogues complete (the four new strings are in
every one).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhNJ6no7FeLu15siczqD3C
CI caught what the local checks did not: packed.ts is generated, and
test-i18n-coverage reads PACKED_LOCALES out of it, so it verified that the
eight catalogues are complete without noticing the table was built before the
strings existed. build-i18n.mjs --check is the one that knows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhNJ6no7FeLu15siczqD3C
@johanhal
johanhal merged commit 8a74c5a into main Sep 10, 2026
3 checks passed
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.

1 participant