Skip to content

docs: JSDoc every public-API export in react, html, and core#1570

Draft
decepulis wants to merge 14 commits into
mainfrom
claude/review-issue-1558-yeEuO
Draft

docs: JSDoc every public-API export in react, html, and core#1570
decepulis wants to merge 14 commits into
mainfrom
claude/review-issue-1558-yeEuO

Conversation

@decepulis
Copy link
Copy Markdown
Collaborator

@decepulis decepulis commented May 20, 2026

Resolves #1558.

Split notice: The skin eject-discoverability portion of this PR — the immediate fix for #1558 — has been split into #1574. That smaller PR is the recommended merge order. This PR keeps the broader rule (JSDoc on every public-API export) and the cross-package sweep for team discussion.

Why

Per the bench data in #1558, AI coding agents Read 15–27 distinct .d.ts files per Video.js install — node_modules/@videojs/{react,html,core}/dist/dev/**/*.d.ts. The file is already in the agent's context window when it Reads it. JSDoc on the published .d.ts ships documentation alongside the symbol — zero extra round trips. The same comment feeds three downstream channels:

  1. The api-docs-builder's reference pages (props tables, util references).
  2. IDE hover-docs.
  3. AI-agent context.

This PR fills the gap and codifies the rule so future exports don't slip back.

What changed

1. CLAUDE.md — "JSDoc on Published Exports" rule (docs(claude) commit). Every public-API export carries JSDoc that survives into the shipped .d.ts. Voice A: one-sentence, declarative, active, present tense, says what the type doesn't. Inherited fields stay at the base; leaves document only the fields they add. Public class members (non-#, non-@internal) get their own summaries. Monolithic surfaces (skins + createPlayer) get richer prose + a single @see link.

Two carve-outs added after review:

  • Skip the summary on leaf wrapper types that extend a base and add no fields (interface FooProps extends BarProps {}).
  • Skip JSDoc on uniform plumbing methods on *Core classes (setProps, getState, getAttrs, setMedia). Behavior methods still get JSDoc.

2. @videojs/react sweep (~80 files post-split). UI primitives, hooks, player factory + context, media adapters, util helpers. createPlayer overloads get @see reference/create-player. Skin files now ship via #1574.

3. @videojs/html sweep (~90 files post-split). Custom-element classes, mixins, controllers, context modules, player factory, *PlayerElement shells. createPlayer overloads get @see reference/html-create-player. Skin defines now ship via #1574.

4. @videojs/core sweep (~126 files). Every *Core class (minus uniform CRUD) + members + constructor; every *Props/*State/*Input/*Capability interface with per-field JSDoc; feature factories; selectors; media element classes (HLS, Dash, Native, Mux, Cast, etc.); gesture + hotkey coordinators; DOM-side factories (createMenu, createPopover, createSlider, etc.).

5. Accuracy audit (docs(packages) commit + 6 audit fixes). Every behavior-describing JSDoc the sweep wrote was verified against the actual implementation. Nine wrong claims fixed (mute-button event ordering, captions-button "default track", seek-indicator timing claims, volume-slider 0–1 vs 0–100, etc.). Six ambiguous claims rewritten after design-philosophy discussion (boundary field wording, factory call-frequency promise, error-dialog responsibility framing).

Test plan

  • pnpm typecheck clean
  • pnpm lint clean
  • pnpm build:packages — all 9 package builds succeed; .d.ts files emit cleanly
  • CI green
  • Reviewer scans @videojs/core *Core classes for behavior-method JSDoc accuracy — largest single chunk of agent-authored prose. Audit pass already flagged and fixed the obvious ones.
  • Reviewer scans the CLAUDE.md rule and carve-outs.

Follow-up

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98

claude added 4 commits May 20, 2026 00:07
Establishes a stronger rule for public-API exports: every symbol
reachable via a package.json "exports" entry carries a JSDoc summary,
including class members and prop fields that the leaf adds. Inherited
fields keep their JSDoc at the base. Monolithic surfaces (skins,
createPlayer) get richer prose plus a single @see link to an existing
docs page — no @example blocks.

Scopes the existing "no JSDoc for self-documenting code" rule to
internal code so it no longer contradicts the published-export
requirement. Absorbs the prior "API reference exports are different"
subsection into the new rule.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
Apply the JSDoc rule from CLAUDE.md to every symbol reachable via
packages/html/package.json "exports": custom-element classes, mixins,
controllers, context modules, player factory, skin elements, and the
*PlayerElement shells.

Skin element classes (incl. Tailwind variants) get richer prose + a
single @see link to /docs/framework/html/concepts/skins. createPlayer
factory overloads link to /docs/framework/html/reference/html-create-player.
Shells (VideoPlayerElement etc.) get a one-line summary per Ring 1.
Public class members (tagName, methods, properties) carry their own
summaries. Inherited fields keep their JSDoc on the base.

No @example blocks: replaced the dev-only @example on createPlayer
with the @see link convention. No TODO placeholders.

Refs #1558.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
Apply the JSDoc rule from CLAUDE.md to every symbol reachable via
packages/react/package.json "exports": player factory, context module,
hooks, UI primitives, skins, media adapters, and util helpers.

Skin variants (incl. Tailwind siblings) get richer prose + a single
@see link to /docs/framework/react/concepts/skins. createPlayer overloads
link to /docs/framework/react/reference/create-player. Leaf Props
interfaces that add no new fields get a one-line summary; leaves that
add fields document only the added ones. Inherited fields stay at the
base.

Replaces stale @example blocks (which drift with the source) with the
@see link convention. Fixes a malformed JSDoc on useOptionalPlayer
where the standalone summary above the overloads wasn't attached to a
symbol. No TODO placeholders.

Refs #1558.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
Apply the JSDoc rule from CLAUDE.md to every symbol reachable via
packages/core/package.json "exports": *Core classes and their members,
*Props / *State / *Input / *Capability interfaces, feature factories,
selectors, media element classes (HlsMedia, DashMedia, NativeHlsMedia,
SimpleHlsMedia, MuxVideoMedia, MuxAudioMedia, GoogleCastMixin),
gesture + hotkey coordinators, DOM utilities, and the dom/ui factory
functions (createMenu, createPopover, createSlider, createThumbnail,
createTransition, createTooltip, createPopupGroup, etc.).

*Core classes are the highest-leverage targets in this sweep — the
control-bar primitives that AI agents read most aggressively per the
bench data in #1558. Each class gets a class-level summary plus a
summary on every public method, property, and the constructor.

Foundational *Props and *State interfaces in core are the bases that
react/html leaves extend, so per-field JSDoc here propagates upward
via TypeScript inheritance.

Refs #1558.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment May 20, 2026 7:24pm

Request Review

@netlify
Copy link
Copy Markdown

netlify Bot commented May 20, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit e19d13d
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a0e0a5400d1f30008702287
😎 Deploy Preview https://deploy-preview-1570--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Size
/video (default) 38.57 kB
/video (default + hls) 172.02 kB
/video (minimal) 38.10 kB
/video (minimal + hls) 171.55 kB
/audio (default) 33.08 kB
/audio (minimal) 30.01 kB
/background 4.23 kB
Media (8)
Entry Size
/media/background-video 1.04 kB
/media/container 1.72 kB
/media/dash-video 236.58 kB
/media/hls-video 134.87 kB
/media/mux-audio 161.08 kB
/media/mux-video 161.00 kB
/media/native-hls-video 4.62 kB
/media/simple-hls-video 16.70 kB
Players (5)
Entry Size
/video/player 7.06 kB
/audio/player 5.18 kB
/background/player 3.92 kB
/live-video/player 7.07 kB
/live-audio/player 5.19 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 4.67 kB
/video/skin.css css 4.73 kB
/video/minimal-skin js 38.02 kB
/video/minimal-skin.tailwind js 38.44 kB
/video/skin js 38.58 kB
/video/skin.tailwind js 38.99 kB
/audio/minimal-skin.css css 2.90 kB
/audio/skin.css css 2.93 kB
/audio/minimal-skin js 29.94 kB
/audio/minimal-skin.tailwind js 30.17 kB
/audio/skin js 33.09 kB
/audio/skin.tailwind js 33.28 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 4.67 kB
/live-video/skin.css css 4.73 kB
/live-video/minimal-skin js 33.19 kB
/live-video/minimal-skin.tailwind js 33.24 kB
/live-video/skin js 33.34 kB
/live-video/skin.tailwind js 33.28 kB
/live-audio/minimal-skin.css css 2.90 kB
/live-audio/skin.css css 2.93 kB
/live-audio/minimal-skin js 25.19 kB
/live-audio/minimal-skin.tailwind js 24.99 kB
/live-audio/skin js 27.86 kB
/live-audio/skin.tailwind js 27.63 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (35)
Entry Size
/ui/alert-dialog 713 B
/ui/alert-dialog-close 363 B
/ui/alert-dialog-description 307 B
/ui/alert-dialog-title 309 B
/ui/buffering-indicator 2.11 kB
/ui/captions-button 2.08 kB
/ui/cast-button 2.08 kB
/ui/compounds 5.53 kB
/ui/controls 1.99 kB
/ui/error-dialog 2.51 kB
/ui/fullscreen-button 2.14 kB
/ui/hotkey 2.77 kB
/ui/menu 2.78 kB
/ui/mute-button 2.10 kB
/ui/pip-button 2.07 kB
/ui/play-button 2.08 kB
/ui/playback-rate-button 2.19 kB
/ui/playback-rate-menu 3.90 kB
/ui/popover 1.87 kB
/ui/poster 1.87 kB
/ui/seek-button 2.07 kB
/ui/seek-indicator 2.71 kB
/ui/seek-indicator-value 306 B
/ui/slider 1.19 kB
/ui/status-announcer 2.47 kB
/ui/status-indicator 2.50 kB
/ui/status-indicator-value 159 B
/ui/thumbnail 2.54 kB
/ui/time 1.92 kB
/ui/time-slider 3.04 kB
/ui/tooltip 1.79 kB
/ui/volume-indicator 2.74 kB
/ui/volume-indicator-fill 335 B
/ui/volume-indicator-value 328 B
/ui/volume-slider 3.56 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Size
/video (default) 32.29 kB
/video (default + hls) 164.70 kB
/video (minimal) 32.33 kB
/video (minimal + hls) 164.64 kB
/audio (default) 26.57 kB
/audio (minimal) 26.60 kB
/background 754 B
Media (7)
Entry Size
/media/background-video 575 B
/media/dash-video 235.21 kB
/media/hls-video 133.39 kB
/media/mux-audio 159.84 kB
/media/mux-video 159.74 kB
/media/native-hls-video 3.13 kB
/media/simple-hls-video 15.27 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 4.58 kB
/video/skin.css css 4.64 kB
/video/minimal-skin js 32.27 kB
/video/minimal-skin.tailwind js 37.00 kB
/video/skin js 32.19 kB
/video/skin.tailwind js 37.04 kB
/audio/minimal-skin.css css 2.77 kB
/audio/skin.css css 2.80 kB
/audio/minimal-skin js 26.54 kB
/audio/minimal-skin.tailwind js 26.25 kB
/audio/skin js 26.46 kB
/audio/skin.tailwind js 29.52 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 4.58 kB
/live-video/skin.css css 4.64 kB
/live-video/minimal-skin js 23.69 kB
/live-video/minimal-skin.tailwind js 28.15 kB
/live-video/skin js 23.74 kB
/live-video/skin.tailwind js 28.15 kB
/live-audio/minimal-skin.css css 2.77 kB
/live-audio/skin.css css 2.80 kB
/live-audio/minimal-skin js 19.95 kB
/live-audio/minimal-skin.tailwind js 22.38 kB
/live-audio/skin js 20.00 kB
/live-audio/skin.tailwind js 22.52 kB
UI Components (29)
Entry Size
/ui/alert-dialog 1.08 kB
/ui/buffering-indicator 1.86 kB
/ui/captions-button 2.08 kB
/ui/cast-button 2.04 kB
/ui/controls 1.84 kB
/ui/error-dialog 2.27 kB
/ui/fullscreen-button 2.08 kB
/ui/gesture 1.32 kB
/ui/hotkey 1.90 kB
/ui/live-button 2.10 kB
/ui/menu 4.29 kB
/ui/mute-button 2.03 kB
/ui/pip-button 2.04 kB
/ui/play-button 2.06 kB
/ui/playback-rate-button 2.04 kB
/ui/playback-rate-menu 4.61 kB
/ui/popover 2.32 kB
/ui/poster 1.69 kB
/ui/seek-button 2.07 kB
/ui/seek-indicator 1.87 kB
/ui/slider 3.22 kB
/ui/status-announcer 1.76 kB
/ui/status-indicator 1.96 kB
/ui/thumbnail 1.95 kB
/ui/time 2.51 kB
/ui/time-slider 2.97 kB
/ui/tooltip 2.72 kB
/ui/volume-indicator 1.98 kB
/ui/volume-slider 2.30 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (9)
Entry Size
. 7.47 kB
/dom 15.30 kB
/dom/media/custom-media-element 1.90 kB
/dom/media/dash 234.36 kB
/dom/media/google-cast 4.07 kB
/dom/media/hls 132.98 kB
/dom/media/mux 159.13 kB
/dom/media/native-hls 2.52 kB
/dom/media/simple-hls 14.62 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 1.96 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (3)
Entry Size
. 4.45 kB
/dom 6.32 kB
/hls 14.03 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

Voice A is the standard for new JSDoc on previously-undocumented
exports — not a license to delete or shrink existing prose, @example
blocks, or @returns descriptions that an author wrote thoughtfully.
Adds a "preserve existing authored content" caveat under the
"JSDoc on Published Exports" rule so future sweeps don't over-edit.

Refs #1558, #1570.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
The initial sweep was too eager. It collapsed multi-paragraph
descriptions, removed @example blocks, and reworded prose that the
original author had written deliberately. Restore that content while
keeping the sweep's new summaries on previously-undocumented exports
and the new @see links on Ring 3 monolithic surfaces.

Restored content in 22 files across @videojs/core, @videojs/html, and
@videojs/react:

- @example blocks: parseHotkeyPattern + createHotkey (core),
  createPlayer + PlayerController (html), PlayButton, PiPButton,
  PlaybackRateButton, Poster, SeekButton, Time.Group/Separator/Value,
  BufferingIndicator, useButton, LiveButton, mergeProps, composeRefs,
  useComposedRefs, renderElement (react).
- Multi-paragraph descriptions on LiveButton (composition + i18n)
  and the live-video / live-audio skin presets (flexible spacer).
- The "useful for components that can operate without player context"
  prose on useOptionalPlayer (kept the structural overload-attachment
  fix from the original sweep).
- The concrete SliderTrackElement subclass example on
  ContextPartElement (showed how to extend the abstract base).

Refs #1558, #1570.

https://claude.ai/code/session_01YDMP31nT51YrdWKSqvkT98
…om JSDoc

Carves out two cases where JSDoc adds noise without serving the rule's
priorities (props in API references, AI-useful info on props and imports):

- Leaf wrapper types with empty bodies (`interface Foo extends Bar {}`,
  `type Foo = Bar`) skip the summary; the base's JSDoc covers it.
- Uniform plumbing methods on `*Core` classes (`setProps`, `getState`,
  `getAttrs`, `setMedia`) skip JSDoc; the names are predictable across
  every core. Behavior methods still get JSDoc.
The sweep's intent was to add JSDoc to undocumented exports, not to
rewrite existing authored content. In 32 files it reworded prose that
was already on main — sometimes losing information (mergeProps had its
event-handler ordering flipped from correct to wrong).

Restores the original wording verbatim across react, html, and core.
Keeps the one structural fix in react/src/player/context.tsx where the
sweep correctly merged an orphaned prose block into the @Label JSDoc
that was hiding it from useOptionalPlayer's first overload.
Leaf wrappers like `interface PlayButtonProps extends UIComponentProps,
PlayButtonCore.Props {}` and `type LiveAudioSkinProps = BaseSkinProps`
add nothing the base hasn't already said. The summary just restates
"Props for the X component" — noise on top of the inherited JSDoc.

Per CLAUDE.md update, leaf wrappers with empty bodies skip the summary.
Wrappers that add fields still get a summary.
setProps/getState/getAttrs/setMedia follow a predictable pattern across
every *Core class — the method name carries the meaning and the JSDoc
just restates it. Behavior methods (toggle, cycle, seek, etc.) keep
their JSDoc because they encode domain semantics.

Per CLAUDE.md update. Files where a CRUD method has a non-uniform
description (e.g. AlertDialogCore.setProps notes the platform-layer
contract) keep their JSDoc as a carve-out.
Accuracy sweep against actual implementations. Corrections so far:

- StatusIndicator.value no longer claims playback-rate as an example
  (status indicators output volume percent, not playback rate).
- TooltipProvider no longer claims "only one opens at a time" — it
  shares hover/focus timing so the next tooltip opens instantly while
  the group is warm.
- Small fixes across seek-indicator / volume-slider / time / slider
  preview elements where prose drifted from implementation.
CaptionsButtonElement toggles subtitle/caption visibility on the media
via the text-track feature's toggleSubtitles, which flips all subtitle
and caption tracks — not a single "default" track.
- seek-indicator-core: currentTime is updated on every event in the
  burst, not anchored to the start. Replaced "at the start of the burst"
  with "at the latest seek event".
- seek-indicator-root (react): the indicator stays mounted through
  closeDelay (~800ms) plus the close transition, not only while a seek
  is in flight. Rewrote to "mounts when a seek burst starts and unmounts
  after the close transition".
- popover/menu/tooltip element boundary: the field accepts viewport and
  container keywords, a selector, or an Element. Replaced "Element the X
  is constrained to" with "Region positioning is clamped within".
- provider-mixin factory: dropped the "once" promise. The factory is
  re-entered if the store is accessed after destroyCallback, so the
  guarantee was inaccurate.
- error-dialog-core: the class itself only overrides setProps to a
  no-op; the "driven by media error state" wiring lives in the consumer
  elements. Rewrote to reflect both — "ignores props" plus "open state
  is driven externally by media error state".
Moves all skin JSDoc additions to #1574 — a smaller, focused PR that
ships the eject-discoverability fix from #1558 with low review burden.
This PR keeps the broader sweep (every public-API export across react /
html / core, plus the CLAUDE.md rule that justifies it) for team
discussion.

Reverts:
- All 16 React skin presets (audio, video, background, live-audio,
  live-video; including Tailwind siblings).
- All 16 HTML skin defines (matching set).
- Background plumbing: define/background/{skin,player}.ts,
  define/media/background-video.ts, react/media/background-video/index.tsx.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Docs: JSDoc the Published .d.ts Exports in @videojs/react and @videojs/html — AI Agents Read 15-27 Type Files per Install

2 participants