From f34ce5bc3d95e63be3247089c3017df220293fca Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 11:44:21 +0000 Subject: [PATCH] docs(spec): format finder spec The unit-test job runs format:check before the tests, so an unformatted markdown file failed the job while every test passed. Promote the bold pseudo-headings in the Phase 3 section to real subheadings. They previously sat directly above numbered list items, which the formatter reads as one paragraph and collapses into a run-on line, mangling items 13-27. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY --- apps/spec/finder.md | 57 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/apps/spec/finder.md b/apps/spec/finder.md index 9a0d26a35..f6c8c5e04 100644 --- a/apps/spec/finder.md +++ b/apps/spec/finder.md @@ -11,12 +11,12 @@ business-development one: is it worth investing in, and can it beat the competit Scope decided for the roadmap: **editor + cuesheet**, **navigation-only** (the Finder reveals entries, it does not act on them), and **results stay in rundown order** — no ranking. -| File | Role | -|---|---| -| `apps/client/src/views/editor/finder/Finder.tsx` | Modal, input, result list, footer hints | -| `apps/client/src/views/editor/finder/useFinder.tsx` | Search logic | -| `apps/client/src/views/editor/finder/Finder.module.scss` | Styling | -| `apps/client/src/features/rundown/placements/FinderPlacement.tsx` | `mod+F` hotkey + mount | +| File | Role | +| ----------------------------------------------------------------- | --------------------------------------- | +| `apps/client/src/views/editor/finder/Finder.tsx` | Modal, input, result list, footer hints | +| `apps/client/src/views/editor/finder/useFinder.tsx` | Search logic | +| `apps/client/src/views/editor/finder/Finder.module.scss` | Styling | +| `apps/client/src/features/rundown/placements/FinderPlacement.tsx` | `mod+F` hotkey + mount | --- @@ -61,7 +61,7 @@ is hardest. ### 1.4 Reach Editor-only. But `apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx:160` already -registers a scroll handler commented *"for explicit jumps (finder/keyboard)"* — the plumbing was +registers a scroll handler commented _"for explicit jumps (finder/keyboard)"_ — the plumbing was anticipated and built, the Finder was never mounted there. Cheapest high-value win available. **Constraint:** the cuesheet is `permission='operator'` and can be exposed via URL presets with @@ -92,7 +92,7 @@ results, so every behaviour described above is unguarded. **A runtime-aware, multi-surface finder is a real wedge.** [Shoflo](https://shoflo.tv/), [Rundown Studio](https://rundownstudio.app/) and [Cuez](https://cuez.app/script-rundown/) are cloud -rundown *editors* — their search is document search over a document you're authoring. Ontime knows +rundown _editors_ — their search is document search over a document you're authoring. Ontime knows show state: what's loaded, what's past, the offset, what's flagged. A finder that marks the loaded event and shows scheduled time and delay per row is a different category of tool, and that data already exists in `RundownMetadata` and is currently discarded. Add that it works identically on the @@ -113,7 +113,7 @@ Sequence accordingly. Estimates assume one developer familiar with the codebase. -### Phase 1 — Correctness *(~0.5 day)* +### Phase 1 — Correctness _(~0.5 day)_ 1. **Track `selectedId`, not `selectedIndex`** — derive the active row by id lookup, falling back to the first. Correct across updates, reordering and result changes; better than "reset to 0", which @@ -129,7 +129,7 @@ Estimates assume one developer familiar with the codebase. latent conflict with inline field editing. 6. **Fix the `index ` bound** to use the event count. -### Phase 2 — Search depth *(~1.5–2 days)* +### Phase 2 — Search depth _(~1.5–2 days)_ 7. **Search cue + title + note + text custom fields** across events, groups and milestones on a bare query. Skip `image`-type custom fields. Cap indexed note length so one pasted script can't @@ -138,8 +138,8 @@ Estimates assume one developer familiar with the codebase. for every key so today's documented syntax keeps working. Drive the footer hint and the `EventEditorEmpty.tsx` cheat sheet from one filter-key constant so they can't drift. 9. **Keep rundown order — no ranking.** Deterministic, simpler, and it preserves the early-exit scan - (today's cap of 12 is a *scan* cap, not a sort cap; ranking would force scanning everything). - *One honest consequence:* widening the searched fields while keeping position order means a + (today's cap of 12 is a _scan_ cap, not a sort cap; ranking would force scanning everything). + _One honest consequence:_ widening the searched fields while keeping position order means a distant cue match can be pushed off the list by nearer note matches. Two mitigations, both cheap — raise the cap and show "showing 12 of 47", and label which field matched so a note hit is obviously a note hit. If that still isn't enough in practice, group by matched field (cue block, @@ -152,18 +152,20 @@ Estimates assume one developer familiar with the codebase. Highest-value case: `index ` staying aligned with 1-based UI event indices when delays, groups and milestones interleave — the current code gets this right and a rewrite is likely to break it. -### Phase 3 — Visibility, UX and polish *(~1.5 days)* +### Phase 3 — Visibility, UX and polish _(~1.5 days)_ This is the phase that changes how many people ever use the feature. -**Visibility** +#### Visibility + 13. **Add a search control to `RundownHeader.tsx`** with the `mod+F` hint visible on it. Single biggest discoverability win. 14. **Add one to `RundownHeaderMobile.tsx`** — mandatory, not optional: without a keyboard the feature currently does not exist on touch devices. 15. **Add one to the cuesheet toolbar** when the Finder mounts there (Phase 4). -**UX** +#### UX + 16. **Fix the empty state** — it shows "No results" before you've typed anything. Show the filter hints, or recent searches, on open. 17. **Align with the app's other search box.** `SettingsSearch.tsx` has a leading `IoSearch` icon and @@ -175,7 +177,8 @@ This is the phase that changes how many people ever use the feature. 21. **Preserve the last query on reopen**, selected so typing replaces it. 22. **`Home` / `End` / `PageUp` / `PageDown`** in the result list, matching `useRundownKeyboard`. -**Polish** +#### Polish + 23. **Use `getAccessibleColour`** (`common/utils/styleUtils`) for the index badge. It currently sets `background: var(--color)` raw with fixed foreground text, so a light entry colour is unreadable. The cuesheet `EventRow`, `MilestoneRow` and `OperatorEvent` already do this correctly — the @@ -187,7 +190,7 @@ This is the phase that changes how many people ever use the feature. 27. **Reconsider the 100 ms debounce** — with a controlled input (item 11) it's unnecessary at these rundown sizes and just reads as lag. -### Phase 4 — Cuesheet *(~1 day)* +### Phase 4 — Cuesheet _(~1 day)_ 28. **Move the Finder out of `features/rundown/placements/`** — it stops being an editor view once it has two homes — and mount it in the cuesheet. Pass surface and permission explicitly from the @@ -198,10 +201,10 @@ This is the phase that changes how many people ever use the feature. delay per row. Source at render time, not by rebuilding the index (§1.5). This is the differentiator from §2. 31. **E2E spec** alongside `209-rundown-shortcuts.spec.ts`, covering both surfaces, with a regression - lock for the click bug: click the third result *without* hovering the first two, assert the third + lock for the click bug: click the third result _without_ hovering the first two, assert the third entry is selected. -### Phase 5 — Find and replace *(~3–4 days)* +### Phase 5 — Find and replace _(~3–4 days)_ The strongest expansion, and a better business case than a command palette: renaming a sponsor, speaker or venue across a 200-entry rundown is a real recurring pain that the competitors' @@ -221,6 +224,7 @@ and replace all agree on what "matches" means. **32. The server gap — this is the actual work.** `batchEditEntries` (`rundown.service.ts:166`) applies **one patch to many ids**. Find-and-replace needs **per-entry distinct values** — each entry's own title with its own substring swapped. Two options: + - N × `putEditEntry`: N round trips, N revision bumps, N `notifyChanges` broadcasts. During a show that's N timer notifications for one user action. Not acceptable at 40 entries. - **A new endpoint taking `Array>` applied inside one @@ -229,18 +233,19 @@ entry's own title with its own substring swapped. Two options: possible. **33. Replaceable fields — deliberately narrow.** -- *Safe:* `title`, `note`, text `custom` values. Free text, no structural meaning. -- *Opt-in and validated:* `cue`. It's a numbering scheme, not free text — `cueUtils` has + +- _Safe:_ `title`, `note`, text `custom` values. Free text, no structural meaning. +- _Opt-in and validated:_ `cue`. It's a numbering scheme, not free text — `cueUtils` has `getIncrement`/`getCueCandidate` and the Renumber dialog exists precisely because of that. The server also rejects an empty cue outright (`rundown.service.ts:177`), so a replace that empties one fails the whole batch. -- *Never:* times, booleans, colours, enums, ids, `image`-type custom fields. +- _Never:_ times, booleans, colours, enums, ids, `image`-type custom fields. **34. Preview is mandatory, because there is no undo anywhere in this app.** I checked — the client has no undo stack of any kind. Find-and-replace would be the first feature that can silently alter dozens of entries. So: show every affected entry with before → after per field, with a per-match -opt-out, before anything is written. Confirmation names the count and the field: *"Replace 'Q' with -'CUE' in title on 23 entries?"* +opt-out, before anything is written. Confirmation names the count and the field: _"Replace 'Q' with +'CUE' in title on 23 entries?"_ **35. A single-step revert.** Because the atomic endpoint gives one revision per operation, capturing the prior values client-side makes "Undo replace" a second batch call. Not an undo history — one @@ -256,7 +261,7 @@ moved. Prevents replacing text the user never saw someone else write. Cheap. **38. Editor only for v1.** The cuesheet is `permission='operator'` and its URL presets can be read-only or column-restricted; a correct implementation would have to gate every field through -`getCuesheetColumnAccessPolicy().canWrite(key)`. *Finding* is safe to expose broadly; *replacing* is +`getCuesheetColumnAccessPolicy().canWrite(key)`. _Finding_ is safe to expose broadly; _replacing_ is not. Warn when playback is running rather than blocking. **39. No regex.** Live operators, no undo history, and one bad pattern destroys a rundown. @@ -276,7 +281,7 @@ tests ~1d. - **Virtualising the result list.** A palette showing 400 rows isn't more useful than one showing 50; scanning is the bottleneck, not rendering. Cap and count instead. - **Implicit cross-rundown search.** Revealing an entry in another rundown means changing the - *loaded* rundown — a destructive runtime action mid-show. Only the cuesheet has a non-destructive + _loaded_ rundown — a destructive runtime action mid-show. Only the cuesheet has a non-destructive "viewed rundown" concept. If ever wanted, an opt-in `rundown:` filter there, never in the editor. - **The operator view and public viewers.** The operator view never calls `setScrollHandler`, so "go to entry" would silently do nothing until one is registered.