diff --git a/context/CONTEXT_2026-02-13_docs-code-investigator.md b/context/CONTEXT_2026-02-13_docs-code-investigator.md new file mode 100644 index 00000000..dae5511f --- /dev/null +++ b/context/CONTEXT_2026-02-13_docs-code-investigator.md @@ -0,0 +1,21 @@ +# Context for Documentation: js/ui/components/VideoModal.js + +- **Agent:** docs-code-investigator (daily scheduler) +- **Date:** 2026-02-13 +- **Target File:** `js/ui/components/VideoModal.js` +- **Line Count:** 6284 lines +- **Reason for Selection:** + - Identified as one of the largest source files in the codebase (ranking #2 after `profileModalController.js`). + - No existing overview documentation found (`docs/VideoModal-overview.md` is missing). + - Contains critical UI logic for video playback, menu rendering, and interaction handling. + - High complexity with multiple imported controllers (`CommentsController`, `ReactionsController`, etc.). + +## Plan +1. Analyze the file structure to understand the `VideoModal` class and its main execution flows. +2. Create `docs/VideoModal-overview.md` to document the high-level architecture, public API, and key invariants. +3. Add JSDoc to the exported `VideoModal` class and its primary methods (`constructor`, `open`, `close`). +4. Add inline comments for complex logic blocks (e.g., rendering, data fetching). +5. Verify changes with `npm run lint` and `npm run test:unit` (to ensure no regressions). + +## Goal +Improve maintainability and developer understanding of this critical UI component by providing clear documentation and entry points. diff --git a/docs/VideoModal-overview.md b/docs/VideoModal-overview.md new file mode 100644 index 00000000..d4d2fd92 --- /dev/null +++ b/docs/VideoModal-overview.md @@ -0,0 +1,82 @@ +# VideoModal Component Overview + +The `VideoModal` (`js/ui/components/VideoModal.js`) is the primary UI controller for the full-screen video playback experience in bitvid. It manages the modal lifecycle, orchestrates playback, and coordinates sub-controllers for social interactions (comments, reactions, moderation). + +## Core Responsibilities + +1. **Lifecycle Management**: Handles lazy-loading of the modal HTML, DOM injection, and visibility toggling. +2. **Playback Orchestration**: Connects the video metadata to the underlying media player (via `mediaLoader` or direct video element manipulation). +3. **Social Coordination**: Instantiates and manages `CommentsController`, `ReactionsController`, `SimilarContentController`, and `ModerationController`. +4. **Accessibility**: Enforces focus trapping and ARIA attributes via `ModalAccessibility`. +5. **Global State Integration**: Updates the application's modal state (`setGlobalModalState`) to pause background feeds and handle back-button navigation. + +## Public API + +The `VideoModal` exposes a minimal public interface for the application orchestrator (`ModalManager` or `App`): + +| Method | Description | +|--------|-------------| +| `constructor(deps)` | Initializes the controller with dependencies (DOM, logger, state helpers). Does *not* touch the DOM. | +| `async load()` | Fetches `components/video-modal.html`, injects it into the DOM, and hydrates references. Safe to call multiple times (idempotent). | +| `open(video, options)` | Resets state, populates the modal with the given `video` object, and makes it visible. | +| `close()` | Hides the modal, pauses playback, clears state, and returns focus to the trigger element. | + +### Dependencies +The constructor requires a robust dependency injection object: +- `document`: The DOM document. +- `setGlobalModalState`: Function to notify the app of modal visibility changes. +- `logger`: Logging utility. +- `mediaLoader`: Service for handling media source resolution. +- `assets`, `state`, `helpers`: Configuration objects. + +## internal Architecture + +### Lazy Loading & Hydration +To improve initial page load performance, `VideoModal` does not render its markup until `load()` is called (typically on the first user interaction). +1. **Fetch**: Retrieves `components/video-modal.html`. +2. **Inject**: Appends the HTML to the `#modalContainer`. +3. **Hydrate**: Queries for all necessary DOM elements (buttons, containers, inputs) and binds event listeners. +4. **Sub-Controller Init**: Initializes `CommentsController`, `ReactionsController`, etc., passing them the fresh DOM references. + +### Sub-Controllers +The `VideoModal` acts as a parent controller, delegating specific logic to specialized classes: +- **`CommentsController`**: Manages the comment thread, composer, and fetching. +- **`ReactionsController`**: Handles likes/dislikes and reaction counts. +- **`SimilarContentController`**: Fetches and renders related videos. +- **`ModerationController`**: Manages content warnings, blurring, and reporting flows. + +## Usage Example + +```javascript +// Instantiation (usually in ModalManager) +const videoModal = new VideoModal({ + document: document, + setGlobalModalState: (key, active) => app.setModalState(key, active), + logger: logger, + mediaLoader: mediaLoader +}); + +// Initialization (lazy) +await videoModal.load(); + +// Opening +const video = { + id: "...", + title: "My Video", + pubkey: "...", + // ... other metadata +}; +videoModal.open(video, { triggerElement: clickedCard }); +``` + +## Key Invariants + +1. **Single Active Instance**: Only one video modal exists in the DOM at a time. Opening a new video replaces the content of the existing modal. +2. **Focus Management**: When open, the modal traps focus. When closed, it restores focus to the element that triggered it (via `options.triggerElement`). +3. **State Synchronization**: The `setGlobalModalState` callback MUST be called on open/close to ensure the back button works correctly and background videos pause. + +## When to Change + +- **Refactoring**: This file is extremely large (>6000 LOC). Consider extracting more logic into sub-controllers (e.g., a `MetadataController` for title/description/tags). +- **Styling**: Changes to the modal layout should be mirrored in `components/video-modal.html` and tested for responsiveness. +- **Playback Logic**: Changes to how videos load or play should likely be done in `mediaLoader` or `PlaybackService` rather than here, unless it's strictly UI-related (e.g., custom controls). diff --git a/docs/agents/task-logs/daily/2026-02-13_15-46-50_docs-code-investigator_completed.md b/docs/agents/task-logs/daily/2026-02-13_15-46-50_docs-code-investigator_completed.md new file mode 100644 index 00000000..28e81fda --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-13_15-46-50_docs-code-investigator_completed.md @@ -0,0 +1,8 @@ +# Agent Task Log Entry + +- **Date:** 2026-02-13 +- **Agent:** docs-code-investigator +- **Prompt:** bitvid-docs-code-investigator.md +- **Status:** completed +- **Branch:** ai/doc-video-modal-20260213 +- **Summary:** Documented `js/ui/components/VideoModal.js` with JSDoc and `docs/VideoModal-overview.md`. diff --git a/docs/agents/task-logs/daily/2026-02-13_15-48-00_docs-code-investigator_started.md b/docs/agents/task-logs/daily/2026-02-13_15-48-00_docs-code-investigator_started.md new file mode 100644 index 00000000..4581dd81 --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-13_15-48-00_docs-code-investigator_started.md @@ -0,0 +1,8 @@ +# Agent Task Log Entry + +- **Date:** 2026-02-13 +- **Agent:** docs-code-investigator +- **Prompt:** bitvid-docs-code-investigator.md +- **Status:** started +- **Branch:** ai/doc-video-modal-20260213 +- **Summary:** Task claimed — execution beginning diff --git a/js/ui/components/VideoModal.js b/js/ui/components/VideoModal.js index 1cd9f598..96b2e261 100644 --- a/js/ui/components/VideoModal.js +++ b/js/ui/components/VideoModal.js @@ -52,7 +52,29 @@ import { UI_FEEDBACK_DELAY_MS } from "../../constants.js"; const SIMILAR_CONTENT_LIMIT = 10; +/** + * Main UI controller for the full-screen video playback modal. + * Manages the modal lifecycle (load, open, close), orchestrates playback, + * and coordinates sub-controllers for comments, reactions, and moderation. + * + * @see {@link docs/VideoModal-overview.md} for architectural details. + */ export class VideoModal { + /** + * Initializes the video modal controller. + * Note: Does not touch the DOM or load HTML until `load()` is called. + * + * @param {Object} options - Configuration options + * @param {Function} [options.removeTrackingScripts] - Helper to sanitize HTML + * @param {Function} options.setGlobalModalState - Callback to update app-wide modal state + * @param {Document} options.document - The document instance (for DOM access) + * @param {Object} [options.logger] - Logger instance (defaults to devLogger) + * @param {Object} [options.mediaLoader] - Service for media resolution + * @param {Object} [options.assets] - Asset configuration (e.g., fallback thumbnails) + * @param {Object} [options.state] - Initial state (e.g., thumbnail cache) + * @param {Object} [options.helpers] - Utility helpers (e.g., npub encoding) + * @throws {Error} If document or setGlobalModalState is missing + */ constructor({ removeTrackingScripts, setGlobalModalState, @@ -475,6 +497,13 @@ export class VideoModal { return this.modalVideo; } + /** + * Lazy-loads the video modal HTML and injects it into the DOM. + * Ensures idempotency: if already loaded and connected, returns immediately. + * + * @returns {Promise} The root element of the video modal + * @throws {Error} If fetching the template fails or container is missing + */ async load() { if (this.loaded) { const root = this.playerModal; @@ -546,12 +575,20 @@ export class VideoModal { return this.playerModal; } + /** + * Binds the controller to the DOM elements within the modal. + * Initializes sub-controllers and sets up event listeners. + * + * @param {HTMLElement} playerModal - The root modal element + */ hydrate(playerModal) { + // 1. Cleanup existing accessibility traps and sub-controllers if (this.modalAccessibility?.destroy) { this.modalAccessibility.destroy(); } this.modalAccessibility = null; + // 2. Cleanup existing popovers (Zap, More Menu) if (this.modalZapPopover?.destroy) { this.modalZapPopover.destroy(); } @@ -3021,6 +3058,14 @@ export class VideoModal { }); } + /** + * Resets the modal state, populates it with the provided video data, and makes it visible. + * Also initializes moderation, comments, and accessibility traps. + * + * @param {Object} video - The video object to display (title, description, tags, etc.) + * @param {Object} [options] - Display options + * @param {HTMLElement} [options.triggerElement] - The element that triggered the modal (for focus restoration) + */ open(video, options = {}) { this.activeVideo = video || null; this.moderationBadgeId = ""; @@ -3069,6 +3114,10 @@ export class VideoModal { this.refreshActiveVideoModeration({ video: this.activeVideo }); } + /** + * Closes the modal, pauses playback, clears active state, and restores focus. + * This is the primary cleanup method when dismissing the modal. + */ close() { this.activeVideo = null; this.applyModerationOverlay(null);