Skip to content

Merge buttonPlacement and featureMenu into a single ButtonController module #1350

Description

@VampireChicken12

Summary

Eliminate the bidirectional coupling between src/features/buttonPlacement/ and src/features/featureMenu/ by merging them into a single ButtonController class that owns all button DOM, menu DOM, fullscreen handling, theater observer, and shared tracking state.

This is Candidate 1 from the architecture exploration doc, next after the config type system (Closes #1348).

Current Architecture

FeatureButtonManager (registry)
  -> btn.add(config) -> addFeatureButton(8 params) -> enableFeatureMenu()
                                                     -> makeFeatureButton()
                                                     -> placeButton()
                                                     -> addFeatureItemToMenu()  (for feature_menu)
                                                     -> trackButton()
                                                       -> startFullscreenObserver()
                                                         -> handleFullscreenChange()
  -> btn.remove()   -> removeFeatureButton()

Friction Points

1. Duplicate container creation

#yte-right-controls-container created with identical code in two files:

  • buttonPlacement/utils.ts (placeButton, player_controls_right case)
  • featureMenu/index.ts (createFeatureMenuButton)

If selectors drift apart, one placement breaks silently.

2. Three parallel tracking systems

  • trackedButtons (Map, buttonPlacement/utils.ts) - button state for fullscreen transitions
  • featuresInMenu (Set, featureMenu/utils.ts) - menu item presence
  • buttonState (Map, featureButtonManager.ts) - enabled/placement per feature

Fullscreen transitions must manually keep all three in sync.

3. 8-positional-parameter surface

addFeatureButton() takes 8 positional params. Every feature's btn.add() repeats the same boilerplate.

4. Generic unsoundness

addFeatureItemToMenu accepts ListenerType<Toggle> but gets ListenerType<boolean> from handleFullscreenChange.

5. Error recovery gap in addFeatureButton

Early return in placement logic runs before trackButton() - if button creation rejects, tracking state goes inconsistent.

6. Theater observer in wrong module

Theater observer lives in buttonPlacement/utils.ts but manages the "below player" container - a cross-cutting DOM concern.

Files Involved

  • src/features/buttonPlacement/index.ts - addFeatureButton, removeFeatureButton, getPlacementSelector
  • src/features/buttonPlacement/utils.ts - makeFeatureButton, placeButton, trackButton, handleFullscreenChange, theater observer
  • src/features/buttonPlacement/fullscreen.ts - fullscreen observer
  • src/features/featureMenu/index.ts - enableFeatureMenu, createFeatureMenuButton, setupFeatureMenuEventListeners
  • src/features/featureMenu/utils.ts - addFeatureItemToMenu, removeFeatureItemFromMenu, featuresInMenu
  • src/features/_registry/featureButtonManager.ts - orchestrator

Proposed Boundary

Create ButtonController that owns:

  • DOM container creation (#yte-right-controls-container, #yte-button-container)
  • Button DOM creation (makeFeatureButton)
  • Menu DOM creation (addFeatureItemToMenu / removeFeatureItemFromMenu)
  • Fullscreen observer + handler
  • Theater observer + handler
  • Combined tracking state (replacing trackedButtons + featuresInMenu)

The outward API surface shrinks to roughly:

class ButtonController {
  async addButton(name, config, icon, listener, isToggle)
  async removeButton(name, placement?)
  getButton(name): HTMLElement | null
}

featureMenu/utils.ts gets inlined into the controller. featureMenu/index.ts keeps only createFeatureMenuButton / setupFeatureMenuEventListeners (the menu toggle UI).

Steps

  1. Create ButtonController class with combined state + all methods
  2. Inline fullscreen.ts observer into the controller
  3. Inline featureMenu/utils.ts exports into the controller
  4. Deduplicate container creation
  5. Collapse addFeatureButton 8 params into options object
  6. Update all 12 feature index.ts files to call buttonController.addButton
  7. Remove old files: buttonPlacement/index.ts, buttonPlacement/utils.ts, buttonPlacement/fullscreen.ts, featureMenu/utils.ts
  8. Prune re-exports and dead code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions