diff --git a/.claude/plans/skin-jsx-migration.md b/.claude/plans/skin-jsx-migration.md index 81fa8d2a..3610df14 100644 --- a/.claude/plans/skin-jsx-migration.md +++ b/.claude/plans/skin-jsx-migration.md @@ -109,7 +109,7 @@ Steps: - Import Tailwind token objects from existing `packages/skins/src/default/tailwind/video.tailwind.ts`. - Keep skin source target-neutral: - no `div`, `span`, `button`, `sup` - - no `render` + - no React `render` props; the only exception is the restricted settings radio-group children template documented below - no `aria-*`, `data-*`, `id`, `role`, `tabIndex`, or `style` - Replace obvious wrappers with semantic Core parts where they already exist. - Capture every missing semantic wrapper/lowering need in a gap list before inventing new API. @@ -333,6 +333,244 @@ Next implementation sequence: - Add `default/audio.skin.tsx`, `minimal/video.skin.tsx`, and `minimal/audio.skin.tsx`. - Defer live/background variants until base audio/video generation is stable. +### Current Default Video Parity Decisions + +This section records the current direction for closing the remaining gap between `packages/skins/src/default/video.skin.tsx` and the manual React Tailwind skin. + +Baseline decisions: +- Source skin stays target-neutral: no lowercase intrinsic elements (`div`, `span`, `sup`, etc.) and no React hooks/render props. The settings radio-group children template is a compile-time template exception, not a general React escape hatch. +- Source skin owns styling decisions and token placement. React lowering should not hide major styling/markup decisions in opaque helper components unless the behavior is inherently React-specific. +- React lowering may still own React-specific mechanics: `render` props, `type="button"`, DOM attribute names, rest props, and imports. +- Do not chase the current manual skin exactly when the manual shape only exists because older code needed wrappers. Prefer the simpler source shape when it preserves behavior and styling. + +Buffering indicator: +- Do not keep the manual inner buffering `container` wrapper. +- Move any still-needed `bufferingIndicator.container` styles into `bufferingIndicator.root`. +- Generated/source target shape: + +```tsx + + + +``` + +Error dialog: +- Keep the simplified generated/source structure. +- Do not add `ErrorDialog.Dialog`, `ErrorDialog.Content`, or `ErrorDialog.Actions` solely to match the current manual React skin. +- Generated/source target shape: + +```tsx + + + Something went wrong. + + OK + + +``` + +Seek buttons: +- Keep the visible seek label, but do not use an extra icon-container wrapper span. +- Add a source-neutral `Text` primitive that React lowers to `span`. +- Move `iconContainer`-style grid/relative placement onto the seek button token. +- Source target shape: + +```tsx + + + {SEEK_TIME} + + + + + {SEEK_TIME} + +``` + +React generated shape: + +```tsx + + + {SEEK_TIME} + +``` + +Thumbnail preview: +- We want nested thumbnail parts because the current thumbnail implementation already has root/image semantics internally. +- Desired source/API shape: + +```tsx + + + + + +``` + +- This requires nested component part support in the manifest/generated component system. Current manifests only support one level like `Slider.Thumbnail`. +- Keep existing `` behavior as an alias to the image part if possible. + +Volume popover: +- Use a special `VolumePopover.Root`, but reuse regular `Popover` parts as children. +- The root owns volume availability/null behavior, similar to how `VolumeSlider.Root` owns the volume feature dependency. +- Source target shape: + +```tsx + + + + + + + + + + + + + + + + + + +``` + +- React lowering can continue converting `Popover.Trigger` children into `render` props. + +Settings menu: +- The settings menu must remain style-owned by source. Do not solve it with an opaque React helper that contains all the styling/markup. +- The radio groups are the right data boundary: `QualityRadioGroup`, `PlaybackRateRadioGroup`, and `CaptionsRadioGroup` already have Core state and React hooks that produce option records. +- Do not add tailored compound parts like `QualityRadioGroup.Label`, `QualityRadioGroup.Tier`, or `QualityRadioGroup.Badge`. Those values are already returned by the React hooks as fields on each option. +- Use a restricted source-level children render function as the option-row template. The group owns the hook data and the generated `map(...)`; the source owns the row markup/styling. +- Treat the render function as a compile-time template language, not as a general React render prop in source. React can execute the generated render body, but HTML lowering must statically translate it to `