Skip to content

Architecture

approved

The target source structure and module boundaries. Designed for this package; deliberately not a copy of any prior implementation.

Folder structure

Proposed, finalized with the first implementation stories:

text
docs/            the binding specification
data/            versioned demo content
demo/            the showcase application
src/
  runtime/       bootstrap, host mounting, the page API
  config/        parsing, validation, defaults, schema
  strings/       resolution machinery and language catalogs
  core/          player instance, media engine adapter, state store
  layouts/       landscape and portrait shells
  capabilities/  chapters, captions, speed, share, context-panel
  ui/            shared primitives
  styles/        tokens and base styles
  lib/           utilities

Module boundaries

  • Player instance: owns the media element (never recreated across configuration updates), the media engine adapter, the state store, and the current resolved configuration.
  • Layout shell: renders the arrangement for the instance's fixed layout.
  • Capability modules: subscribe to the instance's resolved configuration and re-resolve on every update, so dormancy is re-evaluated per video.
  • Context panel: a capability owned by the player instance (see its document).
  • Shorts feed: a separate orchestrator above the player that drives one instance through the public page API only, proving that API sufficient by construction.
  • Media engine: internal. It never appears in the public contract; strings and configuration feed it through one-directional adapters.

Capability module contract

A capability ships its UI, its options, its string defaults, and its documentation section as one unit. One capability is one internal module, declaring:

DeclarationMeaningExample
KeyIts name in capabilitieschapters
AvailabilityA predicate over resolved content and the capability's resolved options deciding whether required data existscontent.chapters non-empty
OptionsIts options schema and defaults, where it has optionsspeed's rate list
StringsThe string keys it owns, with English defaults; the framework aggregates all modules into the catalogscaptionsOff
SurfacesThe UI it contributes, rendered into one or more layout-owned slots (see the responsibility split)the chapters menu
LifecycleHow it participates in mount, configuration application, and disposalbelow

Binding rules:

  • Fixed internal registry. All capability modules are compiled into the player. There is no public plugin or extension API; the config contract is the only extension surface.
  • Always instantiated. Every registered module is built exactly once per player instance, at mount, and disposed only with the instance. Dormancy and removal are states the module re-resolves on every configuration application, never reasons to create or destroy it. A feed swapping between videos with and without chapters flips the chapters module between active and dormant; the module object, and any user memory it holds (such as the chosen caption track), lives on.
  • Final disposal is idempotent. Applying another configuration after disposal is an internal lifecycle error, because it means the runtime applied an update to a destroyed player instance.
  • Availability normally follows content. The share capability is the exception: its canonicalUrl is resolved capability data, so its availability predicate reads that option alongside content.

Layout and capability responsibility split

OwnerOwns
Layout shellArrangement: the named slots (control bar regions, overlay stack, the portrait trigger rail, the landscape top icons), spacing and safe areas, and the show and hide choreography
CapabilityIts widgets and their behavior: what the trigger does, what the menu contains. Identical in both layouts
Core playerThe core controls themselves (play, timeline, volume, time); the layout places them

The capability framework exposes this fixed internal slot set:

SlotPurpose
timelineOptional composition around the core timeline
controlBarLeftLeft control-bar region
controlBarCenterCenter control-bar region
controlBarRightRight control-bar region
topIconsTop-icon region in both layouts
captionsCaption text region
triggerRailPortrait context-trigger rail
overlayMedia overlay stack and top-layer dialog triggers
menuControl-anchored menu region
contextPanelContext content arranged beside or separately from media

Binding rules:

  • A capability never positions itself and never asks which layout it is in. It declares surfaces for named slots; one surface may target several slots so that both layouts can place the same widget. The layout decides where each slot sits and renders only the slots it owns.
  • A layout may omit a slot entirely. Portrait has no fullscreen slot; that fact is encoded in the portrait layout, not in control or capability code.
  • Configuration pointing at an omitted slot is silently ignored: not an error and not a warning. The same configuration legitimately serves both layouts, so enabling something the current layout has no place for is the layout's design at work, not a configuration mistake.
  • The context panel has its own slot. A desktop layout may place contextPanel beside the media frame, while a constrained layout may present the same slot as a dialog. It is not part of the media overlay stack; the layout owns that presentation decision.
  • The timeline slot is compositional. An empty timeline slot means the layout renders the core timeline widget. Non-empty content replaces that widget and composes around the core slider, as chapters does; it never adds a second sibling slider. At most one timeline replacement may be registered.
  • The menu slot is an anchor region only. Its deterministic registration order has no stacking meaning and does not provide one-open-menu exclusivity; modules and the media engine own that behavior.

Styling strategy

  • Central tokens, co-located styles, one stylesheet. The theming document's design tokens live in one central file as CSS custom properties, set on the player root. Each capability co-locates its component styles in its module. Everything compiles into the single stylesheet the package ships.
  • Isolation without Shadow DOM. Every class is prefixed (ltp-*), tokens are scoped to the player root rather than the page's :root, and a defensive reset on the player root guards against hosting page CSS bleeding in. Shadow DOM is deliberately not used: it complicates fonts, caption styling, and the media engine for little practical gain.
  • The font ships in the package. Nunito Sans is bundled with the player and loaded from it; the player never fetches fonts or any styling asset from an external CDN at runtime, per the self-contained principle.