Project library over OPFS: per-project working copies, silent Save, and the side-panel UI (#456) - #458
Merged
Merged
Conversation
…nd the side-panel UI (#456) The side panel returns as a project library over OPFS, and saving becomes a silent commit — never a download. Storage (hyperaudio-save.js): - Every project lives in work/<projectId>/ with a library.json index at the OPFS root (name, starred, timestamps, media meta, summary/topics). Identity is OPFS-native generated ids — none of the file↔workdir matching hazards; re-opening the same .hyperaudio makes a second entry. - Each project dir holds TWO states: saved.json, committed by Save (⌘S / the navbar button) as a SILENT OPFS write, and draft.json, the debounced autosave scratch. Dirty = lastDraftAt > lastSavedAt, so switching projects, closing or crashing loses nothing while the project honestly stays dirty until a real Save. A future format revision records each manual save as a version. - Export Project (.hyperaudio) is a new FILE menu item and the only path that downloads. The native bridge still receives the container on Save, and no-OPFS browsers fall back to the download-as-save. - Switching asks nothing and loses nothing: the outgoing draft flushes to its own directory first. The discard-on-switch dialogs die with the single work slot that required them. - The #450 Web Lock becomes per-project (hyperaudio:project:<id>): two tabs edit different projects, the same project in a second tab gets the guarded banner and queues for promotion. Index writes serialize under an origin-global lock; a BroadcastChannel keeps other tabs' panels honest. - Boot restores the most recently edited project (draft first) — the index replaces the localStorage boot hint — and requests navigator.storage.persist(). The interim single-slot work/ layout (never released) migrates once. The quit guard narrows to the one loss case left: a deleted project's document living only on screen. Panel (hyperaudio-library.js, resurrected from pre-#451 history): - Rows over the index: last-edited order, Starred pinned with the #440 heading swap, current-project highlight, kebab menu (Info, star, inline rename, duplicate, armed two-step delete), delete-current's Restore undo. Rename IS the title Save and Export use — index, stored state files and live session all updated. - Row-height square kebab hover, row-level hover state, 4px row gaps, and a hover popout floated right of the panel (1s dwell) with the full name, summary and topics. - Info moved from the controls row into the kebab: the modal is project-bound — name, media file + duration, stored provenance, summary and topics in uniform sections. apply() rebuilds the Transcription section from stored provenance; it previously kept showing the last engine run regardless of project. Remaining transcription-report gaps are tracked in #457. Fixed along the way: gather() now preserves class="speaker" via a targeted sanitizer instead of the editor's blanket class strip — every save/autosave had been demoting speaker labels to plain words, so paragraphs lost their speaker names and restored labels lost their styling and the Speakers toggle. Latent since the Phase A writer. Tests: 60 unit / 71 e2e green. Note for future specs: page.waitForFunction with an ASYNC predicate resolves immediately (the pending Promise is truthy) — use pollPage in helpers.mjs; several phantom races traced back to exactly that. Closes #456.
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.
Closes #456. Stacked on #404 (base:
403-hyperaudio-save) — the two merge and ship together as one release, so no released version carries the management-UI gap.The model
work/<projectId>/with alibrary.jsonindex at the OPFS root. Identity is OPFS-native generated ids: none of the file↔workdir matching hazards, and re-opening the same.hyperaudiosimply makes a second entry.saved.json— never a download. Autosave writes a separatedraft.jsonscratch, so switching, closing or crashing loses nothing while the project honestly stays dirty (lastDraftAt > lastSavedAt) until a real Save. A future format revision records each manual save as a version.hyperaudio:project:<id>, from Second-tab guard: one Web Lock, read-only banner (#403 Phase A) #450's slot): two tabs edit different projects; the same project in a second tab gets the guarded banner and queues for promotion. Index writes serialize under an origin-global lock; a BroadcastChannel keeps other tabs' panels honest.navigator.storage.persist(), and migrates the interim single-slotwork/layout once (never released — dev copies only).The panel
Resurrected from pre-#451 history and rewired to the index: last-edited order, Starred pinned (#440 heading swap), current-project highlight, kebab menu — Info · Star · Rename · Duplicate · Delete (armed two-step) — and delete-current's Restore undo. Rename is the title Save and Export use. Hover popout right of the panel (1s dwell) with full name, summary and topics. Info is project-bound now: the modal shows name, media file + duration, stored provenance, summary and topics in uniform sections;
apply()rebuilds the Transcription section per project (it previously showed the last engine run regardless of project — remaining report gaps in #457).Also fixed
gather()now preservesclass="speaker"via a targeted sanitizer — the blanket class strip meant every save/autosave demoted speaker labels to plain words (paragraphs lost speaker names; restored labels lost styling and the Speakers toggle). Latent since the Phase A writer; surfaced by the library's round-tripping.Tests
60 unit / 71 e2e green. New
library.spec.mjscovers switching-flush, star/rename/duplicate/delete/restore, boot restore, popout and the project-bound Info. Testing note:page.waitForFunctionwith an async predicate resolves immediately (pending Promise is truthy) —pollPageinhelpers.mjsis the replacement; several phantom races traced to exactly that.