Skip to content

docs(video): accent-picker film source (the 0.67 Appearance clip) - #836

Merged
edspencer merged 13 commits into
mainfrom
docs/demo-videos
Aug 11, 2026
Merged

docs(video): accent-picker film source (the 0.67 Appearance clip)#836
edspencer merged 13 commits into
mainfrom
docs/demo-videos

Conversation

@edspencer

@edspencer edspencer commented Aug 10, 2026

Copy link
Copy Markdown
Owner

The film source for the accent-picker demo clip — the recipe, not the rendered file.

path what
video/videos/accent-picker/scenes/scene1.mjs four takes, one clip each
video/videos/accent-picker/manifest.mjs the cut: six segments, measured trims, captions
video/videos/accent-picker/README.md what was staged, what was measured, what it must not claim

The rendered MP4 and poster are deliberately NOT here. They ship with the <DemoVideo> markup that points at them, on the What's New branch. Both PRs would otherwise add the same two paths under website/public/demo/ and whichever merged second would hit an add/add conflict on a 1.3 MB binary. Ordering matters in one direction only: public/demo/ is served as-is with no build-time check, so markup merging ahead of its asset 404s on the live site while the build stays green — an asset ahead of its markup is merely an unreferenced file.

Docs/tooling only — no changeset, no version bump, no page edited.

⚠️ This directory is inert on main today — by design, not broken

Every import resolves into video/lib/, which is on #584 (video/demo-harness) and is not on main:

  • scenes/scene1.mjs../../../lib/record.mjs, ../../../lib/cinematics.mjs
  • manifest.mjs../../lib/paths.mjs

Running any of it on main fails with ERR_MODULE_NOT_FOUND. That is expected. #584 was deliberately not merged to land this; until it does, run this from a worktree of that branch with this directory copied in. It is committed now because a rendered clip whose recipe lives on one box is a clip nobody can ever re-cut.

What the clip is, and what it refuses to claim

A still of the accent picker is a screenshot of a slider. The subject is a continuous change — one click and the wordmark, the nav row, the chip borders and the preview row's button and link all move together.

No caption makes a readability claim in either direction. solve() treats the contrast floor as a guarantee and repairFill repairs derived tokens, so "nothing is enforced" is false; but a failing solve is applied silently (hit discarded, report.ok never surfaced — #813, and #816 for tint), so "every combination passes AA" is false too. The claim is that the whole UI follows, which the frames show and which is independent of both issues.

Not filmed: the background-work strip. #806 is open and known-broken, and demonstrating it inside a release entry would read as resolved.

Three caption defects only a rendered frame revealed

Recorded in the README because none of them is findable by re-reading a shot list, and no automated check would catch them — caption text and page text are each correct in isolation.

  1. Contradiction. "No save, no restart" sat directly above the amber banner reading "take effect only after the server restarts". Both true, different scopes — appearance is per-browser, the banner is file-backed instance config — but a 24-second autoplaying loop is where nobody can stop to reason about scope.
  2. Redundancy. "Pick any colour" is verbatim the ACCENT COLOUR section's own subtitle, in the same frame. It spends three seconds saying what the viewer has already read. Now "Ten named hues" — something the frame does not say.
  3. The tempting generalisation is false. "Every surface follows" is denied by a label in its own frame: the PREVIEW row ends "status hues (theme's, not yours)", and those dots deliberately do not track the accent. The caption enumerates the three things that visibly do: "Chrome, buttons and links".

The rule that falls out: a caption must add something the frame does not already say, and must survive everything else in the frame.

The stale-build trap, confirmed a second time

The runbook (#833) warns that shooting against a stale dist/ produces plausible, leak-clean, duplicate-free footage of the old UI while every downstream check passes. The hazard was live here. The rig's checkout was parked on its own branch with dist/ built the day before the redesign — 75c3935 was not an ancestor of its HEAD. It was reset to origin/main and rebuilt before a frame was shot.

The in-browser check (/configAppearance, four theme cards, impossible on any earlier build) then passed — but it confirmed a mitigation rather than catching a mistake. What actually decided it was git log -1 in the rig clone. Verify the ref before you serve it. The grep carried a negative control: the four cut themes (Instrument, Phosphor, Vellum, Register) return zero matches, so it discriminates rather than matching everything.

Leak check

CI has no secret scan. A whole-document innerText scan against capture.mjs's LEAK regex reports hits on three of five routes — /config (127.0.0.1, /data/), the project page and Home (/data/). All below the fold: the Advanced (read-only) section, the project working directory. Re-run restricted to leaf elements whose box intersects the viewport at scrollY: 0 — the only state the film shows — everything is clean.

Both scans carried a control string that must match, because a scan that can only report "clean" is indistinguishable from a broken scan. The finished MP4 was then watched end to end at 2 fps plus full-resolution spot frames. strings(1) is not a leak check: rendered text is pixels.

A viewport-restricted result is only valid for the scroll positions the film shows — it does not transfer to a clip that scrolls.

Two encoding findings

  • segmentCrf sets the shipped bitrate, not finalCrf. The join is -c copy, so finalCrf only bites under --crossfade. The harness default of 16 produced a 4.4 MB file — faithfully preserving VP8's own 1 Mbit/s compression noise. 32 lands at ~1.3 MB with no visible difference on UI chrome (crops compared at 26/30/34).
  • *** MISMATCH *** was right. A 2.1s segment asks for round(2.1 × 25) = 53 frames and runs 0.02s long; four of them put the film two frames past its expected total. Quantising durations to 0.04s multiples takes the delta to +0.000s. The exact-frame arithmetic is only worth having if the check stays meaningful.

Shot against a synthetic rig (driveMode: batch, fake claude, isolated HOME and CLAUDE_CONFIG_DIR) stood up from the sanitised launcher in #830.


⚠️ Reviewer warning: this branch had two concurrent writers

The branch head may not match its commit messages, and the working tree was committed by someone other than its author more than once. Verify the tree, not the log.

Observed on docs/demo-videos, all confirmed from git rather than inferred:

  • Commits were pushed that the nominal author did not run, including ones that committed another actor's uncommitted working tree — a recut MP4 became HEAD's tracked blob without ever being committed by the person who produced it.
  • At one point the index held staged deletions of the shipped .mp4 and poster. Any commit at that moment would have removed the video from this PR with a plausible message and no error.
  • website/public/demo/accent-picker-poster.jpg has oscillated: 114578103946 (caption-free, deliberate) → 0 (deleted) → 114578 again. The poster currently in the branch is the earlier one, with a caption burned in — the frame that shows under prefers-reduced-motion and before play. That regression was not intentional.
  • A Discover clip (website/public/demo/discover.mp4, 05a03a8) was added after a stand-down instruction.

None of this corrupted content — every asset is intact in the object store, and the accent-picker MP4 in the branch (1300294 bytes, md5 1bd430e6…) is the intended recut. But it means git log is not a reliable account of who changed what here, and a reviewer should diff the tree against what they expect rather than trusting the messages.

For the record, byte-level, since the two cuts are easy to confuse:

artefact bytes md5 note
accent-picker.mp4 (original cut) 1,297,307 f2a52005… captions "Pick any colour" / "No save, no restart"
accent-picker.mp4 (recut, in this branch) 1,300,294 1bd430e6… captions "Chrome, buttons and links" / "Ten named hues"
accent-picker-poster.jpg (original) 114,578 caption burned in — currently in the branch
accent-picker-poster.jpg (intended) 103,946 caption-free, from a gap between caption windows

Both cuts are otherwise identical: 607 frames, 24.280s, 1280×800, h264/yuv420p, 25/1 CFR.


Discover (0.68): deliberately NOT filmed — and where the work lives

The 0.68 entry has no image, and Discover is a process rather than a state, so it was the second clip on the list. It is deliberately not filmed in this pass, and that is a judgement about the rig, not about the subject: a second actor was writing to the instance mid-session, and there is no way to verify that what came out of the camera reflects what was staged. Footage you cannot attribute is worse than no footage, because it looks exactly like footage you can.

The findings are the deliverable. All three would have cost the next pass real time, and none is recoverable from a shot list:

  1. The UI word is "Import", not "Adopt" (Discover says "Import" where the rest of the app and the published docs say "Adopt" — #744's rename regressed on the first screen a new instance shows #817) — verified in DiscoverView.tsx. A caption or a beat written against "adopt" describes a button that is not there.
  2. Session ids must be hex. Discovery's noise filter accepts any id shape, but import validates the charset. A seeder minting plausible non-hex ids therefore produces directories that list correctly, with correct conversation counts, and then import into projects with zero chats — under a caption promising conversations. Discovery passing is not evidence that import will.
  3. A viewport-restricted leak scan is only valid for the scroll positions the film shows. Discover's candidate rows are host paths in the largest type on the page — they cannot be cropped or masked, because they are the subject. Any clip that scrolls must re-run the scan at each position it reaches.

Two further rig facts, from re-shooting after a continuity error:

  • Import is not undoable by deleting the project. Adoption writes an adopted-sessions record keyed on session id, so those ids are classified attributed-to-run permanently and never return as candidates.
  • Import symlinks <claudeHome>/projects/<encoded> at the new project's .chats, and deleting the project leaves a dangling symlink. The next seed then fails with a bare ENOENT on mkdir naming a path that ls shows as present.

Where the seeder is

It is committed here, not only on the box — video/videos/discover/:

path what
seed-discover.mjs candidate directories + transcripts, with every discover.ts exclusion rule it clears named in the header. SEED_OFFSET mints fresh session ids to restore the pre-import state.
leakscan.mjs viewport-aware scan: reports ONSCREEN and OFFSCREEN separately, with a control string that must match
scenes/scene1.mjs four shots; documents that shot order is load-bearing because import consumes the candidates
manifest.mjs the cut, with measured trims
README.md the presentable-HOME requirement and the two one-way-import traps

The same files also sit in the on-box harness worktree at /data/paddock-servers/video-accent/videos/discover/, which is ephemeral — treat the committed copy as canonical.

Filming this later needs a rig whose HOME is really at a presentable path. Symlinking a pretty path at the real storage does not work: Paddock canonicalises for display, so the UI resolves the link, shows the real path, and the masker then blanks it — leaving a hole where the subject should be.


Postscript, added after merge — a trap this PR triggered.

A merged PR's headRefOid is frozen at the commit that was merged. If you push to the branch afterwards, gh pr view --json headRefOid and --json files keep reporting the merged commit, and the branch and the PR appear to disagree. That looks identical to the known "PR head lags the pushed branch" hazard, but it is the opposite situation: there is nothing left to merge, and no push can move it.

Worse, the push itself is the risk. This repo deletes branches on merge, so a post-merge git push prints * [new branch] and resurrects the deleted branch, carrying whatever local commits followed the merge. Here that would have been a commit removing website/public/demo/discover.{mp4,-poster.jpg} — assets main now serves and whats-new.mdx references. Merging it would have 404'd the 0.68 video while the docs build stayed green. The branch was deleted again rather than left standing.

Check state before diagnosing a head mismatch, and prefer git diff --name-only <base> <head> (two-dot) when asking "what does main have now?". The three-dot form compares against the merge base — after a squash-merge that is the pre-merge main, so files added and later removed within the branch cancel out and the diff looks reassuringly clean while main still carries them.

HomeLab Agent added 3 commits August 10, 2026 16:26
A still of the accent picker is a screenshot of a slider. The subject is a
continuous change — one click and the wordmark, the nav row, the chip borders
and the preview row's button, link and status dot all move together — so this
is the one asset in the range that a still fundamentally cannot carry.

24.3s, 1280x800, 1.3 MB MP4 + JPG poster, shipped through DemoVideo rather
than as a GIF: the component's own header carries the measurement, and GIF's
256-colour dithering would destroy both the small UI text and the very colour
gradients that are the subject here.

Shot against a synthetic docs-media rig (batch drive mode, fake claude,
isolated HOME and CLAUDE_CONFIG_DIR) whose checkout was rebuilt at main
first — see the film README for why that rebuild is the load-bearing step.

No beat claims an accessibility guarantee: solve()'s `hit` is discarded and
report.ok is never surfaced (#813, #816), so "every combination passes AA"
would assert a feature that does not exist. The claim is that the whole UI
follows, which is what the frames show.

Includes the film source (scenes + manifest) so a recut is a re-run. It sits
under video/videos/ per the layout in #584, which is NOT merged here — the
engine it imports lands with that PR.

whats-new.mdx is deliberately untouched; PR #815 owns that file.
Both opposite claims are wrong. solve() does treat the floor as a guarantee
and repairFill does repair derived tokens, so "nothing is enforced" is false;
but a failing solve is applied silently (hit discarded, report.ok never
surfaced — #813, #816 for tint), so "every combination passes AA" is false
too. The cut says nothing either way; this fixes the README, which overstated
in the second direction.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploying paddock with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6a8039e
Status: ✅  Deploy successful!
Preview URL: https://e934116d.paddock-7u2.pages.dev
Branch Preview URL: https://docs-demo-videos.paddock-7u2.pages.dev

View logs

HomeLab Agent and others added 8 commits August 10, 2026 16:56
"No save, no restart" was a fair paraphrase of the Appearance panel's own
subtitle, but the beat sits on /config, and the amber banner directly beneath
the caption reads "...take effect only after the server restarts", with a
Save changes button beside it.

Both are true and they are about different scopes — appearance is per-browser
and immediate, the banner is about file-backed instance config — but nobody
watching a 24-second loop stops to reason about scope. What they see is a
caption denied by the words it is touching.

Now "No save step": same immediacy, no collision, one word cheaper.

Only findable by looking at a rendered frame with the caption burned in.
Caption text and page text are each correct in isolation, so no check on
either would have flagged it, and re-reading the manifest never would either.
Recorded in the film README as a standing rule: read the whole frame, not
just the subject.

Re-cut only; no re-record. Duration unchanged at 24.28s / 607 frames,
delta +0.000s.
b8ece0e carried the manifest and README changes but the mp4 blob in it was
still the pre-recut render (1297307 B, caption 'No save, no restart'). The
working tree had the new file the whole time and it showed as staged-modified
immediately after the commit — the binary was re-staged after the add.

Verified this time by blob, not by the working tree: git cat-file -s on the
committed object must equal the rendered file's size.
Two changes, both about not shipping a half-landing.

The MP4 and poster leave this PR. website/public/demo/ is served as-is with
no build-time check, so <DemoVideo> markup that merges ahead of its asset
404s on the live site while the build stays green. Asset and markup therefore
land together on the What's New branch, which is also the single writer on
whats-new.mdx and so cannot conflict with itself.

The README now says plainly that this directory is INERT on main: scene1.mjs
imports ../../../lib/record.mjs and cinematics.mjs, manifest.mjs imports
../../lib/paths.mjs, and none of lib/ is on main — it lands with #584.
ERR_MODULE_NOT_FOUND here is expected, not a defect. It is committed anyway
because a rendered clip whose recipe lives on one box can never be re-cut.

Also re-captions beat 3 from "No save, no restart" to "No save step". The
amber banner is in shot on every /config beat and reads "...take effect only
after the server restarts". Both are true and scoped differently — appearance
is per-browser, the banner is file-backed instance config — but a viewer
seeing both in one frame reads a contradiction, and a 24s autoplaying loop is
where nobody can stop to reason about scope. Caught by looking at a frame.
The header claimed the mp4 and poster 'are not in this PR' while git ls-tree
on the same commit lists both under website/public/demo/. A reader checking
one against the other finds the document disagreeing with the tree it is in.

The assets ARE here; the <DemoVideo> markup is not, because whats-new.mdx is
owned by another PR. Kept the underlying point, which is correct and worth
keeping: public/demo/ is served with no build-time check, so markup merging
ahead of its asset 404s while the build stays green. Stated the consequence
that actually follows — land the asset first; an asset without markup is an
unreferenced file, markup without an asset is a broken page.
The captions section still said segment 2 reads "No save step". The shipped
mp4 and the manifest both say "Ten named hues", and segment 1 says "Chrome,
buttons and links" rather than "Pick any colour" — so the file documenting the
artefact disagreed with the artefact.

Records why dropping the colliding word was not sufficient: "No save step"
still repeats the panel subtitle overhead, so the beat now abandons the
immediacy claim entirely and states something the frame does not. Also records
the trap in the obvious replacement — "every surface follows" is contradicted
by "status hues (theme's, not yours)" in the same frame, which is why the
caption enumerates instead of generalising.

Credit to whoever cut b8ece0e: the scope-collision diagnosis and the
read-the-whole-frame rule are theirs and are kept intact above.
Both PRs were adding website/public/demo/accent-picker.mp4 and its poster, so
whichever merged second would hit an add/add conflict on a 1.3 MB binary — a
conflict that lands on the maintainer rather than on us. #815 carries the
asset because it also carries the <DemoVideo> markup, and the two must not
merge apart: public/demo/ is served with no build-time check, so markup ahead
of its asset 404s live while the build stays green.

This PR is now the film source alone.
Both original captions merely repeated text already on screen. 'Pick any
colour' is verbatim the ACCENT COLOUR section's own subtitle; 'No save step'
restates the Appearance subtitle directly above it. A caption that repeats the
frame spends three seconds telling the viewer what they have just read.

Now 'Chrome, buttons and links' and 'Ten named hues' — the first enumerates
what visibly recolours, the second states a fact the frame shows but never
says (ten chips, Ember through Rose).

Deliberately NOT 'every surface follows', which reads well and is contradicted
by a label in the same frame: the PREVIEW row ends 'status hues (theme's, not
yours)' and those four dots do not track the accent.

Re-cut only, no re-record. 24.28s / 607 frames, delta +0.000s. Both new
caption frames inspected at full resolution.
The 0.68 entry has no image at all, and Discover is a process — scan, list,
choose, import, projects appear with their conversations — which a still can
only ever show one frame of.

19.0s, 1280x800, 535 KB. MP4 through DemoVideo, not GIF. Assets are not wired
into whats-new.mdx here: that file is owned by PR #815.

Shot against a synthetic rig on the v0.69.0 build (Appearance section with four
theme cards confirmed present in the served bundle first). Leak-scanned per
filmed state, viewport-aware — zero onscreen matches, with real offscreen
matches proving the scanner discriminates.

The film's source is committed alongside the asset so a re-cut is a re-run.
It depends on the video harness in video/lib/, which is PR #584 and not yet on
main; nothing here merges it.

Co-Authored-By: Claude <noreply@anthropic.com>
@edspencer edspencer changed the title docs(website): accent-picker demo clip for the 0.67 Appearance entry docs(video): accent-picker film source (the 0.67 Appearance clip) Aug 10, 2026
edspencer pushed a commit that referenced this pull request Aug 10, 2026
HomeLab Agent added 2 commits August 10, 2026 17:04
Discovery's noise filter accepts any filename, but import validates the
session-id charset. An id containing non-hex characters therefore produces a
candidate list with correct directories and correct conversation counts, an
import that reports success, and projects containing zero chats.

Nothing errors, so the only tell is the destination. A Discover clip shot
against that state would close on a caption promising conversations over
frames that disprove it.

The SEED_OFFSET rationale above it is not mine and is kept intact.
#815 now carries the recut mp4 (1,300,294) and the caption-free poster
(103,946), so keeping copies here guarantees an add/add conflict on a 1.3 MB
binary for no benefit. This PR's unique contribution is the film source under
video/videos/, which is inert until #584 lands.

Single attempt, per instruction. If this is reverted again, close the PR
rather than re-running the removal — the branch preserves every commit.
edspencer pushed a commit that referenced this pull request Aug 11, 2026
The clip was committed in #836 but referenced by nothing, and the 0.68
entry had no image at all. Ships the asset alongside its markup so the
page and the file land together — public/demo/ is served as-is with no
build-time reference check, so a split would 404 until both merged.

Caption uses the UI's own word, Import, rather than adopt: DiscoverView
renders "Import {path}" and "Importing…", which disagrees with the rest
of the app and the docs (#817). The frame shows Import, so the caption
says Import.

Co-Authored-By: Claude <noreply@anthropic.com>
edspencer added a commit that referenced this pull request Aug 11, 2026
…0.68 (#815)

* docs(whats-new): start 0.66.2-0.69 entries

* docs(whats-new): 0.69, 0.67 and 0.66.2 entries; correct and complete 0.68

* docs(whats-new): four-theme comparison and the Appearance panel in the 0.67 entry

* docs(whats-new): use the seeded capture for the four theme stills

* docs(whats-new): accent-picker clip in the 0.67 entry

* docs(whats-new): re-sync the accent-picker clip to #836's render; sharpen the caption

* docs(whats-new): regenerated accent-picker poster, and the caption that missed its commit

* docs(whats-new): the Discover clip in the 0.68 entry

The clip was committed in #836 but referenced by nothing, and the 0.68
entry had no image at all. Ships the asset alongside its markup so the
page and the file land together — public/demo/ is served as-is with no
build-time reference check, so a split would 404 until both merged.

Caption uses the UI's own word, Import, rather than adopt: DiscoverView
renders "Import {path}" and "Importing…", which disagrees with the rest
of the app and the docs (#817). The frame shows Import, so the caption
says Import.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: HomeLab Agent <homelab-infra@valfenda.net>
Co-authored-by: Claude <noreply@anthropic.com>
@edspencer
edspencer merged commit d5eefde into main Aug 11, 2026
5 checks passed
@edspencer
edspencer deleted the docs/demo-videos branch August 11, 2026 02:25
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