Commit 693b93e
authored
feat: admin-defined focus range for recordings (#306)
* feat: add focus_start/focus_end columns (migration v9)
* feat: update all queries and scan for focus_start/focus_end columns
* feat: extend edit endpoint for focus range (set/clear)
* feat: accept focusStart/focusEnd in upload endpoint
* feat: add focusStart/focusEnd to Recording type and API client
* feat: add focus range icons and CSS styles
* feat: implement focus range UI (toolbar, timeline overlays, shortcuts)
Add FocusToolbar component for editing in/out points, timeline scrubber
overlays with drag handles, FOCUS/FULL toggle, and I/O/Esc keyboard
shortcuts. Admins can set, save, clear, and cancel focus ranges that
persist via the edit API.
* fix: match focus range UI to design concept
- Move Panel button from left to right in controls row
- Focus toolbar: space-between layout with label left, buttons right
- Gold-styled Set In / Set Out buttons (matching design)
- Add gold border-bottom on focus toolbar row
- Dynamic --pb-bottom-height when editing (fixes overflow)
- Return 400 on malformed focus values instead of silent ignore
- Consistent 1-frame min gap for handle drag and keyboard
* fix: focus range time color, handle style, keyboard hints
- Time range text color #887744 matching design
- Handles: 8px wide, extend beyond track, rounded outside edge,
inner grip line, gold glow shadow
- Add kbd hints: Set In [I], Set Out [O], Cancel [Esc], Panel [E]
* feat: constrain playback to focus range in FOCUS mode
When FOCUS toggle is active (not FULL, not editing):
- Scrubber zooms into focus range (full width = inFrame..outFrame)
- Heatmap and kill markers only show events within range
- Playback pauses when reaching outFrame
- Seeking via scrubber is clamped to focus range
- Dim overlays hidden (entire scrubber IS the focus range)
Switch to FULL mode to see/seek the entire recording.
* fix: smooth transition on all elements using --pb-bottom-height
Add transition: bottom 0.15s ease to MapControls, SidePanel,
FollowIndicator, and leaflet controls so they animate together
with the bottom bar when focus toolbar toggles.
* refactor: extract gold accent color to --accent-gold CSS variable
Replace all hardcoded #D4A843 and rgba(212,168,67,...) in focus
range styles with var(--accent-gold) and color-mix() derivations.
* docs: add --accent-focus to customization reference
* fix: address PR review feedback (error handling, SQL DRY)
- Check json.Unmarshal errors for missionName/tag/date fields
- Check I/O errors in test helper (gw.Write, gw.Close, writer.Close)
- Check repo.db.Close() error in test cleanup
- Extract duplicated SQL column list to operationColumns constant
- Log errors in saveFocus/clearFocus catch blocks
* test: add coverage for focus range feature
- FocusToolbar: 11 tests (rendering, callbacks, styling, kbd hints)
- TimelineScrubber constrained mode: 5 tests (progress mapping,
dim overlay suppression, kill marker filtering)
- Go handler: table-driven test for invalid field types (missionName,
tag, date, focusStart, focusEnd)
* test: cover remaining focus range gaps across all files
- shortcuts.ts: 5 tests for focus editing keys (i, o, Escape)
- BottomBar.tsx: 8 tests for FocusToolbar visibility, Focus button,
FOCUS toggle, admin gating
- TimelineScrubber.tsx: 9 tests for focus overlays, accent line,
edit mode handles/labels/border, heatmap dimming
- RecordingPlayback.tsx: 2 tests for focus range initialization
from metadata and admin Focus button rendering
* fix: address 3 bugs found during focus range testing
Bug 1: Deduplicate setFocusIn/setFocusOut/cancelFocus — shortcuts
registered in onMount used inline lambdas duplicating the named
functions. Now defined once above onMount and referenced by name.
Bug 2: Remove fragile focusRange()! null assertions — replaced with
defensive null checks in the clamping effect and startFocusEdit.
Bug 3: Reject inverted focus ranges server-side — both EditOperation
and StoreOperation now return 400 when focusStart >= focusEnd.
Includes regression test (TestEditOperation_InvertedFocusRange).
* test: add coverage for focus handle dragging, kill nav, and edit flow
- TimelineScrubber: 5 tests for handle drag (pointerDown/Move/Up on
in/out handles, draft change callbacks, drag state reset)
- BottomBar: 2 tests for prev-kill and next-kill button navigation
- RecordingPlayback: 3 integration tests for focus edit flow
(open+cancel, save with API call, clear with null values)
Total: 1403 tests passing
* test: close remaining coverage gaps for focus range feature
Go:
- TestStoreOperation_InvertedFocusRange: validates upload endpoint
rejects focusStart >= focusEnd
UI (RecordingPlayback):
- toggleBlacklist un-blacklist path (DELETE API call)
- saveFocus API error handling (console.error, edit mode stays open)
- clearFocus API error handling (console.error logged)
Total: 1406 tests passing
* test: cover showFullTimeline toggle and FOCUS/FULL switch
- BottomBar: 2 tests for showFullTimeline=true (FULL text, null
focusRange passed to scrubber)
- RecordingPlayback: FOCUS toggle integration test (click switches
to FULL text)
Total: 1409 tests passing
* fix: clamp navigation to focus range, reject partial focus in API, extract constrainToFocus prop
- Clamp frame to focus bounds on any seek (not just during playback)
- Reject partial focusStart/focusEnd in both upload and edit endpoints
- Extract constrainToFocus as BottomBar prop to eliminate DRY violation
- Add tests reproducing all three bugs before fixing
* fix: use undefined instead of null for optional entity fields in tests
* test: cover setFocusIn, setFocusOut, and pause-at-outFrame paths1 parent 0bc4a1a commit 693b93e
26 files changed
Lines changed: 2559 additions & 67 deletions
File tree
- docs
- internal/server
- ui/src
- components
- data
- pages/recording-playback
- __tests__
- components
- styles
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| 74 | + | |
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| |||
183 | 185 | | |
184 | 186 | | |
185 | 187 | | |
186 | | - | |
| 188 | + | |
| 189 | + | |
187 | 190 | | |
188 | 191 | | |
189 | 192 | | |
| |||
217 | 220 | | |
218 | 221 | | |
219 | 222 | | |
220 | | - | |
| 223 | + | |
| 224 | + | |
221 | 225 | | |
222 | 226 | | |
223 | 227 | | |
| |||
265 | 269 | | |
266 | 270 | | |
267 | 271 | | |
| 272 | + | |
268 | 273 | | |
269 | 274 | | |
270 | 275 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
347 | 347 | | |
348 | 348 | | |
349 | 349 | | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
350 | 373 | | |
351 | 374 | | |
352 | 375 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
4 | 6 | | |
5 | 7 | | |
6 | 8 | | |
| |||
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
16 | 81 | | |
17 | 82 | | |
18 | 83 | | |
| |||
22 | 87 | | |
23 | 88 | | |
24 | 89 | | |
25 | | - | |
26 | | - | |
| 90 | + | |
| 91 | + | |
27 | 92 | | |
28 | 93 | | |
29 | 94 | | |
| |||
43 | 108 | | |
44 | 109 | | |
45 | 110 | | |
46 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
47 | 138 | | |
48 | 139 | | |
49 | 140 | | |
50 | 141 | | |
51 | 142 | | |
52 | 143 | | |
| 144 | + | |
| 145 | + | |
53 | 146 | | |
54 | 147 | | |
55 | 148 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
635 | 635 | | |
636 | 636 | | |
637 | 637 | | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
638 | 822 | | |
639 | 823 | | |
640 | 824 | | |
| |||
0 commit comments