发布

  • feat(media): allocate generated assets through the registry (#1007 part 2, step b) (#1039)

    frostbyte_neo 发布于 2026-08-06 10:49:34 +00:00

    • feat(media): establish shared asset ownership primitives

    Introduce global browser asset-pool ownership, asset-reference collection, stage reclamation planning, and lease-based URL access. Carry allocated media identity through storage and generation boundaries with regression coverage.

    • fix(media): enforce safe resolution across every consumer

    Route image, video, thumbnail, presentation, and video-export consumers through one resolution state machine. Prevent opaque allocated or generated references from reaching render and export sinks, with fallback and ownership tests.

    • fix(media): protect document-owned assets across mutations

    Allocate pool bytes before compatibility writes and document commits, then roll back uncommitted generations safely. Preserve document ownership across edits, retries, imports, speech generation, scene changes, and stage deletion.

    • fix(media): scope retries and tighten ownership guard

    Scope retries to the target scene and slide and refuse ambiguous shared-reference mutations. Expand retry rendering coverage and keep direct pool URL resolution behind the shared lease owner.

    • fix(storage): avoid nested lock during stage cleanup

    Execute prepared reclamation plans against an explicitly deleted document so the compatibility cascade cannot re-enter the per-document lock. Cover deletion of stage-owned media rows even when the document has no references.

    • refactor(media): confine reclamation to stage deletion

    Remove inline pool and compatibility-row cleanup from element, speech, scene, and audio replacement flows. Keep whole-stage reclamation behind explicit stage ownership, preserve stage-less legacy audio rows, and document deferred document-truth sweeping.

    • fix(media): close retry and resolution gaps

    Restore shared source tasks after successful forks, scope retries across both whiteboard locations, and wait for parallel TTS workers before rollback. Resolve background media through import, rendering, and PPTX export paths while keeping retry controls visible over last-good bytes.

    • test(media): execute the consumer safety matrix

    Replace source-substring checks with resolver seam execution across all six UI consumers, stage hydration, and both exporters. Pin each rollback layer independently and harden the ownership guard against aliased pool imports.

    • chore(packages): publish the additive DSL field

    Bump the DSL patch version for the optional speech-action field. Keep the transitional reclamation policy app-owned and document the legitimate transaction rollback removals there.

    • fix(media): make retry rollback task-safe

    Delay allocation task re-keying until final document reconciliation succeeds, restore shared source tasks on failed forks, and require exact stage-whiteboard targets before falling through from a missed scene.

    • fix(media): clear private assets and refresh leases

    Delete the asset-pool database during the confirmed local-data wipe. Notify the app lease layer after same-id replacement so mounted consumers re-resolve current bytes without reaching into storage internals.

    • test(media): pin closure safety guards

    Exercise the CSS allocation boundary, unfiltered legacy-row ownership, delete-and-undo byte survival, and real distinct consumer seams. Restore the video-only manifest overwrite condition and share the direct video resolution hook across both element variants.

    • test(media): narrow rollback element assertion

    Narrow the reconciled slide element to an image before checking its source so the rollback regression remains type-safe under the full root compiler configuration.

    • fix(media): close lease refresh races

    Gate the first batch publication by unique resolved refs, then publish every replacement snapshot without mutating the prior React state object. Serialize invalidation behind pending releases, evict rejected refreshes, and register replacement observation at the pool boundary.

    • fix(media): reopen pool after clear failures

    Always evict the singleton once its store has been closed, including blocked and failed database deletion paths. Report blocked deletion as deferred and prove a later write uses a fresh live store.

    • fix(media): isolate shared retry progress

    Track forked regeneration under the selected element until it receives a fresh asset identity, leaving the shared source task and bytes untouched. Surface targeted failures through renderer task lookup and skip the redundant fork reconciliation lock.

    • fix(media): close final asset retry gaps

    Keep blocked asset clears fail-loud until a successful retry, with actionable settings guidance.

    Clear failed shared-fork state across durable and live key spaces, and pin renderer lookups, lease publication identity, and committed-ref rollback protection.

    • fix: preserve actionable retry failures

    Localize the blocked cache-clear recovery hint across every supported locale and pin the deferred-error mapping.

    Retain durable fork failure rows while retries run, deleting them only after successful generation so unstructured failures survive reload.

    • fix(media): hydrate legacy stored video thumbnails

    Home-page recent-video thumbnails regressed for legacy Dexie mediaFiles
    rows keyed by gen_vid placeholders: the reworked hydration resolved the
    row's bytes through the sealed resolver but never surfaced the stored
    blob (and its poster) as object URLs for the preview card, so the CI
    recent-video-thumbnail e2e specs found no visible element.

    Hydration now materializes legacy stored video rows into blob URLs for
    both the element src and poster while keeping the resolver invariants:
    opaque refs still never reach a DOM src, and concrete addresses are
    never blanked. Unit pins cover the seam so the vitest suite catches this
    class without a browser.

    • fix(media): resolve sole restored legacy video

    Classroom playback restored tasks by exact document media references. Legacy gen_vid references can outlive the key used by the one persisted video row, leaving the player on a placeholder even though bytes were restored.

    Select the sole completed stage video only for legacy sequential refs after exact and reconciled matches. Keep exact failures authoritative, refuse ambiguous candidates, and cover success, ambiguity, and failure precedence in unit tests.

    • fix(media): scope legacy video recovery

    Decide restored legacy video recovery once from the complete document and record it through the shared task lookup consumed by playback, editing, and resolved slides.

    Keep ambiguous documents as placeholders, apply the same decision to thumbnail hydration, and preserve exact failure precedence.

    • fix(media): exclude claimed video recovery tasks

    Model restored legacy recovery as a two-pass match across document video elements and task rows. Remove tasks claimed by exact, targeted, or placeholder lookup before applying the sole-candidate fallback, and cover the ownership/cardinality matrix.

    • fix(media): unify video element resolution

    Centralize source, task, poster, and legacy recovery decisions for every video consumer. Ensure direct URLs win over opaque refs and play_video waits on element-targeted retry tasks.

    • refactor(media): route legacy recovery through resolver

    Let document-aware consumers request legacy video recovery through the unified element binding API, keeping thumbnail hydration on a single decision path.

    • fix(media): preserve import refs and prefer pool bytes

    Recognize unambiguous extensionless relative media addresses during classroom import. Resolve allocated export and thumbnail assets from the shared pool before falling back to lagging compatibility rows.

    • fix(export): preserve concrete video sources

    Route PPTX video elements through the shared media binding resolver so an unresolved opaque reference cannot replace a playable source. Complete browser and persisted-store cleanup when asset-pool deletion is deferred, while retaining distinct hard-failure behavior.

    • fix(media): fork retries without exclusive ownership

    Enumerate logical asset owners across every persisted document before allowing global pool replacement. Thread explicit targets through fresh-id rewrites and cover cross-document aliases plus unreadable ownership.

    • fix(media): guard global asset reclamation

    Share a fail-closed persisted-document liveness check between stage deletion and retry replacement. Preserve cross-document pool aliases while deleting stage-owned compatibility rows and cover enumeration failures.

    • fix(media): cover complete slide asset references

    Route slide media traversal through a shared mutable slot contract so backgrounds participate in export, thumbnail hydration, collection, and rewrite lifecycles. Snapshot complete surviving-document refs once per reclamation and preserve manifest-only owners while retaining fail-closed behavior.

    • fix(media): preserve exclusive retry asset ids

    Allow targeted retries to replace exclusively owned pool assets in place. Keep shared and unprovable ownership paths on fresh allocations, and pin production-shaped retries plus compatibility-row cleanup.

    • fix(media): revalidate asset bindings at completion

    Recheck repository-wide ownership before replacing generated media and fork scoped retries when exclusivity changed. Route video export selection through the unified resolver and keep concrete posters independent of task state.

    • fix(media): count unflushed owners and broadcast replacements

    The completion-time exclusivity proof read only the persisted document, but
    slide duplication updates the Zustand aggregate synchronously and schedules
    persistence behind a debounce. A retry finishing inside that window saw a
    single persisted owner and replaced the bytes behind a reference the
    duplicate also held. The proof now also counts owners in the live stage
    snapshot when that snapshot represents the stage being retried, so an
    unflushed duplicate forks instead.

    Same-id replacement notifications were realm-local, so a second tab showing
    the same classroom kept its lease pinned to the superseded blob URL. The
    notification now travels over a BroadcastChannel; each receiving realm runs
    its own observers against its own pool, so a spoofed message can at most
    force a re-resolve. A missing or failing channel never fails the replacement.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(media): bind replacement listeners and spare shared audio rows

    A realm that only renders never sends a replacement, so binding the channel
    from the sender path left passive tabs deaf to peers. Binding now happens
    where the observer is registered, when the asset-pool module loads, and the
    receiving realm resolves its own pool lazily so a cleared or unavailable
    pool degrades to the next resolve instead of throwing.

    Observer notifications ran under Promise.all, so a rejection surfaced after
    BrowserAssetStore.replace had already committed and turned a durable success
    into a reported failure. They are settled individually now; the callback is
    wrapped because a synchronous throw would otherwise escape before allSettled
    sees the array.

    audioFiles rows are keyed globally by audioId, so deleting a stage removed
    the sole row for an id a surviving document still referenced — playback and
    both export paths read that table directly and cannot fall back to the
    preserved pool blob. Rows are now filtered against surviving references,
    while a failed enumeration still withholds only the irreversible pool
    removal.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(media): serve replaced bytes from the pool everywhere

    Unknown survivor liveness deleted every planned audioFiles row. Those rows
    are keyed globally by audioId, and playback plus both export paths read the
    table directly, so losing one is as irreversible for them as removing the
    pool entry. Unknown liveness now preserves the rows too, leaving bounded
    garbage for a later pass that can prove exclusivity.

    The earlier pool-first change covered PPTX, video collection and thumbnail
    hydration but missed classroom ZIP export, which still serialized the stale
    compatibility row after a lagged same-id replacement, shipping media the
    classroom no longer renders.

    Auditing every direct reader of the media and audio tables surfaced the same
    gap in playback: speech regeneration also replaces bytes under a stable id
    and does not roll the pool back when the compatibility write fails, so the
    player kept serving superseded narration. It now resolves the pool first and
    falls back to stored rows for legacy and imported audio.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(audio): replace exclusively owned speech clips in place

    regenerateSpeechAudio accepted the action's audioId but always passed
    undefined as replaceAssetId, so regenerating an exclusively owned
    pool-backed clip allocated a new asset every time, rewrote the action and
    orphaned the previous pool entry and compatibility row until stage
    reclamation — contradicting the stable-id path generateAndStoreTTS already
    implements for media.

    Ownership is now established before synthesis, and the rule itself moved to
    the shared reference module so media retries, poster replacement and speech
    regeneration consume one implementation instead of restating it. An
    exclusively owned clip keeps its id and has its bytes replaced; a shared
    clip, a legacy id with no pool entry, or unprovable ownership still gets a
    fresh allocation so other holders keep their audio.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • chore(media): drop the import left behind by the ownership move

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(media): resolve audio pool-first and fence peer realms

    Stable-id TTS regeneration commits replaced narration to the pool before the
    audioFiles mirror write, so a failed mirror leaves the row stale. AudioPlayer
    already resolved pool-first, but audioObjectUrl, collectAudioFiles and the
    video timeline dependencies still read Dexie directly and would serve the
    superseded clip. All allocated-audio readers now share one resolver, with
    Dexie kept as the fallback for legacy and imported rows.

    The exclusivity proof modelled unflushed owners in the active realm only, so
    another tab duplicating the same asset during its save debounce could still
    be observed as a single owner and have its bytes replaced globally. A peer's
    pending state cannot be read across realms, so presence is probed instead:
    any realm holding the stage forces the fork path.

    A deferred asset-pool deletion no longer reloads the page. The database is
    still on disk and the guidance asks the user to close the other tab and
    retry, which the reload discarded. The decision moved into a shared helper so
    the rule is pinned rather than living inline in the component.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(media): fail closed when presence cannot be probed

    The presence helper documented that an unanswerable probe must count as a
    peer, but every unavailable path — no BroadcastChannel, a constructor that
    threw, a send that failed, and the window before the pool's asynchronous
    binding completes — returned false, so the ownership proof cleared a single
    local owner and replaced globally shared bytes in place.

    Probing now returns present, absent or unknown, and only a probe that was
    actually sent and went unanswered is absent; the ownership decision treats
    unknown exactly like present. The pool declares its binding intent
    synchronously so a probe issued during the load-time window waits for the
    bind instead of concluding that presence is unavailable, and releases that
    gate if the import fails.

    Coverage reaches the write boundary: with presence unknown, a
    production-shaped targeted retry forks to a fresh id instead of calling
    replace, and the original bytes stay intact for a peer's unflushed owner.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • chore(dsl): bump to 0.6.3 after the release dedupe took 0.6.2

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com

    • fix(i18n): add the deferred-clear guidance to fr-FR

    The locale landed on main after this branch added the key, so the alignment
    check flagged it as the one missing translation.

    Co-Authored-By: Claude Opus 5 noreply@anthropic.com


    Co-authored-by: Claude Opus 5 noreply@anthropic.com
    Co-authored-by: 杨慎 117187635+cosarah@users.noreply.github.com

    下载附件