- HTML5 drag-and-drop reordering of strip participants (per-GM flag) - Shift+click toggles spotlight focus on a participant (gold ring indicator) - Escape exits focus mode - Auto-save strip position on drag end + every 30s with viewport validation - Reset strip position button in Director's Board - French locale strings for reset button
5.5 KiB
title, type, created, status, baseline_commit, context
| title | type | created | status | baseline_commit | context |
|---|---|---|---|---|---|
| Strip: Reorder, Spotlight & Auto-Snapshots | feature | 2026-05-27 | done | 816b7951fb |
Intent
Problem: Strip participants have a fixed order, no way to focus a single participant, and position is only saved on close.
Approach: Add HTML5 drag-and-drop reordering (per-GM flag), Shift+click spotlight (in-memory, one-tile focus mode), and periodic auto-save of strip position (debounced + 30s interval with viewport validation).
Boundaries & Constraints
Always:
- Re-order: persists to
game.user.setFlag('scrying-pool', 'participantOrder', string[]), per-GM only - Spotlight: in-memory
_focusedUserIdonly, no socket/persistence - Auto-snapshots: extends existing
stripStateflag (left, top, width, height, savedAt), backward-compatible - All three features are strip-only (not Director's Board)
- French locale strings in
scrying-pool.lang.fr.json
Ask First: None
Never:
- No external drag-and-drop libraries
- No socket broadcasts for re-order or spotlight
- No CSS animation framework changes
I/O & Edge-Case Matrix
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|---|---|---|---|
| Re-order: drag participant | Drag start on tile | Tile shows opacity: 0.3 during drag |
N/A |
| Re-order: drop between tiles | Drop at new position | Participant order updates, flag saved, strip re-renders | Save failure silently caught |
| Re-order: double-click grip | Double-click grip area | Order resets to connection order | N/A |
| Spotlight: Shift+click | Shift+click participant | Only that participant visible, others display:none in DOM, strip resized |
N/A |
| Spotlight: exit via Escape | Press Escape | Full list restored, _focusedUserId cleared |
N/A |
| Spotlight: exit via button | Click exit-focus button (replaces DB icon) | Same as Escape | N/A |
| Auto-snapshot: drag ends | mouseup after drag | Debounced save to stripState flag |
Save failure silently caught |
| Auto-snapshot: 30s timer | Interval fires | Save current position to stripState |
Save failure silently caught |
| Auto-snapshot: position off-screen | Saved position outside viewport | Fall back to default position | Silent fallback, no error |
Code Map
src/ui/gm/ScryingPoolStrip.js— All three features: drag handlers, spotlight state, auto-save timerstyles/components/_roster-strip.less— Drag feedback (opacity: 0.3), spotlight visual statetemplates/directors-board.hbs— Reset strip position buttonsrc/ui/gm/DirectorsBoard.js— Reset position handlermodule.js— (no changes needed, instantiates the class)scrying-pool.lang.fr.json— French locale strings
Tasks & Acceptance
Execution:
src/ui/gm/ScryingPoolStrip.js— Add_focusedUserId, drag handlers, _savePosition, re-order in _prepareContextstyles/components/_roster-strip.less—.sp-state-focusedgold ring, drag ghost opacitytemplates/directors-board.hbs— Reset strip position buttonsrc/ui/gm/DirectorsBoard.js—_onResetStripPosition()handler
Acceptance Criteria:
- Given a strip with 3+ participants, when GM drags a participant tile to a new position, then the participant order updates and persists across re-renders
- Given any layout, when GM Shift+clicks a participant, then other participants collapse and the focused tile shows gold state ring
- Given spotlight mode is active, when GM presses Escape, then all participants return to normal
- Given the strip is open, when GM drags the strip to a new position and releases, then the position is saved
- Given a saved off-screen position, when the strip re-renders, then it appears at default coordinates
Suggested Review Order
Re-order + Spotlight core logic
-
Entry point: participant filtering, DnD handlers, focus toggle, position save
ScryingPoolStrip.js:149 -
Drag-drop splice with
fromIdx < toIdxadjustment + null guard on elementScryingPoolStrip.js:1013 -
Focus toggle toggles
_focusedUserId, re-renders; Escape exits via document listenerScryingPoolStrip.js:1076 -
Auto-save: called on grip mouseup + 30s interval; cleanup on teardown
ScryingPoolStrip.js:1094 -
Viewport-validated position restore with negative-value guard
ScryingPoolStrip.js:178
Template changes
-
isFocusedconditional class for gold ring on focused participantroster-strip.hbs:54 -
Reset strip position button in Director's Board footer
directors-board.hbs:140
CSS
- Gold state ring for
.sp-state-focused, drag ghost opacity, drop target indicator_roster-strip.less:517
Director's Board
- Reset position handler clears stripState flag
DirectorsBoard.js:861
Locales
- Reset strip button strings (EN + FR)
en.json:82·fr.json:82
Verification
Commands:
npm run lint-- expected: 0 errorsnpm run typecheck-- expected: 0 errorsnpm run test-- expected: all tests pass