Files
uberwald 7a0d935239 Story 5.2: Video Widget Width Customization
Implements configurable video widget widths for small and large dock layouts:
- Add widgetWidthSm and widgetWidthMd world settings (defaults: 83px, 150px)
- Add width dropdown selectors in Director's Board UI
- Apply width settings to participant avatars via CSS custom properties
- Update strip width/height calculations to use custom widths
- Add localization strings for widget width settings
- Add CSS styling for widget width dropdown controls
- Fix Handlebars template syntax for select element selected values

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-25 12:57:24 +02:00

485 lines
17 KiB
Markdown

# 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`.*