diff --git a/_bmad-output/implementation-artifacts/4-1-player-privacy-panel-and-automation-opt-ins.md b/_bmad-output/implementation-artifacts/4-1-player-privacy-panel-and-automation-opt-ins.md new file mode 100644 index 0000000..97b255c --- /dev/null +++ b/_bmad-output/implementation-artifacts/4-1-player-privacy-panel-and-automation-opt-ins.md @@ -0,0 +1,811 @@ +# Story 4.1: Player Privacy Panel & Automation Opt-ins + +**Status:** ready-for-dev + +**Epic:** 4 - Player Privacy Panel + +**Story Key:** 4-1-player-privacy-panel-and-automation-opt-ins + +**Created:** 2026-05-24 + +**Last Updated:** 2026-05-24 + +--- + +## Story Header + +| Field | Value | +|-------|-------| +| **Epic** | 4 - Player Privacy Panel | +| **Story ID** | 4.1 | +| **Story Key** | 4-1-player-privacy-panel-and-automation-opt-ins | +| **Title** | Player Privacy Panel & Automation Opt-ins | +| **Status** | ready-for-dev | +| **Priority** | High | +| **Assigned Agent** | DEV (Amelia) | +| **Created** | 2026-05-24 | +| **Last Updated** | 2026-05-24 | + +--- + +## 📋 Story Requirements + +### User Story + +**As a** player, +**I want to** see and control every automation effect that can change my on-screen presence, and opt in or out at any time, +**So that** I'm never surprised by automatic camera behaviours I didn't agree to. + +### Persona Alignment + +- **Primary:** Elena (Casual Player) - Needs to control her own privacy without GM intervention +- **Primary:** All Players - Requires transparency and consent for automation features +- **Secondary:** GM (Marcus, Jake) - Needs visibility into which players have opted in + +### Acceptance Criteria (BDD Format) + +#### AC-1: Player Privacy Panel Accessible +**Given** the module is active +**When** a player opens FoundryVTT module settings +**Then** the Player Privacy Panel section is visible in the settings UI +**And** it is clearly labeled as "Player Privacy Panel" + +#### AC-2: List All Automation Effects +**Given** the Player Privacy Panel is open +**When** a player views it for their own user +**Then** all automation effects are listed with their current opt-in status +**And** "Reaction Cam" is listed with default state: off +**And** "HP-Reactive Cam Styling" is listed with default state: off + +#### AC-3: Opt-In Flag Persistence (Reaction Cam) +**Given** a player toggles "Reaction Cam" to enabled +**When** the toggle is confirmed +**Then** the opt-in flag is stored via `game.user.setFlag('video-view-manager', 'reactionCamEnabled', true)` +**And** the change persists across page refreshes and session reconnects +**And** it takes effect immediately for all future Reaction Cam triggers + +#### AC-4: Silent Skip When Opt-Out (Reaction Cam) +**Given** "Reaction Cam" is disabled for a player +**When** a Reaction Cam trigger event fires (e.g., combat cinematics) +**Then** that player is silently skipped +**And** no notification is shown +**And** no error is logged +**And** no indication is sent to the GM +**And** the player's camera remains in its current state + +#### AC-5: Director's Board Badge for Enabled Players +**Given** "Reaction Cam" is enabled for a player +**When** the Director's Board is open +**Then** that participant's card shows a "Reaction Cam: Enabled" badge +**And** the badge uses the SP token system for styling +**And** the badge is visible to the GM only + +#### AC-6: GM Read-Only View +**Given** the GM opens another player's Privacy Panel +**When** viewing another player's settings +**Then** all opt-in controls are visible +**And** all controls are disabled (read-only) +**And** no editing is possible through the UI +**And** a message indicates "This player's privacy settings are read-only" + +#### AC-7: HP-Reactive Cam Styling Opt-In Persistence +**Given** a player toggles "HP-Reactive Cam Styling" to enabled +**When** the toggle is confirmed +**Then** the opt-in flag is stored via `game.user.setFlag('video-view-manager', 'hpReactiveCamStylingEnabled', true)` +**And** the change persists across page refreshes and session reconnects +**And** the GM receives no notification of the change + +#### AC-8: Fallback Behavior for Both Opt-Ins +**Given** either "Reaction Cam" or "HP-Reactive Cam Styling" is disabled +**When** the corresponding automation trigger fires +**Then** the player is silently skipped +**And** the feature behaves as if that player has no camera or styling applied +**And** no errors or warnings appear in the console + +### Functional Requirements Covered + +- **FR-23:** Player Privacy Panel accessible from module settings; lists all automation effects with current opt-in status; owning user can edit; GM can view but not edit another Participant's settings; settings persist in world-level user flags. +- **FR-24:** Reaction Cam automation requires explicit Participant opt-in (default: off); Reaction Cam remains disabled until Participant enables it in Player Privacy Panel; Director's Board shows "Reaction Cam: Enabled" badge on opted-in cards; opt-in flag persists across sessions; all Reaction Cam triggers respect and skip opted-out Participants silently. +- **FR-25:** HP-Reactive Cam Styling requires explicit Participant opt-in (default: off); disabled until Participant explicitly enables it; GM is not notified of individual styling opt-in statuses. + +### Success Criteria + +- [ ] All 8 acceptance criteria pass manual testing +- [ ] All unit tests pass (target: +12-15 new tests for PlayerPrivacyManager) +- [ ] `npm run lint` exits 0 (ESLint import boundaries enforced) +- [ ] `npm run typecheck` exits 0 (strict JSDoc compliance) +- [ ] Code review passes with no critical findings +- [ ] Integration test: Player toggles opt-in → verify flag persistence → verify automation respects opt-out + +--- + +## 📝 Tasks / Subtasks + +### Task 1: Create PlayerPrivacyManager Core Logic + +**Files:** `src/core/PlayerPrivacyManager.js`, `src/contracts/privacy-settings.js`, `tests/unit/core/PlayerPrivacyManager.test.js` + +**Subtasks:** +- [ ] 1.1: Create `src/contracts/privacy-settings.js` with opt-in flag contracts + - Define canonical shape for privacy settings: `{ reactionCamEnabled: boolean, hpReactiveCamStylingEnabled: boolean }` + - Export `PRIVACY_SETTINGS_DEFAULT` with both flags defaulting to `false` + - Export `isValidPrivacySettings(data)` validator + - Export `createPrivacySettings(overrides)` factory +- [ ] 1.2: Write TDD red tests for PlayerPrivacyManager methods +- [ ] 1.3: Create `PlayerPrivacyManager` class with constructor `(adapter)` + - Constructor receives FoundryAdapter for user flag access + - No direct `game.*` access (DI enforced) +- [ ] 1.4: Implement `getSettings(userId)` — retrieves privacy settings from user flags + - Returns merged settings with defaults for missing keys + - Handles null/undefined flag gracefully +- [ ] 1.5: Implement `setSetting(userId, key, value)` — updates a single privacy setting + - Validates key is known (reactionCamEnabled or hpReactiveCamStylingEnabled) + - Validates value is boolean + - Calls `adapter.users.setFlag(userId, 'video-view-manager', key, value)` + - Emits change event for subscribers +- [ ] 1.6: Implement `isOptedIn(userId, feature)` — convenience method for feature checks + - Returns boolean for 'reactionCam' or 'hpReactiveCamStyling' + - Defaults to false if setting not found +- [ ] 1.7: Implement `getAllSettings()` — returns all users' privacy settings (GM only) + - Aggregates settings from all connected users + - Only accessible when caller is GM +- [ ] 1.8: Green all PlayerPrivacyManager tests (12-15 tests passing) + +**Acceptance Criteria:** AC-2, AC-3, AC-7, AC-8 + +**Dev Notes:** +- Use user flags for persistence: `game.user.setFlag('video-view-manager', 'reactionCamEnabled', boolean)` +- Both flags default to `false` (opt-in, not opt-out) +- No socket broadcasting for privacy settings — each client reads their own user's flags +- GM can query other users' flags but cannot modify them + +--- + +### Task 2: Create PlayerPrivacyPanel UI Component + +**Files:** `src/ui/player/PlayerPrivacyPanel.js`, `templates/player-privacy-panel.hbs`, `styles/components/_player-privacy-panel.less` + +**Subtasks:** +- [ ] 2.1: Create `PlayerPrivacyPanel` class extending `ApplicationV2` + - Constructor receives `adapter`, `playerPrivacyManager`, and `targetUserId` + - Registers module settings UI component via `game.settings.registerMenu` + - Opens as a dialog/modal window +- [ ] 2.2: Create Handlebars template `player-privacy-panel.hbs` + - Lists all automation effects with current opt-in status + - Shows toggle controls for own user + - Shows disabled (read-only) controls for other users (GM view) + - Includes info text for each automation effect + - Shows "Read-only" notice when viewing another player's settings +- [ ] 2.3: Create LESS styles `_player-privacy-panel.less` + - Styles for panel layout, toggle switches, and badges + - Uses SP token system for colors and spacing + - Responsive to Foundry dark/light themes +- [ ] 2.4: Implement `_onRender()` to populate settings from PlayerPrivacyManager + - Reads current user's privacy settings on open + - Updates toggle states to match saved values +- [ ] 2.5: Implement toggle handlers for Reaction Cam and HP-Reactive Cam Styling + - Calls `playerPrivacyManager.setSetting()` on change + - Updates UI immediately on toggle + - Shows confirmation/save feedback +- [ ] 2.6: Implement read-only mode for GM viewing other players' settings + - Disables all toggle controls when `targetUserId !== game.user.id` + - Shows visual indicator that settings are read-only + - Prevents any modifications + +**Acceptance Criteria:** AC-1, AC-2, AC-3, AC-6, AC-7, AC-8 + +**Dev Notes:** +- Panel should be accessible from module settings menu +- For own user: editable toggles with immediate save +- For other users (GM only): read-only display, no save buttons +- Use FoundryVTT native form controls where possible +- Panel should be keyboard-navigable with proper ARIA labels + +--- + +### Task 3: Extend FoundryAdapter for User Flag Access + +**Files:** `src/foundry/FoundryAdapter.js` + +**Subtasks:** +- [ ] 3.1: Add `setFlag(userId, scope, key, value)` method + - Wraps `game.users.get(userId)?.setFlag(scope, key, value)` + - Validates userId exists + - Returns success/failure status +- [ ] 3.2: Add `getFlag(userId, scope, key)` method + - Wraps `game.users.get(userId)?.getFlag(scope, key)` + - Returns the flag value or undefined if not found + - Returns null if userId doesn't exist +- [ ] 3.3: Add `getFlagModule(userId, key)` convenience method + - Calls `getFlag(userId, 'video-view-manager', key)` + - Used for privacy settings access +- [ ] 3.4: Update existing tests for FoundryAdapter + - Add tests for new user flag methods + - Verify proper error handling for non-existent users + +**Acceptance Criteria:** AC-3, AC-7 + +**Dev Notes:** +- These methods provide the DI layer for user flag operations +- Prevents direct `game.users` access in core modules +- Supports querying other users' flags (GM only) + +--- + +### Task 4: Integrate Privacy Settings with Director's Board + +**Files:** `src/ui/gm/DirectorsBoard.js`, `src/ui/shared/ParticipantCard.js`, `templates/participant-card.hbs` + +**Subtasks:** +- [ ] 4.1: Update `ParticipantCard` to display Reaction Cam badge + - Add badge element to card template + - Show badge when `playerPrivacyManager.isOptedIn(userId, 'reactionCam')` is true + - Style badge using SP token system + - Tooltip: "Reaction Cam: Enabled" +- [ ] 4.2: Update `DirectorsBoard` to inject PlayerPrivacyManager + - Pass `playerPrivacyManager` to ParticipantCard components + - Refresh card display when privacy settings change +- [ ] 4.3: Update `participant-card.hbs` template + - Add badge container with appropriate classes + - Handle missing/opted-out state gracefully + +**Acceptance Criteria:** AC-5 + +**Dev Notes:** +- Badge is GM-only visibility +- Should update in real-time when players change their settings +- Badge should be subtle but clearly visible +- Uses existing SP state token patterns + +--- + +### Task 5: Add Module Settings Registration + +**Files:** `module.js` + +**Subtasks:** +- [ ] 5.1: Register Player Privacy Panel in module settings + - Use `game.settings.registerMenu('video-view-manager', 'playerPrivacyPanel', {...})` + - Menu type: 'PlayerPrivacyPanel' + - Restricted to players (not GM-only) + - Label: "Player Privacy Panel" + - Hint: "Control automation effects for your camera" +- [ ] 5.2: Register Player Privacy Panel for GM access + - Separate menu entry for GM to view all players' settings + - Label: "View Player Privacy Settings" + - Restricted to GM only + - Opens read-only view selector + +**Acceptance Criteria:** AC-1, AC-6 + +**Dev Notes:** +- Player menu: Opens own privacy panel (editable) +- GM menu: Opens selector to view any player's panel (read-only) +- Both use the same PlayerPrivacyPanel component with different targetUserId + +--- + +### Task 6: Localization Strings + +**Files:** `lang/en.json` + +**Subtasks:** +- [ ] 6.1: Add all UI strings for Player Privacy Panel + - Panel title: "Player Privacy Panel" + - Section header: "Automation Opt-ins" + - Reaction Cam label: "Reaction Cam" + - Reaction Cam description: "Automatically show your camera during key moments (combat, rolls, etc.)" + - HP-Reactive Cam Styling label: "HP-Reactive Cam Styling" + - HP-Reactive Cam Styling description: "Apply visual styling to your camera based on your character's HP" + - Toggle on: "Enabled" + - Toggle off: "Disabled" + - Read-only notice: "This player's privacy settings are read-only" + - Save button: "Save Settings" + - Saved notification: "Privacy settings saved" +- [ ] 6.2: Add Director's Board badge tooltip + - "Reaction Cam: Enabled" +- [ ] 6.3: Add module settings menu labels + - Player menu: "Player Privacy Panel" + - GM menu: "View Player Privacy Settings" + +**Acceptance Criteria:** All ACs (UI text requirements) + +**Dev Notes:** +- All strings under `SCRYING_POOL` namespace +- Use plain language per NFR-6 +- Keep technical terms out of player-facing text + +--- + +## 🎯 Developer Context + +### Epic Context + +**Epic 4: Player Privacy Panel** delivers the consent and privacy layer for all future automation features. This epic ensures that players have explicit control over features that affect their on-screen presence, implementing the Progressive Enhancement Architecture's trust and consent layer. Story 4.1 implements the foundational Player Privacy Panel with opt-in controls for Reaction Cam and HP-Reactive Cam Styling. Story 4.2 will add Custom Portrait Fallback selection. + +**Business Value:** Players need agency over automation features that affect their camera feed and on-screen appearance. This builds trust and ensures compliance with privacy expectations. Without this layer, future automation features (Epic 5+) cannot be implemented ethically. + +**Dependencies:** +- Epic 1 (Core Camera Visibility Control) - COMPLETE +- Epic 2 (Player Notifications & Director's Board) - COMPLETE +- Epic 3 (Scene-Aware Camera Automation) - COMPLETE +- FoundryAdapter user flag methods (new in this story) +- No external dependencies required + +**Blockers:** +- **CRITICAL: OQ-GDPR Decision** - Consent Storage Architecture must be resolved before implementation + - **Architecture Recommendation:** Use `localStorage` for v1.0, documented as v2 upgrade path + - **Current Decision:** User flags (`game.user.setFlag`) for v1.0 - world-persistent, linked to user identity + - **Rationale:** User flags provide world-persistent storage that follows the user across sessions in the same world + +### Previous Story Intelligence (Story 3.3) + +**Learnings from Story 3.3 (Preset Import & Export):** +1. **Contract validation is critical** - Exported `isValidScenePreset()` and factory functions caught issues early +2. **File operations use native browser APIs** - No external libraries needed for download/upload +3. **UI follows FoundryVTT ApplicationV2 patterns** - Dialogs extend ApplicationV2, use Handlebars templates +4. **Manager classes handle core logic** - `PresetImportExportManager` separated concerns from UI +5. **Import boundaries strictly enforced** - Core only imports from contracts/utils +6. **TDD approach effective** - 38 tests for PresetImportExportManager ensured reliability + +**Code Patterns to Reuse:** +- Constructor dependency injection for testability +- JSDoc on all exported symbols (enforced by ESLint) +- Private methods prefixed with `_` +- Error handling with descriptive messages +- Type validation at boundaries via contract validators +- Event emission for UI updates (subscription pattern) + +**Files Created/Modified in Story 3.3:** +- `src/core/PresetImportExportManager.js` - Core logic with validation +- `src/ui/gm/PresetExportDialog.js` / `PresetImportDialog.js` - ApplicationV2 dialogs +- `templates/preset-export.hbs` / `preset-import.hbs` - Handlebars templates +- `styles/components/_preset-import-export.less` - LESS styles +- `src/contracts/scene-preset.js` - Contract with validator +- `tests/unit/core/PresetImportExportManager.test.js` - Comprehensive tests + +**Problems Encountered & Solutions:** +- Dialog lifecycle management → Used `_onRender` and `_onClose` methods +- File download in test environment → Used mockable browser APIs +- Validation error collection → Aggregated all errors before reporting +- Merge vs replace logic → Clear separation of concerns in manager + +--- + +### Architecture Compliance + +**Technical Stack:** +- Vanilla JavaScript ES2022+ with native ESM +- LESS 4.6.4 → CSS via chokidar watch +- Handlebars `.hbs` templates (ApplicationV2 PARTS) +- No external UI libraries +- No socketlib +- Font Awesome 6 and Foundry CSS custom properties only + +**Code Structure Rules:** +- All source files in `src/` directory +- Import boundaries enforced by ESLint `import/no-restricted-paths` +- Contract files in `src/contracts/` define canonical data shapes +- Core logic in `src/core/` (testable, zero `game.*` access) +- Foundry adapter layer in `src/foundry/` +- UI components in `src/ui/` (player/ subdirectory for player-facing) + +**Import Restrictions:** +- `src/contracts/` - May import nothing (pure data) +- `src/utils/` - May only import from `src/contracts/` +- `src/core/` - May only import from `src/contracts/`, `src/utils/` +- `src/foundry/` - May import from anywhere (adapter layer) +- `src/ui/` - May import from `src/core/`, `src/foundry/`, `src/contracts/` + +**This Story's Import Plan:** +- `PlayerPrivacyManager` (`src/core/`) → imports from `src/contracts/privacy-settings.js` +- `PlayerPrivacyPanel` (`src/ui/player/`) → imports from `src/core/PlayerPrivacyManager.js`, `src/foundry/FoundryAdapter.js` +- `FoundryAdapter` (`src/foundry/`) → extends existing adapter with user flag methods + +**Architecture Decisions to Follow:** +- **Dependency Injection:** All Foundry API dependencies constructor-injected via FoundryAdapter +- **Side-Effect-Free Constructors:** No hook registration in constructors; use `init()` for setup +- **Role-Differentiated Rendering:** Player and GM UIs are separate component trees +- **State Authority:** PlayerPrivacyManager owns privacy settings; reads/writes via adapter +- **Persistence:** User flags for privacy settings (world-level, user-scoped) + +--- + +### Critical Implementation Requirements + +**1. User Flag Storage Pattern:** +```javascript +// Setting a flag +game.user.setFlag('video-view-manager', 'reactionCamEnabled', true); + +// Getting a flag +const enabled = game.user.getFlag('video-view-manager', 'reactionCamEnabled'); + +// Getting another user's flag (GM only) +const otherEnabled = game.users.get(otherUserId)?.getFlag('video-view-manager', 'reactionCamEnabled'); +``` + +**2. PlayerPrivacyManager Interface:** +```javascript +class PlayerPrivacyManager { + constructor(adapter) {} + + getSettings(userId) { /* returns { reactionCamEnabled, hpReactiveCamStylingEnabled } */ } + setSetting(userId, key, value) { /* validates and saves */ } + isOptedIn(userId, feature) { /* returns boolean */ } + getAllSettings() { /* returns Map for GM */ } + onChange(callback) { /* subscribe to setting changes */ } +} +``` + +**3. Silent Skip Pattern for Automation:** +```javascript +// In any automation trigger (e.g., Reaction Cam) +if (!playerPrivacyManager.isOptedIn(userId, 'reactionCam')) { + return; // Silent skip - no notification, no error +} +``` + +**4. Director's Board Badge Integration:** +```javascript +// In ParticipantCard render +if (playerPrivacyManager.isOptedIn(this.userId, 'reactionCam')) { + card.querySelector('.sp-reaction-cam-badge').classList.remove('hidden'); +} +``` + +**5. Settings Menu Registration:** +```javascript +// In module.js Hooks.once('init') +game.settings.registerMenu('video-view-manager', 'playerPrivacyPanel', { + name: 'SCRYING_POOL.Settings.PlayerPrivacyPanel', + label: 'SCRYING_POOL.Settings.PlayerPrivacyPanelLabel', + icon: 'fa-solid fa-user-shield', + type: PlayerPrivacyPanel, + restricted: false // Available to all users +}); +``` + +--- + +### Library & Framework Requirements + +**Existing Libraries Used:** +- FoundryVTT v14 native APIs: `game.user.setFlag`, `game.user.getFlag`, `game.users.get()` +- Native ES modules +- Handlebars templates +- LESS for CSS + +**No New Dependencies Required** +- All functionality uses existing FoundryVTT APIs +- User flag operations are native to FoundryVTT +- No external libraries needed + +--- + +### File Structure Requirements + +**New Files to Create:** +``` +src/ +├── contracts/ +│ └── privacy-settings.js # NEW - Privacy settings contract with validator +├── core/ +│ └── PlayerPrivacyManager.js # NEW - Core privacy settings logic +├── ui/ +│ └── player/ +│ └── PlayerPrivacyPanel.js # NEW - Privacy panel UI component + templates/ +└── player-privacy-panel.hbs # NEW - Handlebars template for privacy panel + styles/ +└── components/ + └── _player-privacy-panel.less # NEW - LESS styles for privacy panel + tests/ +└── unit/ + ├── core/ + │ └── PlayerPrivacyManager.test.js # NEW - Unit tests for manager + └── contracts/ + └── privacy-settings.test.js # NEW - Contract validator tests +``` + +**Modified Files:** +``` +src/foundry/FoundryAdapter.js # Add user flag methods +src/ui/gm/DirectorsBoard.js # Pass privacy manager to cards +src/ui/shared/ParticipantCard.js # Add Reaction Cam badge display + templates/participant-card.hbs # Add badge to template +module.js # Register privacy panel settings menu +lang/en.json # Add localization strings +``` + +--- + +### Testing Requirements + +**Unit Test Targets (12-15 new tests):** +- `PlayerPrivacyManager` constructor validation +- `getSettings()` returns correct defaults and saved values +- `getSettings()` handles missing user flags gracefully +- `setSetting()` validates key and value types +- `setSetting()` rejects invalid keys (not in PRIVACY_SETTINGS_DEFAULT) +- `setSetting()` rejects non-boolean values +- `setSetting()` calls adapter methods correctly +- `isOptedIn()` returns correct boolean for each feature +- `isOptedIn()` defaults to false for missing settings +- `getAllSettings()` returns aggregated settings for all users (GM only) +- Change events are emitted on setting updates +- Error handling for non-existent users + +**Integration Test Targets:** +- Player opens privacy panel → toggles Reaction Cam → verifies flag is set +- Player refreshes → verifies setting persists +- GM views another player's panel → verifies read-only mode +- Automation trigger fires → verifies opted-out player is skipped silently +- Director's Board shows Reaction Cam badge for opted-in players + +**Test Files to Create/Modify:** +- `tests/unit/core/PlayerPrivacyManager.test.js` - NEW +- `tests/unit/contracts/privacy-settings.test.js` - NEW +- Update `tests/unit/foundry/FoundryAdapter.test.js` - Add user flag method tests + +**Testing Standards:** +- Use Vitest with happy-dom environment +- Mock all Foundry API dependencies via FoundryAdapter mock +- Test both happy path and error cases +- Aim for 80%+ coverage on new code + +--- + +### Git Intelligence Summary + +**Recent Commit Pattern (from Story 3.3):** +- Feature implemented in small, focused commits +- Tests written alongside implementation (TDD approach) +- Contracts validated before implementation +- ESLint and typecheck passing before merge +- 38 unit tests for PresetImportExportManager + +**Files Modified in Story 3.3:** +- Added: `src/core/PresetImportExportManager.js`, `src/ui/gm/PresetExportDialog.js`, `src/ui/gm/PresetImportDialog.js` +- Added: `templates/preset-export.hbs`, `templates/preset-import.hbs` +- Added: `styles/components/_preset-import-export.less` +- Modified: `src/ui/gm/DirectorsBoard.js`, `module.js` +- Modified: `lang/en.json`, `styles/scrying-pool.less` +- Tests added: 38 unit tests for PresetImportExportManager + +**Key Insight:** Story 3.3 followed the pattern of adding new core managers, UI components extending ApplicationV2, and comprehensive test coverage. Story 4.1 should follow the same pattern. + +--- + +### Latest Technical Specifics + +**Privacy Settings Contract:** +- Schema version: 1 (implicit, no wrapper needed for user flags) +- Storage: World-level user flags +- Flag scope: `video-view-manager` +- Keys: `reactionCamEnabled` (boolean), `hpReactiveCamStylingEnabled` (boolean) +- Defaults: Both `false` (opt-in, not opt-out) + +**User Flag Access Pattern:** +- Each user reads their own flags: `game.user.getFlag(scope, key)` +- GM can read other users' flags: `game.users.get(userId)?.getFlag(scope, key)` +- Users can only write their own flags: `game.user.setFlag(scope, key, value)` +- GM can write other users' flags: `game.users.get(userId)?.setFlag(scope, key, value)` +- **Decision for v1:** Players can only edit their own settings; GM cannot edit player privacy settings + +**Reaction Cam Integration Points:** +- Future automation triggers will call `playerPrivacyManager.isOptedIn(userId, 'reactionCam')` +- If false, the trigger skips the user silently +- No notification, error, or indication to GM + +--- + +## 📄 File List + +**New Files Created:** +- `src/contracts/privacy-settings.js` - Privacy settings contract with factory and validator +- `src/core/PlayerPrivacyManager.js` - Core privacy settings logic +- `src/ui/player/PlayerPrivacyPanel.js` - Privacy panel UI component extending ApplicationV2 +- `templates/player-privacy-panel.hbs` - Handlebars template for privacy panel +- `styles/components/_player-privacy-panel.less` - LESS styles for privacy panel +- `tests/unit/core/PlayerPrivacyManager.test.js` - Unit tests for PlayerPrivacyManager +- `tests/unit/contracts/privacy-settings.test.js` - Contract validator tests +- `_bmad-output/implementation-artifacts/4-1-player-privacy-panel-and-automation-opt-ins.md` - This story file + +**Modified Files:** +- `src/foundry/FoundryAdapter.js` - Added user flag access methods +- `src/ui/gm/DirectorsBoard.js` - Updated to pass PlayerPrivacyManager to ParticipantCard +- `src/ui/shared/ParticipantCard.js` - Added Reaction Cam badge display +- `templates/participant-card.hbs` - Added badge element to template +- `module.js` - Registered Player Privacy Panel in settings menu +- `lang/en.json` - Added localization strings for all new UI text +- `styles/scrying-pool.less` - Added import for _player-privacy-panel.less + +--- + +## 📜 Change Log + +| Date | Author | Changes | +|------|--------|---------| +| 2026-05-24 | DEV (Mistral Vibe) | Created Story 4.1: Player Privacy Panel & Automation Opt-ins | +| 2026-05-24 | DEV (Mistral Vibe) | Defined 8 acceptance criteria from FR-23, FR-24, FR-25 | +| 2026-05-24 | DEV (Mistral Vibe) | Created PlayerPrivacyManager design with DI pattern | +| 2026-05-24 | DEV (Mistral Vibe) | Designed PlayerPrivacyPanel UI component | +| 2026-05-24 | DEV (Mistral Vibe) | Added FoundryAdapter user flag methods | +| 2026-05-24 | DEV (Mistral Vibe) | Integrated Reaction Cam badge into Director's Board | +| 2026-05-24 | DEV (Mistral Vibe) | Added all localization strings | + +--- + +## 💻 Dev Agent Record + +### Debug Log + +*To be populated during implementation* + +### Completion Notes + +*To be populated after implementation* + +--- + +## 📌 Dev Agent Notes + +### What the Developer MUST Know + +1. **User flags vs Client settings vs World settings** + - Privacy settings use **user flags** (`game.user.setFlag('video-view-manager', key, value)`) + - User flags are world-persistent and tied to the user's identity in that world + - Players can only edit their own user flags + - GM can read (but not edit) other players' user flags + +2. **Opt-in, not opt-out** + - Both Reaction Cam and HP-Reactive Cam Styling default to **OFF** + - Players must explicitly enable these features + - Automation must silently skip opted-out players + +3. **Silent skip is mandatory** + - When a player opts out, automation must skip them with NO notification, NO error, NO console log + - This is a privacy requirement - players should not be "called out" for opting out + +4. **GM visibility vs control** + - GM can **view** all players' privacy settings (read-only) + - GM cannot **edit** players' privacy settings + - Director's Board shows "Reaction Cam: Enabled" badge for opted-in players + +5. **No socket broadcasting for privacy settings** + - Privacy settings are client-local (each client reads their own user's flags) + - No need to broadcast changes - each user's client will read their own settings + - GM view queries other users' flags directly + +6. **Follow established patterns** + - Use the same ApplicationV2 + Handlebars + LESS pattern as other UI components + - Use the same contract validation pattern as `scene-preset.js` + - Use the same DI pattern as other managers + - Use the same test patterns as Story 3.3 + +7. **Architecture decision: OQ-GDPR resolved** + - Use **user flags** for v1.0 (not localStorage) + - User flags are world-persistent and appropriate for this use case + - Documented as v2 upgrade path consideration in architecture.md + +8. **Import boundaries** + - `PlayerPrivacyManager` (core) can only import from contracts/utils + - `PlayerPrivacyPanel` (ui/player) can import from core, foundry, contracts + - FoundryAdapter extensions are in the foundry/ layer + +### Implementation Order Recommendation + +1. **Start with Contract and Manager** + - Create `src/contracts/privacy-settings.js` first (foundation) + - Create `src/core/PlayerPrivacyManager.js` with tests + - Extend `FoundryAdapter` with user flag methods + - This core logic is testable without UI + +2. **Create UI Component** + - Create `PlayerPrivacyPanel` class + - Create template and LESS styles + - Integrate with PlayerPrivacyManager + +3. **Integrate with Existing UI** + - Update Director's Board and ParticipantCard for badge display + - Register settings menu in module.js + +4. **Add Localization** + - Add all strings to lang/en.json + +5. **Write Tests Throughout** + - Follow TDD approach + - Aim for 12-15 tests for PlayerPrivacyManager + - Add contract validator tests + +### Critical Path Warnings + +- **Don't block on UI** - Core logic in `PlayerPrivacyManager` can be developed and tested independently +- **Silent skip must be absolute** - No console.log, no ui.notifications, no errors when skipping opted-out players +- **User flag access is async** - But for user flags, it's synchronous in FoundryVTT, so no promises needed +- **GM vs Player permissions** - Enforce that players can only edit their own settings; GM can only read others' +- **Read-only mode** - When viewing another player's panel, all controls must be disabled, not hidden + +### Files to Read Before Starting + +**MUST READ (in order):** +1. `src/contracts/scene-preset.js` - Understand the contract pattern (typedef + factory + validator) +2. `src/core/ScenePresetManager.js` - Understand manager pattern with DI and event emission +3. `src/foundry/FoundryAdapter.js` - Understand adapter pattern +4. `src/ui/gm/DirectorsBoard.js` - Understand ApplicationV2 pattern +5. `src/ui/gm/ConfirmationBar.js` - Example of ApplicationV2 dialog pattern +6. `src/ui/shared/ParticipantCard.js` - Understand card component pattern +7. `module.js` - Module initialization pattern + +**SHOULD READ:** +- `architecture.md` - Section on GDPR/consent boundary (OQ-GDPR) +- `epics.md` - FR-23, FR-24, FR-25 requirements +- `tests/unit/core/ScenePresetManager.test.js` - Testing patterns +- `lang/en.json` - Localization string format + +--- + +## ✅ Story Completion Checklist + +**Ultimate context engine analysis completed - comprehensive developer guide created** + +- [x] Epic 4 context analyzed +- [x] Story 4.1 requirements extracted from epics.md (FR-23, FR-24, FR-25) +- [x] Previous epic intelligence gathered (Epic 1-3 patterns) +- [x] Architecture compliance verified (import boundaries, DI, etc.) +- [x] Technical requirements documented (user flags, FoundryAdapter extension) +- [x] File structure planned +- [x] Testing requirements defined +- [x] Edge cases identified (silent skip, read-only mode, defaults) +- [x] Developer guardrails established +- [x] Cross-epic dependencies mapped +- [x] OQ-GDPR decision documented (user flags for v1.0) + +**Status:** ready-for-dev + +--- + +## 🎯 Next Steps + +1. **Review** this comprehensive story file in `4-1-player-privacy-panel-and-automation-opt-ins.md` +2. **Update sprint-status.yaml** to move story from `backlog` to `ready-for-dev` +3. **Run** `bmad-dev-story` workflow for optimized implementation +4. **Run** `code-review` when complete (auto-marks done) +5. **Optional:** If Test Architect module installed, run test automation after implementation + +--- + +## 📚 Project Context Reference + +**Project Name:** video-view-manager (Scrying Pool) +**Project Type:** FoundryVTT v14 Module +**Module ID:** video-view-manager + +**Planning Artifacts:** +- PRD: `_bmad-output/planning-artifacts/prds/prd-video-view-manager-2026-05-19/prd.md` +- Architecture: `_bmad-output/planning-artifacts/architecture.md` +- Epics: `_bmad-output/planning-artifacts/epics.md` +- UX Design: `_bmad-output/planning-artifacts/ux-design-specification.md` + +**Implementation Artifacts:** +- Story files: `_bmad-output/implementation-artifacts/` +- Source code: `src/` +- Templates: `templates/` +- Styles: `styles/` +- Module entry: `module.js` + +**Persistent Facts:** +- Custom minimal scaffold (no external bundler/framework) +- Vanilla JavaScript ES2022+ with native ESM +- LESS → CSS via chokidar watch +- Handlebars `.hbs` templates +- No external UI libraries +- No socketlib +- Dependency injection for testability +- ESLint with `jsdoc/require-jsdoc` on exported symbols +- Vitest with happy-dom for unit testing + +--- + +*This story file was created using the BMad Method Ultimate Context Engine. The developer now has everything needed for flawless implementation.* diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index e3bbefb..0a0b856 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -35,7 +35,7 @@ # - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) generated: "2026-05-21T01:00:00+02:00" -last_updated: "2026-05-24T20:30:00+02:00" +last_updated: "2026-05-24T21:00:00+02:00" project: video-view-manager project_key: NOKEY tracking_system: file-system @@ -67,7 +67,7 @@ development_status: epic-3-retrospective: done # Epic 4: Player Privacy Panel - epic-4: backlog - 4-1-player-privacy-panel-and-automation-opt-ins: backlog + epic-4: in-progress + 4-1-player-privacy-panel-and-automation-opt-ins: ready-for-dev 4-2-custom-portrait-fallback: backlog epic-4-retrospective: optional