Place prepared Takes on an independent Timeline with gaps and overlaps. Present picture, sound and captions through explicit Use rules while preserving source timing and project-authored styles.
Unify Companion bands, parameter ownership and source writeback in Studio. Migrate components, examples and authoring guidance, remove retired Speech/Semantic Track interfaces, and retain protocol identifiers at @1.
Validation: repository suite (786 tests; six sandbox-blocked tests passed on permitted rerun), 43 Studio tests after audit fixes, TypeScript, public declarations, four example-package builds, chat Source check and npm file inventory.
A Windows job on f80a562 sat for 97 minutes after its last output. The suite had
reached "one local Worker admits later Builds while preserving shared Endpoint
capacity", whose poll for two terminal Build outcomes carried no deadline; one
Build did not complete on that run, so the loop polled every 5 ms until the job
was cancelled. Re-running the same commit passed, so the stall is intermittent
and its cause in the Worker launch path is not yet identified.
Three bounds now apply. The poll fails after 30 s and prints each Build's last
outcome. `node --test` receives `--test-timeout=120000`, well above the 5.75 s
slowest test measured on that run. The job takes `timeout-minutes: 20`.
The skill was the real directory at .agents/skills/hypit, with .claude/skills and
.codex/skills as container symlinks pointing at it. That kept one copy, but it made
.agents the de-facto main location and the two agent directories its dependents, and
because the whole skills directory was the link, neither agent could hold a skill of
its own without it landing in the shared directory.
The single copy now lives at skills/hypit, an ordinary repository source alongside
docs, packages and services. Each agent gets a leaf link to it:
.claude/skills/hypit -> ../../skills/hypit
.codex/skills/hypit -> ../../skills/hypit
Claude and Codex are peers, both agent directories stay real directories that can
still take agent-specific skills, and the links are relative so a clone works from
any path. Both Claude Code and the Codex CLI resolve a leaf link of this shape.
A neutral, dot-free location also lets skill catalogs install this skill: several
reject a source path whose segments begin with a dot, so .agents/skills/hypit could
not be addressed at all.
The move drops one directory level, so the fourteen repository-root references in
references/ lose one segment (../../../../docs -> ../../../docs). Paths naming the
skill are updated in reference-video-tools, references/host-setup.md and the
reachability test's skillRoot.
Verified against main: --links reports the same six pre-existing breakages and no
new ones, --orphans is 0 on both, and both --route sentence sets are identical once
the path prefix and the relative-depth rewrite are normalized — the move is a no-op
for a reader, which is what test/hypit-skill-reachability.mjs exists to check.
`hypit-skill-reachability.mjs` started two child processes without `windowsHide`, which the
repository-hygiene suite refuses. That suite is `pnpm test:release`, and `node test/run.mjs` globs
`*.test.ts`, so it never ran it — CI did, on both runners, and main went red on `9a40834f`.
Four commands take a `--batch` and each carried its own copy of the same twenty lines: attempt every
entry, catch per entry, partition into done and failed, shape a result. `runBatch` now owns the
attempting and the isolating; the result's own nouns stay at the call site, because
`compared`/`comparisons` and `reviewed`/`reviews` are what a reader of the output looks for and a
helper that also invented them would need an argument per noun to say nothing extra. Line count is
about flat — the saving is that there is one place where an entry's failure is decided to take only
itself down, not four.
`observe_reference --batch` reported a failed entry under `asked`; it is `input` now, as the other
three already were.
A sweep hands out four tasks per shot, and none of them said which command closed it — the two
commands that hand out a single task each did. `ObservationTaskRequest` now carries `record_with`,
so the caller never picks a recording verb: it copies the line out of the task. That is what makes
two record commands cost nothing, which is the argument for keeping them separate.
`make-placeholder` was the one command spelled with a hyphen while the other fifteen used an
underscore, and the method behind it was already `make_placeholder`. Nothing recorded a reason.
Verified: `pnpm check`, `pnpm test` (501, 0 failures) and `pnpm test:release` (9, 0 failures) — all
three CI gates this time, not two. A batch with one entry naming a shot that does not exist returns
`asked: 2, failed: 1` with the bad input beside its error and the other two answered. A sweep over
four shots hands out 15 tasks, 15 of which carry `record_with`, and the command one of them prints
records against the key it names. `make_placeholder` writes its PNG.
A program authored from a description reached its Build with nothing having looked at it. The
reconstruction route renders each element, has an observer read it, repairs what was named and stops
at a ceiling; the description route went from `preview_check` straight to spending money. The two
routes also disagreed in five places about how the `agent` observer works, none of them following
from the one difference anybody had written down.
`references/element-review.md` owns the round both routes run: one element at a time, render what
the Source configures, mock the layers a Build has not made, one round, two attempt ceilings, measure
rather than guess. `reconstruction/comparison-round.md` and `original-authoring/conformance-round.md`
hold only the delta — a reconstruction has a second picture and asks what differs, a description has
none and asks whether the picture is what it was asked to be. `references/script-time.md` takes the
Segment seam and the `estimate:Speech` duration out of `reconstruction/`, where two shared craft
files were already linking backwards to reach them.
Neither round is a loop: each is capped at one pass, so both files are named for what they are.
`review_element` prepares one picture against an `--intent-file` and hands out the task;
`record_review` closes it; `authoring_check` refuses to pass while any drawing element has never been
looked at. `reconstructionCheck` becomes `authoringCheck` with a mode, so a description project also
reaches the checks that never needed a reference: the `playback` refusal, frame coverage, and Frames
past the Canvas were unreachable to that route only because the function opened by resolving one.
One subagent per picture is now required where the harness has them, and the main agent may not open
the pictures itself. That is what makes the `agent` observer equal to the `gemini` one rather than
merely cheaper: on the paid path the reader is a separate request and cannot be the builder.
Five observer-path defects, none derived from the agent having no sound:
- `NO_SOUND` told the agent to answer from the measured transcript, which no task carried. Tasks now
carry `transcript_words` for their own stretch and `transcript_ref` for the whole file.
- `record_observation` rejected every narrow-question key it had just handed out: a shot id is
`shot-001` and the pattern's character class had no hyphen.
- A narrow question bypassed the cache, so gemini paid again for each repeat and an answer recorded
on the agent path was written where nothing read it. It now shares the sweep's cache, keyed by the
question as well as the shots, and reports `reused`.
- `--batch` held the agent path to the vendor launch gap: six questions at concurrency 1 took 20s of
`setTimeout` to build a list. Now 3ms.
- The shot/render duration assert sat inside the agent branch, so gemini compared a 3s shot against a
1s render without complaint.
Two defects found while exercising this. `render_element` repointed `source=` and `from=` when it
wrote the cut Source three directories down but not `src=`, so any Source with a relative
`media:Image` failed to render once a window was named. `preview.md` still described rendering one
element by hand through a browser test; it now holds `render_element`.
`test/hypit-skill-reachability.mjs` compares the sentences each route can reach, not the links. The
prose is hard-wrapped at 100 columns, so a line-based check passes while a clause goes missing —
`grep -rn "never inspected is a system"` finds three files and there are four.
Verified: `render_element` with no `--reference-id` returns `basis: "estimate"`; a subagent handed
the caption render reported it clipped at both frame edges; `authoring_check` went false → true
across two recorded reviews and refuses a blanked `playback`; re-submitting an unchanged picture
returns `reused` rather than a new task. `reconstruction_check`'s output against a real reconstruction
project is byte-identical before and after the split but for one deliberately changed line. Links 0
broken, orphans 0, description route 2500 → 2727 reachable sentences, every reconstruction-route loss
paired against its replacement by hand. `pnpm check` clean, 501 tests, 0 failures.
Every conflict resolved to the branch side, except CI, which stays as main has
it: the check job runs on ubuntu-latest and windows-latest. Linux is the 1x
billing rate against macOS's 10x, this repository is private, and the suite is
POSIX either way. Nothing else in .github/workflows/ci.yml differed.
The ranking package is taken from the branch whole, not only in its conflicted
files. main's 1b177713 removed the TierBoard variant from render.ts, types.ts,
style.ts and activation.ts; those files merge without a conflict, so keeping
them would leave the branch's component.ts, manifest.ts, surface.ts, schedule.ts
and index.ts referencing symbols that no longer exist. TierBoard is back.
packages/hyperframes/test/browser-visual.test.ts is deleted, as the branch has
it, and main's GPU-default edit to that file goes with it.
Two of main's additions had to follow the branch's model to compile:
- reference-video-tools reads a Selection through its single open/close anchor
pair. The branch's NarrativeSelection carries startAnchorId and endAnchorId
rather than a list of occurrences.
- reference-video-tools builds a StudioAdapterRegistry and passes workspaceRoot,
matching the signatures loadStudioAdapterRegistry introduced, and depends on
@hypit/video-cli for the Distribution package root.
@hypit/studio exports ./src/* so reference-video-tools can reach the modules it
imported before the branch declared an exports field.
`pnpm-workspace.yaml` globs `projects/*/packages/*` so a project-local author package
is one pnpm links, and `.gitignore` excludes `projects/` because a project is working
material. Both are right. Together they mean every local install writes importers a
clone will not have into the file that clone installs from.
The damage was not the importers. It was that the lockfile is dirty after every
install, so its diff stops being read, and seven real dependencies went in without
it — CI refused every run from that commit until it was noticed.
`repository-hygiene.test.mjs` now reads .gitignore and refuses a lockfile naming an
importer under any path it excludes, in two milliseconds, beside the eight invariants
already there. `pnpm lockfile:refresh` writes one it accepts, by setting that
directory aside for the install and putting it back.
The hygiene suite caught the refresh script starting pnpm without windowsHide on its
first run, which is the kind of thing it is for.
Four conflicts. Two were mechanical: `align-transcript.ts` and
`svml-playground/src/pipeline/placeholder.ts` are modify/delete, and the
deletion is this branch's intent — local alignment removes the need to
fabricate a program-audio digest, and Studio has no stand-ins to draw.
The other two are the same bug fixed twice. Both sides found that CSS
`-webkit-text-stroke` is centred on the glyph edge, so half the stroke eats
the letterform, and both moved to `VisualTextPaintLayer` with
`placement: "outside"` — which already existed at the merge base on
`VisualTextFlowElement`. That shared starting point is why they converged an
hour apart.
`packages/caption-fine/src/render.ts` and its test take main's version. Main
extends `VisualTextElement` and the HyperFrames renderer rather than moving
Caption Fine onto text-flow, and it carries two fixes this branch does not:
CSS blur is a radius covering about two deviations while the filter takes a
deviation, so passing the authored number through doubles every shadow and
glow in the repository, and its filter-definition dedup takes 80 defs to 16.
Neither file is touched by `a4f2b31f`, so taking main's reverts `d5ee32c7`
exactly and nothing else. `renderFineCaption` works on `ProgramSpace`, which
survives here as a projection of `SemanticTrack`, so the semantic API change
does not reach it.
The Pill-as-paint-layer and structured-typography work in `d5ee32c7` is a
real gain and is not preserved here; it belongs on top of main's
implementation as its own change.
`pnpm check` clean; `pnpm test` 510 pass, 0 fail, 15 skipped.
Three scripts were written in shell that only one shell speaks. pnpm hands a
script body to /bin/sh on POSIX and to cmd.exe on Windows, and what sh reads as
syntax cmd.exe reads as text.
`VAR=1 command` is the loud one: cmd.exe has no such form and looks for a
program named `HYPIT_BROWSER_TESTS=1`. It fails where you can see it.
The quoted discovery pattern is the quiet one, and it is the same defect this
repository already found in its main suite. sh strips the quotes and unittest
receives `test_*.py`; cmd.exe does not, and unittest receives six characters
that match no file. Both exit 0:
-p 'test_*.py' stripped Ran 11 tests OK exit 0
-p 'test_*.py' literal Ran 0 tests NO TESTS RAN exit 0
Double quotes are stripped by both shells, so the pattern needs no runner —
only the right quote. The two environment gates move into the runner that
already exists for this, as named suites, where they are assignments rather
than syntax. That also settles the third defect: the OpenCV script named the
POSIX interpreter path outright, though `deployment.ts` has branched on the
platform for that venv all along.
An environment already set by the caller still wins. These are defaults for
running a suite, not a policy about it.
The patterns were written for sh and quoted for it, but pnpm hands a script
body to cmd.exe on Windows, which neither strips the quotes nor expands the
pattern. They would have arrived at `node --test` as literal text — and
`node --test` reports "tests 0" and exits 0 on a pattern that matches nothing,
so a suite that ran not one test reads exactly like a suite that passed.
Expanding the patterns in Node takes the shell out of the question everywhere,
passes explicit paths rather than patterns, and gives the count somewhere to be
checked. The unquoted spelling would have fixed the first half and left the
second: sh has no globstar, so today's patterns survive to Node's glob by
accident, and dropping the quotes would silently narrow the suite to whatever a
future nested test directory happened to match.
Each rule filters the scanned path with a forward-slash pattern, and the path
came from `relative()` over two `URL.pathname` values. On Windows that yields
backslashes and a leading slash before the drive letter, so four of the six
rules matched nothing and passed — a scan that inspected the repository and a
scan that never reached it report the same thing.
The guard is the point of the commit: it names a path the scan must reach,
refuses a separator that is not "/", and holds the source and manifest counts
to a floor. Without it the same silence returns wearing a different cause.
Two of mine. `git add -A` swept a reverse-engineering scratch project into
33ebbebb — twenty-one files of real but disposable output that were deliberately
supposed to stay on disk. Removed and ignored by shape.
And the preview check read raw text, so the scaffold's own template matched: it
reported `preview/${tag}.png` as a missing picture. Same mistake I had just
fixed in the import check and left standing one function away. Both now read the
syntax tree. Verified still sharp by deleting a real preview and watching it
fail.
The suite is green for the first time since the preview gate landed.
The preview check only fired once a preview was declared, so a package that
skipped the declaration escaped it — and a component that cannot draw itself is
exactly the one whose author never got a picture out of it. Publishing a
VisualTrack is the structural signal, independent of whether anyone remembered
to promise one. Every existing drawing Surface already passes.
The import check read raw text, so the scaffold templates — which contain the
imports the *generated* package will have — counted as the generator's own, and
would have demanded packages/cli depend on everything it can generate. It now
reads the syntax tree, where a string is a string. Verified still sharp by
adding a real undeclared import and watching it fail.
I pushed the scaffold with that break in it: I verified with a narrower command
than the repository's own suite.
The shot detector accepted any boundary more than 0.3 seconds after the last
one, so a 45-second reference produced four shots under a second and two of
0.33s that the model rejected outright with INVALID_ARGUMENT — a request, a
retry loop and no evidence. A shot is now at least a second, and a trailing
fragment joins the shot before it instead of becoming one: 23 shots became 20,
and none are too short to observe.
The same-take and overlay questions sent an identical part list — two clips and
a tail frame — to learn two things about one cut. They are now one observation
under two headings, which halves the boundary requests and the bytes they
carry. Together with the clip encoding now being reused within an invocation
rather than repeated for each of the five observations that mention a shot,
this takes the sweep from 113 requests to 79.
Pacing moves to HYPIT_REFERENCE_CONCURRENCY and HYPIT_REFERENCE_LAUNCH_GAP_MS.
It depends on the quota behind the credentials, which the calling agent cannot
know, so it stays out of the CLI surface.
A declared Surface preview must now name a picture that exists and is not
empty. A component that cannot draw itself cannot produce one, so this is the
cheapest available check for a component demanding something the graph has to
supply — the failure it was written for was already sitting in the tree.
`tools/` held one file once the Playgrounds moved to `packages/`, and that file
is a test. A directory named for tools that contains no tool only invites the
question of what belongs there; `test/` already owns repository-level material,
holding the shared fixtures the boundary tests read.
The scan roots drop `tools` along with the directory. Leaving the name behind
would have failed every one of the four tests with ENOENT rather than a
boundary report, since the scan reads each root directly.
The layout guide named `tools/` but never named `test/`, so the entry is
replaced rather than removed.
Replace the generic playground with a file-backed Caption Playground, separate source workspaces from installed package roots, and derive exact transitive Module providers in Node package locks.
ValueSchema gains `format` and TypeDeclaration gains `default`. A schema
could say a value is a string but not whether it is a colour or a font
family, and nothing could say what an unspecified one should be -- so every
consumer decided both for itself, and the answers drifted. A colour and a
digest have an exact shape, so validateStoredValue now checks them: a
format nothing enforces goes stale the same way a copied constant does.
Each module now states its own Recipe once, in a recipe.ts its Surface
reads too, with the required key set taken off the default rather than
restated beside it. Four literals that had to be kept in step by hand are
gone.
The playground lists nothing any more. It asks the manifests which modules
declare a Producer outputting a VisualTrack -- exactly the four that do --
builds a form from the declared input schemas, seeds it from the declared
defaults, and calls the module's own bound handler. Its five adapter files
are deleted. A module that gains a visual Producer now appears there on its
own; one that loses it disappears.
Where a module has no honest default it says nothing: a value carrying
content-addressed media cannot have one, because the module has no bytes to
point at, and the form asks for a file instead.
Media is addressed the way the Runtime addresses it: the picker hashes the
file with Web Crypto and registers a real SHA-256 digest, because
assertHyperframesDocument cross-checks every placeholder against the
document's Artifacts and runs isDigest on each. The form value is the
digest and the bytes stay in the registry, so a component's parameters
remain a plain canonical value a test can build without a browser.
A component with no media yet contributes no Track rather than a stub
digest the compiler would reject -- an empty picker is a state to pass
through, not an error to report. A digest with no bytes draws a placeholder
and a warning instead of blanking the frame.
B-roll playback is derived, not offered: the compiler requires freeze for a
still and forbids it for a moving one, so a control could only offer a way
to be wrong.
speech-basis takes no appearance at all -- projectSpeechVisual always emits
a full-frame cover element -- so the component has clips and nothing else.
That is why speech.full stays in the unmatched list: it is declared in the
golden stylesheet and no Surface reads it.
Selecting an .svs imports every Recipe in it and previews each one
separately. A sheet is a discovery source, never a dependency: what it
produces is form values, and from there the form is the only source of
truth -- nothing in the render path touches parseSvs unless you chose a
sheet.
Recipes are matched by exact property key set. A Film Recipe is not a
preview subject; it is where the frame size comes from, so opening the
golden sheet fills the canvas with 1080x1920 at 30. A Recipe that matches
nothing is listed with its keys rather than dropped -- font.*, speech.full
and broll.product are real Recipes with no preview yet, and naming them is
the difference between a gap and a silent omission.
Browsing is served by the dev server rather than the File System Access
API, which is Chromium-only and cannot follow a stylesheet's relative
asset path without a second grant. Containment is checked on the resolved
path, so traversal is refused however it is spelled.
Also pins source files as text: a raw NUL in a separator literal works at
runtime and reads as a space, but makes git treat the file as binary and
stop diffing it.
Each adapter converts form values into its package's own Program type and
then calls that package's real renderer -- renderCaptionTrack and
renderTextTrack -- so the preview is the production lowering rather than a
lookalike. Both are pure and free of node imports, which is what lets them
run in the browser at all.
The form is generated from each component's ValueSchema. Nothing in the
playground validates: the packages' seal and assert functions are the
judge, because they are what a real Build runs and their messages are
already good. A failure shows that message beside the form and leaves the
last good frame on the stage.
Recipes are matched by exact property key set, never by path prefix --
caption.dialogue and caption.short-cues share a prefix and describe
unrelated things, and the compiler itself matches on the key set. The two
key sets are copied from their Surface handlers, so a test reads the
literal back out of the source and fails if they drift.
planCaptionPresentation turned out to read only display text and timing,
so synthetic cues with no alignment tokens preview all three modes
faithfully rather than needing a reduced one.