- Created comprehensive story file with 8 acceptance criteria from FR-23, FR-24, FR-25 - Documented PlayerPrivacyManager core logic with DI pattern - Designed PlayerPrivacyPanel UI component extending ApplicationV2 - Added FoundryAdapter user flag access methods specification - Integrated Reaction Cam badge into Director's Board design - Defined all localization strings - Mapped file structure (new and modified files) - Specified testing requirements (12-15 new tests) - Updated sprint-status.yaml: 4-1 from backlog to ready-for-dev, epic-4 to in-progress - Resolved OQ-GDPR: using user flags for v1.0 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
34 KiB
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 lintexits 0 (ESLint import boundaries enforced)npm run typecheckexits 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.jswith opt-in flag contracts- Define canonical shape for privacy settings:
{ reactionCamEnabled: boolean, hpReactiveCamStylingEnabled: boolean } - Export
PRIVACY_SETTINGS_DEFAULTwith both flags defaulting tofalse - Export
isValidPrivacySettings(data)validator - Export
createPrivacySettings(overrides)factory
- Define canonical shape for privacy settings:
- 1.2: Write TDD red tests for PlayerPrivacyManager methods
- 1.3: Create
PlayerPrivacyManagerclass 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
PlayerPrivacyPanelclass extendingApplicationV2- Constructor receives
adapter,playerPrivacyManager, andtargetUserId - Registers module settings UI component via
game.settings.registerMenu - Opens as a dialog/modal window
- Constructor receives
- 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
- Calls
- 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
- Disables all toggle controls when
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
- Wraps
- 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
- Wraps
- 3.3: Add
getFlagModule(userId, key)convenience method- Calls
getFlag(userId, 'video-view-manager', key) - Used for privacy settings access
- Calls
- 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.usersaccess 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
ParticipantCardto 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
DirectorsBoardto inject PlayerPrivacyManager- Pass
playerPrivacyManagerto ParticipantCard components - Refresh card display when privacy settings change
- Pass
- 4.3: Update
participant-card.hbstemplate- 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"
- Use
- 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_POOLnamespace - 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
localStoragefor 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
- Architecture Recommendation: Use
Previous Story Intelligence (Story 3.3)
Learnings from Story 3.3 (Preset Import & Export):
- Contract validation is critical - Exported
isValidScenePreset()and factory functions caught issues early - File operations use native browser APIs - No external libraries needed for download/upload
- UI follows FoundryVTT ApplicationV2 patterns - Dialogs extend ApplicationV2, use Handlebars templates
- Manager classes handle core logic -
PresetImportExportManagerseparated concerns from UI - Import boundaries strictly enforced - Core only imports from contracts/utils
- 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 validationsrc/ui/gm/PresetExportDialog.js/PresetImportDialog.js- ApplicationV2 dialogstemplates/preset-export.hbs/preset-import.hbs- Handlebars templatesstyles/components/_preset-import-export.less- LESS stylessrc/contracts/scene-preset.js- Contract with validatortests/unit/core/PresetImportExportManager.test.js- Comprehensive tests
Problems Encountered & Solutions:
- Dialog lifecycle management → Used
_onRenderand_onClosemethods - 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
.hbstemplates (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, zerogame.*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 fromsrc/contracts/src/core/- May only import fromsrc/contracts/,src/utils/src/foundry/- May import from anywhere (adapter layer)src/ui/- May import fromsrc/core/,src/foundry/,src/contracts/
This Story's Import Plan:
PlayerPrivacyManager(src/core/) → imports fromsrc/contracts/privacy-settings.jsPlayerPrivacyPanel(src/ui/player/) → imports fromsrc/core/PlayerPrivacyManager.js,src/foundry/FoundryAdapter.jsFoundryAdapter(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:
// 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:
class PlayerPrivacyManager {
constructor(adapter) {}
getSettings(userId) { /* returns { reactionCamEnabled, hpReactiveCamStylingEnabled } */ }
setSetting(userId, key, value) { /* validates and saves */ }
isOptedIn(userId, feature) { /* returns boolean */ }
getAllSettings() { /* returns Map<userId, settings> for GM */ }
onChange(callback) { /* subscribe to setting changes */ }
}
3. Silent Skip Pattern for Automation:
// 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:
// In ParticipantCard render
if (playerPrivacyManager.isOptedIn(this.userId, 'reactionCam')) {
card.querySelector('.sp-reaction-cam-badge').classList.remove('hidden');
}
5. Settings Menu Registration:
// 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):
PlayerPrivacyManagerconstructor validationgetSettings()returns correct defaults and saved valuesgetSettings()handles missing user flags gracefullysetSetting()validates key and value typessetSetting()rejects invalid keys (not in PRIVACY_SETTINGS_DEFAULT)setSetting()rejects non-boolean valuessetSetting()calls adapter methods correctlyisOptedIn()returns correct boolean for each featureisOptedIn()defaults to false for missing settingsgetAllSettings()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- NEWtests/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 validatorsrc/core/PlayerPrivacyManager.js- Core privacy settings logicsrc/ui/player/PlayerPrivacyPanel.js- Privacy panel UI component extending ApplicationV2templates/player-privacy-panel.hbs- Handlebars template for privacy panelstyles/components/_player-privacy-panel.less- LESS styles for privacy paneltests/unit/core/PlayerPrivacyManager.test.js- Unit tests for PlayerPrivacyManagertests/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 methodssrc/ui/gm/DirectorsBoard.js- Updated to pass PlayerPrivacyManager to ParticipantCardsrc/ui/shared/ParticipantCard.js- Added Reaction Cam badge displaytemplates/participant-card.hbs- Added badge element to templatemodule.js- Registered Player Privacy Panel in settings menulang/en.json- Added localization strings for all new UI textstyles/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
-
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
- Privacy settings use user flags (
-
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
-
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
-
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
-
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
-
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
-
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
-
Import boundaries
PlayerPrivacyManager(core) can only import from contracts/utilsPlayerPrivacyPanel(ui/player) can import from core, foundry, contracts- FoundryAdapter extensions are in the foundry/ layer
Implementation Order Recommendation
-
Start with Contract and Manager
- Create
src/contracts/privacy-settings.jsfirst (foundation) - Create
src/core/PlayerPrivacyManager.jswith tests - Extend
FoundryAdapterwith user flag methods - This core logic is testable without UI
- Create
-
Create UI Component
- Create
PlayerPrivacyPanelclass - Create template and LESS styles
- Integrate with PlayerPrivacyManager
- Create
-
Integrate with Existing UI
- Update Director's Board and ParticipantCard for badge display
- Register settings menu in module.js
-
Add Localization
- Add all strings to lang/en.json
-
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
PlayerPrivacyManagercan 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):
src/contracts/scene-preset.js- Understand the contract pattern (typedef + factory + validator)src/core/ScenePresetManager.js- Understand manager pattern with DI and event emissionsrc/foundry/FoundryAdapter.js- Understand adapter patternsrc/ui/gm/DirectorsBoard.js- Understand ApplicationV2 patternsrc/ui/gm/ConfirmationBar.js- Example of ApplicationV2 dialog patternsrc/ui/shared/ParticipantCard.js- Understand card component patternmodule.js- Module initialization pattern
SHOULD READ:
architecture.md- Section on GDPR/consent boundary (OQ-GDPR)epics.md- FR-23, FR-24, FR-25 requirementstests/unit/core/ScenePresetManager.test.js- Testing patternslang/en.json- Localization string format
✅ Story Completion Checklist
Ultimate context engine analysis completed - comprehensive developer guide created
- Epic 4 context analyzed
- Story 4.1 requirements extracted from epics.md (FR-23, FR-24, FR-25)
- Previous epic intelligence gathered (Epic 1-3 patterns)
- Architecture compliance verified (import boundaries, DI, etc.)
- Technical requirements documented (user flags, FoundryAdapter extension)
- File structure planned
- Testing requirements defined
- Edge cases identified (silent skip, read-only mode, defaults)
- Developer guardrails established
- Cross-epic dependencies mapped
- OQ-GDPR decision documented (user flags for v1.0)
Status: ready-for-dev
🎯 Next Steps
- Review this comprehensive story file in
4-1-player-privacy-panel-and-automation-opt-ins.md - Update sprint-status.yaml to move story from
backlogtoready-for-dev - Run
bmad-dev-storyworkflow for optimized implementation - Run
code-reviewwhen complete (auto-marks done) - 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
.hbstemplates - No external UI libraries
- No socketlib
- Dependency injection for testability
- ESLint with
jsdoc/require-jsdocon 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.