Commit graph

99 commits

Author SHA1 Message Date
Your Name
4ae79b6422 refactor: anchor annotation marks to clips and unify visibility edges 2026-09-17 15:10:52 -04:00
Your Name
d54a6ae8f8 fix: rescue orphaned annotations to root in the pane
Found by loading the real Challengers project through re-frame-test:
3 of 15 stored annotations were filed under a since-deleted parent.
Under the :in model their sole host was a ghost, so ::all-annotations
never surfaced them — invisible and unrefileable.

::all-annotations now drops :in hosts that no longer exist and rescues an
annotation left with none to root (full-scene context, so peer-delta safe).

Tests (re-frame-test, high-level events + pane sub):
- orphaned-annotation-is-rescued-to-root
- legacy-parent-data-migrates-through-peer-delta (the real DB shape)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:16:20 -04:00
Your Name
65a80857be feat: :in membership edges replace transclusion/mark-home placement
Placement is now asserted, not derived. An annotation carries :in — an
ordered vector of the mark-groups it's filed under (first = primary home,
scene/home). Membership decides listing; mark resolution independently
decides whether bars draw. Marks are never moved or re-cut.

Rebuilt from the clean pre-transclusion base, keeping only the codex
frame-integer (assert-frame) work:
- delete ref-owner / mark-home / mark-homes / :home-marks / rehome-marks
- annotations drop :parent entirely; restore-annotations migrates
  legacy :parent -> :in [parent]; :in is a vector (JSON-round-trips
  like :tags/:notes)
- file-edge (shared by ::reparent drag + ::file-into picker): move vs
  add/link; ::unfile guards primary + last; files-under? cycle guard
- ::associate-marks no longer auto-files (adding marks != placement)
- root identified by (= :timeline (:type)), not (nil? :parent)
- membership-chips `in:` row; drag ⌥ = link; .reference cards

Tests: real day8.re-frame.test run-test-sync suite driving events +
live subscriptions (pane, context, reveal, reparent, associate, unfile,
cycle, JSON wire). 62 tests / 243 assertions, 0 failures; 0 residue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 13:07:49 -04:00
Your Name
4b29962677 transclusion fixes and other things by chat 2026-07-06 23:48:01 -04:00
Your Name
d2d803e38b feat: transclusion completion by chat 2026-07-06 23:10:20 -04:00
Your Name
40dd3b0d59 feat: transclusion (wip commit) 2026-07-06 22:54:46 -04:00
Your Name
28f441f172 fix: remove active annotation filter 2026-07-05 18:20:29 -04:00
Your Name
649e36c14c feat: add annotation search filters 2026-07-05 18:17:09 -04:00
Your Name
dfa92a478f feat: improve annotation mark editing 2026-07-05 18:10:44 -04:00
Your Name
7ac8f27b57 claud erefactor checkpoint 2026-07-05 17:45:49 -04:00
Your Name
06dd2b597c fix: half-open boundaries, per-mark lane interaction, unified :at addressing
Fixes the resize-drift and boundary bugs found while exercising the marks-first
flow, plus Chunk 7 (orphan polish). Frame model made consistent rather than
patched.

- Boundaries are half-open [start,end) everywhere; drop the merge-bars ≤1-frame
  fudge and the independent from-otio rounding that created the gaps it papered
  over. Clips stay EXACT (they tile at shared boundaries); frame-accuracy is
  applied at the mark the user creates (selection->marks rounds :at) and at
  display, never by rounding clips or bars.
- Unify the ref :at <-> source conversion: at->local / at->src / src->at, all via
  the target's full resolution, correct for scattered multi-clip (nested) proxy
  targets. Delete target-range (the lossy single-segment [xs xe] shortcut that
  made selection->marks / point-frame / at->frame / seg-point wrong when nested).
  This is what fixed the resize dragging one handle moving the other.
- Lane interaction is per-MARK, not per-visual-bar: one draggable unit + two
  end-handles at the mark's exact extent (scene/mark-extent), driven so the
  dragged endpoint rounds and the fixed one stays exact. Timeline drag-select;
  click = scrub. Visible edge grips.
- Share display-point between the editor rows and the jump popover (context-local
  whole frame). Drawing autocommits; toolbar moved off the video.
- Chunk 7: broken annotations greyed + sorted down + per-mark warnings.

Suite green except the pre-existing annotations-survive-json-roundtrip; app
compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 12:22:17 -04:00
Your Name
213602c4cb feat: lane editing, drag-select, frame snapping, drawing autocommit
Chunk 5 (draft/edit lane) + follow-up polish on the marks-first flow.

- Lane bars are [lo hi mark-id]; draft/edit bars are interactive via mark-drag!:
  click = draw on that mark, body-drag = move, edge handles = resize. All route
  through ::reroll-proxy -> roll-proxy (in-place within a clip, add/drop across a
  boundary). Active mark shows a ring in the lane and an accent row in the pane.
- Timeline drag-select: drag across clips/empty space to select a region (proxy +
  auto-draw); a plain click moves the playhead (scrubbing works in marking mode).
- Frame accuracy: snap OTIO's fractional RationalTime to whole frames at the seed
  boundary (from-otio) so nothing sub-frame enters the model; round display too.
- Drawing autocommits (no save/cancel buttons — the form's Save/Cancel is the
  rollback net); toolbar moved to a bottom strip off the video; draw mode tears
  down on form finish.

Tests: from-otio snapping, per-mark lane bars carry mark-id. Suite green
(pre-existing json-roundtrip failure only); app compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 03:29:43 -04:00
Your Name
b66d189a31 feat: marks-first annotation authoring with proxy synthetic-clips
Invert the authoring flow around a new "proxy" mark-group and rework how
cross-clip selections are modelled, displayed, and edited. See
annotation_flow_plan.md for the chunked plan.

- scene: resolve a ref mark by slicing its target's resolved timeline in
  local frames, so a scattered-source proxy resolves piece-by-piece with the
  same {:ref g :at n} grammar as a clip (proxy = synthetic clip). Add
  make-proxy / roll-proxy / proxy-ref, proxy-aware mark-row/marks->rows, a
  :proxy restore branch, and per-mark lane-bars (distinct abutting marks no
  longer fuse into one bar).
- events/subs/views: selections now create a proxy + one collapsed mark and
  set an active-mark pointer; proxies persist alongside their annotation. A
  two-stage draft form (choosing -> creating): ranges + a single Title
  autocomplete that either associates with an existing annotation
  (transclusion) or creates a new one. Completing a new selection auto-enters
  drawing mode bound to the active mark.

Full test suite green (proxy resolve/roll, collapsed row, restore round-trip,
per-mark bars); app compiles clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 02:08:17 -04:00
Your Name
23154ac284 fix: pane scroll on NEWLY-active annotation (set diff)
Track the full set of annotations the playhead is inside and diff against last
tick; scroll only for newly-entered ones, so a permanently-active annotation
(e.g. a full-timeline one) counts as new just once and stops hijacking the
scroll. Pick one deterministically among the newly active (greatest lo, then
tightest bar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:18:49 -04:00
Your Name
183e776727 fix: pane scroll picks the most-recently-entered annotation
The prior "first active in anns" pick was masked by a full-timeline annotation
(bars [[0 N]]) that always won, so primary never changed and the scroll never
re-fired. Pick the active bar with the greatest lo (ties → tighter bar) instead,
so entering a nested/later annotation changes the pick and scrolls to it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:14:01 -04:00
Your Name
4b4d6183c1 fix: deterministic playhead-driven pane scroll in playhead-fx
Move the commentary-pane auto-scroll into playhead-fx (the single per-playhead
side-effect hook): pick ONE annotation deterministically (first active in the
sorted anns) so overlapping annotations don't race per-card, and scrollIntoView
its card when that pick changes. Runs on any playhead move (playback or scrub).
Drops the unreliable commentary-scroll/active-annotation component.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:02:25 -04:00
Your Name
0022c6bedd fix: scroll pane to annotation from the card's own active? (highlight path)
Drop the separate commentary-scroll/active-annotation component and trigger the
scroll where the highlight already fires: when a card's ::annotation-active?
flips true (playhead entered), scrollIntoView it — the same call the lane click
uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:58:43 -04:00
Your Name
f6e887c14d fix: reliable playhead-driven pane scroll to the active annotation
Rewrite commentary-scroll as a lifecycle component that scrolls in
component-did-update via scrollIntoView (offsetParent-agnostic, so it works for
nested cards too), replacing the fragile after-render + manual offsetTop math.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:46:34 -04:00
Your Name
28bdac1142 fix: half-open mark ranges + robust active-card scroll
- Playhead-in-bar tests (active annotation(s), drawing/note bindings) are now
  half-open [lo hi) via a shared in-bars? helper. merge-bars ceils the exclusive
  end up to the next clip's first frame, so the old inclusive <= double-counted
  the boundary: two annotations lit at once and clip A's drawing bled onto clip
  B's first frame.
- commentary-scroll uses scrollIntoView instead of manual offsetTop math, which
  broke once cards can nest (a nested card's offsetParent is its parent .ann).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:34:23 -04:00
Your Name
8dd2c9030f feat: always show current-timeline crumb as a reparent drop target
Render the breadcrumb bar even at root so the current context's crumb is always
visible — it already acts as a drop target while dragging, so you can drag an
annotation out onto the current timeline to un-nest it. Back button stays gated
to nested contexts. Also drop the child-card left spine (indentation is enough).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:21:55 -04:00
Your Name
65acfbc886 feat: reveal child annotations in-place + lane packing + range warnings
- Greedy annotation lane collapse: non-overlapping annotations share a lane
  (packed by earliest start, then longest span); each annotation keeps all its
  segments in one lane.
- Clicking a lane bar jumps the playhead to that mark and scrolls its card into
  view instead of drilling into the annotation timeline.
- "Show N annotations" toggle nests an annotation's children (recursively) in the
  commentary pane and surfaces them in the lane; children reparent-drag correctly
  (stopPropagation so the child, not the parent, is moved).
- Out-of-range warning (⚠) on cards and the content display when an annotation
  references frames its parent timeline trims; the lane shows the best-effort
  in-range portion (scene/nested-src + scene/clip-loss?).
- Annotation-card markdown: 12px body, larger heading ramp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:17:03 -04:00
Your Name
2508066381 feat: annotation tags + timeline tag filter
Tags live in annotation :meta :tags (plain strings). The edit form gains a Tags
section: cute price-tag chips + a shared string autocomplete that suggests any
tag used elsewhere in the project (allow-new). Tags render small at the card's
bottom-left.

The timeline controls gain a "▽ Tags" filter opening a dropdown of every project
tag (plus an "Untagged" entry mapping to a :untagged sentinel), each with an
eye to show/hide. Hidden tags drop matching annotations from both the commentary
list and the timeline lane via a new ::hidden-tags view state.

Both the adder and the filter share one `autocomplete` component (just different
callbacks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:27:44 -04:00
Your Name
ab16763f8d fix: label jump-popover targets from the mark's clip ref
The jump dropdown described each target with clip-at, which re-derived the clip
by looking up the (floored) bar-edge frame in the content segments — at a clip
boundary that lands one clip early, so a mark on clip B could read "clip A <last
frame>" even though the jump seeks correctly. The annotation editor labels each
mark straight from its :ref via clip-label, so the two disagreed.

Add scene/jump-targets (built on runs, keyed by the owning mark) and label the
popover with clip-label + the mark-time frame — the same basis as the editor.
Drops the now-unused clip-at.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:29:58 -04:00
Your Name
1c6283059e style: drop-target and mark-editor styles
Glowing drop targets for annotation reparenting (cards + breadcrumbs), the
per-endpoint re-pick ✕, and the drag-to-reorder grip + drop line — the visuals
for the interactions landed in 7f3d903.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:11:17 -04:00
Your Name
0cf261c440 style: brighter, colored picker-mode glow
Replace the 1-bit black pulse on pickable clips/frame-readout/annotation bars
with a cyan↔magenta glow that swells and shifts hue, so what's clickable in
picker mode reads at a glance. Adds --pick-a/--pick-b for reuse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:09:34 -04:00
Your Name
7f3d903452 feat: allow drag reordering marks, allow reparenting, allow removing endpoints of mark 2026-07-02 10:05:36 -04:00
Your Name
0c5456d6df perf: stop toolbar/share-menu re-rendering every frame in playback
Both derefed the raw ::playhead in their render bodies, so the whole toolbar
(zoom/row sliders) and the share menu (even while closed) reconciled 60fps
during playback. toolbar now subscribes coarse ::at-start?/::at-end? edges
(flip only at the timeline ends) and reads the exact playhead at click time;
share-menu reads the playhead at click time too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:25:14 -04:00
Your Name
05514f3522 perf: stop full annotation-list re-render on every bar crossing
commentary dereferenced active-annotation/active-annotations/active-note-set
in its body, so the whole card list rebuilt (and React reconciled every card)
on every playhead bar-crossing during playback and on context enter — the
annotation-only jank + the enter "double flash".

Push the per-item active state into granular per-id subs
(::annotation-active? / ::note-active?): each card and each bound-note chip
subscribes to its own state, so a crossing re-renders only the item(s) that
flipped. The active-card scroll moves into a nil-rendering component that owns
the active-annotation sub, so commentary no longer re-renders for it either.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:22:01 -04:00
Your Name
d22bff7654 optimization: perf improvements 2026-07-01 19:53:57 -04:00
Your Name
40ff2ecf52 fix: live-sync drawings/notes; cut per-frame sub cost; pen color+size
- backend: the scene PUT only broadcast to peers when annotation counts
  changed, so drawing/script-note edits saved but never synced live (only
  after a reload). Broadcast any gid that actually moved, regardless of type.
- perf: active-drawings/active-note-set recomputed fully on every playhead
  tick (active-drawings even re-resolved each annotation per mark). Split the
  bar computation into memoized ::drawing-bars/::note-bars (scene/ctx/segs
  only) and reduce per-frame work to cheap interval tests.
- perf: only subscribe drawing-layer to the boil clock when something is
  actually animating (no more 7fps idle re-renders).
- drawings: pen colour picker + stroke-size slider, still defaulting to the
  annotation colour per session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:29:32 -04:00
Your Name
9f06b51f76 feat: wiggly-paint drawings bound to marks
A drawing is a first-class :type :drawing entity (normalized strokes +
seed), bound to a mark via mark :drawings [gid] — same referrer-pointer
model as notes. It renders as a boiling SVG overlay on the video whenever
the playhead is in the bound mark's span (the same hook that lights an
annotation active), including when that annotation is the pushed context.

- Author in an overlay on the video monitor: pencil, eraser (deletes a
  whole stroke), undo; the 🖼 button on each mark row enters draw mode
  and seeks to the mark's frame.
- Boil is procedural: 3 seeded jitter variants of the base paths cycled
  at ~7fps; only the strokes persist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:03:44 -04:00
Your Name
b4924aa992 feat: script notes — named PDF-region annotations bound to marks
Replace the old per-annotation :script rects with a first-class
:script-note entity (name, color, id'd regions), persisted/synced
through the same generic scene channel as every other group.

- Author regions by selecting text on the pdf.js text layer; a region
  stores char offsets + per-line rects + per-range commentary.
- Bind notes to an annotation or a specific mark (drag-drop); a bound
  note shows »»» while the playhead is within its span.
- Script pane: note rail with per-note eye toggles, fit-to-width + zoom
  controls, plain-highlighter rendering (multiply blend, no borders).
- Click a highlight to open + focus its note; click a note chip on an
  annotation card to jump to its first passage.
- Schema v2 migration drops the legacy :script rects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:34:29 -04:00
Your Name
89533a13cd fix: prevent scroll reset when hovering over hidden annotation
Keep button container always rendered but use visibility:hidden to hide
buttons instead of conditionally rendering them. This preserves DOM
structure and prevents scroll position reset on hover.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:51:35 -04:00
Your Name
06b28e2a9c refine: hidden annotations show edit + expand buttons on hover
Remove the unhide button. Instead show Edit (✎) and Expand (⤢) buttons
on hover so users can edit for any reason or expand to zoom in.

Make title text smaller (10px) and reduce card height to 24px.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:49:37 -04:00
Your Name
4ffd831551 feat: show hidden annotations as collapsed cards in annotation pane
Hidden annotations appear in the annotation pane as small, disabled-looking
cards with the dither pattern overlay (var(--desktop)) at 40% opacity.

On hover, an "Unhide" button appears to remove the hidden flag.
Full content, controls, and metadata are hidden.

Hidden annotations still work in marks/autocomplete and maintain their marks.
They appear as normal bars in the timeline.

Add 'Hide from timeline' checkbox in the annotation edit form.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:38:38 -04:00
Your Name
1ea675567c feat: add hidden annotation meta for decluttering timeline
Add 'hidden' meta flag to annotations to hide them from the timeline lane
while keeping them available for linking. Hidden annotations appear as
small 6px colored rectangles with faint color and dither pattern overlay.

Click on a hidden rectangle to unhide it. Hidden annotations' marks are
not resolved/used, and they don't trigger scroll-to-clip during playback.

Add 'Hide from timeline' checkbox in annotation create/edit form.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:27:19 -04:00
Your Name
e964347e51 fix: adjust timeline layout for time ruler height
Increase gutter-lanes height by 24px to align with the time ruler above the
annotation lanes. Update timeline-body-scroll padding to match.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:10:26 -04:00
Your Name
1cdb0de042 fix: scale time ruler tick marks to prevent overlap, use HH:MM:SS format
Dynamically calculate tick intervals to maintain ~80px minimum spacing between
marks regardless of zoom level. Prevents overlapping labels at high zoom.

Change time format from adaptive (m/s/·) to consistent HH:MM:SS or MM:SS format.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:07:05 -04:00
Your Name
d93b92f4a5 feat: add time ruler with tick marks above timeline
Display adaptive tick marks and time labels above the timeline to indicate
intervals. Tick spacing automatically adjusts based on zoom level:
- < 10 px/sec: 30s intervals
- < 20 px/sec: 10s intervals
- < 40 px/sec: 5s intervals
- < 80 px/sec: 1s intervals
- >= 80 px/sec: 0.5s intervals

Labels show minutes (m), seconds (s), or deciseconds (·) depending on interval.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:15:11 -04:00
Your Name
52c700b843 fix: scroll timeline to deep-linked playhead on first load
When a project opens with a ?f=<frame> query parameter, the playhead position
is now visible in the timeline view. Previously, the playhead was positioned
correctly in the UI but the timeline didn't scroll to show it. This triggered
scroll-to-seg-track on first render when playhead > 0 and segments are loaded.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:06:20 -04:00
Your Name
d91ea6c78a fix: dedupe scroll-to-clip by bar boundary instead of track
When an annotation with scroll-to enabled spans multiple bars (discontinuities),
only scroll when the playhead enters a new bar, not for every mark. Track
[ann-id bar-idx] pairs instead of just the current track to avoid excessive
scrolling as the playhead moves within a bar.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:05:58 -04:00
Your Name
6a02b0665b feat: clear project db on nav + clean editor loading/error states
Stale/fail flashes came from the editor rendering the previous project's
:project/:scene/:view (and a stale :load error) while the next loaded. Reset
those keys when opening a project or leaving to the list/create page, and render
the editor body only when :ready — otherwise a centered spinner ("Loading…") or
error message, with the top bar (and back button) still available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:29:47 -04:00
Your Name
d9059ad132 fix: start video at deep-linked time via #t= media fragment (iOS)
The real iOS issue: it won't load the media until the first tap and ignores JS
currentTime until the element is seekable (populated very late), so every
load/seek hack started playback from 0. A #t=<seconds> media fragment on the
src is honoured by the media loader during the tap-triggered load itself, so it
starts at the right frame (and renders that frame instead of black). Bake the
deep-linked playhead into the src fragment, cached per clip so live playhead
changes don't reload the element; currentTime still drives in-session seeks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:21:33 -04:00
Your Name
a62af2584b Revert "fix: play from the playhead, not currentTime (iOS deep-link seek)"
This reverts commit 5c58af9036.
2026-06-30 11:17:42 -04:00
Your Name
484351746f Revert "fix: defer video seek until seekable, flush on loadedmetadata (iOS)"
This reverts commit 7585dd4e7f.
2026-06-30 11:17:42 -04:00
Your Name
7585dd4e7f fix: defer video seek until seekable, flush on loadedmetadata (iOS)
The previous fix still failed on iOS: at the first tap the element isn't
seekable (readyState 0), so the seek in engage-play! was dropped, and the
existing on-loadedmetadata seek is guarded by (when-not @play) — already true by
then — so it was skipped too, playing from 0. Now seek-video! stashes the target
when the element isn't seekable yet, and loadedmetadata/canplay flush it
(regardless of play state).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:02:54 -04:00
Your Name
5c58af9036 fix: play from the playhead, not currentTime (iOS deep-link seek)
On iOS the element doesn't load (so a deep-linked pre-seek doesn't stick) until
the user's first tap, so engage-play! read currentTime=0 and played from the
top. Seek to the playhead inside the play gesture and drive from there; for
normal pause/resume the playhead already equals currentTime, so it's a no-op.
Drops now-unused seg-at-src.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:54:53 -04:00
Your Name
28aa2622f6 ui: unhighlight between marks; translucent clip label
- ::active-annotations no longer falls back to the nearest annotation, so when
  the playhead is outside every mark nothing is highlighted.
- clip label background is translucent (color-mix paper 70%) like the lane
  bars, so the thumbnail reads through while text stays legible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:47:33 -04:00
Your Name
d877655b7a ui: bolder/colored active annotation, readable clip labels, multi-active
- active annotation is now loud: thick colour spine, a wash of its own colour,
  a hard offset shadow, bold title (was just a faint 2px shadow). Annotation
  colour drives it via a --ann-color custom property; added a colour swatch by
  each title so the commentary reads its colours.
- clip name is a solid bordered label chip pinned top-left instead of black
  text + white halo, which muddied over busy thumbnails.
- highlight ALL annotations whose marks contain the playhead, not just one
  (new ::active-annotations set sub), so overlapping marks light up together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:45:34 -04:00
Your Name
7e32dc70dd feat: PWA restores last project + playhead/stack on reopen
A homescreen launch opens the manifest start_url ("/") with no route, so the
deep state was lost. Persist the in-app URL (already kept current with stack +
playhead) to localStorage, and on a standalone cold launch with a bare hash,
re-enter it before the router reads the URL. Browser tabs are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:45:34 -04:00