Skip to content

Segment bar: stable clip numbers, scroll preservation, boundary-accurate playback - #127

Open
morepriyam wants to merge 1 commit into
mainfrom
fix/segment-bar-ux
Open

Segment bar: stable clip numbers, scroll preservation, boundary-accurate playback#127
morepriyam wants to merge 1 commit into
mainfrom
fix/segment-bar-ux

Conversation

@morepriyam

@morepriyam morepriyam commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes #126

What changed

Stable clip numbers

  • New label column on segments, minted from a new monotonic last_clip_number counter on projects — assigned once when the clip is recorded/imported, never renumbered on reorder, gaps left by deletes are intentional. Shown as a small badge on each thumb, so "move 7 between 3 and 12" stays meaningful however the draft is shuffled. label is cosmetic metadata only — it is never part of the segment-set signature, so a future rename feature can't invalidate a merged transcript.
  • Migration backfills existing rows from sort_order, dedupes any historically raced order values, seeds last_clip_number per draft, then adds a unique (project_id, sort_order) index (Add DB indexes (schema defines none) #64). The renumber step avoids SQLite window functions (no ROW_NUMBER()), so it runs on older bundled SQLite builds.
  • addSegment computes order via max()+1 and mints label from last_clip_number inside one transaction — fixes the count() order race (recorder: stopRecording idempotency, cameraReady latch, segment-order race #96, item 3). reorderSegments renumbers in two bulk passes (park on negatives, then a single CASE update) so the unique index holds mid-update without per-row round-trips. .pulse imports renumber sequentially (a foreign bundle with duplicate orders can no longer abort the import).

Scroll ownership

  • No more auto-scroll-to-newest — recording a clip never moves the bar; the user's scroll position wins.
  • Delete preserves your place — the pre-delete offset is restored (clamped to the new content width) instead of snapping to the start.

Preview playback correctness + playhead polish

  • Playhead boundary jank fixed: advancing (or tapping) to another clip left positionMs at the outgoing clip's near-end value for one render, so the knob jumped mid-thumb into the next thumbnail, then slid back to its left edge. Position is now reset in the same render as the selection swap — the knob lands exactly on the incoming thumb's left edge.
  • Clips play to their true end: the timeUpdate handler advanced up to 60 ms before the out-point, cutting the last frames of every clip. Under the destructive-edit model the out-point is the file's end, so playToEnd (which always fires) is now the sole advance trigger.
  • End of draft parks at the end (was: seeked to the START of the last clip while paused — the seek bug from player: gapless segment preview (preload next player) + end-of-draft seek bug + missing-file handling #97).
  • Failed clip load can't wedge the session: replaceAsync(...).then(begin) now has a .catch that disarms the swap guards (also from player: gapless segment preview (preload next player) + end-of-draft seek bug + missing-file handling #97).

Tests

  • The px↔ms track mapping is extracted to track-mapping.ts and unit-tested: draft start → x 0, draft end → last thumb's right edge, segment boundaries → incoming thumb's LEFT edge, proportional in-clip mapping, zero-length-clip skipping, px↔ms round-trip.

Scope notes

Manual test checklist

  • Record 10+ clips, scroll to the middle, record another → bar does not move
  • Delete a mid clip (preview → trash) → scroll position preserved
  • Reorder far-apart clips → number badges unchanged
  • Play through a whole draft → knob lands on each thumb's left edge at every boundary, no mid-thumb jump
  • Last clip plays to its final frames; knob parks at the track's end; play again restarts
  • Import a .pulse bundle → clips numbered 1..n in bundle order

Screenshots

Stable clip numbers — delete clip 2 of 5

Before (main: bar renumbers) After (badge labels keep their numbers)
Segment bar before — clips renumbered after delete Segment bar after — badge labels stable after delete

Scroll preservation on delete + stable numbers through reorder

ScreenRecording_07-26-2026.14-08-40_1.mov

@horner

horner commented Jul 25, 2026

Copy link
Copy Markdown
Member

@morepriyam Do we have a test package? I want to be able to click a button and pull down 2-3 projects that we know to be simple, average, and complex. Average being 10-15 clips, Complex being like 80.

@morepriyam

morepriyam commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

@horner Yes, locally. Dev builds have one-tap seed buttons on the library screen (src/dev/seed.ts) that build 4 purpose-built drafts from bundled fixtures:

  1. Curated mixed (6 clips): mismatched portrait/landscape, 1080p/4k, 30/60fps, h264/hevc. The simple one.
  2. Speed uniform (20 clips, ~2 min): 20x the recorder-match clip, exercises the passthrough fast path.
  3. Speed mixed (20 clips): 14 recorder-match + 6 outliers, the average real-world case.
  4. Wild imports (12 clips): one per hostile format (HDR 10-bit, 120fps slo-mo, VFR, WhatsApp baseline, screen-rec, no-audio, Opus, rotation variants, square, NTSC, mono 44.1k).

Seeding is idempotent, re-pressing is a no-op.

I haven't gone as high as 80. Long runs at that scale heat my phone up noticeably and repeated test runs take a real bite out of the battery. Can add a ~80 clip tier from the same fixtures if we want it, it just needs a device someone doesn't mind cooking.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the recorder segment bar’s UX and the preview player’s boundary correctness while introducing a stable, per-clip creation ordinal in the DB so segment identity survives reorder/delete. It also extracts and tests the track px↔ms mapping used by the playhead cursor for more reliable boundary behavior.

Changes:

  • Add created_ordinal to segments plus a unique (project_id, sort_order) index; update segment insertion/reorder/import paths to be race-safe and ordinal-stable.
  • Preserve segment-bar scroll position on delete (and stop auto-scroll-to-newest on record) while updating the bar’s drag handle UI to display the stable ordinal badge.
  • Fix preview playback boundary behavior (end-of-draft parking, boundary position reset, play-to-end advance) and extract track mapping math into unit-tested helpers.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/features/recorder/use-preview.ts Playback boundary correctness, end-of-draft behavior, and swap error handling improvements.
src/features/recorder/track-metrics.ts Adjust vertical lane metrics to accommodate the new ordinal/handle pill.
src/features/recorder/track-mapping.ts New pure ms↔px mapping helpers extracted from the playhead cursor for testability.
src/features/recorder/track-mapping.test.ts Unit tests covering draft/segment boundary mapping and round-trips.
src/features/recorder/segment-bar.tsx Scroll preservation on delete, removal of auto-scroll-to-newest, and ordinal pill drag-handle UI.
src/features/recorder/playhead-cursor.tsx Use extracted mapping helpers and tweak cursor line sizing/layout.
src/features/draft-transfer/unpack.ts Ensure imported segments get sequential createdOrdinal after sorting to satisfy unique order index.
src/db/schema.ts Add createdOrdinal column and unique index definition for (projectId, order).
src/db/legacy-migration.ts Backfill createdOrdinal for legacy draft migration inserts.
src/db/drafts.ts Transactional max()+1 order/ordinal assignment and two-pass reorder to satisfy the unique index.
drizzle/migrations.js Register the new Drizzle migration.
drizzle/meta/0011_snapshot.json Drizzle schema snapshot update reflecting the new column and index.
drizzle/meta/_journal.json Drizzle journal update for migration 0011.
drizzle/0011_segment_ordinal.sql Migration to add/backfill created_ordinal, dedupe/renumber sort_order, and add unique index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/features/recorder/use-preview.ts Outdated
Copilot AI review requested due to automatic review settings July 26, 2026 00:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread src/features/recorder/use-preview.ts
Comment thread src/features/recorder/track-mapping.ts Outdated
Copilot AI review requested due to automatic review settings July 26, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread src/features/recorder/use-preview.ts
Copilot AI review requested due to automatic review settings July 26, 2026 17:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 26, 2026 18:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.

@morepriyam
morepriyam enabled auto-merge July 26, 2026 18:55
@horner

horner commented Jul 26, 2026

Copy link
Copy Markdown
Member

This is a really good PR. I love the screenshots.

Copilot AI review requested due to automatic review settings July 26, 2026 19:39
@morepriyam

Copy link
Copy Markdown
Collaborator Author

301ac80 adds forward-looking schema support for user-renamable clips: a nullable label column on segments plus migration 0012_segment_label (ALTER TABLE segments ADD label text;, no backfill needed — NULL = unnamed).

Design notes:

  • label sits alongside created_ordinal as cosmetic identity: reorder/delete never touch it, and the UI is expected to fall back to the ordinal badge when unnamed.
  • It's excluded from the segment-set signature, so renaming a clip can never invalidate a merged transcript.
  • No reads/writes yet — the rename UI, renameSegment helper, and draft-transfer pack/unpack support are intentionally left for a follow-up.

Type-check and full test suite pass (119 tests).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 1 comment.

Comment thread drizzle/0011_segment_ordinal.sql Outdated
Copilot AI review requested due to automatic review settings July 26, 2026 20:21
@morepriyam
morepriyam force-pushed the fix/segment-bar-ux branch from d6fd01a to fa183a8 Compare July 26, 2026 20:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 1 comment.

Comment thread src/db/drafts.ts Outdated
Copilot AI review requested due to automatic review settings July 26, 2026 20:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 2 comments.

Comment thread src/features/recorder/track-mapping.test.ts
Comment thread src/db/schema.ts
@kadenhorner kadenhorner moved this from Ready to In Progress in Scrum Team Jerry Jul 27, 2026
Copilot AI review requested due to automatic review settings July 27, 2026 19:43
@morepriyam
morepriyam force-pushed the fix/segment-bar-ux branch from fa183a8 to c55bb21 Compare July 27, 2026 19:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 27 changed files in this pull request and generated 4 comments.

Comment thread src/app/recorder.tsx Outdated
Comment thread src/features/recorder/use-audio-focus.ts
Comment thread src/features/recorder/use-audio-focus.ts
Comment thread src/app/index.tsx
…te playback

- add stable clip badges via segments.label minted from projects.lastClipNumber

- migration backfills labels/order and adds unique (project_id, sort_order) index

- preserve segment bar scroll position on delete; drop auto-scroll-to-newest

- merge ordinal pill and drag handle; swap strategy for reorder drags

- fix preview boundary jank, play-to-end advance, and end-of-draft parking

- extract px<->ms track mapping into track-mapping.ts with unit tests

- migration uses SQLite-compatible ranking instead of ROW_NUMBER()

- reorderSegments uses bulk two-pass updates instead of per-row awaits

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 27, 2026 19:57
@morepriyam
morepriyam force-pushed the fix/segment-bar-ux branch from c55bb21 to febf743 Compare July 27, 2026 19:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/db/schema.ts:50

  • The PR description (and linked issue notes) still describe a new created_ordinal column on segments, but the shipped schema implements stable clip identity via projects.lastClipNumber + segments.label. Please update the PR description to match the actual schema (or rename the columns to match the documented concept) so future migrations/data-debugging don’t have two competing models for “stable clip numbers.”
  // Monotonic badge counter: the highest clip number ever minted for this draft. Bumped on
  // every clip added, never decremented — so deleting (or renaming) a clip can never cause
  // its number to be reused.
  lastClipNumber: integer('last_clip_number').notNull().default(0),

@morepriyam
morepriyam disabled auto-merge July 27, 2026 20:19
@morepriyam

Copy link
Copy Markdown
Collaborator Author

The tests are done on a physical device again after the changes, ready to merge

@morepriyam
morepriyam enabled auto-merge July 27, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Segment bar: simplify gesture surface, keep scroll position on delete, stable per-clip numbers

4 participants