From 963e8087a800d8604a621af53ebeaddf900c465b Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 15 Jul 2026 15:01:00 +0700 Subject: [PATCH] docs: clean legacy plans and document local GIFs --- renderer/.gitignore | 1 + renderer/README.md | 59 +++++++- .../260715-1124-bold-wrapped-wheel-labels.md | 35 ----- ...adable-adaptive-wheel-labels-brainstorm.md | 34 ----- ...715-1437-readable-adaptive-wheel-labels.md | 34 ----- .../phase-01-implementation.md | 57 -------- .../phase-02-verification.md | 56 -------- .../plan.md | 51 ------- .../reports/harness/context-snippets.json | 27 ---- .../reports/harness/review-decision.json | 33 ----- .../reports/harness/risk-gate.json | 16 --- .../reports/harness/verification.json | 50 ------- ...phase-01-tests-first-and-implementation.md | 95 ------------- .../phase-02-renderer-verification.md | 83 ----------- .../plan.md | 62 --------- .../harness/adversarial-validation.json | 11 -- .../reports/harness/context-snippets.json | 29 ---- .../reports/harness/review-decision.json | 39 ------ .../reports/harness/risk-gate.json | 18 --- .../reports/harness/verification.json | 63 --------- ...adable-adaptive-wheel-labels-brainstorm.md | 130 ------------------ 21 files changed, 56 insertions(+), 927 deletions(-) delete mode 100644 renderer/docs/journals/260715-1124-bold-wrapped-wheel-labels.md delete mode 100644 renderer/docs/journals/260715-1302-readable-adaptive-wheel-labels-brainstorm.md delete mode 100644 renderer/docs/journals/260715-1437-readable-adaptive-wheel-labels.md delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-01-implementation.md delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-02-verification.md delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/plan.md delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/context-snippets.json delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/review-decision.json delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/risk-gate.json delete mode 100644 renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/verification.json delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-01-tests-first-and-implementation.md delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-02-renderer-verification.md delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/plan.md delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/adversarial-validation.json delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/context-snippets.json delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/review-decision.json delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/risk-gate.json delete mode 100644 renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/verification.json delete mode 100644 renderer/plans/reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md diff --git a/renderer/.gitignore b/renderer/.gitignore index f4a3dc6..b279e90 100644 --- a/renderer/.gitignore +++ b/renderer/.gitignore @@ -6,4 +6,5 @@ coverage/ !.env.example .tmp/ fixtures/*.gif +/wheel.gif *.log diff --git a/renderer/README.md b/renderer/README.md index 9741d7e..74987f3 100644 --- a/renderer/README.md +++ b/renderer/README.md @@ -33,22 +33,73 @@ Response is `image/gif` with winner metadata headers: ## Local +Install dependencies and Chromium once: + ```sh pnpm install +pnpm browser:ensure +``` + +Start the local API: + +```sh pnpm dev ``` -Smoke render: +### Generate GIF files locally + +Generate the quick smoke fixture at the git-ignored path +`fixtures/smoke.gif`: ```sh -pnpm browser:ensure pnpm render:smoke -pnpm api:smoke ``` -Quality gates: +Generate the complete fixture set at `fixtures/smoke.gif`, +`fixtures/vietnamese.gif`, and `fixtures/sixteen-options.gif`: ```sh +pnpm render:fixtures +``` + +For a custom GIF, keep `pnpm dev` running in one terminal, then run one of +these commands in another. + +PowerShell: + +```powershell +$body = @{ + options = @("Chiều nay uống CraneTea", "Chiều nay uống CraneTea", "Chiều nay uống CraneTea", "Chiều nay uống CraneTea") + winnerIndex = 1 + durationMs = 6500 + holdMs = 1200 + fps = 15 + size = 512 + theme = "classic" +} | ConvertTo-Json + +Invoke-WebRequest -Method Post -Uri http://localhost:3000/api/gif -ContentType "application/json" -Body $body -OutFile wheel.gif +``` + +macOS, Linux, or Git Bash: + +```sh +curl --request POST http://localhost:3000/api/gif \ + --header "Content-Type: application/json" \ + --output wheel.gif \ + --data '{"options":["Chiều nay uống CraneTea","Chiều nay uống CraneTea","Chiều nay uống CraneTea","Chiều nay uống CraneTea"],"winnerIndex":1,"durationMs":6500,"holdMs":1200,"fps":15,"size":512,"theme":"classic"}' +``` + +The local API does not need an authorization header by default. If you +configure `API_TOKEN`, add `--header "Authorization: Bearer "` to the +request. Generated GIF files are git-ignored and safe to delete. + +### Verify + +Run the API smoke test and quality gates: + +```sh +pnpm api:smoke pnpm lint pnpm typecheck pnpm test diff --git a/renderer/docs/journals/260715-1124-bold-wrapped-wheel-labels.md b/renderer/docs/journals/260715-1124-bold-wrapped-wheel-labels.md deleted file mode 100644 index a082f43..0000000 --- a/renderer/docs/journals/260715-1124-bold-wrapped-wheel-labels.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -date: 2026-07-15 -session: bold-wrapped-wheel-labels ---- - -# Journal: 2026-07-15 — Bold Wrapped Wheel Labels - -## Context - -Wheel labels needed bold typography and automatic line wrapping so long entries remained readable without changing the GIF API or wheel geometry. - -## What Happened - -- Added bold, Unicode-safe multiline label layout with balanced word-boundary wrapping and bounded line height. -- Independent review exposed a 384px regression: short labels such as `alpha` wrapped because the first implementation wrapped before trying the supported one-line font-size range. -- Corrected the layout order to preserve one-line text whenever it fits at 8px or larger, then wrap only when necessary. -- Measured fit against content width after excluding horizontal padding, preventing padding from being counted twice. -- Validation passed: lint, typecheck, 29 tests, render smoke and visual inspection, a 135-case layout probe, and independent review scored 9.7/10. - -## Reflection - -The initial wrapping feature handled long content but optimized the wrong constraint first. Minimum-size rendering needed explicit regression coverage at the smallest supported wheel size. Separating content width from padding and defining the 8px one-line threshold made the behavior deterministic across short, long, and unbroken labels. - -## Decisions Made - -| Decision | Rationale | Impact | -|----------|-----------|--------| -| Attempt one-line fit before wrapping | Short names should remain intact at supported sizes | Prevents unnecessary wrapping at 384px | -| Use 8px as the one-line fit threshold | Preserves readability while allowing modest shrinking | Long labels wrap only after the readable one-line range is exhausted | -| Exclude horizontal padding from content width | Layout measurements must represent the actual text box | Avoids double-counting padding and false overflow | -| Keep the public API unchanged | The change is internal presentation behavior | Existing clients, routes, and winner metadata remain compatible | - -## Next Steps - -- No follow-up required; retain the minimum-size and multiline cases as regression coverage. diff --git a/renderer/docs/journals/260715-1302-readable-adaptive-wheel-labels-brainstorm.md b/renderer/docs/journals/260715-1302-readable-adaptive-wheel-labels-brainstorm.md deleted file mode 100644 index f106136..0000000 --- a/renderer/docs/journals/260715-1302-readable-adaptive-wheel-labels-brainstorm.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: 2026-07-15 -session: readable-adaptive-wheel-labels-brainstorm ---- - -# Journal: 2026-07-15 — Readable Adaptive Wheel Labels - -## Context - -The label `Chiều nay uống CraneTea` remains a single 9px line on a 512px wheel. The current layout wraps only when text reaches the 8px hard minimum, so it treats technical fit as sufficient even when the result is difficult to read. - -## What Happened - -- Reframed the issue as a readability-threshold problem rather than simply a newline problem. -- Approved 14px as the preferred readable font-size threshold. -- Agreed to wrap labels into two or three balanced lines when slice geometry safely permits. -- Preserved the complete smaller label as the intentional fallback for dense wheels where multiline text would overlap adjacent slices. -- No code was implemented during this brainstorm. - -## Reflection - -The existing 8px rule protects against overflow but not readability. Separating the 14px preferred threshold from the 8px hard minimum makes the intended behavior explicit: use available vertical space when geometry permits, while acknowledging that dense wheels cannot provide large text, full content, and non-overlap simultaneously. - -## Decisions Made - -| Decision | Rationale | Impact | -|---|---|---| -| Use 14px as the preferred threshold | Fixes the unreadable 9–13px one-line range | Medium-length labels wrap sooner | -| Wrap only when two or three lines fit | Prevents adjacent-slice overlap | Layout remains bounded across supported densities | -| Preserve full smaller text for dense wheels | Avoids truncation and API changes | Complete labels remain visible when geometry cannot support wrapping | - -## Next Steps - -- Create a tests-first implementation plan with `/ck:plan --tdd` using the approved brainstorm report. diff --git a/renderer/docs/journals/260715-1437-readable-adaptive-wheel-labels.md b/renderer/docs/journals/260715-1437-readable-adaptive-wheel-labels.md deleted file mode 100644 index b81fdd5..0000000 --- a/renderer/docs/journals/260715-1437-readable-adaptive-wheel-labels.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -date: 2026-07-15 -session: readable-adaptive-wheel-labels ---- - -# Journal: 2026-07-15 — Readable Adaptive Wheel Labels - -## Context - -The repeated example `Chiều nay uống CraneTea` remained on one line and shrank too far to read. The existing layout wrapped only when text reached the hard 8px minimum, so a one-line 9px fit bypassed wrapping despite available slice height. - -## What Happened - -- Used strict TDD: nine targeted regressions failed first, then all 22 focused tests passed after the layout change. -- Separated the preferred 14px readability threshold from the hard 8px fallback. Text below 14px now wraps only when slice geometry can contain the extra lines. -- Confirmed adaptive output for the example: 19px across three lines at 2 and 8 entries; 14px across two lines at 12 and 16 entries; complete 9px one-line fallback at 24 and 32 entries. -- Full verification passed: lint, typecheck, 41 tests, render smoke, exact Vietnamese visual inspection, independent review at 9.8/10, and the ClaudeKit artifact gate. -- Kept the API, Remotion composition, and wheel geometry unchanged. Temporary GIF and PNG verification artifacts were cleaned up. - -## Reflection - -A hard rendering minimum is not a readability target. Treating 14px as a preference and 8px as a last-resort floor lets roomy wheels use multiline labels while dense wheels retain complete text without overlap. - -## Decisions Made - -| Decision | Rationale | Impact | -|----------|-----------|--------| -| Prefer 14px before accepting a smaller one-line fit | 9–13px text can technically fit but remain hard to read | Long labels wrap earlier when space permits | -| Gate wrapping by available slice geometry | Dense wheels cannot safely contain multiple readable lines | 24–32-entry wheels retain the complete bounded fallback | -| Preserve existing public and rendering contracts | This is a label-layout correction | No API, composition, or geometry migration required | - -## Next Steps - -- Retain the Vietnamese phrase and 9–13px boundary cases as regression coverage. diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-01-implementation.md b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-01-implementation.md deleted file mode 100644 index 91a29f4..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-01-implementation.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -phase: 1 -title: Implementation -status: completed -priority: P1 -dependencies: [] -effort: small ---- - -# Phase 1: Implementation - -## Overview - -Add deterministic wrapped-label metadata to the pure layout helper and consume it in the Remotion composition. Keep the change local to wheel label presentation. - -## Requirements - -- Functional: split long labels into balanced lines, retain one line for short labels, size against the longest output line, render at bold weight, and clip overflow. -- Non-functional: deterministic output across frames; JavaScript + JSDoc; no public API, theme, or wheel geometry changes. - -## Architecture - -`getRadialLabelLayout()` remains the composition-facing boundary. Extend its return value with the rendered line list and a bounded text-block height. A pure wrapping helper should normalize whitespace, prefer word boundaries, and choose the most balanced split whose longest estimated line best fits the radial track. Preserve text for unbroken long tokens with a deterministic fallback rather than relying on browser-dependent wrapping. Pass the longest returned line to `getLabelFontSize()` so wrapping reduces shrinkage without removing the existing minimum-size safeguard. - -`WheelComposition.jsx` renders the precomputed lines in a centered block using `fontWeight: 700`, compact line height, and explicit width/height plus `overflow: hidden`. Do not use ellipsis or `nowrap`, because the helper owns the line breaks. - -## Related Code Files - -- Modify: `C:/Users/miti99/Workspaces/tiennm99/wheelofnames/src/remotion/wheel-label-layout.js` — wrapping, longest-line sizing, returned layout metadata/JSDoc. -- Modify: `C:/Users/miti99/Workspaces/tiennm99/wheelofnames/src/remotion/WheelComposition.jsx` — bold multi-line rendering and clipping. -- Create: none. -- Delete: none. - -## Implementation Steps - -1. Add a small exported pure helper for label-line selection. Return `[text]` when the base-size estimate fits; otherwise evaluate sensible word-boundary splits and select balanced lines by minimizing the longest estimated line, with deterministic handling for whitespace and single long tokens. -2. Update `getRadialLabelLayout()` and its JSDoc shape to return `lines` and a bounded label-block height while computing `fontSize` from the longest returned line. -3. Update the label element in `WheelComposition.jsx` to render the prepared lines at weight `700`, center them with compact multi-line spacing, and clip to the returned width/height. -4. Preserve radial coordinates, rotation, slice rendering, color contrast, and all component props. - -## Success Criteria - -- [x] Short text yields one line and keeps the normal base size when space permits. -- [x] Long phrase yields balanced multiple lines and a larger readable size than whole-label fitting would allow. -- [x] Font size is calculated from the longest returned line. -- [x] Rendered labels are bold, centered, and clipped inside their label box. -- [x] No API/schema, theme, or wheel geometry file changes. - -## Risk Assessment - -- Dense wheels can have limited cross-track height. Mitigate with a bounded line count/height and clipping derived from existing label geometry. -- Width estimates are approximate and bold glyphs are wider. Keep conservative padding and verify the rendered smoke GIF. -- Unbroken or non-Latin text may not have word boundaries. Use deterministic grapheme-safe fallback behavior and cover it with a focused unit case if the helper supports splitting it. - -## Security Considerations - -No new input surface or HTML injection path. React continues to escape label text; preserve request validation and do not use raw HTML. diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-02-verification.md b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-02-verification.md deleted file mode 100644 index 2dac6f6..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/phase-02-verification.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -phase: 2 -title: Verification -status: completed -priority: P1 -dependencies: - - 1 -effort: small ---- - -# Phase 2: Verification - -## Overview - -Lock the layout contract with focused unit tests, then verify static quality gates and actual Remotion output. - -## Requirements - -- Functional: tests witness one-line, balanced multi-line, and longest-line sizing behavior. -- Non-functional: all required repository gates pass; rendered smoke output completes without label-layout regressions. - -## Architecture - -Unit tests exercise the pure helpers so line-selection and sizing regressions fail cheaply. `render:smoke` is the visual/runtime witness for React styles, clipping, Chromium font layout, and GIF generation. - -## Related Code Files - -- Modify: `C:/Users/miti99/Workspaces/tiennm99/wheelofnames/test/wheel-label-layout.test.js` — focused wrapping and sizing assertions. -- Witness: `C:/Users/miti99/Workspaces/tiennm99/wheelofnames/scripts/render-fixtures.js` via `pnpm render:smoke` — no planned script change. -- Create: none. -- Delete: none. - -## Implementation Steps - -1. Replace the old whole-label shrink expectation with assertions that a short label remains one line and a long phrase wraps into balanced lines. -2. Assert the returned font size matches sizing based on the longest rendered line and is not based on the unsplit full label. -3. Add edge coverage for whitespace normalization and/or a long unbroken label according to the implemented helper contract. -4. Run `pnpm lint`, `pnpm typecheck`, and `pnpm test`. -5. Run `pnpm render:smoke`; inspect command success and confirm the generated smoke frame/GIF does not overflow or truncate labels unexpectedly. Do not commit generated GIFs or temporary render artifacts. - -## Success Criteria - -- [x] `pnpm lint` passes. -- [x] `pnpm typecheck` passes. -- [x] `pnpm test` passes with focused wrapping witnesses. -- [x] `pnpm render:smoke` passes and visually witnesses bold, readable labels without clipping. -- [x] `git diff` contains only the planned source, test, and plan changes; no generated GIFs or secrets. - -## Risk Assessment - -- A smoke fixture may not contain a sufficiently long label. If its existing input cannot witness wrapping, make the smallest fixture-input adjustment without changing production API behavior; never commit the generated GIF. -- Font rendering varies by host. Treat pure-helper tests as the deterministic contract and smoke rendering as integration evidence. - -## Security Considerations - -No security behavior changes. Ensure test fixtures contain only synthetic labels and no private data. diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/plan.md b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/plan.md deleted file mode 100644 index 0eba19f..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/plan.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Bold Wrapped Wheel Labels -description: >- - Render wheel option labels in bold and wrap long text into balanced lines - without changing the API or wheel geometry. -status: completed -priority: P2 -branch: main -tags: - - feature - - frontend -blockedBy: [] -blocks: [] -created: '2026-07-15T03:47:02.124Z' -createdBy: 'ck:plan' -source: skill ---- - -# Bold Wrapped Wheel Labels - -## Overview - -Replace whole-label shrink-to-fit behavior with deterministic multi-line layout. Short labels remain one line; long labels are split at sensible boundaries, balanced across the available radial track, and sized from the longest rendered line. The Remotion composition renders the returned lines at bold weight and clips the text block to its label area. - -## Scope - -- Modify label layout and rendering only. -- Preserve `/api` request/response schemas, themes, and wheel geometry. -- Use JavaScript + JSDoc and existing Remotion/CSS patterns; add no custom font or SVG work. - -## Phases - -| Phase | Name | Status | -|-------|------|--------| -| 1 | [Implementation](./phase-01-implementation.md) | Completed | -| 2 | [Verification](./phase-02-verification.md) | Completed | - -## Dependencies - -- Cross-plan: none. -- Runtime: existing React, Remotion, and CSS rendering stack. -- Quality gates: Node.js 24, `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm render:smoke`. - -## Acceptance Criteria - -- Labels render with a bold font weight. -- Long labels become balanced multiple lines within the radial label track. -- Font sizing uses the longest rendered line, not the original full label. -- Short labels remain one line. -- Multi-line content remains clipped within the computed label area. -- API/schema, themes, and wheel geometry remain unchanged. diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/context-snippets.json b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/context-snippets.json deleted file mode 100644 index 769993a..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/context-snippets.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "skill": "ck:code-review", - "mode": "review", - "task": "Independent re-review of the short-label wrapping fix", - "acceptanceCriteria": [ - "labels use bold font weight", - "long labels wrap deterministically into balanced lines", - "font sizing uses the longest rendered line", - "short labels remain one line at every supported output size", - "multi-line content is clipped within a bounded label area", - "API schemas, routes, themes, slices, and radial geometry remain unchanged" - ], - "touchpoints": [ - "src/remotion/wheel-label-layout.js", - "src/remotion/WheelComposition.jsx", - "test/wheel-label-layout.test.js" - ], - "publicContracts": [ - "POST /api/gif request and response contract remains unchanged", - "getRadialLabelLayout supplies deterministic presentation metadata to the Remotion composition" - ], - "blastRadius": [ - "wheel label line breaking, font sizing, padding, and clipping", - "Remotion label rendering at 384px, 480px, and 512px with 2-32 options" - ], - "scoutSummary": "The fix addresses the layout decision itself: it first computes a fitted one-line size and wraps only when that size reaches the existing 8px floor and slice height permits multiple lines. The padded content width is used for measurement while the unchanged track width remains the border-box width. No character-specific patch, API change, theme change, slice change, or radial-position change was found." -} diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/review-decision.json b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/review-decision.json deleted file mode 100644 index 7ce8485..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/review-decision.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "decision": "PASS", - "score": 9.7, - "criticalCount": 0, - "warningCount": 0, - "suggestionCount": 0, - "criticals": [], - "warnings": [], - "suggestions": [], - "acceptanceCoverage": [ - "fontWeight 700 is applied by the Remotion composition", - "short labels are fitted as one line before wrapping is considered", - "the original 384px alpha/beta/gamma/delta blocker is covered by an exact regression test and fresh direct probe", - "long labels wrap deterministically at word boundaries or Unicode grapheme boundaries and size from the longest rendered line", - "content width subtracts horizontal padding while the outer width remains the unchanged radial track width", - "height remains bounded by per-slice arc length and dense 32-option layouts stay to one line", - "API, schema, route, render flow, themes, slice geometry, contrast behavior, and radial coordinates are unchanged" - ], - "regressionProof": [ - "lint and JSDoc typecheck pass", - "all 29 tests pass", - "the exact 384px probe keeps alpha, beta, gamma, and delta on one line", - "a 135-case supported-size, density, maximum-length, and Unicode matrix reports no failures", - "render smoke completes with a 407905-byte GIF in 3163ms" - ], - "contractStatus": "OK", - "blockingReasons": [], - "sideEffects": [ - "the intended bold font changes text metrics", - "very long labels wrap only when one-line fitting would reach the 8px floor and slice height permits it", - "padding now consumes space inside the existing label width, preventing CSS border-box overflow" - ] -} diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/risk-gate.json b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/risk-gate.json deleted file mode 100644 index 96acca1..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/risk-gate.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "highRisk": false, - "reasons": [ - "presentation-only Remotion label layout change", - "the change retains the existing 8px font floor, three-line cap, track coordinates, slice geometry, contrast helper, and API contracts", - "candidate generation is bounded by the configured 40-character option limit and at most three lines" - ], - "autoStopRequired": false, - "humanApproved": true, - "largeDiff": false, - "sideEffects": [ - "labels that cannot fit above the 8px floor may now wrap when slice height permits", - "label padding is now included inside the existing radial-track border box instead of increasing its rendered width", - "dense 32-option layouts remain one clipped line because their available slice height cannot support wrapping" - ] -} diff --git a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/verification.json b/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/verification.json deleted file mode 100644 index 5b29924..0000000 --- a/renderer/plans/260715-1047-bold-wrapped-wheel-labels/reports/harness/verification.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "commands": [ - { - "command": "pnpm lint", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "ESLint completed without errors." - }, - { - "command": "pnpm typecheck", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "TypeScript JSDoc checking completed without errors." - }, - { - "command": "pnpm test", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "All 5 test files and 29 tests passed." - }, - { - "command": "node --input-type=module -e ", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "All 135 cases passed across 384/480/512px, 2/4/8/16/32 options, 40-character text, emoji, ZWJ graphemes, and combining marks. The exact 384px four-option probe returned alpha=[alpha] at 21px, beta=[beta] at 22px, gamma=[gamma] at 20px, and delta=[delta] at 21px." - }, - { - "command": "pnpm render:smoke", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "Rendered a 407905-byte GIF in 3163ms; the generated fixture was removed after verification." - }, - { - "command": "git diff --check", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T11:21:43.4859125+07:00", - "summary": "No whitespace errors were reported." - } - ], - "beforeAfter": { - "before": "The first wrapping implementation broke common short labels at 384px because it wrapped at base size before attempting a modest one-line shrink, and CSS padding was not subtracted from the measured width.", - "after": "The layout first fits a single line against padded content width, wraps only at the 8px floor when height allows, and preserves the radial track as the outer border-box width." - } -} diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-01-tests-first-and-implementation.md b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-01-tests-first-and-implementation.md deleted file mode 100644 index 604eeb2..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-01-tests-first-and-implementation.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -phase: 1 -title: Tests First and Implementation -status: completed -priority: P1 -dependencies: [] -effort: small ---- - -# Phase 1: Tests First and Implementation - -## Overview - -Lock the desired readability behavior in failing unit regressions, then make the smallest layout-helper change that separates preferred readability from hard minimum fit. - -## Context Links - -- [Approved brainstorm](../reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md) -- [Completed wrapping plan](../260715-1047-bold-wrapped-wheel-labels/plan.md) -- [Project README](../../README.md) - -## Requirements - -- Functional: prefer 14px; wrap below that threshold when `maxLines >= 2`; keep full text at the 8px hard fallback when only one line fits safely. -- Functional: the 512px example wraps for 2–16 options when geometry permits; 24–32 options stay bounded and complete. -- Non-functional: deterministic output; Unicode-safe breaks; no public contract, theme, or geometry change. - -## Architecture - -Keep decision-making inside `getRadialLabelLayout`. Compute one-line fit first, compare it with a named 14px preferred threshold, and call the existing deterministic `getLabelLines` path only when geometry allows multiple lines. Continue sizing from the longest chosen line and bounding height by tangential arc availability. Do not add renderer state or a second wrapping implementation. - -## Related Code Files - -| Action | Absolute path | Purpose | -|---|---|---| -| Modify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\test\wheel-label-layout.test.js` | Add failing threshold, boundary, Unicode, short-label, and dense-layout regressions. | -| Modify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\src\remotion\wheel-label-layout.js` | Separate 14px preferred readability from the 8px hard minimum and geometry-gate wrapping. | -| Conditional modify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\src\remotion\WheelComposition.jsx` | Only if implementation proves new layout metadata is required; otherwise leave unchanged. | - -## Implementation Steps - -### Tests Before - -1. Add a table-driven regression for `Chiều nay uống CraneTea` at size 512 and representative supported counts across 2–16. Assert multiple lines only when computed geometry permits, full text reconstruction, bounded height, and wrapped `fontSize >= 14`. -2. Add targeted cases whose current one-line fit lands at 9, 10, 11, 12, and 13px. Assert they no longer remain one line when `maxLines >= 2`. -3. Run `pnpm test -- test/wheel-label-layout.test.js`; record that new regressions fail against the current 8px-only trigger for the intended reason. - -### Refactor - -4. Introduce a named preferred label font size of 14px beside the existing 8px minimum; keep their responsibilities separate. -5. Change the wrap decision from equality with the hard minimum to `singleLineFontSize < preferredLabelFontSize && maxLines >= 2`. -6. Reuse `getLabelLines`, longest-line sizing, content width, and height bounding. Avoid API changes and avoid touching `WheelComposition.jsx` unless metadata is strictly necessary. -7. Run the focused test file until the tests-before regressions pass. - -### Tests After - -8. Add or refine boundary coverage: exactly 14px remains one line; 13px wraps when permitted; one-line-only geometry preserves full text at the smaller fallback. -9. Retain regressions for 384px/512px short labels on one line, 24–32 dense layouts bounded, whitespace normalization, unbroken tokens, and Vietnamese/emoji grapheme reconstruction without broken segments. -10. Assert no selected layout exceeds available tangential height and all selected lines fit padded `contentWidth` under the estimator. - -### Regression Gate - -11. Run `pnpm test -- test/wheel-label-layout.test.js`. -12. Run `pnpm typecheck` and `pnpm lint` before phase completion. - -## Todo List - -- [x] Tests-before cases fail for the existing 9–13px blind spot. -- [x] Preferred and hard-minimum constants have distinct roles. -- [x] Geometry-gated wrapping passes exact example and boundary tests. -- [x] Unicode, short-label, and dense fallback regressions pass. -- [x] Focused tests, typecheck, and lint pass. - -## Success Criteria - -- [x] `Chiều nay uống CraneTea` wraps at 512px for 2–16 options whenever `maxLines >= 2`, reconstructs exactly, and targets at least 14px. -- [x] Short labels remain one line at supported sizes. -- [x] 24–32 option layouts preserve full text at the smaller fallback and remain height-bounded. -- [x] Vietnamese graphemes and existing unbroken-token behavior remain intact. -- [x] No API/schema, theme, geometry, or winner metadata code changes. -- [x] Focused tests, typecheck, and lint pass. - -## Risk Assessment - -- Risk: a hard 14px trigger may wrap labels that are only marginally smaller. Mitigation: exact 13/14px boundary tests and short-label fixtures. -- Risk: wrapping at 16 options may exceed tangential space. Mitigation: derive `maxLines` from existing arc height and assert the final box stays bounded. -- Risk: width estimation can split Vietnamese incorrectly. Mitigation: preserve `Intl.Segmenter` and assert exact grapheme-safe reconstruction. - -## Security Considerations - -No new input, I/O, authentication, or external dependency surface. Existing request validation remains unchanged. - -## Next Steps - -Proceed to renderer verification only after the regression gate is green. diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-02-renderer-verification.md b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-02-renderer-verification.md deleted file mode 100644 index 92e91cf..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/phase-02-renderer-verification.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -phase: 2 -title: Renderer Verification -status: completed -priority: P1 -dependencies: - - 1 -effort: small ---- - -# Phase 2: Renderer Verification - -## Overview - -Run the full project gates and produce a targeted Vietnamese rendering witness that confirms readable wrapping without clipping or adjacent-slice overlap. - -## Context Links - -- [Approved brainstorm](../reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md) -- [Project README](../../README.md) -- [Render benchmarks](../../docs/render-benchmarks.md) - -## Requirements - -- Functional: verify exact Vietnamese text visually at 512px and low/medium supported density. -- Non-functional: all repository gates pass; public API contracts remain unchanged; no generated GIF remains in version control or the worktree. - -## Architecture - -Verification exercises the pure layout helper through Vitest and the existing Remotion render pipeline through project scripts. Prefer changing only a Vietnamese fixture input to include the exact phrase; do not add a new renderer path. API compatibility is established through existing schema/route tests and diff inspection. - -## Related Code Files - -| Action | Absolute path | Purpose | -|---|---|---| -| Verify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\src\remotion\wheel-label-layout.js` | Confirm final threshold and geometry logic. | -| Verify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\test\wheel-label-layout.test.js` | Confirm all targeted and regression cases. | -| Conditional modify | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\scripts\render-fixtures.js` | Smallest fixture-input-only change needed to render `Chiều nay uống CraneTea`. | -| Verify only | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\src\remotion\WheelComposition.jsx` | Confirm bold rendering consumes existing `lines`, `fontSize`, and bounded height unchanged. | -| Cleanup | `C:\Users\miti99\Workspaces\tiennm99\wheelofnames\fixtures\*.gif` | Remove generated visual artifacts after inspection. | - -## Implementation Steps - -1. Run `pnpm lint`, `pnpm typecheck`, and `pnpm test`; require zero failures. -2. Run `pnpm render:smoke` to protect the 384px renderer, bundle reuse, and GIF output path. -3. Render a 512px Vietnamese witness containing `Chiều nay uống CraneTea`. Prefer the existing `vietnamese` fixture with the smallest input-only edit; run `pnpm render:fixtures` when that fixture path is used. -4. Inspect the witness at a stable frame: exact graphemes present, bold text, two/three balanced lines where geometry permits, font visually readable, no radial clipping, and no adjacent-slice overlap. -5. Confirm dense behavior through unit cases for 24–32 options; render an additional dense witness only if unit bounds or visual inspection are ambiguous. -6. Run existing route/schema tests as part of `pnpm test`; inspect the diff to confirm no `/api` request/response schema, response headers, themes, or wheel geometry changed. -7. Remove all generated GIFs and temporary render artifacts. Run `git status --short` and confirm only intended source/test/optional fixture input and plan files remain. -8. Re-run any gate affected by cleanup or fixture edits. - -## Todo List - -- [x] Full lint, typecheck, and test suite pass. -- [x] `render:smoke` passes. -- [x] Vietnamese 512px witness passes visual inspection. -- [x] Dense 24–32 fallback remains bounded and complete. -- [x] API/schema/theme/geometry contracts remain unchanged. -- [x] Generated artifacts are removed. - -## Success Criteria - -- [x] `pnpm lint`, `pnpm typecheck`, and `pnpm test` pass. -- [x] `pnpm render:smoke` produces a valid GIF. -- [x] Targeted Vietnamese visual evidence confirms readable wrapping and intact graphemes without clipping/overlap. -- [x] 24–32 dense layouts retain complete labels within computed height. -- [x] Winner metadata and `/api` behavior remain covered and unchanged. -- [x] No generated GIF or temporary renderer artifact remains. - -## Risk Assessment - -- Risk: visual verification is subjective. Mitigation: pair it with numeric font-size, width, reconstruction, and height assertions. -- Risk: full fixture rendering is slow. Mitigation: use the existing targeted fixture and avoid adding redundant fixtures. -- Risk: generated outputs get committed. Mitigation: cleanup plus final `git status --short` inspection. - -## Security Considerations - -No security behavior changes. Verification must not expose tokens or persist environment files; production API authentication tests remain untouched. - -## Next Steps - -Mark the plan complete only after both automated and visual gates pass and artifacts are clean. diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/plan.md b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/plan.md deleted file mode 100644 index ab1925b..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/plan.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Readable Adaptive Wheel Labels -description: >- - Wrap medium-length wheel labels at a preferred 14px readability threshold - while preserving bounded full-text fallback for dense wheels. -status: completed -priority: P2 -branch: main -tags: - - bugfix - - frontend - - renderer - - tdd -blockedBy: [] -blocks: [] -created: '2026-07-15T06:29:40.959Z' -createdBy: 'ck:plan' -source: skill ---- - -# Readable Adaptive Wheel Labels - -## Overview - -Fix the 9–13px readability blind spot in radial wheel labels. Keep 8px as the hard full-text fallback, but prefer deterministic two/three-line wrapping when the one-line result is below 14px and slice geometry permits. Preserve short-label behavior, Vietnamese graphemes, API contracts, themes, and wheel geometry. - -Approved design: [Readable Adaptive Wheel Labels Brainstorm](../reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md). - -## Phases - -| Phase | Name | Status | -|-------|------|--------| -| 1 | [Tests First and Implementation](./phase-01-tests-first-and-implementation.md) | Completed | -| 2 | [Renderer Verification](./phase-02-renderer-verification.md) | Completed | - -## Dependencies - -- Cross-plan dependencies: none. The completed [Bold Wrapped Wheel Labels](../260715-1047-bold-wrapped-wheel-labels/plan.md) plan is implementation history, not a blocker. -- Runtime: existing JavaScript + JSDoc, React, Remotion, and CSS renderer stack. -- Quality gates: Node.js 24, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm render:smoke`, and Vietnamese visual evidence. - -## Scope - -- Primary touchpoints: `src/remotion/wheel-label-layout.js` and `test/wheel-label-layout.test.js`. -- Fixture input may change only when needed for the Vietnamese visual witness. -- `WheelComposition.jsx` changes only if layout metadata is proven necessary. -- No API/schema, theme, wheel geometry, truncation, custom font, or curved-text changes. - -## Acceptance Criteria - -- At 512px, `Chiều nay uống CraneTea` wraps for 2–16 options whenever geometry permits; wrapped text targets at least 14px. -- Ordinary short labels remain one line. -- 24–32 option layouts remain bounded and preserve complete text at the smaller fallback size. -- Vietnamese grapheme clusters remain intact. -- No API/schema, theme, winner metadata, or wheel geometry changes. -- Full automated gates and visual renderer evidence pass; generated artifacts are removed. - -## Risks - -- Estimated width may differ from browser glyph width. Mitigate with exact unit boundaries plus rendered Vietnamese evidence. -- Threshold changes can over-wrap short labels. Mitigate with 384px and 512px one-line regressions. -- Dense slices cannot satisfy large text and non-overlap simultaneously. Mitigate with an explicit bounded full-text fallback. diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/adversarial-validation.json b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/adversarial-validation.json deleted file mode 100644 index f10ad62..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/adversarial-validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "decision": "PASS", - "disprovenClaims": [], - "unverifiedClaims": [], - "missingProof": [], - "reachableRegressions": [], - "evidence": [ - "The finalize artifact gate requires this file even though no separate high-risk adversarial review was otherwise warranted.", - "Strict threshold boundaries, all supported sizes, representative densities, maximum-length candidate complexity, combining marks, emoji graphemes, API metadata tests, and Remotion smoke output were independently checked." - ] -} diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/context-snippets.json b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/context-snippets.json deleted file mode 100644 index 8e16dd1..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/context-snippets.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "skill": "ck:code-review", - "mode": "review", - "task": "Independent review of readable adaptive wheel labels", - "acceptanceCriteria": [ - "at 512px the exact Vietnamese phrase wraps for 2, 8, 12, and 16 options when at least two lines fit, with wrapped font size at least 14px", - "ordinary short labels remain one line, including the prior 384px alpha, beta, gamma, and delta regression cases", - "at 24 and 32 options the exact phrase remains complete, one-line, width-fitting, and height-bounded at the smaller fallback size", - "Vietnamese and unbroken Unicode input preserve text and grapheme boundaries", - "radial coordinates, track width, content width, padding, and height bounds remain unchanged", - "WheelComposition, GIF route, winner metadata, API schemas, themes, geometry, environment, and public contracts remain unchanged" - ], - "touchpoints": [ - "src/remotion/wheel-label-layout.js", - "test/wheel-label-layout.test.js", - "src/remotion/WheelComposition.jsx", - "src/routes/gif.js" - ], - "publicContracts": [ - "POST /api/gif request and response behavior remains unchanged", - "X-Wheel-Winner-Index and X-Wheel-Winner metadata remain unchanged", - "getRadialLabelLayout continues returning the same presentation metadata shape" - ], - "blastRadius": [ - "one-line versus multiline selection for wheel labels whose fitted one-line size is 9 through 13px", - "Remotion text rendering across supported 384px, 480px, and 512px sizes and 2 through 32 options" - ], - "scoutSummary": "The pending implementation changes one decision predicate inside getRadialLabelLayout: wrapping is now selected below a named 14px preferred threshold when geometry permits multiple lines. Existing deterministic word/grapheme splitting, longest-line sizing, 8px hard floor, radial coordinates, content width, height bounding, and WheelComposition consumption are reused unchanged. The diff adds focused threshold, Vietnamese, short-label, and dense-fallback tests; no API, schema, route, theme, geometry, environment, or composition file is modified." -} diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/review-decision.json b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/review-decision.json deleted file mode 100644 index b886353..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/review-decision.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "decision": "PASS", - "score": 9.8, - "criticalCount": 0, - "warningCount": 0, - "suggestionCount": 1, - "criticals": [], - "warnings": [], - "suggestions": [ - "If product requirements later demand every allowed 40-character label be visually complete on dense 384px wheels, define a separate truncation, sub-8px, or larger-canvas policy; that pre-existing geometry trade-off is outside this threshold fix." - ], - "acceptanceCoverage": [ - "the 14px preferred threshold is separate from the unchanged 8px hard minimum and uses strict less-than semantics, so exactly 14px remains one line", - "the exact 512px Vietnamese phrase wraps at counts 2 and 8 into three 19px lines and at counts 12 and 16 into two 14px lines", - "the exact phrase remains complete, one-line, width-fitting, and height-bounded at 9px for counts 24 and 32", - "ordinary short labels remain one line, including alpha, beta, gamma, and delta at 384px", - "word and Intl.Segmenter grapheme paths are unchanged; text reconstruction, combining-mark, emoji, and Vietnamese checks preserve content", - "radial x/y coordinates, rotation, track width, padded content width, line-height estimator, and available-height bounding are unchanged", - "WheelComposition, GIF route, winner headers, request schema, themes, geometry, environment handling, and public API files have no pending diff" - ], - "regressionProof": [ - "fresh lint and JSDoc typecheck pass", - "all 41 tests pass", - "the supplied TDD red state had exactly 9 intended failures and the green state has 22 of 22 focused tests passing", - "a fresh supported-size and density probe confirms all requested threshold and short-label outputs", - "fresh Remotion smoke render completes with the canonical 407905-byte GIF", - "the supplied exact Vietnamese visual witness confirms bold three-line rendering at 19px with intact diacritics and no clipping or overlap", - "git diff inspection confirms only the layout helper and its test are implementation changes" - ], - "contractStatus": "OK", - "blockingReasons": [], - "sideEffects": [ - "9 through 13px one-line candidates now become balanced multiline labels when geometry permits", - "exactly 14px remains one line by design", - "low-density examples use three lines while medium-density examples use two lines, derived from the existing arc-height estimator", - "dense 24 and 32 option examples retain the existing complete one-line 9px fallback", - "line-candidate work increases for newly wrapped labels but remains bounded by 40 input characters and three lines; a 1000-iteration worst-case probe took 1496.33ms" - ] -} diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/risk-gate.json b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/risk-gate.json deleted file mode 100644 index c088b06..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/risk-gate.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "highRisk": false, - "reasons": [ - "the production diff is a one-line presentation decision change plus one named constant", - "the 8px hard floor, three-line cap, Unicode segmentation, track coordinates, and renderer contract are unchanged", - "candidate generation remains bounded by the existing 40-character request limit and three-line maximum", - "fresh unit, static, matrix, performance, and Remotion smoke evidence cover the affected path" - ], - "autoStopRequired": false, - "humanApproved": true, - "largeDiff": false, - "sideEffects": [ - "labels whose one-line fitted size is 9 through 13px now wrap when slice geometry allows at least two lines", - "a one-line fit of exactly 14px intentionally remains one line", - "dense slices that cannot support two lines intentionally retain the existing smaller complete-string fallback", - "the existing 8px hard floor can still make arbitrary maximum-length dense labels wider than the content box; this limitation predates and is not expanded by the threshold-only source change" - ] -} diff --git a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/verification.json b/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/verification.json deleted file mode 100644 index 8c9fb1f..0000000 --- a/renderer/plans/260715-1328-readable-adaptive-wheel-labels/reports/harness/verification.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "commands": [ - { - "command": "pnpm lint", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "Fresh ESLint run completed without errors." - }, - { - "command": "pnpm typecheck", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "Fresh TypeScript JSDoc checking completed without errors." - }, - { - "command": "pnpm test", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "All 5 test files and 41 tests passed, including 22 focused wheel-label tests and 4 GIF route metadata tests." - }, - { - "command": "node --input-type=module -e ", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "The exact 512px Vietnamese phrase produced three lines at 19px for counts 2 and 8, two lines at 14px for counts 12 and 16, and one complete width-fitting line at 9px for counts 24 and 32. The 384px alpha, beta, gamma, and delta cases remained one line, combining-mark and emoji inputs reconstructed exactly, and the command reported zero failures. A separate exploratory matrix documented the pre-existing 8px width-floor limit for unrelated arbitrary maximum-length dense labels." - }, - { - "command": "node --input-type=module -e <1000-iteration worst-case line-candidate timing probe>", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "One thousand 40-character, three-line candidate layouts completed in 1496.33ms; request limits cap candidates and the measured cost does not indicate a renderer blocker." - }, - { - "command": "pnpm render:smoke", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "Fresh Remotion smoke render produced a 407905-byte GIF in 2498ms; the generated GIF was removed after verification." - }, - { - "command": "git diff --check", - "status": "pass", - "exitCode": 0, - "timestamp": "2026-07-15T14:34:24.0775388+07:00", - "summary": "No whitespace errors were reported." - } - ], - "tddEvidence": { - "red": "Before the source predicate changed, 9 intended readability regressions failed while 13 existing focused tests passed.", - "green": "After the source predicate changed, all 22 focused tests passed." - }, - "visualEvidence": { - "exactPhrase": "Chiều nay uống CraneTea", - "result": "A supplied 512px repeated-phrase witness rendered bold text on three balanced lines at 19px with intact diacritics and no clipping, overlap, or boundary breach.", - "bytes": 542174, - "durationMs": 2667 - } -} diff --git a/renderer/plans/reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md b/renderer/plans/reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md deleted file mode 100644 index fb28453..0000000 --- a/renderer/plans/reports/260715-1301-readable-adaptive-wheel-labels-brainstorm.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: "Readable Adaptive Wheel Labels Brainstorm" -date: 2026-07-15 -status: approved -mode: markdown -tags: [brainstorm, renderer, typography, ux] ---- - -# Readable Adaptive Wheel Labels Brainstorm - -## Summary - -Approved direction: treat 14px as the preferred readability threshold. Attempt one-line fit first; when it falls below 14px and slice height permits, wrap deterministically into two or three balanced lines. For dense wheels that cannot safely fit multiple lines, preserve the complete label at the existing smaller fallback size. - -## Problem-First Analysis - -### 1. Solution-Jumping Diagnosis - -The request is not fundamentally “add more newlines.” The observed failure is that the current wrapping trigger models technical fit at the 8px hard floor, not practical readability. - -### 2. Underlying Problem - -Users cannot comfortably read medium-length option labels even when the slice has enough vertical space to display them across multiple lines. - -### 3. Assumption Challenges - -| Assumption | Risk if wrong | Validation | -|---|---|---| -| 8px is a sufficient wrapping threshold | Text technically fits but remains unreadable | Probe and render 9–13px labels | -| Every long label can wrap | Dense slices overlap adjacent labels | Derive maximum lines from slice arc height | -| Character count predicts readability | Wide/narrow glyphs and Vietnamese marks vary | Continue using estimated rendered width | -| Full text must always remain visible | Dense wheels may force unreadably small type | Preserve full text as explicit dense fallback | - -### 4. Problem Statement - -- Users: GIF API consumers rendering descriptive wheel entries. -- Context: supported 384px, 480px, and 512px wheels with 2–32 options. -- Struggle: a phrase such as `Chiều nay uống CraneTea` remains one line at 9px on a 512px wheel. -- Cause: wrapping only activates when the one-line fit reaches exactly 8px. -- Consequence: available slice height goes unused and labels are difficult to read. -- Success: medium-length labels wrap at readable sizes whenever geometry permits, without overlap or API changes. - -### 5. Alternative Framings - -1. Threshold problem: the 8px trigger is too late; use a preferred readable size. -2. Geometry problem: allocate lines from both radial width and tangential slice height. -3. Information-density problem: dense wheels cannot simultaneously preserve full text, large type, and non-overlap. - -### 6. Evidence Status - -Medium. The user supplied a concrete production-style Vietnamese label, and direct layout probes reproduce the issue across all option counts at 512px. Existing tests cover only the 8px boundary, not the unreadable 9–13px range. - -### 7. Validation Plan - -- Add exact layout cases for `Chiều nay uống CraneTea` at every supported size. -- Cover low, medium, and dense option counts. -- Assert preferred font size when wrapping is possible and bounded height when it is not. -- Render a Vietnamese fixture and visually check boldness, clipping, line balance, and adjacent-slice separation. -- Kill the design if 14px wrapping causes overlap at supported counts where the helper reports multiple lines. - -### 8. Stakeholder Message - -We will improve readability without changing the API: labels below the preferred 14px one-line size will wrap when the slice has room. Dense wheels will continue showing complete text at a smaller size because enlarging it would overlap neighboring slices. - -## Evaluated Approaches - -### A. Fixed Readability Threshold — Approved - -Trigger wrapping when one-line fit is below 14px and at least two lines fit safely. - -- Pros: predictable UX; directly fixes 9–13px blind spot; easy to test; keeps current geometry model. -- Cons: 14px is a product decision; dense wheels still require small text. -- Example at 512px: `Chiều / nay uống / CraneTea` for low counts; two balanced lines when only two fit. - -### B. Relative Threshold - -Wrap when one-line fit falls below a percentage of the computed base font size. - -- Pros: scales with wheel and option density. -- Cons: harder to explain; may still allow unreadably small absolute sizes; more boundary churn. - -### C. Always Wrap Multiword Labels - -Prefer two or three lines for every multiword entry. - -- Pros: maximizes font size for phrases. -- Cons: over-wraps short labels, increases visual noise, and repeats the regression already caught for ordinary names. - -## Approved Design - -1. Keep deterministic, Unicode-safe word/grapheme splitting. -2. Compute one-line size against padded content width. -3. Define a preferred readable threshold of 14px, separate from the existing 8px hard minimum. -4. If one-line size is at least 14px, preserve one line. -5. If it is below 14px and `maxLines >= 2`, choose the most balanced two/three-line layout and size from its longest line. -6. If geometry allows only one line, preserve the complete label using the existing 8px minimum behavior. -7. Keep bold weight, radial track footprint, rotations, API schemas, themes, and winner metadata unchanged. - -## Exact Requirements - -- Expected output: updated Remotion label layout behavior plus focused tests and visual render evidence. -- Acceptance: the example phrase wraps at 512px for 2–16 options when geometry permits; resulting wrapped text targets at least 14px; ordinary short labels remain one line; 24–32 option layouts remain bounded and preserve full text; Vietnamese graphemes remain intact. -- Out of scope: truncation, tooltips, larger API sizes, curved SVG text, API/schema changes, theme redesign. -- Constraints: Node.js 24, JavaScript + JSDoc, existing React/Remotion/CSS patterns, deterministic output. -- Touchpoints: `src/remotion/wheel-label-layout.js`, `test/wheel-label-layout.test.js`, and renderer fixture/smoke evidence; `WheelComposition.jsx` only if new layout metadata is required. - -## Risks and Mitigations - -| Risk | Mitigation | -|---|---| -| 14px bold estimate still clips | Measure against padded content width and visually render fixtures | -| Dense wheels remain small | Document as intentional geometry fallback; keep full text | -| New threshold re-wraps short names | Regression-test 384px smoke labels and 512px short labels | -| Vietnamese split corruption | Preserve `Intl.Segmenter` grapheme fallback and add accented-text tests | - -## Success Metrics - -- Example phrase no longer renders as a 9px single line where two/three lines fit. -- No supported-size regression for short one-line labels. -- No label box exceeds its computed tangential height. -- Lint, typecheck, tests, `render:smoke`, and a Vietnamese visual fixture pass. -- Public API contracts remain unchanged. - -## Unresolved Questions - -None. Dense-wheel fallback and 14px threshold are approved. - -## Next Step - -Create a tests-first implementation plan because this changes existing label layout behavior with established regression coverage.