fix(product-launch): consolidate media brand and audio contracts#2408
Conversation
vanceingalls
left a comment
There was a problem hiding this comment.
Lens: runtime-interop + cross-layer contract audit of the four absorbed slices (#2340 timing / #2231 brand fonts / #2229 TTS provider / #2431 video geometry). Peer @rjusso holds the test + observability lens in parallel — deferring test-coverage depth to that lens except where it directly reflects contract preservation. Reviewed at head deecd153c.
Audited: scripts/assemble-index.mjs, scripts/audio.mjs, scripts/lib/tokens.mjs, scripts/build-frame.mjs, scripts/media-contract.test.mjs, scripts/audio.test.mjs, scripts/lib/tokens.test.mjs, sub-agents/frame-worker.md, and each source-PR diff (#2340/#2231/#2229/#2431 all closed-not-merged; proposals not shipped baselines).
Strengths
scripts/assemble-index.mjs:180—data-media-startadded to the forward allowlist alongsideid/src/poster/preload/aria-label. Sanitization boundary held.scripts/assemble-index.mjs:190-225—approvedVideoLayoutvalidates geometry as a unit (all-or-none), enum-validatesfit, and produces a safe CSS string built fromNumber()-coerced values.style="${video.layoutStyle}"at line 510 is airtight:values.x/y/w/h.toString()output uses only[0-9.eE+-]so it cannot escape the quoted attribute, andfitis enum-matched.scripts/audio.mjs:127,146—--providerflag +HF_TTS_PROVIDERenv fallback shape preserved 1:1 from #2229. Precedence + fallback semantics verified byaudio.test.mjs.scripts/lib/tokens.mjs:76,126,136—pickAccent(pre-existing at line 76, used foraccentat line 126) is now the source of truth foraccent2as well. Structurally symmetric.- The four contracts land byte-for-byte identically to their source PRs; the merge did not collapse edge cases from one slice into another.
data-media-startforwarding (#2340) anddata-frame-video-*layout extraction (#2431) operate on orthogonal attribute namespaces and compose cleanly inhoistApprovedVideos.
Findings (all sub-blocker)
-
important —
accent2semantics changed, not just refactored (scripts/lib/tokens.mjs:135-136). The pre-#2231accent2REQUIREDinteractiveBg > 0and sorted byinteractiveBgdesc — only chip/button fills qualified. The newpickAccent(...)drops the interactiveBg requirement and ranks by role-diversity + palette prominence + count. That means a text-only pervasive color can now win theaccent2slot from a chip-fill that would have won before.tokens.test.mjspins ONE positive case (#2D1238interactiveBg:3 beats#F3E62BinteractiveBg:0) but no test guards the specific pre→post inversion where an interactive-fill loses to a text-only color, or the tie-break under the new sort keys. Behavior change deserves an explicit before/after fixture so a futurepickAccenttweak doesn't silently regress the intended accent2. -
important —
isIconFontexpansion over-matches brand names ending in "font" (scripts/lib/tokens.mjs:52). Thefont$alternation matches any family whose name ends with the word "font" case-insensitively — including legitimate brand text faces like "Rockwell Font" or a bespokeSomeBrandFontthat IS the body face, not an icon glyph set. Downstream inbuild-frame.mjs:179,185those get filtered out ofbrandFontsand their weights excluded frombrandFontWeights, silently degrading the brand type.tokens.test.mjscoversvidaXLfont/BrandGlyphFont/Poppinspositives but has no negative-case guard for a "…Font" brand text face. Suggest tightening to/(?:^|[\s_-])font$/ior requiring a preceding icon-domain marker. -
important — Sibling-skill drift (Rule 2 audit surface). Both refactored primitives live only in product-launch-video.
skills/pr-to-video/scripts/build-frame.mjs:166andskills/faceless-explainer/scripts/build-frame.mjs:166still inline the OLD narrowerisIconFontregex;skills/pr-to-video/scripts/lib/tokens.mjsandskills/faceless-explainer/scripts/lib/tokens.mjsstill carry the OLD interactive-onlyaccent2computation. Follow-up ticket, not a #2408 blocker (separate skills, each with its own contract); worth landing while the intent is fresh. -
nit — Empty-string geometry silently passes validation (
scripts/assemble-index.mjs:200-205).raw[name] === nulldoes not catchraw[name] === "";Number("")is0andNumber.isFinite(0)istrue, sodata-frame-video-x=""bypasses the "must all be finite numeric values" error and emitsleft:0px. Output is safe; contract message is misleading. Atrim()+non-empty precheck orraw[name] == null || raw[name] === ""guard would align code with docs. (Guard onvalues.width/height <= 0correctly rejects empty width/height.) -
nit — Docs vs code asymmetry on frame-worker geometry (
sub-agents/frame-worker.md:49). Reads "Give every approved video explicit host geometry" butapprovedVideoLayouttreats geometry as fully optional (all-or-none: absent → hoist proceeds withoutstyle=). No enforcement error if a frame-worker sub-agent omits geometry entirely. Safe direction, but the sub-agent won't be corrected by a failing assemble. -
nit — No combined-slice regression test.
media-contract.test.mjscoversdata-media-startpropagation (#2340) anddata-frame-video-*geometry (#2431) in separate fixtures, but no test declares BOTH on a single approved video. The paths are orthogonal so this is defense-in-depth, but a single combined fixture would harden the absorption boundary against future drift.
CI + freshness
mergeable_state=blocked at review time reflects REVIEW_REQUIRED (reviewer-gate), not CI-gate — required checks visible are SUCCESS or IN_PROGRESS, none FAILURE. Reviewed against head deecd153ce690b4a0d95f0b09363a4a59cba10a4.
Verdict: COMMENT
Reasoning: No blockers on the runtime-interop lens. Contract preservation across the four absorbed slices is faithful. Two accent2/isIconFont behavioral concerns (#1, #2) inherited from #2231 deserve targeted test coverage before merge — non-blocking because #2231 was closed-not-merged so this is the first time either behavior ships, and both are correctness-adjacent (silent brand-token degradation), not correctness-breaking. Sibling drift (#3) is a follow-up. Deferring approve/RC to @rjusso's test-lens verdict + CI green.
— Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at deecd153ce690b4a0d95f0b09363a4a59cba10a4.
Ran a source-completeness audit against each of the four absorbed PRs, walked the new logic (approvedVideoLayout, isIconFont extraction, pickAccent-driven accent2, --provider threading), and dispatched a fresh adversarial pass over the merged shape (Rule 15 protocol, first-pass conclusions barred from the subagent brief).
What I verified
- Source-completeness across the four PRs. Every hunk from
#2340/#2231/#2229/#2431at the source-PR HEAD SHAs (30c87f87…,f232eb32…,44c16439…,c1a8d8dc…) survives in this consolidation byte-for-byte. The only textual change I found is cosmetic —#2340's multi-line forwarded-attrs array ("id","src","poster","preload","aria-label","data-media-start") got reformatted to a single-line array in the consolidation. No cross-PR collisions on shared files (#2340+#2431onassemble-index.mjs/media-contract.test.mjsland in non-overlapping hunks;#2229+#2431manifest bumps are sequenced through main). - CSS-injection resistance in the emitted layout style.
values.x/y/width/heightareNumber()-parsed and finite-checked; any non-numeric leakage ("5;background:url(x)") →NaN→ rejected.fitis exact-string whitelisted against["cover","contain","fill","none","scale-down"]via.includes().1e100is technically finite and producesleft:1e+100px— nonsense, not a vuln. No path fromdata-frame-video-*attributes to CSS-attribute-injection. - Error-abort behavior on invalid geometry.
approvedVideoLayoutreturning{error}pushes intohoistApprovedVideos's errors array; caller aborts assembly and skipsindex.html. Confirmed by running the assembler against a synthetic frame withdata-frame-video-width="calc(100% + 1px)"— non-zero exit, noindex.html, stderr matches the "must all be finite numeric values" message. Test atmedia-contract.test.mjs:83-105covers this end-to-end. pickAccentsemantic equivalence with the removed accent2 filter. The old filter requiredinteractiveBg > 0;pickAccentbroadens this to any semantic role (interactiveBgORtextCountORbgCount). UA defaults are still excluded (viaban = new Set([...exclude, ...UA_DEFAULT_COLORS])), and the excluded set param carries the[canvas, ink, accent]guard. The intentional broadening is exactly what the newpreserves a prominent second accent used outside interactive backgroundstest validates (#F3E62BhastextCount: 3, interactiveBg: 0— would have been rejected by the old filter, wins under the new one).- Test-to-code coverage matches the PR body's promises. All 7 new test cases (3 in
audio.test.mjs, 2 inlib/tokens.test.mjs, 2 inmedia-contract.test.mjs) wire to the code paths they claim to test. Focused suite is 87/87 locally (media-contract 6/6, tokens 2/2, audio 3/3). - Cross-repo consumer sweep. No
isIconFontorbrandRolesFromStatsconsumers inheygen-com/hyperframes-internal. Zero external drift risk on the extracted symbols. - Sanitization boundary holds on emit.
data-frame-video-{x,y,width,height,fit}attributes are stripped from the hoisted<video>(confirmed byassert.doesNotMatch(index, /data-frame-video-(?:x|y|width|height|fit)=/)in the geometry test). Only the derived numeric/whitelisted CSS crosses the boundary. - Manifest hash reproduces from local content (
6a874885252066f1, 23 files) — no drift from unrelated staged changes.
Concerns (inline above)
None rise to blocker. Three code-vs-contract mismatches worth closing:
- 🟠
skills/product-launch-video/scripts/assemble-index.mjs:197—approvedVideoLayoutsilently accepts approved videos with NO geometry, contradicting the updatedframe-worker.mdcontract. The doc atsub-agents/frame-worker.md:49now reads "Give every approved video explicit host geometry with numericdata-frame-video-x,data-frame-video-y,data-frame-video-width, anddata-frame-video-height" — positioning geometry as MANDATORY. ButapprovedVideoLayouttreatshasLayout=false(no attrs at all) as an accept path (return { style: null, error: null }). Live-tested: a frame with<video data-frame-video="approved" src="clip.mp4" data-start="0" data-duration="1" data-track-index="0">(no geometry attrs) assembles cleanly, exits 0, and the emitted<video>hasclass="clip"with NO style — since there's no.clipCSS rule in the emitted head, the video renders at intrinsic dimensions in DOM-flow position instead of the intended host geometry. Perfeedback_code_vs_stated_contract: the fix is to make code match the stated intent —hasLayout=falseshould error (or be explicitly documented as opt-out inframe-worker.md). - 🟡
skills/product-launch-video/scripts/lib/tokens.mjs:52—isIconFont'sfont$alternative misclassifies real brand text faces. With/i,font$matches ANY string ending case-insensitively infont. Concrete examples (allisIconFont(x) === true):HelveticaFont,"Airbnb Cereal Font","SF Pro Text Font","Uber Move Font","Circular Std font","brand-font". All are legitimate brand TEXT faces from real customer captures — the classifier now demotes them to icon-fonts atbuild-frame.mjs:179(filter((f) => !isIconFont(f))), which drops them from display/body font selection AND drops their weight contributions. Silent + one-way: the frame renders with a fallback system font instead of the actual brand face. Test atlib/tokens.test.mjs:5-9covers only the intended positives (vidaXLfont,BrandGlyphFont) and a negative that doesn't happen to end in "font" (Poppins). Sharper alternatives:\b(?:icon|glyph|symbol|pictogram)[-_ ]?font\b(bounded), or(?:^|[\s_-])font$(requires word-boundary), or add a false-positive-shape assertion (e.g.isIconFont("HelveticaFont") === false) and let the failure drive the sharpening. - 🟡
skills/product-launch-video/scripts/lib/tokens.mjs:51—isIconFontsibling-drift. The extraction + widening applies only toproduct-launch-video. Sibling skills atskills/faceless-explainer/scripts/build-frame.mjs:166-168andskills/pr-to-video/scripts/build-frame.mjs:166-168still carry the OLD narrow regex baked as a localconst. Before this PR all three copies agreed; post-PR onlyproduct-launch-videohas the widened alternatives. Same brand token → different classification depending on which skill renders. If the widening is right for one skill, hard to see why it isn't right for the other two — they all read the same brand-capture output.
Nits (not inline)
data-frame-video-x=""silently becomesleft:0px.attrValueFromreturns""(notnull) for a present-but-empty attribute;Number("") === 0, which passesNumber.isFiniteand thewidth/height > 0gate applies only to width/height. Confirmed by running the assembler withdata-frame-video-x=""alongside valid width/height — emitsleft:0pxwith no error. Sharper:raw[name] === null || raw[name] === ""in the null-check.- No
--providerwhitelist inaudio.mjs. The flag accepts any string and passes through to the downstream engine's request JSON. Reasonable to defer validation to the engine — flagging in case there's a preference. skills-manifest.jsonhash bump forhyperframes-creative(826e4eafaedf8936→e0750456fba2cf22) appears in the manifest diff but no source file underskills/hyperframes-creative/is in this PR. Presumably manifest regen catching an upstream change on main — worth a sanity double-check that no unrelated skill was silently pulled into the consolidation branch.
What I didn't verify
- Runtime-interop / cross-layer contract slice — Via's lens per our split.
- Live smoke against a real brand-token capture corpus — the
isIconFontwidening's real-world false-positive rate is best judged against actual customer captures, not synthesized inputs. Testing on names like "Airbnb Cereal Font" strongly suggests the FP rate is non-trivial.
LGTM from my side aside from the mandatory-geometry gate + isIconFont widening. Layout sanitization is well-defended, the source-preservation is byte-clean, and the test surface matches the claimed contract. The three concerns are load-bearing on stated contracts (the doc says geometry is mandatory; the code doesn't enforce it) and on soft quality regressions (real brand fonts being demoted to icon-fonts is a customer-visible typography loss).
| ); | ||
| const rawFit = attrValueFrom(attrs, "data-frame-video-fit"); | ||
| const hasLayout = names.some((name) => raw[name] !== null) || rawFit !== null; | ||
| if (!hasLayout) return { style: null, error: null }; |
There was a problem hiding this comment.
🟠 Code-vs-stated-contract: this silently accepts approved videos with NO geometry. The hasLayout=false branch returns { style: null, error: null } — an accept path — but the updated sub-agents/frame-worker.md:49 explicitly says:
Give every approved video explicit host geometry with numeric
data-frame-video-x,data-frame-video-y,data-frame-video-width, anddata-frame-video-height
Mandatory in the doc; opt-out in the code. Live-tested against the assembler:
<video data-frame-video="approved" src="clip.mp4"
data-start="0" data-duration="1" data-track-index="0"></video>Exits 0. Emits a host <video class="clip"> with NO style attribute. And since the emitted <head> defines no .clip CSS rule (checked lines 640-666 of this file), the video renders at intrinsic dimensions at the DOM-flow position instead of the host geometry the sub-agent doc says every approved video must declare.
Default fix per feedback_code_vs_stated_contract: make the code match the stated intent — reject hasLayout=false as an error alongside the other layout-invalid cases. Alternative: change the sub-agent doc to say geometry is optional and document the fallback layout behavior. Whichever, the current shape ships a doc claim the code can't back.
— Rames D Jusso
There was a problem hiding this comment.
Fixed in ae32715. Approved videos now require all four numeric geometry attributes; missing and empty values abort assembly. Added end-to-end regressions for both no geometry and empty x, and updated the baseline hoist fixture to exercise valid full-canvas geometry.
| ); | ||
|
|
||
| export function isIconFont(name) { | ||
| return /(?:^|[\s_-])icons?(?:[\s_-]|$)|icomoon|font\s*-?awesome|glyphicons?|material\s*icons|feather\s*icons|(?:icon|glyph).*font|font.*(?:icon|glyph)|font$/i.test( |
There was a problem hiding this comment.
🟡 font$ alternative misclassifies real brand text faces as icon fonts. With /i, font$ matches ANY case-insensitive string ending in font:
HelveticaFont → true (false positive)
"Airbnb Cereal Font" → true (false positive)
"SF Pro Text Font" → true (false positive)
"Uber Move Font" → true (false positive)
"Circular Std font" → true (false positive)
"brand-font" → true (false positive)
vidaXLfont → true (test intent)
BrandGlyphFont → true (covered by earlier (?:icon|glyph).*font)
Poppins → false
Most of the first six are real brand TEXT faces from actual customer captures — Airbnb Cereal, SF Pro Text, Uber Move, Circular Std are all in the wild. Misclassification is silent + one-way: build-frame.mjs:179 filters via filter((f) => !isIconFont(f)), dropping the family entirely from display/body font selection AND dropping its weight contributions. Silent quality regression: the frame renders with a fallback system font.
The test at lib/tokens.test.mjs:5-9 only covers the intended positives (vidaXLfont, BrandGlyphFont) and a negative that doesn't happen to end in font (Poppins). It doesn't assert against the false-positive shape.
Sharper alternatives (any works):
- Bounded word:
\b(?:icon|glyph|symbol|pictogram)[-_ ]?font\b— catchesiconfont,icon-font,glyph_font, skipsHelveticaFont. - Anchored to a boundary:
(?:^|[\s_-])font$— requires a boundary beforefont$. Catchesvida-font,webflow_font,xl font; skipsSharpFont. - Add a false-positive assertion (
isIconFont("HelveticaFont") === false) — let the failing test drive the sharpening.
— Rames D Jusso
There was a problem hiding this comment.
Fixed in ae32715. Removed the broad font$ alternative, retained the two intended fused positives through bounded/explicit alternatives, and added all six reported real-brand false-positive cases as regressions.
| ["#0000EE", "#0000FF", "#0000CC", "#1A0DAB", "#551A8B", "#EE0000"].map((c) => c.toUpperCase()), | ||
| ); | ||
|
|
||
| export function isIconFont(name) { |
There was a problem hiding this comment.
🟡 isIconFont sibling-drift. This extraction + widening applies only to product-launch-video. The two sibling skills still have byte-identical pre-widening copies as local consts:
skills/faceless-explainer/scripts/build-frame.mjs:166-168— localconst isIconFontwith the old narrow regex.skills/pr-to-video/scripts/build-frame.mjs:166-168— same.
Before this PR, all three regexes agreed byte-for-byte. Post-PR they diverge: product-launch-video classifies vidaXLfont / BrandGlyphFont as icon fonts (and filters them out of brand tokens), the other two treat them as regular fonts. Same brand capture → different rendered typography depending on which skill built the frame.
Three ways to close:
- Extract
isIconFontto a shared location the sibling skills can import too (there isn't one today; eachskills/*/scripts/lib/tokens.mjsis a per-skill copy). - Sync-widen the two sibling local consts to match the new regex in this same PR.
- Document why product-launch alone should widen (probably a decision about what brand capture inputs are in scope) and leave the siblings.
If the widening is right for one skill, hard to see why it isn't right for the other two — they all read the same brand-capture output.
— Rames D Jusso
There was a problem hiding this comment.
Fixed in ae32715. Faceless-explainer and pr-to-video now use the same classifier pattern, with a cross-skill regression that fails if either sibling drifts from the product-launch contract.
|
Addressed the complete review at
The Verification:
Please re-review the new head. I will keep monitoring CI/review state and will not self-approve or merge. |
ae32715 to
52b78eb
Compare
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 52b78ebbbcc92e146ba1c97e5c339ad868026e79 — R2 delta since my R1 (4698142391). Focused on verification of the 3 R1 findings + adversarial pass over the new gate + regex.
R1 finding verdicts
🟠 R1 finding 1 — approved-video mandatory-geometry gate: RESOLVED. approvedVideoLayout at assemble-index.mjs:190-227 now rejects the previously-silent hasLayout=false accept path:
- All four of
data-frame-video-x/y/width/heightmust be present, non-empty, and finite numeric (line 197-200 rejectsnull, whitespace-only strings via.trim() === "", and non-finite via!Number.isFinite). width <= 0 || height <= 0is a hard reject (line 200-202).data-frame-video-fitvalidated against an explicit allowlist (line 213-219), defaulting to"cover"only when the attr isnull. An empty-string fit (data-frame-video-fit="") — whichattrValueFromreturns as""notnull— falls through??and is caught by the allowlist. Correct.- Caller at line 260-264 wires
layout.errorintoerrorsand returnsfullso the video stays as-is with a diagnostic (no silent geometry drift). - Tests at
media-contract.test.mjs:102-158cover the important corners: non-numeric (calc(...)), empty string (data-frame-video-x=""), and missing-attr — all assert the "finite numeric" error surfaces.
🟡 R1 finding 2 — isIconFont font$ false-positive: RESOLVED. The font$ catch-all is gone from tokens.mjs:52; replaced with ^vidaxlfont$ (anchored, case-insensitive) — a specific literal-name match that won't false-positive on real brand fonts like Airbnb Cereal Font, SF Pro Text Font, Uber Move Font, HelveticaFont, or Circular Std Font. The other alternatives (icons?, icomoon, font-awesome, glyphicons?, material icons, feather icons, (?:icon|glyph).*font, font.*(?:icon|glyph)) still catch real icon fonts.
🟡 R1 finding 3 — sibling regex drift: RESOLVED. All three skills now share the identical pattern:
skills/product-launch-video/scripts/lib/tokens.mjs:51-52skills/faceless-explainer/scripts/build-frame.mjs:166-167skills/pr-to-video/scripts/build-frame.mjs:166-167
Byte-for-byte match. Note: the skills each carry their own copy (no shared import — architectural choice for self-contained skill units); a future edit to any one will still need manual propagation to the other two. Not this PR's problem — flagging for context on the next regex touch.
Adversarial pass on new R2 surface
Verified against the geometry gate and fit allowlist:
Number("0")returns0(finite) →x=0/y=0legitimately pass the finite-check, then get rejected bywidth <= 0 || height <= 0only for the size dims (correct — position at origin is valid, zero-size is not).Infinity/-Infinity/NaNall failNumber.isFinite→ caught.- Very large values (e.g.
x=999999) pass — producer-authored, trust boundary. data-frame-video-fit="COVER"(uppercase) → not in the allowlist → rejected. Case-sensitive match matches CSS spec (object-fitvalues are lowercase). Correct.- No path where
layout.styleis emitted with a missing dimension.
Nothing new to flag.
Verdict framing
Clean R2. All 3 R1 findings substantively closed with test coverage. LGTM from my side — deferring the stamp decision to the human reviewer.
vanceingalls
left a comment
There was a problem hiding this comment.
R2 adversarial re-review at head 52b78ebbb after Magi's fix push. R1 was deecd153c (--comment, three important + three nits). Adversarial-user pass dispatched fresh-slate to a sibling sub-agent per Rule 15; findings reconciled below.
R1 finding disposition
-
accent2semantic pin (R1#1) — fixed.scripts/lib/tokens.test.mjs:36-51pins the specific pre→post inversion:#F3E62B(textCount:3, interactiveBg:0) winsaccentover#2D1238(textCount:4, interactiveBg:3), and#2D1238reachesaccent2viapickAccent's fallback path. That fixture would regress ifpickAccentreverted to "top interactiveBg." The behavior semantics are not reverted (nor did R1 demand a revert) — the test guards the intended new behavior. -
isIconFontover-match onfont$(R1#2) — fixed.scripts/lib/tokens.mjs:51-56extracts the regex toICON_FONT_PATTERN, drops the broadfont$alternative, and replaces it with^vidaxlfont$(single anchored brand exception). All six R1-flagged brand text faces evaluate false with regressions attokens.test.mjs:10-20(HelveticaFont,Airbnb Cereal Font,SF Pro Text Font,Uber Move Font,Circular Std font,brand-font). -
Sibling drift (R1#3) — partially fixed.
isIconFontsibling drift: fixed.skills/faceless-explainer/scripts/build-frame.mjs:166-168andskills/pr-to-video/scripts/build-frame.mjs:166-168now inline the same widenedICON_FONT_PATTERN;tokens.test.mjs:22-30string-matches the pattern source across both siblings so a future desync fails CI.accent2sibling drift: not fixed — see below.
New / carried finding
important — brandRolesFromStats.accent2 still diverges between the three "shared brand contract" skills. skills/product-launch-video/scripts/lib/tokens.mjs:136-137 now computes accent2 = pickAccent(v, colorsInOrder ?? v.map(s=>s.hex), [canvas, ink, accent]) ?? accent, but the sibling copies at skills/faceless-explainer/scripts/lib/tokens.mjs:130-138 and skills/pr-to-video/scripts/lib/tokens.mjs:130-138 still hold the pre-PR interactive-fill-only computation (v.filter(s => (s.interactiveBg||0)>0 && chroma(s.hex)>40 && !UA_default).sort(interactiveBg desc)[0]?.hex ?? accent).
Concrete divergence on the test's canonical input (colors=["#FFFFFF","#2D1238","#F3E62B","#111111"], stats as in tokens.test.mjs:38-43):
| Skill | canvas | ink | accent | accent2 |
|---|---|---|---|---|
| product-launch-video | #FFFFFF |
#111111 |
#F3E62B |
#2D1238 |
| faceless-explainer | #FFFFFF |
#111111 |
#F3E62B |
#F3E62B (= accent, degenerate) |
| pr-to-video | #FFFFFF |
#111111 |
#F3E62B |
#F3E62B (= accent, degenerate) |
#2D1238 has chroma 38, so the sibling chroma > 40 filter empties → accent2 falls back to accent. Same brand capture, same tokens.json → the two sibling skills' build-frame.mjs remix loses the second-accent role (line 220 propagates the degenerate value into br.accent2, and the preset→brand remix at lines 263-264 then repaints the preset's distinct accent2 with the same hue as its primary accent).
The PR title reads "consolidate media brand and audio contracts" and the fix-push comment claims "synchronized faceless-explainer and pr-to-video, with a cross-skill drift regression." Both frame accent2 as consolidated; only ICON_FONT_PATTERN is. Rames' inline sibling-drift comment (line 54) was scoped narrowly to isIconFont, which is why the fix stopped where it did — but R1#3 in this review body called out both isIconFont AND accent2, and the drift-coverage test at tokens.test.mjs:22-30 intentionally string-matches only ICON_FONT_PATTERN (not brandRolesFromStats), so a future silent re-drift on accent2 will not fail CI either.
Two acceptable closures: (a) sync-port the pickAccent-based accent2 line into both sibling lib/tokens.mjs files and extend the drift test to string-match brandRolesFromStats too; or (b) document explicitly in this PR's description why product-launch alone widens accent2 and leave the siblings on the interactive-fill semantics. Silent divergence with an overreaching "consolidated" claim is the state to avoid.
Adversarial-pass nits
nit — scripts/assemble-index.mjs:198-207 — approvedVideoLayout gates values.width/height <= 0 but does not gate x/y sign. data-frame-video-x="-999999" is Number.isFinite and emits left:-999999px (video positioned entirely off-canvas). Not a security issue; consider bounding to non-negative or to [0, root width - width] for defense-in-depth.
nit — sub-agents/frame-worker.md:49 documents data-frame-video-fit="cover|contain|fill|none|scale-down" but the assembler's whitelist at assemble-index.mjs:212 is case-sensitive. "Cover" is rejected with the enum-error. Document the case sensitivity or .toLowerCase() before whitelist check.
nit — tokens.test.mjs:22-30 reads sibling build-frame.mjs off disk and re-tests the ICON_FONT_PATTERN literal, but there is no analogous drift assertion for brandRolesFromStats even though it is duplicated three times. If the resolution to the important finding is "sync-port", extending the drift test costs another 4-6 lines and closes the class.
Freshness + CI
Freshness pull at draft-time: head still 52b78ebbb. CI: Skills: manifest in sync pass, Test: skills pass, Format pass. mergeable_state=blocked is the reviewer-gate (REVIEW_REQUIRED), not CI. Manifest diff at head touches exactly the three intended skill hashes (faceless-explainer 15e0c104…, pr-to-video d8f579ba…, product-launch-video 9a8cb20b…, files 21→23 for the two added tests) — no unrelated skill absorbed, consistent with Magi's rebase-regeneration claim.
Verdict
Verdict: COMMENT
Reasoning: Two of three R1-important findings are cleanly resolved with regression tests. The third (sibling drift) is half-resolved: isIconFont fully synced with drift coverage; accent2 remains divergent between product-launch-video and its two siblings while the PR framing claims consolidation. Non-blocking because the accent2 divergence is a per-skill silent brand-token degradation (fewer visual distinction), not a correctness break, and R1 originally accepted it as follow-up. Preferred closure is a sync-port + extended drift test, or explicit documentation of the intentional divergence in the PR description.
— Via
|
Addressed the remaining R2
TDD evidence: the new sibling assertion failed first ( Please re-review the new head. No self-approval or merge. |
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 98e318b88336bb8655dbd5beb4369c59d7791a6e — R3 delta since my R2 (4698303827).
R3 change scope
Small, focused: faceless-explainer/scripts/lib/tokens.mjs and pr-to-video/scripts/lib/tokens.mjs now compute accent2 via pickAccent(v, colorsInOrder ?? v.map(s => s.hex), [canvas, ink, accent]) — matching product-launch-video's pattern. New cross-skill parity test at product-launch-video/scripts/lib/tokens.test.mjs:53-56 imports both sibling brandRolesFromStats and asserts deepEqual against the anchor skill.
Verification
- All three
brandRolesFromStatsimplementations are byte-identical (verified via diff —faceless-explainer/tokens.mjsmatchespr-to-video/tokens.mjsexactly, and both matchproduct-launch-video/tokens.mjsat the function level). The prioraccent2divergence (per-skill inlineinteractiveBg-only filter with different sort semantics) is gone. - Cross-skill parity test is executable and load-bearing — running
for (const sibling of [facelessBrandRolesFromStats, prBrandRolesFromStats]) { assert.deepEqual(sibling(stats, colors), brandRolesFromStats(stats, colors)); }would fail on any future edit that reintroduces drift on the "preserves a prominent second accent" fixture. This is exactly the executable invariant the R1 sibling-drift concern called for. pickAccent's exclude list is now the full role set[canvas, ink, accent]— matches product-launch-video's semantic (never letaccent2re-use an already-assigned role) whereas the OLD inline filter only excluded that same triple via![canvas, ink, accent].includes(s.hex). No behavioral drift on the exclusion; the fix is entirely in the ranking (palette-order + textCount+bgCount fallback vs. interactiveBg-only).
Adversarial pass
- What if a skill in the future needs a different
accent2semantic? The parity test would fail — good, forces a conscious decision rather than silent drift. If skills legitimately diverge later, the test needs to move to a shared fixture with per-skill oracles rather than deepEqual. Not this PR's problem. colorsInOrderfallback (?? v.map((s) => s.hex)) is duplicated across all three skills. Correct default when tokens.colors is unavailable. No divergence.
Verdict framing
Clean R3. Cross-skill parity is now executable, not just aspirational. LGTM from my side.
vanceingalls
left a comment
There was a problem hiding this comment.
R3 verify at head 98e318b88 — resolution check on my R2 half-resolved concern.
R2 called out accent2 still using old interactive-fill-only semantics in lib/tokens.mjs, producing degenerate accent2 === accent on the canonical ["#FFFFFF","#2D1238","#F3E62B","#111111"]. This head resolves it.
Verified:
- All three tokens.mjs are byte-identical for the accent2-relevant surface (
pickAccent,brandRolesFromStats,semanticColors,STATUS_ROLE_KEY,UA_DEFAULT_COLORS). Only structural delta is that product-launch-video additionally exportsICON_FONT_PATTERN/isIconFont(siblings inline these inbuild-frame.mjs, already asserted equal viatokens.test.mjs:24-32). - The cross-skill parity test at
skills/product-launch-video/scripts/lib/tokens.test.mjs:38-57importsbrandRolesFromStatsfrom all three siblings and assertssibling(stats, colors) === brandRolesFromStats(stats, colors)on the R2 canonical input — executable and load-bearing, not a hash/snapshot.node --testpasses 3/3 at head. - The R2 divergent-accent2 assertion is still present as a regression guard (
accent2:"#2D1238"on the R2 canonical input); it was TDD-reproduced first and not deleted after passing. - Adversarial fuzz (2000 pseudo-random
stats + colorsinputs, xorshift32-seeded, varying UA-default and status-color spikes) →diverged=0across the three siblings.
nit (follow-up, non-gating): the executable parity test guards brandRolesFromStats only. semanticColors, pickAccent, parseFonts, parseColors, and the STATUS_ROLE_KEY / UA_DEFAULT_COLORS constants rely on byte-identity discipline across three copies, not on an executable assertion. A well-intentioned future edit to semanticColors (the stats-missing fallback path invoked from build-frame.mjs) in one skill could silently diverge without the parity test catching it. Broadening the parity test to cover semanticColors(colors) on a canonical fixture — or a filesystem-level readFileSync byte-equality assertion modulo the trailing icon-font export block — would harden this. Fine as follow-up; not blocking.
Strength (skills/product-launch-video/scripts/lib/tokens.test.mjs:38-57): parity test genuinely runs all three siblings against the R2 canonical input, so future divergence in brandRolesFromStats will fail CI at the diff. This is the right shape of executable cross-skill contract.
Verdict: APPROVE
Reasoning: R2's remaining accent2 half-resolved concern is fully addressed at head 98e318b88. Three-way byte identity plus an executable parity test guards brandRolesFromStats divergence; adversarial fuzz confirms no reachable divergent input. Structural fragility flag on the fallback path is nit-tier follow-up.
— Via
Summary
Verification
Merge order
This consolidated PR replaces #2340, #2231, #2229, and #2431. Merge this PR directly; no dependent source PR should merge first.
Human review is required; this PR is not self-approved or merged.