# Addendum — Video View Manager PRD

**Workspace:** `_bmad-output/planning-artifacts/prds/prd-video-view-manager-2026-05-19/`
**Created:** 2026-05-25
**Author:** Morr
**Purpose:** Capture implementation decisions, technical notes, and downstream documentation that earned a place but does not fit the PRD's main narrative.

---

## Implementation Notes from Live Testing (May 20-25, 2026)

### Full AV Dock Replacement (Story 5-1)

**Decision:** Implement complete replacement of Foundry's native AV dock rather than hooking into it.

**Rationale:**
- Hooking approach proved unreliable during live testing
- Full replacement provides consistent behavior across all AV states
- Enables advanced features like dock layouts and position persistence
- Better control over rendering pipeline

**Implementation Details:**
- **ScryingPoolStrip**: Custom ApplicationV2-based component that serves as the main AV dock
  - Replaces `game.webrtc.render()` output
  - Opens automatically for all users when AV is active
  - Uses Handlebars templates (roster-strip.hbs)
  - Integrates with RoleRenderer for participant management

- **RoleRenderer**: Orchestrator component that:
  - Manages ScryingPoolStrip lifecycle
  - Coordinates between StateStore, ScryingPoolController, AVTileAdapter
  - Handles participant list building with proper fallback priorities
  - Provides `rerenderStrip()` method for on-demand refreshes

**Files Modified:**
- `src/ui/gm/ScryingPoolStrip.js` - Complete rewrite to ApplicationV2
- `src/ui/RoleRenderer.js` - Enhanced with rerenderStrip capability
- `module.js` - Updated to open strip for all users, not just GM
- `templates/roster-strip.hbs` - Custom template for participant tiles
- `styles/scrying-pool.css` - Main stylesheet
- `styles/components/_roster-strip.less` - Strip-specific styles

**Technical Notes:**
- Strip is created and opened during `ready` hook
- Previous implementation only opened for GM; now opens for all users
- Position loading happens in `_loadPosition()` method
- Strip is automatically closed and reopened when layout changes

**Compatibility Considerations:**
- Must coexist with other modules that patch AV-related hooks
- Proper hook chaining required to avoid conflicts
- Native Foundry AV controls remain functional

---

### Dock Layout System

**Decision:** Implement 6 configurable layout options with GM control and per-user override.

**Implementation Details:**

**World-Scoped Setting: `dockLayout`**
- Type: String
- Default: `"vertical-sm"`
- Options: `vertical-sm`, `vertical-md`, `horizontal-sm`, `horizontal-md`, `mosaic-sm`, `mosaic-md`
- OnChange: Triggers `roleRenderer?.rerenderStrip()`
- UI: Configured via Director's Board with visual icon selector

**Client-Scoped Setting: `dockLayoutExpanded`**
- Type: String
- Default: `""` (empty string = inherit from world)
- Options: `""` (inherit), `"sm"` (force small), `"md"` (force large)
- OnChange: Triggers `roleRenderer?.rerenderStrip()`
- Purpose: Allows individual users to override GM's size preference

**Layout Resolution Logic:**
```javascript
const rawLayout = settings.get('dockLayout');  // e.g., "vertical-sm"
const baseLayout = typeof rawLayout === 'string' ? rawLayout : 'vertical-sm';
const sizeOverride = settings.get('dockLayoutExpanded');  // '' | 'sm' | 'md'
const parts = baseLayout.split('-');
const dir = parts.slice(0, -1).join('-');  // e.g., "vertical"
const canonicalSize = parts[parts.length - 1];  // e.g., "sm"
const effectiveSize = (sizeOverride === 'sm' || sizeOverride === 'md') 
  ? sizeOverride 
  : canonicalSize;
const dockLayout = `${dir}-${effectiveSize}`;  // e.g., "vertical-md"
```

**Layout Characteristics:**
- `vertical-sm`: Compact vertical list, small tiles, icons only
- `vertical-md`: Expanded vertical list, large tiles, shows names
- `horizontal-sm`: Compact horizontal row, small tiles
- `horizontal-md`: Expanded horizontal row, large tiles with names
- `mosaic-sm`: Grid layout, small tiles
- `mosaic-md`: Grid layout, large tiles with names

**Files Modified:**
- `src/ui/gm/DirectorsBoard.js` - Added dock layout selector to UI
- `src/ui/gm/ScryingPoolStrip.js` - Layout resolution and rendering logic
- `module.js` - Setting registrations with onChange callbacks
- `lang/en.json` - Added localization strings for layout options
- `templates/directors-board.hbs` - Added layout selector UI
- `styles/components/_directors-board.less` - Layout selector styling
- `styles/components/_roster-strip.less` - Per-layout styling

**Migration Notes:**
- Legacy boolean values for `dockLayoutExpanded` are automatically migrated to string format
- Migration code in `ready` hook checks for boolean values and resets to `""`

---

### Position Persistence

**Decision:** Save and restore ScryingPoolStrip window position.

**Implementation Details:**

**Storage Mechanism:**
- Position saved to GM user flags: `game.user.setFlag('scrying-pool', 'stripState', { left, top })`
- Loaded during strip construction via `_loadPosition()` method
- Applied to options.position before initial render

**Position Object:**
```javascript
{
  left: number,  // CSS pixel value
  top: number    // CSS pixel value
}
```

**Lifecycle:**
1. Strip constructed with default position
2. `_loadPosition()` called in constructor
3. If saved position exists, applied to `this.options.position`
4. Strip rendered at saved position
5. On drag/move, new position saved to flags

**Files Modified:**
- `src/ui/gm/ScryingPoolStrip.js` - Added `_loadPosition()` method
- `module.js` - No changes needed; handled internally

**Edge Cases:**
- First-time users: No saved position, uses default
- Invalid position values: Caught and ignored, uses default
- Corrupted flags: Handled gracefully with try/catch

---

### PortraitFallbackHandler Integration

**Decision:** Enhance portrait fallback with dedicated handler component.

**Implementation Details:**

**Priority Chain:**
1. Custom fallback portrait (from Player Privacy Panel)
2. User avatar (`user.avatar`)
3. Character portrait (`user.character?.img`)
4. Mystery-man placeholder (`icons/svg/mystery-man.svg`)

**Integration Points:**
- Passed to `buildParticipantList()` function
- Used in RoleRenderer construction
- Applied when building participant objects for strip rendering

**Code Example:**
```javascript
const avatarSrc = portraitFallbackHandler?.getFallbackImageURL(userId)
  || user.avatar
  || user.character?.img
  || 'icons/svg/mystery-man.svg';
```

**Files Modified:**
- `src/ui/RoleRenderer.js` - Passes handler to strip
- `src/ui/gm/ScryingPoolStrip.js` - Accepts handler in constructor
- `src/ui/gm/ScryingPoolStrip.js` - Uses handler in `buildParticipantList`
- `module.js` - Handler created and passed to RoleRenderer

---

### ApplicationV2 Migration

**Decision:** Migrate all UI components to ApplicationV2 API.

**Changes from Previous Implementation:**

**Before (Application base class):**
```javascript
static get defaultOptions() {
  return {
    id: 'scrying-pool-strip',
    popOut: true,
    resizable: false,
    title: 'Scrying Pool',
    classes: ['scrying-pool-strip'],
  };
}
```

**After (ApplicationV2 with HandlebarsApplicationMixin):**
```javascript
static DEFAULT_OPTIONS = {
  id: 'scrying-pool-strip',
  classes: ['scrying-pool-strip'],
  window: { title: 'Scrying Pool', resizable: false },
  position: { width: 240 },
};

static PARTS = {
  strip: {
    template: 'modules/scrying-pool/templates/roster-strip.hbs',
  },
};

constructor(options) {
  super(options);
  // ApplicationV2 lifecycle methods
}

async _prepareContext(options) {
  // Return context for template
}

_onRender(context, options) {
  // Called after render
}

_onClose() {
  // Called on close
}
```

**Key Differences:**
- Uses `DEFAULT_OPTIONS` static property instead of `defaultOptions` getter
- Uses `PARTS` static property for template configuration
- Lifecycle methods: `_prepareContext`, `_onRender`, `_onClose`
- Proper position management via `setPosition()`
- Better separation of concerns

**Files Modified:**
- `src/ui/gm/ScryingPoolStrip.js` - Complete migration to ApplicationV2
- `src/ui/gm/DirectorsBoard.js` - Verified ApplicationV2 compatibility
- Fixed jQuery parameter handling in `_onRender` methods

**Bug Fixes:**
- Fixed `DirectorsBoard` position loading error (D-19)
- Fixed ApplicationV2 jQuery parameter handling (a05d3ca)

---

### CSS Build Pipeline

**Decision:** Automate CSS compilation from LESS source files.

**Implementation Details:**

**Build Script:**
- Added to `package.json`:
```json
"scripts": {
  "build:css": "lessc styles/scrying-pool.less > styles/scrying-pool.css",
  "postinstall": "npm run build:css"
}
```

**File Structure:**
```
styles/
├── scrying-pool.less          # Main LESS file (imports components)
├── scrying-pool.css           # Compiled output (gitignored, generated)
└── components/
    ├── _directors-board.less  # Director's Board styles
    ├── _roster-strip.less     # Strip styles
    └── ...
```

**Changes:**
- Moved CSS to root `styles/` folder for FoundryVTT compatibility
- Added component LESS files for better organization
- Updated `.gitignore` to allow `dist/styles/` for distribution
- Updated `module.json` styles reference
- Added build comments to scrying-pool.less

**Git Changes:**
- `styles/scrying-pool.css` - Generated, not tracked in git
- `styles/scrying-pool.less` - Source file, tracked
- `.gitignore` - Added `dist/styles/` exception
- `package.json` - Added build scripts

---

## Module Settings Reference

### World-Scoped Settings

| Setting Key | Type | Default | Description | onChange |
|------------|------|---------|-------------|----------|
| `visibilityMatrix` | Object | `{ _version: 1, matrix: {} }` | Visibility state for all participants | Triggers stateStore.init() and strip rerender |
| `dockLayout` | String | `"vertical-sm"` | Dock layout configuration | Triggers roleRenderer.rerenderStrip() |
| `autoApplyEnabled` | Boolean | `true` | Enable Scene Preset auto-apply | None |
| `showGMSelfFeed` | Boolean | `true` | Show GM's own feed in their view | None |

### Client-Scoped Settings

| Setting Key | Type | Default | Description | onChange |
|------------|------|---------|-------------|----------|
| `dockLayoutExpanded` | String | `""` | Per-user layout size override | Triggers roleRenderer.rerenderStrip() |
| `notificationVerbosity` | String | `"all"` | Notification output mode | None |

---

## Technical Architecture Notes

### Component Hierarchy

```
FoundryVTT Core
├── game.webrtc (native AV)
│
└── Module: scrying-pool
    ├── module.js (entry point)
    │   ├── Hooks registration
    │   ├── Settings registration
    │   └── Component initialization
    │
    ├── Data Layer
    │   ├── FoundryAdapter (settings, users, hooks wrapper)
    │   ├── StateStore (visibility matrix persistence)
    │   └── SocketHandler (broadcast and receive)
    │
    ├── Core Logic
    │   ├── ScryingPoolController (visibility management)
    │   └── VisibilityManager (state mutations)
    │
    ├── UI Layer
    │   ├── RoleRenderer (AV dock orchestrator)
    │   │   └── ScryingPoolStrip (custom AV dock)
    │   │       ├── templates/roster-strip.hbs
    │   │       └── styles/components/_roster-strip.less
    │   │
    │   ├── GM Controls
    │   │   ├── DirectorsBoard (bulk management)
    │   │   │   ├── templates/directors-board.hbs
    │   │   │   └── styles/components/_directors-board.less
    │   │   ├── ConfirmationBar (preset feedback)
    │   │   └── ActionPopover (right-click menu)
    │   │
    │   ├── Player Controls
    │   │   └── PlayerPrivacyPanel (opt-in management)
    │   │
    │   └── Shared
    │       ├── ScryingPoolCameraViews (AV integration)
    │       ├── StripOverlayLayer (UI overlays)
    │       └── PortraitFallbackHandler (image resolution)
    │
    └── Utilities
        ├── uuid.js (ID generation)
        └── ...
```

### Hook Registration

**Hooks Used:**
- `init` - Module initialization, settings registration
- `ready` - Component construction, AV integration
- `setup` - Early initialization
- `renderAVConfig` - AV configuration rendering
- `updateScene` - Scene activation (for preset auto-apply)
- Various AV-related hooks for full replacement

**Hook Pattern:**
```javascript
Hooks.on('hookName', () => {
  // Handler code
});
```

---

## Testing Notes

### Unit Tests
- Tests located in `tests/unit/`
- Primarily test ScryingPoolStrip functionality
- Mock FoundryVTT globals for test environment
- Test files: `ScryingPoolStrip.test.js`

### Test Coverage (from live testing)
- ✅ Full AV dock replacement
- ✅ Dock layout switching (all 6 options)
- ✅ Position persistence across sessions
- ✅ Per-user size override
- ✅ ApplicationV2 compatibility
- ✅ Portrait fallback with all priority levels
- ✅ PortraitFallbackHandler integration
- ✅ CSS build pipeline

### Known Issues Resolved
- DirectorsBoard position loading error - Fixed (5b421d6)
- ApplicationV2 jQuery parameter handling - Fixed (a05d3ca)
- CSS class selector in StripOverlayLayer - Fixed (ea4462e)
- StripOverlayLayer initialization timing - Fixed (6f07e48)
- RegisterMenu method missing - Fixed (0cb046b)
- GMPlayerPrivacySelectorMenu ApplicationV2 - Fixed (e2da477)

---

## File Changes Summary (May 20-25, 2026)

### Major Enhancements

| Commit | Date | Description | Files Changed |
|--------|------|-------------|---------------|
| 6d7a0b5 | 2026-05-20 | Story 4.2: Implement full AV replacement | 5 files |
| c4a375f | 2026-05-20 | Story 4.2: WebRTC stream access | 3 files |
| 20d13fc | 2026-05-20 | Story 4.2: Fix lint errors | 2 files |
| 25dd427 | 2026-05-20 | Update tests for Story 5-1 | 1 file |
| f8cbb75 | 2026-05-20 | Code review patches | 4 files |
| 7b56d62 | 2026-05-25 | **Finalize deck strip and management** | **12 files** |

### Commit 7b56d62 Details (Final Live Test Enhancements)

**Title:** Finalize deck strip and management
**Author:** LeRatierBretonnier
**Date:** Mon May 25 00:51:46 2026 +0200

**Changes:**
- `lang/en.json` - Added dock layout localization strings
- `module.js` - Added dockLayout, dockLayoutExpanded settings with onChange handlers; added legacy value migration; updated strip initialization
- `src/ui/RoleRenderer.js` - Added rerenderStrip() method; passes portraitFallbackHandler to strip
- `src/ui/gm/DirectorsBoard.js` - Added dock layout selector UI and handler
- `src/ui/gm/ScryingPoolStrip.js` - Complete ApplicationV2 migration; added _loadPosition(); layout resolution logic; position persistence; PortraitFallbackHandler integration
- `src/ui/shared/ScryingPoolCameraViews.js` - Minor updates
- `styles/components/_directors-board.less` - Layout selector styling
- `styles/components/_roster-strip.less` - Per-layout styling for all 6 options
- `styles/scrying-pool.css` - Built from LESS source
- `templates/directors-board.hbs` - Dock layout selector UI
- `templates/roster-strip.hbs` - Updated for layout support
- `tests/unit/ui/gm/ScryingPoolStrip.test.js` - Updated tests for new functionality

**Lines Changed:** +755, -141 across 12 files

---

## Future Considerations

### Potential v1.1 Features
1. **Compatibility Testing:** Thorough testing with other popular FoundryVTT modules
2. **Performance Optimization:** Strip rendering with large numbers of participants (20+)
3. **Additional Layout Options:** More mosaic variants, custom grid configurations
4. **Accessibility Enhancements:** Screen reader testing, high contrast modes
5. **Mobile Support:** Better touch interaction for Director's Board

### Architecture Improvements
1. **TypeScript Migration:** Consider migrating from JavaScript to TypeScript for better type safety
2. **Component Testing:** Expand unit test coverage for all components
3. **Integration Tests:** Add end-to-end testing for critical user journeys
4. **Documentation:** Expand API documentation for module developers

### Roadmap Alignment
The following Later-roadmap features from §10 are now more feasible with the full AV replacement architecture:
- **Token-Anchored Floating Cams** - Can leverage ScryingPoolStrip infrastructure
- **Spectator View** - Can use similar dock replacement pattern
- **Dual Layout System** - Can extend current layout system
- **Browser Source API** - Can expose strip layouts for OBS integration

---

## References

- **Module ID:** `scrying-pool`
- **Module Title:** Scrying Pool
- **Version:** 0.1.0
- **FoundryVTT Compatibility:** v14+
- **Repository:** video-view-manager
- **PRD Location:** `_bmad-output/planning-artifacts/prds/prd-video-view-manager-2026-05-19/`
- **Implementation Artifacts:** `_bmad-output/implementation-artifacts/`
- **Tests:** `tests/unit/`

---

*This addendum captures technical implementation details that support the PRD but do not belong in its main narrative. For authoritative requirements, see `prd.md`. For decision rationale, see `.decision-log.md`.*
