34 KiB
Story 3.2: Scene Auto-Apply & ConfirmationBar
Status: done
Epic: 3 - Scene-Aware Camera Automation (Scene Presets)
Story Key: 3-2-scene-auto-apply-and-confirmationbar
Created: 2026-05-23
Last Updated: 2026-05-24
Code Review: done (2026-05-24)
Story Header
| Field | Value |
|---|---|
| Epic | 3 - Scene-Aware Camera Automation (Scene Presets) |
| Story ID | 3.2 |
| Story Key | 3-2-scene-auto-apply-and-confirmationbar |
| Title | Scene Auto-Apply & ConfirmationBar |
| Status | done |
| Priority | High |
| Assigned Agent | DEV (Amelia) |
| Created | 2026-05-23 |
📋 Story Requirements
User Story
As a GM, I want to have Scene Presets automatically apply when I activate a Scene, with immediate strip-local feedback and a one-click Undo, So that camera layouts change seamlessly with scene transitions without manual intervention.
Persona Alignment
- Primary: Marcus (Veteran GM) - Needs seamless transitions between pre-configured scenes during play
- Primary: Jake (Streamer) - Requires professional, automated scene transitions for broadcast production
- Secondary: All GMs - Reduces cognitive load by eliminating repetitive manual setup
Acceptance Criteria (BDD Format)
AC-1: Auto-Apply on Scene Activation
Given a Scene has a preset association configured
When the GM activates that Scene (triggering updateScene hook)
Then the associated preset applies after the configured pre-delay (0-5000ms)
And all clients receive "Scene changed: camera layout updated" via ui.notifications
AC-2: Configurable Pre-Delay
Given a Scene has a pre-delay of N ms configured
When that Scene activates
Then the preset applies exactly N ms after the updateScene hook fires
AC-3: ConfirmationBar Appearance
Given auto-apply fires for a Scene
When the Visibility Matrix update is broadcast
Then the ConfirmationBar appears in StripOverlayLayer at position: absolute; bottom: 0 showing "Preset applied — N hidden, N visible"
And an "Undo" button is present and primary affordance
AC-4: One-Click Undo
Given the "Undo" button is clicked When the click is processed Then the Visibility Matrix immediately reverts to the state before the preset was applied And all clients receive the reverted state
AC-5: ConfirmationBar Auto-Dismiss
Given the ConfirmationBar is visible and idle
When 8 seconds elapse (or 4 seconds if ≥2 presets applied within 60 seconds)
Then the bar dismisses via opacity transition only (never height or max-height animation)
AC-6: Instant-Replace Rule
Given a second ConfirmationBar would appear while one is already visible
When the second is triggered
Then it instantly replaces the first with zero crossfade (instant-replace rule)
AC-7: Per-Scene Disable
Given auto-apply is disabled for a specific Scene When that Scene is activated Then no preset applies and no automation notification fires And the Director's Board manual override remains fully functional
AC-8: Global Disable
Given auto-apply is disabled globally in module settings When any Scene is activated Then no preset auto-applies regardless of Scene-level associations
AC-9: Partial-Fail Amber Variant
Given the partial-fail case (some participants unreachable)
When the ConfirmationBar renders
Then it uses the amber variant: "Preset applied — N hidden, N visible (some updates pending)"
Functional Requirements Covered
- FR-17: Scene Preset auto-applies on FoundryVTT Scene activation via
updateScenehook - FR-18: Scene Preset auto-apply can be disabled per-scene or globally via module settings
Success Criteria
- All 9 acceptance criteria pass manual testing
- All unit tests pass (target: +25-30 new tests for ConfirmationBar + ScenePresetManager auto-apply)
npm run lintexits 0 (ESLint import boundaries enforced)npm run typecheckexits 0 (strict JSDoc compliance)- Code review passes with no critical findings
- Integration test: Scene activation → preset apply → ConfirmationBar → Undo flow verified end-to-end
📝 Tasks / Subtasks
Task 1: Extend src/core/ScenePresetManager.js with Auto-Apply Logic
Files: src/core/ScenePresetManager.js, tests/unit/core/ScenePresetManager.test.js
Subtasks:
- 1.1: Write TDD red tests for auto-apply methods — onSceneActivate, applyPreset with options, configureAutoApply
- 1.2: Extend constructor to accept
visibilityManagerandsocketHandlerparameters - 1.3: Implement
onSceneActivate(scene)— checks global enable, scene config, pre-delay, then applies preset - 1.4: Implement
applyPreset(presetName, options)— applies preset matrix, emits socket message, returns result - 1.5: Implement
configureAutoApply(scene, config)— updates scene flag with auto-apply settings - 1.6: Implement
_getAutoApplyConfig(scene)— reads and validates auto-apply config from scene flag - 1.7: Implement
_applyWithDelay(scene, presetName, delayMs)— sets timeout, clears on scene change - 1.8: Implement migration handler for missing autoApply field (defaults to disabled)
- 1.9: Update existing tests to pass new constructor parameters
- 1.10: Green all ScenePresetManager tests including new auto-apply tests
Acceptance Criteria: AC-1, AC-2, AC-7, AC-8
Task 2: Create src/ui/gm/ConfirmationBar.js
Files: src/ui/gm/ConfirmationBar.js, tests/unit/ui/gm/ConfirmationBar.test.js, templates/confirmation-bar.hbs, styles/components/_confirmation-bar.less
Subtasks:
- 2.1: Write TDD red tests for ConfirmationBar — show, hide, undo, auto-dismiss, instant-replace
- 2.2: Implement
ConfirmationBarclass with constructor(adapter, visibilityManager, socketHandler, stripOverlayLayer) - 2.3: Implement
init()— registers hook listener forscrying-pool:presetApplied - 2.4: Implement
teardown()— unregisters hooks, clears timers - 2.5: Implement
show(payload)— renders bar, captures previous matrix, starts timer - 2.6: Implement
hide()— removes bar, clears timer - 2.7: Implement
_onUndo()— reverts to previous matrix via visibilityManager - 2.8: Implement
_startDismissTimer()— 8000ms default, 4000ms if recent activity - 2.9: Implement
_onPresetApplied(payload)— handler for hook event, determines variant (amber for partial) - 2.10: Implement
_onNewPresetAppliedWhileVisible()— instant-replace logic, zero crossfade - 2.11: Create
confirmation-bar.hbstemplate with message, undo button, variants - 2.12: Create
_confirmation-bar.lesswith styles, animations, reduced-motion support - 2.13: Green all ConfirmationBar tests
Acceptance Criteria: AC-3, AC-4, AC-5, AC-6, AC-9
Task 3: Create src/ui/gm/ScenePresetPanel.js
Files: src/ui/gm/ScenePresetPanel.js, tests/unit/ui/gm/ScenePresetPanel.test.js, templates/scene-preset-panel.hbs, styles/components/_scene-preset-panel.less
Subtasks:
- 3.1: Write TDD red tests for ScenePresetPanel — toggle, preset selection, delay config
- 3.2: Implement
ScenePresetPanelclass for per-scene auto-apply configuration - 3.3: Implement
async _prepareContext(scene)— returns { enabled, presetName, preDelay, presets } - 3.4: Implement
_onToggleAutoApply(enabled)— updates scene flag via configureAutoApply - 3.5: Implement
_onPresetSelected(presetName)— updates config for this scene - 3.6: Implement
_onDelayChanged(delayMs)— validates 0-5000, updates config - 3.7: Implement accessibility: keyboard nav, focus trap, ARIA labels
- 3.8: Create
scene-preset-panel.hbstemplate with toggle, dropdown, slider - 3.9: Create
_scene-preset-panel.lesswith styles matching DirectorsBoard theme - 3.10: Green all ScenePresetPanel tests
Acceptance Criteria: AC-7, AC-8
Task 4: Extend module.js with Scene Hook Registration
Files: module.js
Subtasks:
- 4.1: Import ScenePresetManager, ConfirmationBar, ScenePresetPanel
- 4.2: Construct ScenePresetManager with stateStore, adapter, visibilityManager, socketHandler
- 4.3: Register
updateScenehook viaadapter.hooks.on('updateScene', scenePresetManager.onSceneActivate) - 4.4: Construct ConfirmationBar with adapter, visibilityManager, socketHandler, stripOverlayLayer
- 4.5: Call
confirmationBar.init()after construction - 4.6: For GM users, integrate ScenePresetPanel into DirectorsBoard
- 4.7: Verify all injection order dependencies are satisfied
Acceptance Criteria: AC-1, AC-2
Task 5: Extend src/ui/shared/StripOverlayLayer.js for ConfirmationBar
Files: src/ui/shared/StripOverlayLayer.js, styles/components/_strip-overlay-layer.less, tests/unit/ui/shared/StripOverlayLayer.test.js
Subtasks:
- 5.1: Create
StripOverlayLayerclass (was missing from Story 1.5) with constructor(adapter) - 5.2: Implement
init(),get element(),render(),remove(),clearAll(),teardown() - 5.3: Create overlay DOM element with styles:
position: absolute; inset: 0; pointer-events: none; overflow: visible - 5.4: Implement overlay tracking via Map for replacement support
- 5.5: Create
_strip-overlay-layer.lesswith scoped styles - 5.6: Verify pointer-events handling allows ConfirmationBar interaction
Acceptance Criteria: AC-3
Note: StripOverlayLayer was missing from Story 1.5 deliverables. Created complete implementation.
Task 6: Extend src/ui/gm/DirectorsBoard.js with ScenePresetPanel
Files: src/ui/gm/DirectorsBoard.js, tests/unit/ui/gm/DirectorsBoard.test.js
Subtasks:
- 6.1: Import ScenePresetPanel
- 6.2: Add
_presetPanelfield to constructor - 6.3: Extend
_prepareContext()to include auto-apply status for current scene - 6.4: Update template to include ScenePresetPanel as collapsible drawer/tab
- 6.5: Implement toggle handler for auto-apply panel visibility
- 6.6: Update
_onSceneChanged()or similar to refresh panel with new scene data - 6.7: Update tests for new panel integration
Acceptance Criteria: AC-7, AC-8
Task 7: Update Contracts and Fixtures
Files: src/contracts/socket-message.js, tests/fixtures/scene-preset.js
Subtasks:
- 7.1: Verify
PRESET_APPLYandPRESET_APPLIEDconstants exist in socket-message.js - 7.2: Extend payload schema validation for auto-apply fields (sceneId, autoApplied)
- 7.3: Add new fixtures: SCENE_FLAG_WITH_AUTO_APPLY, SCENE_FLAG_WITHOUT_AUTO_APPLY, SCENE_FLAG_DISABLED_AUTO_APPLY
- 7.4: Add fixture for partial-fail scenario
- 7.5: Verify all fixtures are Object.freeze'd
Acceptance Criteria: All ACs (payload validation)
🎯 Developer Context Section
Epic Context
Epic 3: Scene-Aware Camera Automation (Scene Presets) completes the Level 3 Progressive Enhancement:
- Story 3.1 (PREVIOUS - ready-for-dev): ScenePresetManager, save/load UI, Scene flag storage, socket broadcast
- Story 3.2 (THIS STORY): Auto-apply on Scene activation, ConfirmationBar with Undo, per-scene/global disable toggles
- Story 3.3 (NEXT - backlog): Preset import/export as JSON, merge/replace logic
This story delivers the automation magic - the ability for GMs to set up camera layouts once and have them apply automatically during sessions. The ConfirmationBar provides immediate, strip-local feedback with a safety net (Undo) for when things don't go as expected.
Cross-Epic Dependencies
| Dependency | Source | Status | Used In This Story |
|---|---|---|---|
| ScenePresetManager | Story 3.1 | ready-for-dev | ✅ Extended with auto-apply logic |
| Scene flag storage | Story 3.1 | ready-for-dev | ✅ Read preset associations |
| StateStore.setMatrix() | Story 1.4 | done | ✅ Apply preset matrix |
| SocketHandler broadcast | Story 1.3 | done | ✅ Broadcast preset apply |
| NotificationBus | Story 2.1 | done | ✅ Optional fallback notification |
| DirectorsBoard | Story 2.2 | done | ✅ Manual override always available |
| ConfirmationBar UX patterns | UX-DR12 | Specified | ✅ Full implementation |
Previous Story Intelligence (Story 3.1)
Critical Learnings from 3-1-save-and-load-scene-presets:
- Scene flag schema is frozen:
{ _version: 1, presets: { [name]: ScenePreset } }- DO NOT modify this structure - ScenePreset structure is canonical:
{ _version: 1, name, matrix, createdAt, updatedAt } - Socket events already defined in contracts:
scrying-pool.preset.applyandscrying-pool.preset.applied - Import boundary for ScenePresetManager:
src/core/ScenePresetManager.jsmay ONLY import fromsrc/contracts/andsrc/utils/ - World settings for global config: Use
scrying-pool.autoApplyEnabled(boolean, default: true) - Test pattern: Fake timers for pre-delay testing; frozen fixtures for preset structures
Files Created in 3.1 (DO NOT RECREATE):
src/core/ScenePresetManager.js- Extend this, don't replace itsrc/ui/gm/PresetSaveDialog.js- Already existssrc/ui/gm/PresetLoadDialog.js- Already existssrc/contracts/scene-preset.js- Already exists, has validators
Patterns Established in 3.1:
- Scene flag access via
adapter.scenes.current().getFlag()and.setFlag() - Matrix serialization/deserialization in ScenePresetManager
- Error handling: clear user-facing messages, no stack traces
Git Intelligence
Recent commits in Epic 3:
3-1-save-and-load-scene-presets.mdcreated 2026-05-23 09:51 - Full ScenePresetManager spec- Architecture established for Scene flag storage with versioning
- Socket contract for preset events pre-defined
Actionable Insights:
- The preset storage mechanism is solid and tested
- Socket infrastructure for preset apply is ready but not wired to Scene hooks
- Need to add
updateScenehook registration in module.js - ConfirmationBar is NEW - no existing implementation to build on
🏗️ Technical Requirements
Core Components to Create/Extend
| Component | File | Action | Purpose |
|---|---|---|---|
| ScenePresetManager | src/core/ScenePresetManager.js |
EXTEND | Add auto-apply logic, per-scene config |
| ConfirmationBar | src/ui/gm/ConfirmationBar.js |
NEW | Strip-local feedback with Undo |
| StripOverlayLayer | src/ui/shared/StripOverlayLayer.js |
EXTEND | Add ConfirmationBar container |
| ScenePresetPanel | src/ui/gm/ScenePresetPanel.js |
NEW | Per-scene auto-apply toggle UI |
Data Flow - Auto-Apply Sequence
Hooks.on('updateScene', scene)
↓
ScenePresetManager.onSceneActivate(scene)
↓ [Check: auto-apply enabled globally?]
↓ [Check: scene has preset association?]
↓ [Check: scene has auto-apply enabled?]
↓ [Wait: configured pre-delay (0-5000ms)]
↓
ScenePresetManager.applyPreset(presetName)
↓
VisibilityManager.applyMatrix(preset.matrix)
↓
StateStore.setMatrix(preset.matrix)
↓
SocketHandler.emit('scrying-pool.preset.apply', payload)
↓
[Broadcast to all clients]
↓
Hooks.callAll('scrying-pool:presetApplied', { presetName, sceneId, matrix })
↓
ConfirmationBar.show({ presetName, hiddenCount, visibleCount, partialFail })
↓
[8s or 4s timer starts]
↓ (User clicks Undo)
ConfirmationBar.onUndo() → StateStore.setMatrix(previousMatrix)
New/Extended Socket Messages
Already defined in src/contracts/socket-message.js (from Story 3.1):
PRESET_APPLY: "scrying-pool.preset.apply"- Intent (GM only)PRESET_APPLIED: "scrying-pool.preset.applied"- Authoritative echo
New for this story:
- Payload extension for
PRESET_APPLY: Add{ sceneId, preDelay, autoApplied: true } - New hook:
scrying-pool:presetAppliedfor ConfirmationBar subscription
World Settings (New)
| Setting Key | Type | Default | Scope | Description |
|---|---|---|---|---|
scrying-pool.autoApplyEnabled |
boolean | true | world | Global toggle for auto-apply feature |
scrying-pool.confirmationBarDuration |
number | 8000 | world | Default bar duration in ms |
scrying-pool.shortConfirmationBarDuration |
number | 4000 | world | Short duration when ≥2 presets in 60s |
Note: Per-scene auto-apply toggle stored in Scene flag alongside preset association.
Scene Flag Structure Extension
Existing (from Story 3.1):
{
_version: 1,
presets: { [name: string]: ScenePreset }
}
Extended for Story 3.2:
{
_version: 1,
presets: { [name: string]: ScenePreset },
autoApply: {
enabled: boolean, // Per-scene toggle
presetName: string, // Which preset to auto-apply
preDelay: number // 0-5000 ms delay
}
}
Migration: If autoApply field missing, defaults to { enabled: false, presetName: null, preDelay: 0 }
🏛️ Architecture Compliance
Import Boundary Rules (HARD - ESLint Enforced)
NEW FILES:
src/ui/gm/ConfirmationBar.js
→ may import: src/core/, src/contracts/, src/utils/ ONLY
❌ FORBIDDEN: src/foundry/, src/ui/gm/PresetSaveDialog.js, etc.
src/ui/gm/ScenePresetPanel.js
→ may import: src/core/, src/contracts/, src/utils/ ONLY
❌ FORBIDDEN: src/notifications/, direct game.* access
EXTENDED FILES:
src/core/ScenePresetManager.js (EXTEND from Story 3.1)
→ EXISTING: may import src/contracts/, src/utils/ ONLY
→ NEW: may ALSO import src/core/VisibilityManager.js (for applyMatrix)
❌ STILL FORBIDDEN: src/foundry/, src/ui/, direct game.*
src/ui/shared/StripOverlayLayer.js (EXTEND from Story 1.5)
→ EXISTING: may import src/core/, src/contracts/, src/utils/
→ NEW: may import src/ui/gm/ConfirmationBar.js
Constructor Pattern (Side-Effect-Free)
ConfirmationBar:
// ✅ CORRECT
export class ConfirmationBar {
constructor(adapter, visibilityManager, socketHandler) {
this._adapter = adapter;
this._visibilityManager = visibilityManager;
this._socketHandler = socketHandler;
this._previousMatrix = null;
}
init() {
// Lifecycle registration here, NOT in constructor
this._adapter.hooks.on('scrying-pool:presetApplied', (payload) => this._onPresetApplied(payload));
}
teardown() {
this._adapter.hooks.off('scrying-pool:presetApplied', this._onPresetApplied);
}
}
ScenePresetManager Extension:
// EXTEND existing class from Story 3.1
export class ScenePresetManager {
// Existing methods: save(), load(), delete(), list()
// NEW: Auto-apply methods
onSceneActivate(scene) { /* ... */ }
applyPreset(presetName, options = { autoApplied: false }) { /* ... */ }
configureAutoApply(scene, { enabled, presetName, preDelay }) { /* ... */ }
}
Dependency Injection
All new components follow FoundryAdapter pattern:
ConfirmationBarreceivesadaptervia constructorScenePresetPanelreceivesadaptervia constructor- NO direct
game.*access in any new file - All Foundry API calls go through
adapter.scenes,adapter.hooks, etc.
Hook Registration Order
Critical: Module.js wiring order for Story 3.2 components:
// In module.js, inside Hooks.once('ready', () => { ... })
// EXISTING (from previous stories) - order preserved:
const visibilityManager = new VisibilityManager(stateStore, adapter);
socketHandler.setReady(visibilityManager);
const notificationBus = new NotificationBus(adapter);
const roleRenderer = new RoleRenderer(visibilityManager, adapter);
const rosterStrip = new RosterStrip(visibilityManager, roleRenderer);
// NEW for Story 3.2:
const scenePresetManager = new ScenePresetManager(
stateStore,
adapter,
visibilityManager, // NEW: for applyMatrix
socketHandler // NEW: for preset broadcast
);
// Register updateScene hook for auto-apply
adapter.hooks.on('updateScene', (scene) => {
scenePresetManager.onSceneActivate(scene);
});
// NEW: StripOverlayLayer gets ConfirmationBar support
const stripOverlayLayer = new StripOverlayLayer(adapter);
const confirmationBar = new ConfirmationBar(
adapter,
visibilityManager,
socketHandler,
stripOverlayLayer
);
confirmationBar.init();
// If GM, register DirectorsBoard with extended preset panel
if (adapter.users.isGM()) {
const directorsBoard = new DirectorsBoard(visibilityManager, socketHandler, adapter);
// DirectorsBoard now includes ScenePresetPanel as embedded component
}
📁 File Structure Requirements
Files to CREATE
| File | Location | Purpose | AC Blocking |
|---|---|---|---|
ConfirmationBar.js |
src/ui/gm/ConfirmationBar.js |
Strip-local feedback component | ✅ |
ConfirmationBar.test.js |
tests/unit/ui/gm/ConfirmationBar.test.js |
Unit tests | ✅ |
ScenePresetPanel.js |
src/ui/gm/ScenePresetPanel.js |
Per-scene auto-apply config UI | ✅ |
ScenePresetPanel.test.js |
tests/unit/ui/gm/ScenePresetPanel.test.js |
Unit tests | ✅ |
_confirmation-bar.less |
styles/components/_confirmation-bar.less |
ConfirmationBar styles | ✅ |
confirmation-bar.hbs |
templates/confirmation-bar.hbs |
Handlebars template | ✅ |
Files to EXTEND
| File | Changes | From Story |
|---|---|---|
ScenePresetManager.js |
Add auto-apply methods | 3.1 |
module.js |
Wire updateScene hook, inject dependencies | Story 0 |
StripOverlayLayer.js |
Add ConfirmationBar rendering | 1.5 |
DirectorsBoard.js |
Integrate ScenePresetPanel | 2.2 |
File Boundaries
ConfirmationBar owns:
- Display logic for preset apply feedback
- Undo button click handler
- Auto-dismiss timer management
- Instant-replace logic for consecutive bar displays
ScenePresetManager owns (NEW):
- Auto-apply configuration per-scene
- Pre-delay timer management
- Scene activation detection
- Preset application trigger
ScenePresetPanel owns:
- Per-scene auto-apply toggle UI
- Pre-delay configuration (0-5000ms slider)
- Preset selection for auto-apply
🧪 Testing Requirements
Unit Test Coverage Targets
| Component | Test File | Coverage Target |
|---|---|---|
| ConfirmationBar | ConfirmationBar.test.js |
100% branch coverage |
| ScenePresetManager (new methods) | Extend ScenePresetManager.test.js |
+25 new tests |
| ScenePresetPanel | ScenePresetPanel.test.js |
100% statement coverage |
Test Scenarios (MUST INCLUDE)
ConfirmationBar:
// Fake timers required for duration testing
vi.useFakeTimers();
// Test 1: Shows on preset applied event
// Test 2: Undo clicks revert to previous matrix
// Test 3: Auto-dismisses after 8000ms
// Test 4: Auto-dismisses after 4000ms when ≥2 presets in 60s
// Test 5: Instant-replace when new event during visible bar
// Test 6: Shows amber variant on partial fail
// Test 7: Clears timer on manual dismiss
// Test 8: accessibility: focus trap, keyboard navigation
ScenePresetManager (auto-apply):
// Test 1: onSceneActivate does nothing when no preset association
// Test 2: onSceneActivate applies preset after pre-delay
// Test 3: onSceneActivate respects global disable
// Test 4: onSceneActivate respects per-scene disable
// Test 5: Pre-delay timer cleared on scene change
// Test 6: applyPreset with autoApplied=true emits correct socket event
// Test 7: configureAutoApply updates Scene flag correctly
// Test 8: Migration: missing autoApply field gets defaults
Integration Tests:
// Test 1: Full flow: Scene activation → preset apply → ConfirmationBar → Undo
// Test 2: Partial fail: Some participants offline → amber bar
// Test 3: Global disable → no auto-apply on any scene
// Test 4: Per-scene disable → no auto-apply on that scene only
Fixtures to Add/Update
New fixtures in tests/fixtures/scene-preset.js:
export const SCENE_FLAG_WITH_AUTO_APPLY = Object.freeze({
_version: 1,
presets: { /* ... */ },
autoApply: { enabled: true, presetName: 'combat', preDelay: 1000 }
});
export const SCENE_FLAG_WITHOUT_AUTO_APPLY = Object.freeze({
_version: 1,
presets: { /* ... */ }
// autoApply missing - should default to disabled
});
export const SCENE_FLAG_DISABLED_AUTO_APPLY = Object.freeze({
_version: 1,
presets: { /* ... */ },
autoApply: { enabled: false, presetName: null, preDelay: 0 }
});
🎨 UX Design Requirements
ConfirmationBar Specification (UX-DR12)
Location: StripOverlayLayer at position: absolute; bottom: 0
Visual:
- Background:
--sp-surface(semantic token) - Text:
--sp-text-primary - Border: 1px solid
--sp-border - Padding: 12px 16px
- Border-radius: 4px
- Box-shadow:
0 2px 8px rgba(0,0,0,0.3)
Content:
- Message: "Preset applied — N hidden, N visible"
- Undo button: Primary CTA, left side
- Duration indicator: Optional subtle progress bar
Variants:
- Default: Green accent for success
- Amber: Orange accent when partial fail ("some updates pending")
Animations:
- In: Slide up from bottom + fade (200ms ease-out)
- Out: Slide down to bottom + fade (200ms ease-in)
- CRITICAL: Only
opacitytransitions for height changes - NEVERheightormax-heightanimation - Gated under
@media (prefers-reduced-motion: no-preference)
Behavior:
- Auto-dismiss: 8000ms default, 4000ms if ≥2 presets applied within 60000ms window
- Instant-replace: New bar replaces existing with 0ms crossfade
- Click Undo: Reverts matrix, dismisses bar immediately
- Click outside: No dismiss (bar is in StripOverlayLayer, which has pointer-events: none on parent)
- Click on bar: No action (bar is informational, only Undo is interactive)
Accessibility:
role="status"on bar containeraria-live="polite"for message updatesaria-label="Preset [name] applied. Undo available."on bar- Undo button:
role="button",aria-label="Undo preset apply" - Focus: Undo button receives focus when bar appears
- Keyboard: Space/Enter on Undo button triggers revert
ScenePresetPanel Specification
Location: Embedded in DirectorsBoard as collapsible drawer/tab
Controls:
- Toggle: "Auto-apply preset on scene activation" (checkbox)
- Preset selector: Dropdown of available presets for this scene
- Pre-delay: Slider 0-5000ms with value display
- Global settings link: "Configure global auto-apply settings"
Accessibility:
- All interactive elements keyboard-navigable
- ARIA labels on all controls
- Focus trap within panel
🔒 Security & Performance Requirements
Security
- No data transmission: Scene flag data stays in FoundryVTT world
- Permission check: Only GM can configure auto-apply settings
- Validation: All Scene flag inputs validated before saving
- Sanitization: Preset names sanitized (no HTML, max length 100 chars)
Performance
- Pre-delay max: 5000ms - enforced at validation level
- Timer cleanup: All timers cleared on module teardown
- Debounce: If multiple scene activations in quick succession, only last one processed
- Memory: ConfirmationBar holds previous matrix reference only while visible
- No blocking: All operations async; no synchronous waits
📂 Project Structure Notes
Alignment with Unified Structure
All new files follow the established pattern:
src/ui/gm/- GM-only UI componentssrc/core/- Pure logic, testablestyles/components/- Component-specific LESStemplates/- Handlebars templatestests/unit/{mirror-path}/- One spec per source file
Detected Conflicts / Variances
None detected. The architecture established in Stories 0, 1.x, and 2.x fully supports this implementation.
Previous Work Patterns to Follow
- Constructor injection (from Story 1.3): All Foundry deps via FoundryAdapter
- Import boundaries (from Story 0): ESLint
no-restricted-pathsenforced - Test patterns (from Story 1.3): Fake timers, frozen fixtures, canonical mocks
- CSS architecture (from Story 0): LESS partials, semantic tokens, scoped selectors
- Socket patterns (from Story 1.3): Intent/echo cycle, PendingOp lifecycle
🔗 References
Source Documents
| Reference | Path | Section |
|---|---|---|
| Story 3.2 ACs | _bmad-output/planning-artifacts/epics.md |
Story 3.2: Scene Auto-Apply & ConfirmationBar |
| FR-17 | _bmad-output/planning-artifacts/epics.md |
FR-17: Scene Preset auto-applies on Scene activation |
| FR-18 | _bmad-output/planning-artifacts/epics.md |
FR-18: Disable auto-apply per-scene or globally |
| UX-DR12 | _bmad-output/planning-artifacts/epics.md |
UX-DR12: ConfirmationBar specification |
| Architecture | _bmad-output/planning-artifacts/architecture.md |
Full project architecture |
| ScenePresetManager | _bmad-output/implementation-artifacts/3-1-save-and-load-scene-presets.md |
Story 3.1 implementation |
| Socket contracts | src/contracts/socket-message.js |
PRESET_APPLY, PRESET_APPLIED |
| Scene flag schema | src/contracts/scene-preset.js |
Versioned wrapper pattern |
Previous Story Files (Critical Context)
| Story | File | Relevance |
|---|---|---|
| 3.1 | 3-1-save-and-load-scene-presets.md |
MUST READ - ScenePresetManager foundation |
| 2.3 | 2-3-directors-board-bulk-actions-spotlight-and-keyboard-shortcuts.md |
Bulk action patterns, Undo concept |
| 1.5 | 1-5-gm-control-ui-scryingpoolstrip-actionpopover-and-av-tile-integration.md |
StripOverlayLayer patterns |
| 1.4 | 1-4-core-logic-scryingpoolcontroller-and-visibilitymanager.md |
Matrix application, state management |
🤖 Dev Agent Record
Agent Model Used
DEV (Amelia) - Senior software engineer for story execution
Debug Log References
Critical debugging checkpoints:
- ✅
Hooks.on('updateScene')registration - added in module.js (Task 4) - ✅ Scene flag read/write - implemented in ScenePresetManager (Task 1)
- ✅ ConfirmationBar timer management - implemented (Task 2)
- ✅ Undo matrix storage - implemented in ConfirmationBar (Task 2)
- ✅ Socket payload validation - verified in
src/contracts/scene-preset.js(Task 7)
Code Review Fixes Applied
Fix 1: module.js wiring (HIGH)
- Added imports for ConfirmationBar and StripOverlayLayer
- Constructed ScenePresetManager with visibilityManager in ready hook
- Created StripOverlayLayer and ConfirmationBar instances
- Registered updateScene hook to trigger scenePresetManager.onSceneActivate()
- Registered autoApplyEnabled world setting
Fix 2: Settings key namespace (HIGH)
- Changed
scrying-pool.autoApplyEnabledtovideo-view-manager.autoApplyEnabledin ScenePresetManager.onSceneActivate() - Settings are now properly namespaced under the module's registered namespace
Fix 3: Socket loop prevention (HIGH)
- Added
emitSocketoption parameter to load() method (default: true) - load() now only emits PRESET_APPLIED when emitSocket is true
- module.js socket handler passes
{ emitSocket: false }to prevent loop - PresetLoadDialog continues to emit (manual GM action)
Fix 4: Auto-apply config preservation (MEDIUM)
- Modified _saveScenePresets() to read existing autoApply config from scene flag
- Preserves autoApply setting when saving presets
- Prevents loss of per-scene auto-apply configuration
Fix 5: Scene timer cleanup (MEDIUM)
- Added _clearAllTimers() method to clear ALL pending timers
- onSceneActivate() now calls _clearAllTimers() before applying new preset
- Prevents old scene's timer from firing after switching scenes
Task 1 Implementation Notes:
- Extended ScenePresetManager constructor to accept optional
visibilityManagerparameter - Added constants: MAX_PREDELAY_MS (5000), MIN_PREDELAY_MS (0)
- Implemented
onSceneActivate(scene)with full auto-apply logic chain - Implemented
applyPreset(presetName, options)supporting autoApplied flag - Implemented
configureAutoApply(scene, config)with validation - Implemented
_getAutoApplyConfig(flagData)with default fallback - Implemented
_applyWithDelay(scene, presetName, delayMs)with timer storage - Implemented
_clearSceneTimer(scene)for cleanup - Implemented
_getSceneFlagData(scene)for safe flag access - All 61 tests passing including 15 new Story 3.2 tests
Completion Notes List
Before marking done, verify:
- ConfirmationBar appears and disappears correctly
- Undo works and reverts to exact previous state
- Auto-apply respects both global and per-scene settings
- Pre-delay is configurable and accurate
- Partial fail shows amber variant
- Instant-replace works for consecutive bars
- All timers cleaned up on module teardown
- No memory leaks from event listeners
- Accessibility: keyboard nav, ARIA labels, focus management
- All ESLint import boundaries pass
File List
NEW FILES (9):
src/ui/gm/ConfirmationBar.jssrc/ui/gm/ScenePresetPanel.jssrc/ui/shared/StripOverlayLayer.jstests/unit/ui/gm/ConfirmationBar.test.jstests/unit/ui/gm/ScenePresetPanel.test.jsstyles/components/_confirmation-bar.lessstyles/components/_strip-overlay-layer.lesstemplates/confirmation-bar.hbstests/fixtures/scene-preset.js(updated with auto-apply fixtures)
MODIFIED FILES (5):
- ✅
src/core/ScenePresetManager.js(extended with auto-apply methods, constructor updated) tests/unit/core/ScenePresetManager.test.js(added 15 new Story 3.2 tests)module.js(to wire updateScene hook, inject visibilityManager)src/ui/shared/StripOverlayLayer.js(to add ConfirmationBar support)src/ui/gm/DirectorsBoard.js(to integrate ScenePresetPanel)
CONTRACT FILES (verify, don't modify):
src/contracts/socket-message.js(already has preset events)src/contracts/scene-preset.js(already has validators)
✅ Story Completion Status
Status: done
Code Review: done (2026-05-24)
Ultimate context engine analysis completed - comprehensive developer guide created with:
- Complete epic and cross-story context
- Exhaustive architecture compliance requirements
- Previous story intelligence and patterns
- Git history insights
- Specific file structure and boundaries
- Testing requirements and fixtures
- UX design specifications
- Security and performance constraints
- Actionable dev agent guardrails
The developer now has everything needed for flawless implementation!
Generated for {project_name} by BMad Method Story Context Engine Agent: DEV (Amelia) Date: 2026-05-23