- Add 'Face Group' mode to search modes
- Use search_identities API temporarily
- Update summary: 'Identity' → 'FS'
- Will replace with Core Team's search_tkg_node_face_trace_profile API
- Rename computed property to match current API semantics
- face_groups are definitive, not pending
- Add detailed console.log for debugging empty groups issue
- Explicitly define data source for Move Faces Modal
- Use clusterAsPending directly
- Add console.log for debugging
Purpose: Diagnose why only one group shows in Move Faces Modal
Problem:
- Some groups may not have name field
- Display would show blank or undefined
Solution:
- Add fallback: c.name || 'Unnamed Group'
- Add fallback: c.trace_count || 0
- Ensures always shows meaningful text
Impact:
- Move Faces Modal now always shows group names
- No blank entries even with incomplete data
Replace merge_trace implementation with merge_groups API
Changes:
1. src/api/index.ts:
- Add merge_groups API definition
- POST /api/v1/file/:file_uuid/groups/merge
- Parameters: file_uuid, source_groups[], target_group_name
2. src/views/PeopleView.vue:
- Rewrite executeMerge() function
- Use group names instead of trace IDs
- Single API call for all groups
- Remove trace-level loop
3. src/views/PeopleView.vue:
- Delete updateClusterAfterMerge() function
- No longer needed with new API
Benefits:
- ✅ Atomic operation (single API call)
- ✅ Correct merge behavior (groups, not traces)
- ✅ Better performance (1 call vs N calls)
- ✅ Data consistency guaranteed
API behavior:
- Merge N groups into 1 target group
- All traces from source groups merged to target
- Source groups deleted after merge
- Returns traces_merged count
Core API: Implemented by Core Team (2026-07-25)
Status: Ready for testing
Display format with # separator:
- Frame: F#{number} (e.g., F#333)
- Face Trace: FS#{number} (e.g., FS#233)
Changes:
- Line 105: Frame range display (F#2219–F#2225)
- Line 143: Face Detail Modal (file_uuid:FS#233)
- Line 262: Cluster strip fallback (FS#233)
- Line 264: Cluster strip label (FS#233)
Benefits:
- # separator makes ID clearer
- Consistent format across all displays
- Visual distinction between type and number
Critical bug fix:
- updateClusterAfterMerge() was called after EACH trace merge
- This caused source group to be deleted after first trace
- Remaining traces had no group to merge into
Problem flow:
1. Merge trace #1 from Group B → Group A
2. updateClusterAfterMerge(B, A) deletes Group B
3. Try to merge trace #2 from Group B → Group B is gone!
4. Result: All source groups deleted after first trace
Solution:
- Remove updateClusterAfterMerge() from loop
- Let all traces merge successfully
- Reload cluster data at the end (Line 1813)
- Core API handles the merge, we just refresh UI
Impact:
- Merge now works correctly for all traces in a group
- No premature deletion of source groups
- UI refreshes after all operations complete
Problem:
- After merge, source groups become empty
- Empty groups stay in UI as garbage data
- User sees empty groups with 0 faces
Solution:
- updateClusterAfterMerge() now removes source groups after merge
- Added removal from clusterAsPending computed property
- Ensures clean UI after merge operation
Behavior:
- Source group is deleted after all members merged to target
- Target group receives all members
- No empty groups left in UI
Related: merge functionality debug logging added in previous commit
Add debug logs to diagnose merge not working issue:
1. batchMergeGroups():
- Log selected groupIds
- Log mergeCandidateGroups count
2. executeMerge():
- Log targetGroupId and targetGroup
- Log targetTraceId type and value
- Log targetTrace data
- Log sourceGroups count
- Log totalTraces and validTraces
- Log each merge_trace API call params
- Log success/fail counts
Purpose: Identify why merge fails when move works
Next: Check browser console for specific errors
Problem:
- 'F13' display text confused with Frame numbers
- Users cannot distinguish between Trace ID and Frame Number
Solution:
- Change display format from 'F{trace_id}' to 'Face{trace_id}'
- Example: F13 → Face13, F142 → Face142
Changes:
- src/views/PeopleView.vue:264 - Cluster Detail Modal label
Benefits:
- Clear semantic meaning (Face vs Frame)
- No ambiguity for users
- Display layer only, data layer unchanged (still trace_id)
Problem:
- syncFileStatus() doesn't load pipeline progress data
- Files stuck in 'processing' without percentage display
- pollPipelineProgress() not started when pipeline data missing
Solution:
1. Add loadPipelineStats() call in syncFileStatus() for processing files
2. Start pollPipelineProgress() even when pipeline data is missing
3. Ensure progress bar always has data to display
Impact:
- Progress bar now shows percentage during deep scan
- Files no longer stuck in 'processing' state indefinitely
- Better handling of edge cases when API data is missing
Related: Core team needs to verify get_pipeline_stats API accuracy
Problem:
- Status badge showed 'Scanning'
- Progress bar showed 'Processing...'
- Two different texts displayed simultaneously
Solution:
- Change progress bar text to 'Scanning...'
- Now both badge and progress bar use consistent terminology
Core Principle: Frame is the ONLY standard
Changes:
- VideoPlayer: Remove startTime/endTime props
- VideoPlayer: Add mandatory startFrame/endFrame check
- VideoPlayer: UI shows F123 (51.2s) format
- All Views: Only set startFrame/endFrame
- All Views: Only pass :start-frame/:end-frame
- Remove all startTime/endTime from currentVideo refs
Time is only used for display (auxiliary).
Frame is the source of truth for all positioning.
Core Principle: Frame is the only standard
Changes:
- VideoPlayer: Add startFrame/endFrame props
- VideoPlayer: Use frame parameters in simple playback path
- SearchView: Remove frame/time conversion (stop using fps = 24)
- PeopleView: Pass frames directly instead of time
- PersonDetailView: Pass frames directly instead of time
All video streams now use startFrame/endFrame API parameters.
Time conversion only happens in VideoPlayer internals for UI display.
Ref: frame-based positioning standards research
Core Changes:
- Fix SearchView to use start_frame/end_frame directly (no time*fps conversion)
- Add hard_delete support to delete_trace API
- VideoPlayer: Main timeline + Mark system foundation
- Proxy: Add local routes for auth, media, identity-matches, cluster-results
- Add .gitignore to exclude build artifacts and dependencies
Design Documents:
- Multi-track Mark system design (.opencode/plans/)
- Video editing positioning standards research
Files Modified:
- src/views/SearchView.vue: Frame positioning, ensureMinDuration (240 frames)
- src/views/PeopleView.vue: batchDeleteGroups with hard_delete
- src/api/index.ts: delete_trace with hard_delete body
- src/components/VideoPlayer.vue: Timeline + Mark UI
- src-tauri/src/proxy.rs: New local routes
- AGENTS.md: Update documentation
- Removed Identity checkbox from processor selection in context menu
- Removed trace_chunks from PostgreSQL section (deprecated)
- Added relationship_chunks to PostgreSQL section
- Core API: bind/unbind accept id (integer PK) when face_id is null
- Tauri proxy: stream video responses instead of buffering for seek bar
- VideoPlayer: remove muted attribute, unmute by default
- LibraryView: call invalidateFiles() before ensureFiles() on register/unregister
- PeopleView: close sort panel on click-outside, not just hamburger toggle
- bind_face: prefer face_id, fallback to id when face_id is null