7a0d935239
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>
485 lines
17 KiB
Markdown
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`.*
|