Files
wehub-resource-sync 85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00

12 KiB
Raw Permalink Blame History

Anti-Patterns

You default to these. Stop.

Each entry: the bad habit, what it produces, what to do instead. Written in the voice of someone who has watched an agent do this 10 times — because we have.


Layout

You default to center-aligned crown.

.crown-plane { left: 0; right: 0; text-align: center } is the template default, and you'll leave it on every video because it worked once. On a subject that sits right-of-center (Jobs in a 16:9 frame), the body eats the middle 60% of the word and "THE BEATLES" becomes "THE ___ S".

Before using the default crown, run the three conditions in layout-heuristics.md § Crown placement:

  1. Subject centered within 10% of frame center
  2. Clean zones ≥ 15% on each side
  3. Crown width > subject width + 400px

If any one fails, move the crown to the larger clean zone with a narrower container and smaller font. Don't keep centering a word that's about to be 60% occluded.

You compute text leftmost as plane_left + padding.

That's correct for left-aligned. Wrong for the templates you're using, which are right-aligned on the main column and center-aligned on crowns. The real leftmost depends on the word width.

Alignment leftmost_x
Left plane_left + padding_left
Right plane_right padding_right longest_line_width
Center plane_center longest_line_width / 2

Compute it against the longest wrapped line, not the whole phrase. "four very talented guys" wraps to 3 lines — the widest is "talented" (~8 chars), not 23.

You treat plane_left as the text's leftmost edge.

It isn't. With left: 180px and a right-aligned 468px word, the text starts 104px before plane_left relative to the plane, but actually lands somewhere inside the plane box. The plane box just sets the coordinate space — the text positions inside it based on alignment. Check the compiled output, not the plane attribute.

You center text on the frame when the subject is off-center.

Look at the subject's body center, not the frame center. Jobs sits at x=1100 in a 1920 frame. The scene's center of gravity is 1100, not 960. Center-aligning text to 960 is center-aligning to the empty left third, not to anything meaningful.

You copy-paste position values from memory-wall to a new video.

top: 40, right: 30, width: 720, rotateY: -13 worked for a 1280×720 frame with an acoustic foam wall on the right. It will not work on a 1920×1080 frame with a bookshelf backdrop. Run the 6-item checklist in scene-types.md for the new video before reusing any numbers.


Typography

You use template default font sizes regardless of column width.

Template defaults (66/78/92/140) are tuned for a ~560px column. When you bump the plane to 700px+ and don't touch the fonts, the captions feel underweight — small text swimming in negative space.

Use the Font-size × column-width matrix. For a 700px column, that's 78/108/128/220 — a 30-40% bump across the board. Re-check pillarbox safety after bumping (bigger right-aligned text extends further left).

You pick intro style for every first caption.

Intro = italic, smaller, contemplative. Fine for filler discourse markers ("You know,", "So,", "Well,"). Not for the first line when it's actually the thesis ("I've had this kind of upbringing"). Read the words semantically, not positionally.

You skip emphasis entirely because "every caption looks clean."

A story without an emph or crown is typographically flat — viewers can't feel the crescendo. Reserve at least ONE emph per video for the line that lands. Skip it only for truly monotone content (policy statements, warnings).

You give every group its own style.

Style is supposed to signal hierarchy. Using intro/phrase/emph/dream/crown all in one 15-second video means none of them signal anything. Pick 2-3 styles max for a short clip.

You only use the 5 preset class names.

intro / phrase / emph / dream / crown are scaffolding, not a closed set. The canonical memory-wall.html uses cap-1 / cap-2 / cap-3 / cap-4position-indexed with bespoke typography per position. That's how it achieves the 3-line right-aligned cascade on the climax. You can't express "this cap has a hanging indent at position 2" with "style": "dream".

The "style" field in plan.json accepts any string — it becomes class="cap-<string>". Define the class in custom_css:

"custom_css": ".cap-1 { font-size: 78px; ... } .cap-2 { padding-right: 44px; }",
"groups": [{"id": "cg-0", "style": "1", ...}]

When the scene needs per-position bespoke typography, do this. Don't force-fit into intro / phrase / emph.

You regenerate from plan.json when a canonical example already has the answer.

When scene framing matches references/example-renders/memory-wall.html or champion.html, clone that HTML into <project>/index.html and only swap the GROUPS array. Don't re-derive the design from presets — you'll lose the specific per-position typography that took many iterations to validate. See bespoke-vs-presets.md § The clone-and-tweak workflow.


Blending

You default to mix-blend-mode: overlay.

Overlay works on mid-tone backgrounds. On dark bookshelves (<60 luminance), overlay makes white text render as black. On sunny backgrounds (>180 luminance), overlay blows out. Check the actual luminance of the caption region in a sampled frame. Pick:

  • mid-tone surface (60-180) → overlay
  • dark surface (<60) → screen
  • bright surface (>180) → normal with opaque text

You animate letter-spacing on the word entry.

You saw a "typewriter breath" effect somewhere and added letterSpacing: "0.04em" to the fromTo "from" state. Result: inline-block reflow every frame → the whole .cap line box recomputes → captions visibly jump between rows. See the "Some → line 2" bug from memory-wall.

Animate only opacity + transform. If you want a breath effect, use scale or y, not letter-spacing.


Animation

You fade both the group container AND each word.

Default feels safer: fade the container in, then fade each word in. But container.opacity × word.opacity produces a non-linear curve — at progress 40% the combined opacity is 16%, not 40%. Visible result: captions seem to "pop" into view around halfway.

Set container opacity to 1 at entrance via tl.set, then fade only the words. Single-layer opacity = linear perceived fade.

You stack captions in a flex column.

Flex-direction: column with multiple .cap children looks tidy in source. At runtime, hidden captions (visibility: hidden) still reserve flex space. The newly-entering caption shows up at the bottom of the column instead of the designed position — landing in the gesture zone, getting clipped.

Use position: absolute for each .cap inside the plane so hidden ones don't occupy layout. Both shipped templates do this.

You start the timeline at t=0.

Caption at exactly t=0 feels like it was there before the video started. Offset 0.1-0.3s (hyperframes motion-principles.md agrees). Same for the very last caption — let it exit before the video fades.


Scene admission

You trust that "looks like one speaker" = "is one speaker throughout."

TV archive clips cut to B-roll mid-sentence. Interview clips insert cutaways to the interviewer. You didn't check frames beyond the first one, so you rendered captions across a shot transition. Result: captions designed for Subject A are placed relative to Subject B's position (or empty frame) for half the render.

Sample frames at 20%, 50%, 80% BEFORE planning. If the scene changes, trim to the largest single-subject segment.

You ignore baked-in captions / pillarbox / watermarks.

You saw black bars on the sides and didn't computing the safe-zone. Your captions cross the pillarbox. Or: the source already has burned-in subtitles and you added more — two caption systems fighting for attention.

Run the letterbox probe first. If the source has existing captions, refuse with: "source already captioned, adding more would conflict."

You ship Whisper's transcript without checking timings against the beat.

Transcription is Whisper (via transcribe.cjs, no API key) — good word timings, but not infallible: a word can land with a near-zero duration or a timestamp a beat off. This skill is verbatim + on-beat, so check-timing.cjs --strict (80ms tolerance) is the gate, not a suggestion — fix drift in plan.json before rendering, and never pack two transcript words into one timed entry (the second inherits the first's timestamp and fires early).


Matting

You enable CoreML for the matting ONNX.

It's the Apple way, obviously faster. No. CoreML partitions the ONNX graph across providers. The mixed-precision boundary produced (observed with the previous RVM engine) alpha=30 inside the subject's face while background correctly reads 0. Captions shine through face. Pin the CPU execution provider only (onnxruntime-node). Our matte.cjs already does this; don't "optimize" it by re-adding CoreML.

You pick a matte model by "general vs human."

DECISION FLIPPED 2026-06-12 after a 5-model × 6-scene A/B with caption renders: the matte's job here is CAPTION LAYERING, not prop fidelity. u2net_human_seg (via hyperframes remove-background) usually excludes thin offset furniture (mic boom arms) from the matte — words stop being sliced by booms, which beat PP-MattingV2's prop-preserving behavior on real caption videos. It is NOT surgical: large salient objects near the subject (telescope rigs) can still leak in — always sample frames_fg/. Known cost: HELD products can drop out intermittently (captions pass in front) — route product-demo climaxes away from held objects. isnet-general-use lost outright (backlit-hair collapse). birefnet-portrait (MIT) beat everything semantically (keeps held items AND drops furniture) but is 928 MB / ~7 s-per-frame CPU — a future quality tier, not the default.


Grouping

You caption every word.

Transcripts are verbose — "you know, um, I, I mean, you know" is 5 words from a single beat. Captioning all of them clutters the screen and breaks reading rhythm.

Editorially drop filler. Use the rules in caption-grouping.md: merge short fragments, cut repeated discourse markers, keep the meaning, trim the noise. You are writing typography to support speech, not a court transcript.

You group by fixed word-count.

"3 words per caption, always" makes every caption look the same and fights the natural cadence of speech. Break on sentence boundaries, 250ms+ pauses, and semantic units instead. Caption sizes will naturally vary from 2 words to 5 — that's correct.


The meta anti-pattern

You read one reference doc and skip the rest.

You'll read this file alone and feel covered. These anti-patterns reference concepts defined in layout-heuristics.md, typography-presets.md, scene-types.md. If you haven't read those, the fix advice here won't make sense.

Order of reading for a new video:

  1. SKILL.md (decision gate + pipeline + pre-flight probes)
  2. bespoke-vs-presets.md (first check if a canonical example fits — clone if so)
  3. scene-types.md (template selection — all 4 wall conditions)
  4. layout-heuristics.md (positions, sides, crown, font scale, pillarbox formula)
  5. typography-presets.md (font-size × column-width table, starting points)
  6. caption-grouping.md (word → group)
  7. This file last (to catch yourself before committing to plan.json)

If you're pressed for time, still read this one — it flags the failures you're about to make.