Skip to content

feat(registry): make the 5 caption catalog components data-driven#2750

Merged
vanceingalls merged 12 commits into
mainfrom
feat/caption-data-driven
Jul 24, 2026
Merged

feat(registry): make the 5 caption catalog components data-driven#2750
vanceingalls merged 12 commits into
mainfrom
feat/caption-data-driven

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Retrofits caption-editorial-emphasis, caption-emoji-pop, caption-highlight, caption-pill-karaoke, caption-weight-shift (registry/components/*) to attach real caption data (timed words, however sourced) at runtime instead of a hardcoded demo transcript — via inline window.__HF_CAPTION__ props or a sibling caption-data.json fetch (shared hfAttach/window.__HF_CAPTION_ATTACH__ contract, see companion internal-repo PR for the full data-driven-captions design spec).
  • Each component now: derives its own duration from the attached data, applies whichever of brand.primaryColor/brand.accentColor its design consumes (per-identity brandSupport is documented in the internal package's descriptors; both CSS custom properties are set/cleared uniformly on every attach, and the one a given design doesn't use is a deliberate no-op), and publishes a window.__HF_CAPTION_STATUS health/gate signal after layout settles — replacing per-request server-side compile + gates.
  • caption-highlight additionally replaces its hardcoded word-index groupings with a real automatic grouping algorithm; caption-editorial-emphasis replaces its hand-authored emphasis annotations with a generalized long-non-stopword heuristic; caption-emoji-pop's keyword/emoji lexicon is generalized from ~7 hardcoded entries to ~50.
  • Fixes two real bugs found along the way: a GSAP timeline quirk where tl.seek(0) is a no-op on a fresh timeline (fixed via tl.render(0,true,true)), and an O(n²) redundant "hide all other groups" loop that caused test timeouts under load.
  • All 5 components keep their built-in demo transcript as a fallback, so the public catalog pages render unchanged with zero attachments.

Context

Sibling internal-repo PR vendors these components into a new @app/caption-templates package (test harness, validator, self-contained offline build, publish pipeline) — see that PR for the full design spec and rollout plan.

Test plan

  • Full test suite (pnpm --filter @app/caption-templates test in the sibling internal repo, which exercises these components directly) — 77/77 passing
  • Each retrofit independently task-reviewed + a final whole-branch review pass, including empirical verification of the brand-color and GSAP fixes
  • Catalog demo pages spot-checked visually post-merge (zero-attachment fallback path)

🤖 Generated with Claude Code

vanceingalls and others added 10 commits July 23, 2026 20:18
…a runtime

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… lexicon

Retrofits caption-emoji-pop onto the shared HyperFrames caption-data runtime
(Blocks A-D) and the emphasis heuristic (Block E), replacing the hardcoded
7-word emoji map, fixed KEYWORDS set, and TRANSITION_WORDS set with a
generic ~40-entry lexicon and HF_STOPWORDS/hfIsEmphasisWord. The component
now consumes brand.primaryColor (hfPrimaryColor) and brand.accentColor
(hfAccentColors) and rescales stage/font/emoji sizing to the runtime
resolution via layout.fontScale/scaleX/scaleY.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…uping

Replaces the hardcoded RAW_GROUPS index-pair array with hfMakeGroups, a
real fitting/pause/punctuation-based grouper, and wires the component
into the shared caption-data runtime (validate/gate/attach). Adds CSS
brand hooks: .hl-word text color from --hf-caption-primary, .hl-word-bg
gradient from --hf-caption-accent (second stop via color-mix()).
…asis heuristic

Replaces the hand-authored BLOCKS literal with hfMakeBlocks, a heuristic that
groups words into blocks/lines from timing (pauses, punctuation, max words per
block) and hfIsEmphasisWord (long, non-stopword content words) to decide which
word gets the large Playfair Display emphasis treatment and its own slide-in
line. computeLineSize/buildBlocks/fitBlocks and timeline construction now live
inside hfBuild, closing over layout-scaled font sizes and widths. Adds the
shared Blocks A-E caption-data runtime (attach/gate/brand config) and the
.word/.word--emphasis CSS brand hook for --hf-caption-primary. This is the
last of the five caption identities to go data-driven.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ndant hide-all-others loop

Both identities looped over every OTHER group/block to force its opacity
to 0 at each group's own start time, in addition to each group already
setting its own opacity to 0 at its own end. Since groups/blocks occupy
non-overlapping time windows and already own their full opacity
lifecycle, that loop was dead weight — but it made timeline construction
O(n^2) in the number of groups/blocks. Under the Task 7 stress transcript
(long, frequent "emphasis" words forcing near single-word blocks/groups),
n reached ~2000 and the page hung well past a 30s test timeout for both
identities.

Verified behavior-preserving: full templates.test.ts (41 tests, incl.
opacity/seek assertions) and the new limits.test.ts stress suite pass
against both identities after the removal.
…ch; remove dead italic path

hfApplyStageConfig (Block B, verbatim across all 5 retrofitted caption
identities) only ever set --hf-caption-primary/--hf-caption-accent when the
corresponding brand.primaryColor/accentColor key was present, with no else
branch to clear it when absent. Re-attaching a brand-less payload after a
branded one left the custom property (and any JS-cached color derived from
it, e.g. caption-pill-karaoke's hfColorActive and caption-emoji-pop's
hfAccentColors) stuck at the stale value instead of reverting to the CSS
fallback, violating idempotent re-attach.

Also removes caption-editorial-emphasis's dead .word--italic CSS rule and
CLASS_MAP.i entry — hfMakeBlocks only ever emits "n"/"e" tags, "i" was only
reachable via the old hand-authored BLOCKS literal this task replaced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
caption-highlight was already IIFE-wrapped; the other four leaked their
runtime (hfAttach/hfBuild/...) as script-scope globals. Harmless when boot
was synchronous, but the data-driven retrofit defers attach behind
fonts.ready + the sibling fetch — with two caption components pasted into
one composition document, every script finishes before any deferred boot
runs, the last script's definitions win the shared scope, and the first
component never registers its timeline (a renderer waiting on it hangs to
timeout). Wrapping each template's script keeps its internals private so
each boot attaches its own component. window.__HF_CAPTION_ATTACH__ remains
intentionally window-scoped (last-defined-wins).

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

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SSOT Review: data-driven caption catalog components

PR: Make the 5 caption catalog components data-driven — 5 files, +2518/-1142


What this does

Retrofits 5 standalone HTML caption components (editorial-emphasis, emoji-pop, highlight, pill-karaoke, weight-shift) from hardcoded demo transcripts to a runtime-attached data model via window.__HF_CAPTION__ / window.__HF_CAPTION_ATTACH__ / caption-data.json fetch fallback. Each component now derives its own duration from attached data, applies brand colors via CSS custom properties, and publishes a health/gate signal on window.__HF_CAPTION_STATUS.

SSOT checks

  1. Shared runtime contract is byte-identical across all 5 components. hfFlattenSegments, hfDeriveDuration, hfValidate, hfApplyStageConfig, hfPublishStatus, hfRunGate — verified identical across all 5 files. Structurally forced by the standalone-HTML IIFE architecture (no import mechanism). Acceptable for v1. ✓

  2. Boot sequence is uniform. All 5: check window.__HF_CAPTION__ → fetch caption-data.json → fall back to HF_DEMO_DATA. All wait for document.fonts.ready. Same lifecycle: validate → teardown → applyStageConfig → build → pin timeline → render(0) → register → gate. ✓

  3. window.__HF_CAPTION_STATUS gate signal is uniform. All 5 publish the same shape: { ok, present, contained, version, durationS, reason? }. Published via identical hfPublishStatus and hfRunGate. The data-caption-status attribute is set on the root in all cases. ✓

  4. Duration derivation replaces hardcoded DURATION = 8. All 5 components now use hfDeriveDuration(words) (max word.end scan). Hardcoded layout constants (e.g. x: -1920) replaced with data-derived values (e.g. x: -layout.W). ✓

  5. Hardcoded groupings replaced with algorithms. highlight: RAW_GROUPShfMakeGroups. editorial-emphasis: BLOCKShfMakeBlocks. emoji-pop: ~7 hardcoded keyword/emoji entries → ~50 generalized lexicon + hfIsEmphasisWord stopword heuristic. ✓

Bug fixes

GSAP tl.seek(0) no-op — CORRECT. GSAP 3.x treats seek(0) as "already there" on a fresh timeline (position already 0), skipping .set() calls at t=0. Fix: tl.render(0, true, true) with force=true. Applied uniformly to all 5 components — defensive and correct. ✓

O(n²) "hide all groups" loop — CORRECT. Old pattern: allGroupEls.forEach(other => if (oi !== bi) tl.set(other, { opacity: 0 }, visibleStart)) — n*(n-1) timeline entries. Fix: rely on each group's own lifecycle (opacity=1 at entry, opacity=0 at exit). Groups occupy non-overlapping time windows, so this is behavior-preserving. Applied to all 4 components that had this pattern (highlight used visibility-based show/hide instead). ✓

Observations (non-blocking)

  • window.__HF_CAPTION_ATTACH__ is last-writer-wins. If two caption components coexist in one composition, the second overwrites the first's attach function. Consider namespacing per component ID if multi-caption-per-page is a real use case.

  • Double normalization in 3 of 5 components. hfFlattenSegments normalizes words, then emoji-pop/pill-karaoke/weight-shift call their own normalizeWords() which re-processes and adds a DURATION clamp. The emoji-pop version also enriches with emoji associations — consider renaming to enrichWordsWithEmoji to distinguish enrichment from normalization.

  • Brand color consumption is intentionally asymmetric. hfApplyStageConfig sets both --hf-caption-primary and --hf-caption-accent in all 5, but editorial-emphasis and weight-shift never consume accent, and pill-karaoke never consumes primary. The no-op properties are harmless but could give users the false impression that all brand properties are supported. Consider documenting supported properties per component in descriptors.

Blocking SSOT issues

None found.

Verdict

Approve. The contract is consistent, the bug fixes are correct and well-documented, and the SSOT duplication is a known structural constraint of standalone HTML compositions. The non-blocking suggestions are improvements for the next iteration. Ship it.

— Miga

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed at e2846eb7cc81821f7fc21a9c4dccdd85c7ef3429.

Solid retrofit — five templates converted end-to-end from hardcoded demo transcript to runtime-attached ASR data, with real bug fixes threaded in (tl.seek(0) no-op, O(n²) hide loop) and honest test coverage in the sibling internal PR. The two auto-heuristic generalizations (caption-highlight grouping, caption-editorial-emphasis emphasis) look deterministic and terminate cleanly. Fallback-to-demo path preserves the public-catalog surface.

Two cross-cutting themes worth surfacing:

1. Brand-color surface is inconsistent across the five. caption-emoji-pop gates brand colors behind a strict ^#[0-9a-f]{6}$ regex — same brand payload that works in caption-highlight (#fff, rgb(...), hsl(...), red) silently falls back to the built-in defaults there. Meanwhile three templates SET one of the two CSS custom properties from brand.{primary,accent}Color but never CONSUME the property in their own CSS/JS — so brand.accentColor on caption-editorial-emphasis/caption-weight-shift, and brand.primaryColor on caption-pill-karaoke, are dead writes. The PR body claims all five apply both — worth either (a) actually consuming the ignored property or (b) narrowing the PR body / spec to say "each template applies whichever of the two its design uses". Inline anchors below.

2. The ~250-line hfFlattenSegments / hfDeriveDuration / hfValidate / hfApplyStageConfig / hfPublishStatus / hfRunGate block is byte-duplicated across all five templates. The section comment even labels it /* shared across caption templates */ but it isn't — every future edit needs five near-identical patches, and there's no CI check to catch drift. If future R2 tightens the displayMode grammar in hfValidate, the fix landing in three templates and missing two would ship unnoticed. Consider extracting to a registry/lib/caption-runtime.js shipped alongside each template, or an md5-parity CI check on the shared region.

Real bugs I want fixed (see inline):

  • 🔴 caption-emoji-pop hex-only brand-color gate silently drops any brand color that isn't exact 6-digit #RRGGBB (caption-emoji-pop.html:689-691).
  • 🟠 Dead brand-color writes in three templates (caption-editorial-emphasis.html:240, caption-pill-karaoke.html:244, caption-weight-shift.html).
  • 🟠 Shared runtime block byte-duplicated across all five templates (caption-highlight.html:187 and equivalents).
  • 🟠 caption-weight-shift.fitFontSize fits joined-text width while rendering across two lines → over-shrinks (caption-weight-shift.html:462-473).
  • 🟠 avoidSingleWordGroups merges without re-checking fitsInTwoLines → downstream splitLines can return overflowing split (caption-weight-shift.html:393-420).
  • 🟡 fitFontSize returns minSize unchecked when text still overflows at min — visible clipping under narrow safe-zone (caption-highlight.html:132-141 and identical in the other four).

Not blocking but worth a look:

  • 🔵 MAX_WORDS_PER_BLOCK = 5 / MAX_WORDS_PER_GROUP = 4 / = 3 — three templates use the same-looking constant with three different values. Not necessarily wrong (each design has its own density budget) but easy to misread.
  • ❓ Is the strict-hex gate on caption-emoji-pop intentional (some safety concern about the text-shadow color-mix?) or accidental divergence from the permissive pattern used by the sibling templates? If intentional, worth a code comment; if not, harmonize.
  • caption-editorial-emphasis emphasis density under the demo transcript trips ~14/28 words → ~1.75 block flashes/sec. Intended density, or should we cap? A future heuristic tweak could halve or double block count without any test failing — worth a snapshot fixture.

🟢 Verified-safe

  • tl.seek(0)tl.render(0, true, true) correctly swapped everywhere; render(time, suppressEvents, force=true) bypasses GSAP 3.14.2's "already at time 0 → skip" no-op.
  • O(n²) hide-all-others loop cleanly removed in three templates; behavior preserved via each group/block owning its own opacity lifecycle (set at start / clear at nextStart), same-position tween pairs firing in add-order.
  • Brand-color removeProperty correctly fires when the brand block is absent (all five) — CSS var(--x, fallback) falls to the fallback for the three CSS-consumer templates.
  • IIFE encapsulation applied to the four previously-unwrapped templates; caption-highlight was already wrapped. window.__HF_CAPTION_ATTACH__ remains last-defined-wins as documented.
  • hfValidate failure path uniform across all five: publishes __HF_CAPTION_STATUS = {ok:false, present:false, contained:false, reason, ...}.
  • hfRunGate publish shape identical across all five: {ok, present, contained, reason?, version, durationS}.
  • caption-highlight grouping algorithm is deterministic (pure function of words, fontPx, safeWidth, durationS), O(N) per word, caps at 4-word groups, handles single-word/empty/overlapping-word inputs.
  • hfFlattenSegments handles missing w.word/w.text, negative/missing start, missing end (clamps to start), sorts by start.
  • document.fonts.ready gate awaited in every template's hfBoot before hfAttachfitFontSize / canvas measurements use the intended font metrics.
  • Data-driven duration flows correctly: hfDeriveDuration(words)v.durationShfBuild(_, _, durationS)tl.set({}, {}, v.durationS) pin → data-duration attribute updated on the root.
  • Fetch error path (caption-data.json missing / non-2xx / parse error) falls back to HF_DEMO_DATA cleanly.
  • Word emphasis heuristic (clean.length >= 5 && !HF_STOPWORDS.has(clean)) — deterministic, case-insensitive.
  • caption-emoji-pop emoji lexicon lookup — case-insensitive via .toLowerCase() + replace(/[^a-z]/g, ""); no keyword collisions in the ~50-entry map (verified by inspection); no-match yields null and the group publishes without an emoji sprite.
  • caption-pill-karaoke splitLinesForGroup correctly rejects lines with any word exceeding maxLineWidth (returns null), forcing that word into its own group.

Review by Rames D Jusso

var accent = getComputedStyle(rootEl).getPropertyValue("--hf-caption-accent").trim();
hfPrimaryColor = /^#[0-9a-f]{6}$/i.test(primary) ? primary : "#FFFFFF";
hfAccentColors = /^#[0-9a-f]{6}$/i.test(accent)
? [accent]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Silent brand-color drop — same brand payload accepted by 4 sibling templates in this PR gets silently rejected here.

hfPrimaryColor = /^#[0-9a-f]{6}$/i.test(primary) ? primary : "#FFFFFF";
hfAccentColors = /^#[0-9a-f]{6}$/i.test(accent)
  ? [accent]
  : ["#FF76FF", "#FF0002", "#B2F7FF"];

Compare caption-pill-karaoke.html:525-526:

var accent = getComputedStyle(rootEl).getPropertyValue("--hf-caption-accent").trim();
hfColorActive = accent || COLOR_ACTIVE;

no format check, and caption-highlight / caption-editorial-emphasis / caption-weight-shift rely on CSS var(--x, fallback) (any valid CSS color works).

Repro: consumer sets brand: { primaryColor: "#fff", accentColor: "rgb(255,0,0)" } on a composition using caption-highlight (red gradient renders) AND caption-emoji-pop (falls back to the pink/red/blue random rotation, primary reverts to white because 3-digit #fff fails the 6-digit regex). Same brand payload, opposite outcomes. "#FFF", "red", "hsl(0 100% 50%)", "var(--tenant-red)" all silently fall back here while working elsewhere.

Either strip the regex (mirror pill-karaoke), or if there's a security reason (text-shadow color-mix?) document it and harmonize the pattern — but a silent brand-color loss inconsistent with the sibling templates in the same PR is a real regression on the wire contract.

Review by Rames D Jusso

root.style.removeProperty("--hf-caption-primary");
}
if (typeof brand.accentColor === "string" && brand.accentColor) {
root.style.setProperty("--hf-caption-accent", brand.accentColor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 --hf-caption-accent is set/cleared here but never consumed anywhere in this file — grep 'var(--hf-caption-accent' caption-editorial-emphasis.html returns 0 hits, and no getPropertyValue("--hf-caption-accent") either. brand.accentColor is dead work on this template.

Same shape in caption-pill-karaoke.html:244-248 (--hf-caption-primary dead) and caption-weight-shift.html:235-238 (--hf-caption-accent dead).

PR body claims all 5 templates "apply brand.primaryColor / brand.accentColor via CSS custom properties" — in practice 3 of 5 apply only one of the two. Fix by either (a) actually consuming the ignored property in a sensible design mapping, or (b) narrowing the PR body / spec to say "each template applies whichever of the two its design uses". As-is, callers will spend cycles debugging why one brand color renders on one template and not another.

Review by Rames D Jusso


var container = document.getElementById("hl-container");
var tl = gsap.timeline({ paused: true });
function hfFlattenSegments(segments) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 The ~250-line block from here through the end of hfRunGate is byte-duplicated across all 5 templates. Section comment even says /* HyperFrames caption-data runtime (shared across caption templates) */ — but it's not actually shared. hfFlattenSegments, hfDeriveDuration, hfValidate, hfApplyStageConfig, hfPublishStatus, hfRunGate are identical to those in caption-editorial-emphasis:178-316, caption-emoji-pop:181-319, caption-pill-karaoke:187-325, caption-weight-shift:173-311.

Failure mode: a future tightening of hfValidate (e.g. accepting w.punctuation, stricter displayMode grammar) lands in 3 templates, misses 2 — no CI catches it, the two silently drift for a release. Same risk on any bug fix to the grouping/duration helpers.

Consider either (a) extracting to a registry/lib/caption-runtime.js shipped alongside each template, or (b) an md5-parity CI check on the shared region across the 5 files. This is the same class of drift risk as feedback_shared_skill_file_byte_identity in skills/{faceless-explainer,pr-to-video,product-launch-video}.

Review by Rames D Jusso

CAPTION_FONT_SIZE,
"700",
"Montserrat",
MAX_LINE_WIDTH,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 fitFontSize is fitting the joined group text to MAX_LINE_WIDTH, but the group actually renders across two lines (see splitLines at :368-391 and the two-line .caption-line DOM at :479-493).

var groupText = group.words.map(function (w) { return w.text.toLowerCase(); }).join(" ");
var computedSize = fitFontSize(
  groupText,
  CAPTION_FONT_SIZE,
  "700",
  "Montserrat",
  MAX_LINE_WIDTH,
);

Repro: a 5-word group like ["professional","hyperframes","video","cinema","studio"] joined ≈ 1400px at base 72px, MAX_LINE_WIDTH ≈ 1360. fitFontSize shrinks to ~68px. Split as [a,b,c] / [d,e] each line is ~800px, well under MAX_LINE_WIDTH at 72px — so the shrink was unnecessary.

Fix: pass Math.max(measureLineWidth(line1), measureLineWidth(line2)) (widest of the two split lines) as the target, not joined text. Not correctness-breaking — visual only — but noticeable on ~half of two-line groups.

Review by Rames D Jusso

if (
group.words.length === 1 &&
out.length > 0 &&
out[out.length - 1].words.length < MAX_WORDS_PER_GROUP

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 avoidSingleWordGroups merges single-word groups into the previous group without re-checking fitsInTwoLines. makeGroups at :428 guards every addition with !fitsInTwoLines(testGroup); this second-pass merge skips that guard, so it can produce a group that splitLines (:368-391) cannot fit within MAX_LINE_WIDTH.

Repro: a 3-word group filled to ~90% MAX_LINE_WIDTH (e.g. ["professional","hyperframes","video"]) followed by a single-word "cinema". Merge produces 4 words at ~110% width. splitLines finds no valid split, falls to naive bestSplit = Math.ceil(words.length/2) = 2, both halves still overflow. fitFontSize shrinks (per the F4 concern above, aggressively because it fits joined width), render survives via overflow-hidden. Not a crash but reasoning about correctness breaks.

Fix: mirror the initial fitsInTwoLines guard inside avoidSingleWordGroups, or refuse to merge when the concatenated group would overflow.

Review by Rames D Jusso

@@ -132,129 +140,375 @@
return minSize;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 fitFontSize never actually tests the text at minSize before returning it. Loop terminates when size <= minSize, then returns minSize unchecked.

var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
  _fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
  if (_fitCtx.measureText(text).width <= maxWidth) return size;
  size -= 2;
}
return minSize;

Repro: a pathological long single word or a group forced into a narrow safe zone (custom resolution < 1920 wide with a 4-word group of long words) → returns minSize = 45% of base font. On the 80px caption-highlight base that's ~36px, which may still overflow the container → visible clipping (overflow-hidden truncates or line-clamp cuts).

Cheap harden: check _fitCtx.measureText(text).width <= maxWidth at minSize too, return null/base-with-overflow-hint when it still doesn't fit so the caller can pick a fallback. Same shape in the identical fitFontSize in each of the other 4 templates.

Review by Rames D Jusso

…ever clobbers a manual attach

Two review findings on the caption-data runtime, applied to all 5 templates:
- hfValidate's version gate used Math.floor(Number(v)) > HF_CONTRACT_VERSION,
  and Number("v2") is NaN — NaN comparisons are always false, so malformed
  versions slid through with no unsupported-version signal. An explicit
  Number.isFinite check closes it.
- hfBoot's sibling-fetch .then called hfAttach unconditionally; a manual
  window.__HF_CAPTION_ATTACH__ call landing while the fetch or fonts.ready
  was still pending got clobbered by the late boot payload. Boot now yields
  if a timeline already exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vanceingalls vanceingalls changed the title Make the 5 caption catalog components data-driven feat(registry): make the 5 caption catalog components data-driven Jul 24, 2026

@james-russo-rames-d-jusso james-russo-rames-d-jusso left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

R2 delta from e2846eb7c5c2981d066480000d623e4d023e9cf918b2772e3, +55/-10 across the 5 template files. Two additive fixes mirrored uniformly across every template:

  1. hfValidate version gate hardened against NaN. Number("v2") → NaN and NaN > x is always false, so malformed data.version strings (like "v2", "abc", NaN, {}) slid past the unsupported-version gate before this delta. Now Number.isFinite(hfVersion) short-circuits the comparison. Comment at each site explains the reasoning. This closes a real correctness gap I missed in R1.

  2. Boot-fetch race guard. A manual window.__HF_CAPTION_ATTACH__(data) call that landed while caption-data.json fetch or document.fonts.ready were still pending was getting clobbered by the late-arriving boot payload. Now if (hfCurrentTimeline) return; at the start of the boot ready.then(...) handler preserves the manual attach. Also a real correctness gap I missed in R1.

Delta is byte-identical across all 5 templates (each template got the same two hunks in hfValidate and the boot start function). Paired commit landed in HFI#521 (fa416450) with matching validator update + unit test asserting the version gate rejects ["v2", "abc", NaN, {}].

None of my R1 concerns are addressed by this delta — those are separate fixes still open. R1 findings stand as posted: caption-emoji-pop hex regex silently drops non-6-hex brand colors, dead brand-color writes in 3 templates, shared runtime byte-duplicated across 5, caption-weight-shift.fitFontSize fits joined-text width for 2-line render, avoidSingleWordGroups merges without re-checking two-line fit, fitFontSize returns unchecked minSize.

Review by Rames D Jusso

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Freshness review at exact head 5c2981d066480000d623e4d023e9cf918b2772e3.

Miga's prior contract review covers the shared runtime and boot sequence; Rames subsequently identified the blocker below. I independently verified it at the current head.

blocker — registry/components/caption-emoji-pop/caption-emoji-pop.html:692-697: this template writes the same brand.primaryColor / brand.accentColor CSS variables as its four siblings, then uniquely accepts only six-digit hex when reading them back. Valid inputs such as #fff, rgb(...), hsl(...), or a named color work in the sibling templates but silently fall back here. This is a wire-contract inconsistency and directly matches the internal validator mismatch raised on HFI #521. Please use the same valid-CSS-color behavior across the five templates (or enforce one canonical grammar everywhere) and add a cross-template regression for an accepted non-six-digit color.

The coordinated version/boot-race guard delta itself is clean. The semantic title is now fixed, but the current required regression aggregate is red while its rerun settles, so this head is not approvable yet independently of the code blocker.

Verdict: REQUEST CHANGES
Reasoning: One template silently rejects brand-color values accepted by the shared contract and all four siblings; exact-head required CI is also not green.

— Magi

…, 8192 clamp

- caption-emoji-pop: shadowForColor now builds its glow via color-mix()
  instead of hex-pair slicing, so the strict 6-digit brand-color gate is
  gone — any CSS color the sibling templates accept (#fff, rgb(), named)
  now renders instead of silently falling back to the default palette

- caption-weight-shift: fitFontSize now sizes against the WIDEST split
  line rather than the joined group text (two-line groups no longer shrink
  unnecessarily), and avoidSingleWordGroups' merges re-check fitsInTwoLines
  like makeGroups' first pass does (merged groups can no longer overflow
  the split budget)

- all 5: hfApplyStageConfig clamps resolution to the published validator's
  <=8192 bound (validator is optional pre-flight; unbounded stages OOM
  render workers), and fit floors carry a comment documenting that the
  minimum size is returned unverified by design

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

Copy link
Copy Markdown
Collaborator Author

Review feedback addressed in 5c2981d06 + 8bf939043 (verified by the internal package's suite, 107/107 at hyperframes-internal#521 c154ffba):

  • 🔴 emoji-pop 6-digit brand gate — gone. shadowForColor now builds its glow via color-mix() instead of hex-pair slicing, which was the only reason the gate existed; emoji-pop now accepts any non-empty CSS color, mirroring pill-karaoke and the CSS-var() templates. Covered by a new internal test attaching #ff0/#0ff (3-digit) and asserting the rendered word colors.
  • 🟠 dead brand-var writes — took option (b): the PR body now says each template applies whichever property its design consumes (per-identity brandSupport lives in the internal descriptors; the uniform set/clear of both vars in Block B is deliberate). Option (a) — inventing accent mappings for designs that don't have one — felt like scope creep on the identities' looks.
  • 🟠 duplicated runtime block — kept the single-file architecture (registry components must stay paste-able), but drift can no longer ship silently: the internal suite now has a shared-runtime parity test that extracts hfFlattenSegments/hfDeriveDuration/hfValidate/hfApplyStageConfig/hfPublishStatus/hfRunGate/hfAttach from all 5 vendored templates and asserts whitespace-normalized identity. A future fix landing in 3 of 5 fails CI on the next vendor.
  • 🟠 weight-shift joined-text fitting — fixed: fitFontSize now sizes against the widest split line, so two-line groups that fit at full size no longer shrink.
  • 🟠 avoidSingleWordGroups unguarded merges — fixed: all three merge sites now re-check fitsInTwoLines, mirroring makeGroups' first-pass guard.
  • 🟡 fitFontSize floor returned unverified — that's by design (self-heal accepts clipped overflow at the floor rather than shrinking below legibility); now documented with a comment at every fit floor, including pill-karaoke's fontSizeForGroup.
  • Also: hfApplyStageConfig now clamps resolution to the published validator's ≤8192 bound in all 5 (the validator is an optional pre-flight; unbounded stages would OOM render workers).

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed exact head 8bf939043fb44e05f6dd7caba81822ac5ef18334.

The prior blocker is resolved: caption-emoji-pop now preserves the same CSS-color input contract as the other four templates instead of silently rejecting non-6-digit colors, and the internal regression covers 3-digit hex values end to end. I also re-verified:

  • the seven shared runtime functions are identical across all five components, with an internal parity test guarding drift;
  • the version, resolution, and boot-race guards are consistently applied;
  • the caption-weight-shift line-fit and widest-line sizing fixes address the reviewed edge cases;
  • all exact-head required checks are green, including both Windows jobs and the fresh eight-shard regression matrix.

No remaining blocking findings.

Verdict: APPROVE
Reasoning: The reported correctness issue is fixed with regression coverage, the shared contract remains synchronized, and exact-head CI is fully green.

— Magi

@vanceingalls
vanceingalls merged commit 9d4c349 into main Jul 24, 2026
83 of 95 checks passed
@vanceingalls
vanceingalls deleted the feat/caption-data-driven branch July 24, 2026 07:00
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.

4 participants