Skip to content

feat(skills,studio,media-use): the review loop and user memory — sketch-first review, per-frame board comments, remembered defaults, recipes#2133

Open
WaterrrForever wants to merge 4 commits into
mainfrom
feat/skills-review-loop
Open

feat(skills,studio,media-use): the review loop and user memory — sketch-first review, per-frame board comments, remembered defaults, recipes#2133
WaterrrForever wants to merge 4 commits into
mainfrom
feat/skills-review-loop

Conversation

@WaterrrForever

@WaterrrForever WaterrrForever commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

The review loop — collaborative runs of the narrative workflows review in three passes of rising fidelity: text plan → wireframe sketch board → animated build, each gated on the user, with a per-frame comment channel on the Studio board that batches into a sidecar file the agent picks up on its next reply. Autonomous mode is untouched: no board, no sketch pass, one preview-or-render question before render.

User memory — two tiers on media-use's existing storage split. Preferences: confirmed brief answers (destination, aspect, language, mode, voice, style preset) become next run's recommended defaults with receipts naming their source. Recipes: one approved run frozen as a named, versioned bundle (design spec + storyboard skeleton + confirmed values); the next "make another weekly-promo" fills the brief, skips the design step, and drafts from the skeleton — while every review gate still runs.

Changes — skills (the flow)

  • hyperframes-core/references/review-loop.md (new) — the single source for the three passes: the plan proposed on a live board (§ 1), wireframe sketches marked built with one layout question (§ 2 — real words on plain blocks, run no CLI; a confirmed board is itself a valid deliverable when the user asked for a storyboard, not a video), the build dressing confirmed layouts (§ 3, worker or inline), the final look plus a once-only recipe-freeze offer whose confirmation teaches the recall phrase (§ 4).
  • The three narrative workflows' Steps 3/4/6 collapse to references plus their sketch stand-ins (captured-asset blocks for product-launch-video, plain code panels for pr-to-video); the confirmed-sketch handoff stays in each frame-worker prompt. general-video plans on a board for multi-scene narrative pieces — its sketch pass is layout-before-animation with the user watching.
  • The router treats "I want a storyboard" as a process request rather than a route, closes exploratory intake by recommending a route plus how the run will review, and recognizes a named recipe or "like last time".
  • brief-contract.md § 2, Remembered defaults — read the merged preferences before Round 2; a remembered value becomes the recommended option with a receipt ("1:1 — you confirmed this in world-cup-explained"). The comments channel (silent submit, one reply picks it up, check the file before the words) lands in § 1; the sidecar schema and the built status rung in storyboard-format.md.

Changes — studio + server (the surfaces)

  • Per-frame comments — a comment box on every board tile, batch Submit comments (N) writing .hyperframes/frame-comments.json (a resubmit wins per frame; unconsumed comments on other frames are kept); submitted-but-unconsumed comments stay visible — toolbar banner + per-tile echo — until the agent consumes the file.
  • The board keeps itself current — new GET /projects/:id/signature (cheap: the CLI adapter's watcher-invalidated cache), the storyboard payload carries the signature it was derived from, and the view polls at 2 s (hidden tabs skipped), refetching in place with no loading flash. Posters bake the signature into their URL so tiles fill in as sketches land; the empty state upgrades itself when STORYBOARD.md appears, and its handoff prompt points the agent at the review loop.
  • preview lands where review is — the storyboard view while any frame is built or the board is pure planning; the timeline once assembled.

Changes — media-use (the memory engine)

  • scripts/lib/prefs-store.mjs + scripts/prefs.mjs — project .media/preferences.json (committed, the team inherits it) + personal ~/.media/preferences.json; a value earns the personal tier by being confirmed in two different projects (a user-side sightings ledger accumulates the cross-project evidence), so a one-off choice never pollutes the global defaults.
  • scripts/lib/recipe-store.mjs + scripts/recipe.mjs — named versioned folders, not content hashes (a recipe is an evolving bundle): freeze skeletonizes the storyboard (structure kept — durations, transitions, srcs, Video direction; statuses reset; content blanked to per-frame fill-ins naming each beat's role), list merges tiers, use imports and copies the frame spec in. resolve --type recipe --entity <name> delegates like grade/lut.

The trust model

  • Only confirmed answers enter memory — an inferred value, or a default nobody chose (the pipeline's TTS voice), is not an answer; a "go" that accepts the recommended defaults is. The first record announces itself once.
  • Preferences change the default, never the question — every ask-marked field still gets asked, and what the request says this time beats what was picked last time. A wrong guess costs nothing.
  • Recipes may skip the questions they answer — the bundle was approved as a whole, and adoption itself is the question. Gates are never skipped: a recipe fills in answers, not approvals.

Tests / verification

  • studio 1397 green (+7 comments-contract, +6 signature-poll); studio-server 271 (signature endpoint, storyboard signature); cli 1439 (+5 storyboard-landing); media-use lib 168/168 under node --test (+9 preferences, +7 recipes: merge precedence, promotion at two projects, provenance reset on value change, skeletonization, version bump + archive, tier import).
  • Typecheck across studio/studio-server/cli; oxlint + oxfmt clean; skills-manifest regenerated.
  • Server-side E2E: the comments sidecar PUT lands in the contract shape; the signature moves when a frame file changes; preview --type recipe freeze→fresh-project use round-trips through the user tier.

…d, status-aware preview landing

Per-frame comment boxes on the storyboard board batch into
.hyperframes/frame-comments.json (a resubmit wins per frame; unconsumed
comments on other frames are kept). Submitted-but-unconsumed comments
stay visible — a toolbar banner plus a per-tile echo — until the agent
consumes the file; the banner also says what to do next (reply anything
in the agent chat).

The board keeps itself current: GET /projects/:id/signature exposes the
watcher-cached project signature, the storyboard payload carries the
signature it was derived from, and the view polls at 2s (hidden tabs
skipped, re-checked on visibility), refetching in place with no loading
flash. Posters bake the signature into their URL so tiles fill in as
sketches land and a poster that failed mid-write retries on the next
version; the empty state upgrades itself when STORYBOARD.md appears,
and its handoff prompt now points the agent at the review loop and uses
the parser's real status vocabulary (outline, not planned).

preview lands the browser on the storyboard view while the board is the
review surface — any frame built, or pure planning (srcs declared, none
on disk yet) — and on the timeline once the video is assembled.
…cess

hyperframes-core/references/review-loop.md is the single source for the
three-pass collaborative review: the plan proposed on a live board
(§ 1), wireframe sketches marked built with one layout question (§ 2 —
real words on plain blocks, run no CLI; a confirmed board is itself a
valid deliverable when the user asked for a storyboard, not a video),
the build dressing confirmed layouts (§ 3, worker or inline), and the
final look (§ 4). Autonomous runs skip every gate and keep one question
before render.

The three narrative workflows' Steps 3/4/6 collapse to references plus
their sketch stand-ins (captured-asset blocks for product-launch-video,
plain code panels for pr-to-video); the confirmed-sketch handoff stays
in each frame-worker prompt. general-video plans on a board for
multi-scene narrative pieces in collaborative mode — its sketch pass is
layout-before-animation with the user watching. The router treats
"I want a storyboard" as a process request rather than a route, and
closes exploratory intake by recommending a route plus how the run will
review.

The supporting contracts land next door: the comments channel (silent
submit, one reply picks it up, check the file before the words) in
brief-contract § 1; the sidecar schema and the built status rung in
storyboard-format; the mode question asked first and alone in the three
workflows' Step 0.
Two tiers of memory on media-use's existing two-tier storage split.

Preferences (lightweight): confirmed brief answers — destination, aspect,
language, mode, voice, style preset — recorded to the project's
.media/preferences.json (committed, the team inherits it) and promoted
to the personal ~/.media/preferences.json once the same value is
confirmed in two different projects (a sightings ledger accumulates the
cross-project evidence user-side, since project files can't see each
other). prefs.mjs get/record; merge reads project-over-user; a changed
value restarts its provenance.

Recipes (heavyweight): one approved run frozen as a named, versioned
bundle — frame.md, the storyboard skeleton (structure kept: durations,
transitions, srcs, Video direction; statuses reset to outline; content
blanked to per-frame fill-ins naming the beat's role), and the confirmed
brief values. Named folders, not content hashes: re-freezing bumps
version and archives <name>@v<N>; a freeze is already confirmed, so it
promotes to the user tier immediately. recipe.mjs freeze/list/use, plus
resolve --type recipe --entity <name> delegating like grade/lut.

16 new node --test cases; the media-use lib suite is 168/168.
brief-contract § 2 gains Remembered defaults: read the merged
preferences before Round 2 and let a remembered value become the
recommended option with a receipt naming its source project. Memory
changes the default, never the question — every ask-marked field still
gets asked, and what the request says this time beats what was picked
last time. Record only what the user actually confirmed (a defaulted
voice nobody chose is not an answer; a "go" that accepts the
recommended defaults is). The first record announces itself once;
after that the receipts carry the reminder. In autonomous mode a
remembered value becomes the decided value, receipt included.

The three narrative workflows read the remembered defaults before
Round 2, record the confirmed answers at the Step 0 gate, record the
chosen preset at the Step 2 gate (pr-to-video excepted — its preset is
fixed), and fall back to the remembered voice when the request names
none. general-video's discovery reads the same defaults.

Recipes wire in at both ends: Step 0 checks for a matching recipe
before the mode question — one question, plural-aware, and adopting
one fills the brief, skips the design step, and drafts the storyboard
from the frozen skeleton while every review gate still runs. The
review loop's final look (§ 4) offers the freeze once after approval,
and the confirmation teaches the recall phrase — the name is something
the system reminds the user of, never something they must remember.
The router recognizes a named recipe or "like last time" as a route.

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat(skills,studio,media-use): the review loop and user memory

Big feature, well-structured. Six interleaved pieces that compose into the three-pass review flow + user memory. I'll take them in the order they build on each other.

1. Project signature polling (studio-server + studio)

resolveProjectSignature extracted from preview.ts into projectSignature.ts and reused by preview, projects, and storyboard routes — good SSOT improvement, was inline before.

New /projects/:id/signature endpoint is a cheap poll target. useProjectSignaturePoll polls every 2s, skips hidden tabs, avoids concurrent requests, and no-ops when the baseline is undefined (initial load not yet done). Well-tested: signature mismatch fires, match stays quiet, hidden tabs skip, re-arms after caller refresh, cleanup on unmount.

The storyboard response now carries signature in both the absent and present branches — tested in storyboard.test.ts. The board uses it as posterVersion on FramePoster (cache-bust on the thumbnail URL via a ?sig= param).

2. Preview landing on storyboard view

studioLandingSearch determines the landing view from frame statuses: any built → board (sketch review in progress), all outline with declared srcs but no files → board (planning), otherwise timeline. Exported and tested. The deep-link URL puts ?view=storyboard before the hash — documented in preview-render.md.

3. Frame comments (studio + shared contract)

Clean separation between pure logic (frameComments.ts — all the data transforms, fully tested) and React state (useFrameComments.ts — the hook that wires drafts, submit, and pending-refresh to the file manager context).

The buildCommentsFile merge logic is right: a resubmit before the agent consumes the previous batch keeps unconsumed entries for frames the new batch doesn't mention, overwrites the ones it does, sorts by frame index. The passForFrames derivation (furthest status on the board determines the pass) is simple and correct.

The comment box per tile (StoryboardFrameTile.tsx) and the batch submit bar (CommentsSubmitBar in StoryboardLoaded.tsx) are clean UI additions. The useEffect on data.signature in StoryboardLoaded re-checks the comments file when the board refreshes — catches the moment the agent deletes it after consuming.

The contract is defined in storyboard-format.md § Frame comments and cross-referenced from brief-contract.md § 1 ("the comments channel"). Good.

4. Preferences — two-tier remembered defaults (media-use)

prefs-store.mjs: atomic writes (tmp + rename), tolerant reads (corrupt = empty), two-tier with a clean promotion rule (PROMOTE_AT = 2 distinct projects). style_preset keyed per workflow via preferenceKeyFor. User-tier sightings ledger keeps cross-project evidence in the only place that can see it (the user file). Best-effort user-tier writes — a read-only home directory never fails a brief.

Test coverage is solid: record + merge, no one-project promotion, two-project promotion, idempotency, changed-value provenance reset, project-overrides-user, per-workflow keys, unknown key rejection, corrupt file handling.

The consumption contract (brief-contract § 2, "Remembered defaults") is clear: a remembered value changes the recommended default with a receipt — never skips a question, and explicit request content always wins. Each workflow's Step 0 references this section.

5. Recipes — frozen video bundles (media-use)

recipe-store.mjs: skeletonizeStoryboard is the interesting piece — parses markdown, keeps structure (durations, transitions, src paths, Video direction block, style frontmatter), blanks content (message, audience, narration, per-frame prose → one <fill in> placeholder per frame naming the beat's role), resets all statuses to outline. Well-tested against a realistic storyboard fixture.

freezeRecipe: merges preferences into recipe.json, copies frame.md, generates the skeleton, appends a manifest record, promotes to user-tier immediately (a freeze is already confirmed). Re-freezing bumps version and archives the old folder as <name>@v<N>. useRecipe: imports from user tier if the project doesn't have it, copies frame.md over, returns brief values + skeleton path. listRecipes: two-tier merged (project wins), skips @v archives.

Also wired into resolve.mjs as --type recipe — consistent with the existing resolve pattern for other asset types.

6. Skill updates (faceless-explainer, pr-to-video, product-launch-video, general-video, hyperframes)

The same changes replicated across all storyboard-planning workflows:

  • Recipe check before Round 1
  • Round 1 sent alone (explicit "wait before Round 2" wording)
  • Remembered defaults before Round 2 questions
  • Sketch pass at Step 4 with workflow-specific stand-ins
  • Worker context includes confirmed-sketch flag + keep-layout rule in frame-worker.md
  • Gates include sketch board confirmation (collaborative)
  • Voice fallback to remembered voice before pipeline default
  • Review loop reference at plan presentation and final look

This is intentional replication — each workflow SKILL.md is a standalone document loaded independently. The shared process lives in review-loop.md; each skill wires its steps to specific sections. Correct pattern for the skills architecture.

SSOT check

  • resolveProjectSignature unified — previously inline in preview.ts, now shared across three routes. Good.
  • Frame comments contract: defined once in storyboard-format.md, implemented in frameComments.ts, cross-referenced from brief-contract.md. Single source.
  • Preferences consumption rule: defined once in brief-contract.md § 2, referenced by every workflow. Single source.
  • Review loop process: defined once in review-loop.md, wired into each workflow at the appropriate steps. Single source.
  • Skill replication: intentional — standalone docs, not shared code.

CI note

Skills: manifest in sync is failing — the manifest hash/count may need a refresh after the skill file changes.

Verdict

LGTM. Clean architecture, good test coverage, well-separated concerns. The CI manifest check needs a fix before merge. Looking forward to the additional tests Miao mentioned.

Review by Miga

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HF-specialist review — COMMENT (external author; stamp = James's call)

Audited (in addition to Miga's general coverage): the new shared-contract file (review-loop.md) end-to-end and its integration with brief-contract.md + storyboard-format.md; the Studio-side signature-polling design (useProjectSignaturePoll.ts + server-side projectSignature.ts); the frame-comments file protocol (frameComments.ts); the preferences store's two-tier promotion logic (prefs-store.mjs); and skeletonizeStoryboard's structure-keep/content-blank behavior (recipe-store.mjs).

Trusting: Miga's coverage on the 6-piece composition (project-signature polling, preview landing, frame comments, prefs two-tier, recipes, skill updates). Not duplicating; adds below focus on the HF architectural angle.

Design fit — shared-contract pattern

review-loop.md (+41 lines, new) follows the exact shape established by brief-contract.md and story-spine.md (HF#2058): shared rules file + workflow SKILLs bind to it via markdown pointers + explicit cross-references between siblings. Same "invariants centralized, executable script local" split. Load-bearing cross-refs from review-loop.md §§ 1-4 back into brief-contract.md § 1 (interaction mode / comments channel) and storyboard-format.md (frame statuses, comments file) are all present and land correctly on the referenced targets. Fits cleanly.

brief-contract.md gets two additions — "The comments channel" (§ 1) and "Remembered defaults" (§ 2) — that widen its scope from interaction to interaction + memory. Reasonable expansion; the file already handled ordering + gates + fields, so preferences memory as a field-level attribute belongs here.

Studio storyboard board — signature polling design

useProjectSignaturePoll.ts:29-67: 2s interval, skips when document.hidden, skips when previous request inFlight, visibility change triggers immediate re-check, no baseline (signatureRef.current === undefined) → no compare (safe), any fetch failure → skip tick (fails open). Solid shape. Comparison baseline is the signature the caller's data was loaded with, so refetch naturally re-arms.

Server-side projectSignature.ts computes a stable content-aware hash: text files ≤ 2MB use content, everything else uses mtime + size. Cache keyed by (mtime + size + relative-path) fingerprint avoids re-reading unchanged files — good. Excluded dirs list at projectSignature.ts:18-30 covers the standard cases (.git, node_modules, outputs, renders, .next, .cache, dist, build, coverage, .hyperframes, .vite).

Concern (nit-level, not blocking): STUDIO_SIGNATURE_MANIFEST_PATHS at projectSignature.ts:32-35 is a hand-maintained allowlist of .hyperframes/* files that DO count toward the signature (studio-manual-edits.json, studio-motion.json). The .hyperframes/ dir itself is otherwise excluded. This means a future Studio subsystem writing to .hyperframes/newthing.json will silently NOT trigger poll refreshes until someone remembers to add it here. Two mitigations to consider (either fine, both fine): (a) invert to a denylist that skips .hyperframes/frame-comments.json (outbound feedback, doesn't need to trigger self-refresh) and includes everything else under .hyperframes/; or (b) add a test that pins this list against the actual Studio-server write surface so a mismatch fails CI.

Concern (nit-level): collectProjectSignatureFiles at projectSignature.ts:65-99 recurses without a depth or file-count cap. Excluded-dirs handles the common problem cases, but a non-standard build directory (someone's .next renamed to custom-build) would churn the 2s poll traversing files. The cache short-circuits on unchanged fingerprints so steady state is cheap, but the first-hit or after-a-refactor cost is uncapped. Consider a soft cap or a file-count guard. Not blocking.

Frame comments file — the review's structured feedback channel

frameComments.ts:104-115 buildCommentsFile correctly handles the "resubmit before agent consumed" case: keeps entries for frames the new batch doesn't mention, overwrites the ones it does. Right invariant — a partial resubmit doesn't lose feedback on other frames.

parseCommentsFile:79-97 is lenient — malformed JSON, missing fields, or non-array comments returns null → treated as "no file". Aligned with the storyboard parser's lenient pattern (storyboard-format.md "The parser is lenient"). Good consistency across the read-side contract.

The cross-repo contract comment at frameComments.ts:6-10 explicitly pins the file shape to skills/hyperframes-core/references/storyboard-format.md § Frame comments. Nice discipline — keeps the shape from drifting between the Studio writer and the SKILL.md documented contract. A regression test that reads both the TS interface and the SKILL.md section and asserts field parity would tighten this further; not blocking.

skeletonizeStoryboard — structure/content split

recipe-store.mjs:123-141. What's kept: frame count, headings, duration, transition_in, src, style-ish frontmatter (format, arc). What's blanked: frontmatter content keys (message, audience, mode), per-frame content keys (voiceover / vo / voice_over / narration / scene / description / summary / caption / asset_candidates), every frame's status → outline, per-frame prose → placeholder <fill in: this video's content for the "..." beat — keep the layout role, replace the words.>.

Verified correct: mode correctly drops (interaction mode is per-run content, not reusable structure) per brief-contract.md § 1. arc correctly kept (reusable narrative structure) per story-spine.md. Case-insensitive detection via .toLowerCase() in skeletonFrameLine:96-101 matches how storyboard-format.md defines these keys.

Consistency observation: FRAME_CONTENT_KEYS at recipe-store.mjs:37-49 includes asset_candidates which isn't documented in storyboard-format.md's per-frame table. If that's an emerging convention worth documenting, add it to the storyboard-format table so both sides stay in lockstep. Not blocking — the skeletonizer treating unknown-but-content-shaped keys as content is the safer default.

Concern (nit-level): recipe archive accumulation. freezeRecipe:174-181 renames the old folder to <name>@v<N> before writing the new one, and scanRecipesDir:229-238 filters out @v entries from listings. No garbage collection — long-lived projects that re-freeze the same name repeatedly accumulate @v1, @v2, @v3, ... indefinitely. Not a bug, but worth a follow-up (either a hard cap like keep-last-3 or an explicit recipe.mjs gc command).

Preferences store — two-tier promotion

prefs-store.mjs:130-146 recordUserSighting: accumulates project sightings under sightings[fullKey][value] (per-value), promotes to preferences[fullKey] when seenIn.length >= PROMOTE_AT (2). Atomic writes via writePrefsFile:70-76 (tmp + rename) — good. Best-effort user-tier write in recordPreference:167-173 so a read-only home directory never fails a brief.

Concern (subtle, nit-level): sightings[fullKey][value] is keyed on basename(resolve(projectDir)) at prefs-store.mjs:161. Two distinct repos with the same basename (git clone in two different parents, e.g. ~/work/foo and ~/personal/foo) both writing the same value would deduplicate to ONE sighting via the dedupe([...]) on line 134, delaying promotion. Conversely, two separate projects with different basenames but the same identity (a directory rename) would double-count. The unaffected common case is the one that matters here (most people don't clone the same repo twice), so this is a low-frequency concern. Fix if it comes up: salt with a project-hash slice.

The keepProvenance logic in recordProjectTier:113-124 is nice: a changed value starts provenance over (old confirmations vouched for the old value), so a mid-flight preference change doesn't inherit stale trust. Right shape.

Manifest CI failure

Skills: manifest in sync fails at the "Verify skills-manifest.json matches skills/" step. Confirmed via the failing job log + the diff shape: hyperframes-core shows +1 file (14 → 15 — the new review-loop.md) and multiple hash rows updated for touched skills. This is a regen-only fixbun scripts/gen-skills-manifest.ts (or whatever the local generator command is) will produce a manifest that matches. Miga's read is correct.

Verdict

COMMENT — the review-loop / preferences / recipes design fits HF's shared-contract pattern cleanly (same shape as brief-contract.md + story-spine.md); the Studio signature polling is thoughtful (visibility-aware, in-flight-guarded, fails-open); skeletonizeStoryboard's structure/content split is coherent with brief-contract.md § 2's memory semantics; frame-comments file protocol matches its SKILL.md documented contract. Concerns found are all nit-level (allowlist maintenance risk on signature manifest paths, no file-count cap in signature traversal, no GC on @v archives, project-basename collision in sightings ledger). Manifest CI failure is regen-only. Stamp = @jrusso1020's call (external author WaterrrForever; requester Miao is allowlisted but not a trusted-stamper, per delegation rule).

Given Miao is explicitly holding on merge for more tests to land, none of these nits need to block the eventual merge — noting them for the next follow-up.

— Rames Jusso

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.

3 participants