diff --git a/CLAUDE.md b/CLAUDE.md index 6cb4ed8..12c14a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repo identity and current state -The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool. The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0–M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1–B5, B-cap), Phase D1/D2 Design View (D1–D5, D2-W1–W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1–R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button), M11 in full (batch capture, action-button strip + keybinding labels, conform-on-insert verified-extant, native OS drag-out), Phase L L1 (shared LICE drawing kit: `theme`/palette module, `component_geometry` geometry helpers, `draw_kit` shell, GDI DrawText retired in `bank_panel`), Phase L L2 (dock-panel layout redesign: `action_bar` pure task-grouped layout module, full M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit), Phase L L4 (dock-panel button layout enhancement: three-zone layout — top capture/placement/maintenance toolbar, bottom Design-View tagging/switching toolbar, footer narrow-toggle + Tail-button + Prune; `footer_bar` pure module + `footer_bar_tests`; `ActionCluster::Tagging`/`Switching` in `action_bar`), Phase L L5 (dock-panel button refinements: top-bar overflow menu for Batch Items/Batch Razor/Capture RT via `overflow_menu` pure module; custom LICE-kit hover-delay tooltips via `tooltip` pure module; four opposite-mode Item/Track tag buttons + Show Both on the bottom toolbar; Toggle + Activate-Arrange/Design buttons removed; grouping spacing widened — `mode_enable` pure opposite-mode enablement predicate), and Phase L L6 (toolbar polish: single-row short-label button faces — keybinding micro sub-row removed from `ActionBarSlot`, keybinding now shown in hover tooltip as "`phrase — binding`"; Cancel RT moved from visible bar into the overflow menu — More (⋯) now lists Batch Items / Batch Razor / Capture RT / Cancel RT; visible top-bar cluster order Capture → Maintenance → Placement; toolbar height 40→28 px) have all landed. M9 slots deferred indefinitely. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout. +The CMake project and binary are now named `reaper_reasampler`. This is **ReaSampler** — a per-project audio sample-bank capture tool. The MPE modules (`mpe_model`, `mpe_view`) have been removed. M0–M8 are complete (bank_model, peaks, capture offline+realtime, persist, bank_panel, insert, capture action family, RealtimeRecordBackend, tail T1+T2+T1-followons). Phase B multi-bank (B1–B5, B-cap), Phase D1/D2 Design View (D1–D5, D2-W1–W3-B), Phase V versioning/beta-channel (V1/V3, V4), M10 provenance + re-capture from source, Phase R Reclaim (R1–R3: prune-reconcile core, dry-run shell, guarded deletion + action + panel button), M11 in full (batch capture, action-button strip + keybinding labels, conform-on-insert verified-extant, native OS drag-out), Phase L L1 (shared LICE drawing kit: `theme`/palette module, `component_geometry` geometry helpers, `draw_kit` shell, GDI DrawText retired in `bank_panel`), Phase L L2 (dock-panel layout redesign: `action_bar` pure task-grouped layout module, full M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit), Phase L L4 (dock-panel button layout enhancement: three-zone layout — top capture/placement/maintenance toolbar, bottom Design-View tagging/switching toolbar, footer narrow-toggle + Tail-button + Prune; `footer_bar` pure module + `footer_bar_tests`; `ActionCluster::Tagging`/`Switching` in `action_bar`), Phase L L5 (dock-panel button refinements: top-bar overflow menu for Batch Items/Batch Razor/Capture RT via `overflow_menu` pure module; custom LICE-kit hover-delay tooltips via `tooltip` pure module; four opposite-mode Item/Track tag buttons + Show Both on the bottom toolbar; Toggle + Activate-Arrange/Design buttons removed; grouping spacing widened — `mode_enable` pure opposite-mode enablement predicate), Phase L L6 (toolbar polish: single-row short-label button faces — keybinding micro sub-row removed from `ActionBarSlot`, keybinding now shown in hover tooltip as "`phrase — binding`"; Cancel RT moved from visible bar into the overflow menu — More (⋯) now lists Batch Items / Batch Razor / Capture RT / Cancel RT; visible top-bar cluster order Capture → Maintenance → Placement; toolbar height 40→28 px), and Phase L L7 (capture ordering, card metadata, and selection styling: per-bank `SlotMap` (id→slot) in `bank_book` — gap-preserving persisted display order, `reorderSample`/`replaceSample`/`orderedSampleIds`/`reconcileSlots` mutators; `captureTimeSigNum`/`captureTimeSigDenom` stamped on `Sample` at capture via `TimeMap_GetTimeSigAtTime`; `card_drag` pure module — gesture precedence + SWELL stock cursor cues; `card_meta` pure module — bars.beats.subdivisions + s.ms formatters; `bank_panel` sparse-grid rendering with gap cells, drop dispatch + cursor cues, metadata overlay, tertiary-border selection) have all landed. M9 slots deferred indefinitely. The *discipline* — pure REAPER-free testable core split from REAPER-facing shells — is **preserved** throughout. CONTEXT.md is the authoritative spec and build roadmap. Read it first for any non-trivial task. Every REAPER API name cited there is correct-by-intent; verify argument order, types, and flag values against `vendor/reaper-sdk/sdk/reaper_plugin_functions.h` before use. @@ -49,6 +49,8 @@ Key targets (see CMakeLists.txt for the full list): | `overflow_menu_tests` | executable | Pure unit tests for `overflow_menu` — no REAPER, no DAW. | | `mode_enable_tests` | executable | Pure unit tests for `mode_enable` — no REAPER, no DAW. | | `tooltip_tests` | executable | Pure unit tests for `tooltip` — no REAPER, no DAW. | +| `card_drag_tests` | executable | Pure unit tests for `card_drag` — no REAPER, no DAW. | +| `card_meta_tests` | executable | Pure unit tests for `card_meta` — no REAPER, no DAW. | | `reaper_reasampler` | loadable module | The actual extension binary (`.dll` / `.dylib` / `.so`). | ### Beta channel build (Phase V, V4) @@ -75,12 +77,12 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde ## Architecture: the load-bearing split **Pure core (no REAPER types, unit-testable outside the DAW):** -- `bank_model` — `Sample` metadata struct + `BankIndex` (add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart. +- `bank_model` — `Sample` metadata struct + `BankIndex` (add/remove/query/tier/dedup-by-hash + JSON round-trip). Test it hard — it is the heart. `Sample` carries `captureTimeSigNum`/`captureTimeSigDenom` (L7: stamped at capture via `TimeMap_GetTimeSigAtTime`, refreshed on re-capture). - `peaks` — waveform min/max bin computation from raw PCM. Fed a known signal, asserts envelope. Does not depend on REAPER's peak API. - `view_mode_model` — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, JSON round-trip. Mirror of `bank_model` for the Design View phase. - `view_tree` — pure `I_FOLDERDEPTH`→FolderTree helper for the Design View shell; no REAPER types at the boundary. - `mode_switch` — REAPER-free segment layout + hit-test math for the bank_panel's Design View mode switch; divides a header rectangle into N equal segments and hit-tests a point to a segment. Mirror of `bank_grid`. -- `bank_book` — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a `BankIndex`. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy/remove of a sample between or from banks, `hashReferencedElsewhere` cross-bank reference query, JSON round-trip + legacy-`bank_index`→pool migration. Wraps `BankIndex` (bank_model untouched; no `bankId` on `Sample`). +- `bank_book` — multi-bank registry (Phase B): an ordered set of banks (pool seeded as bank-zero + named banks), each wrapping a `BankIndex`. Owns create/rename/reorder/delete of named banks, pool privileges (un-deletable/un-renamable/un-evacuable, never zero banks) enforced in-model, active-bank id, index-only move/copy/remove of a sample between or from banks, `hashReferencedElsewhere` cross-bank reference query, JSON round-trip + legacy-`bank_index`→pool migration. Wraps `BankIndex` (bank_model untouched; no `bankId` on `Sample`). L7 adds: per-`Bank` gap-preserving **`SlotMap`** (id→slot) for deterministic display order — persisted inside the existing `"banks"` blob; `reorderSample` (insert-before-shift; same-slot = no-op), `replaceSample` (occupant index-removal via standard remove path + pool-guard inheritance; no-op on reject), `orderedSampleIds`, `reconcileSlots` (pre-L7 migration seeds dense insertion order on load). - `owned_manifest` — owned-file manifest seam (Phase B B-cap): the set of project-relative files the capture path itself created, persisted under the `"owned_files"` ext-state key, so Phase R prune can distinguish the bank system's own orphans from hand-dropped files. Deliberately decoupled from `bank_book` — tracks files created, not index membership. Phase R (R1/R2) consumes it; no prune logic here. - `app_version` — REAPER-free version/channel identity (Phase V, V1+V4): CMake-sourced semver constant (`appVersion()`), ext-state stamp value (`stampVersion()` — numeric triple only, no channel suffix), `parseVersion`/`versionLess`/`classifyWritingVersion`, and the full set of channel-derived identity accessors (`extStateNamespace()`, `commandIdPrefix()`, `actionDisplayPrefix()`, `binaryName()`, `dockTitle()`, `dockIdent()`, `channelCommandId()`, `channelActionName()`). All channel strings derive from the one `REASAMPLER_CHANNEL_IS_BETA` bit threaded in via `configure_file` → `version_generated.h`; no scattered `#ifdef`s in the shells. - `wav_trim` — 32-bit-float WAV parse + header-aware truncate plan (RIFF/data size rewrite) for the realtime tail's PCM decay-scan trim (T2). Rejects WAVE_FORMAT_EXTENSIBLE with non-float SubFormat GUID. Depends on `peaks` for the `AudioSample` float alias. @@ -97,12 +99,14 @@ There is no hot-reload. Copy the built binary into REAPER's `UserPlugins/` folde - `overflow_menu` — Phase L pure overflow-menu-button geometry/reserve/hit-test (L5): right-anchored More (⋯) button rect computation and hit-test for the top-toolbar overflow. No LICE or REAPER types. Mirror of `prune_button`. - `mode_enable` — Phase L pure opposite-mode enablement predicate (L5): given the active mode, computes per-button live/disabled state for the four Item/Track × Arrange/Design tag buttons on the bottom toolbar. No LICE or REAPER types. - `tooltip` — Phase L pure tooltip placement + prefix-strip (L5): custom LICE-kit hover-delay tooltip geometry and text derivation — strips the `ReaSampler:` display prefix from the registered action phrase; tooltip box width clamped to the client rect. No LICE or REAPER types. +- `card_drag` — Phase L pure drag-gesture precedence + slot hit-test (L7): leave-client → OS drag-out; other-bank → move/copy; same-bank → reorder / Alt-over-occupied → replace. Returns the resolved gesture so the shell can map it to a SWELL stock cursor cue. Sparse `computeSlotRects` / `hitTestSlot`. No LICE or REAPER types. Mirror of `drag_out` / `prune_button`. +- `card_meta` — Phase L pure card-metadata formatters (L7): bars.beats.subdivisions from `lengthSeconds` + `captureTempo` + capture-time-signature stamp; seconds.milliseconds rounded. Both blank when the sample is unstamped. No LICE or REAPER types. **REAPER-facing shells:** - `capture` — `ICaptureBackend` interface; `OfflineRenderBackend` (deterministic default) and `RealtimeRecordBackend`. Input: `CaptureRequest`. Output: finished file + populated `Sample` handed to `bank_model`. - `insert` — placement via `InsertMedia`; conform-to-project-tempo is an explicit opt-in flag, never silent stretching. -- `bank_panel` — docked LICE-drawn grid: thumbnails, audition, multi-select, keyboard navigation; three-zone layout (L4/L5/L6): **top toolbar** = visible bar in cluster order Capture (Capture Item, Capture Track) → Maintenance (Re-capture) → Placement (Insert, Insert Conform) via `action_bar`, each button showing a single-row short label, firing via `NamedCommandLookup` + `Main_OnCommand`; a right-anchored **More (⋯) overflow menu button** (`overflow_menu`) opens a `TrackPopupMenu` listing Batch Items / Batch Razor / Capture RT / Cancel RT; **hover tooltips** (`tooltip`) show the full action name with the `ReaSampler:` prefix stripped plus the live keybinding ("`phrase — binding`" when bound, bare phrase when unbound), sourced from the registered action phrase + `kbd_getTextFromCmd`; toolbar height 28 px; **bottom toolbar** = four opposite-mode tag buttons (Item: Arrange / Item: Design / Track: Arrange / Track: Design — live only for the opposite of the active mode, `Disabled` otherwise via `mode_enable`) + set-apart Show Both (`VIEW_SHOW_BOTH`); Toggle and Activate-Arrange/Activate-Design buttons removed (actions stay registered); grouping `clusterGap` 24 / `buttonGap` 4; **footer** = narrow `[Arrange|Design]` toggle · per-mode count · Tail button (cycles tail on click, proper kit button states) · set-apart `warn` Prune (rightmost) via `footer_bar`; entire panel draws through the L1 kit by palette role with hover on interactive elements, single `KitColor→LICE_pixel` boundary via `draw_kit`; OS drag-out hook (M11) initiating an OS-level copy drag when the pointer leaves the panel client rect, via `drag_out` + `drag_out_win`. -- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON (`"banks"` key) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key) + writing-version stamp (`"version"` key, written via `stampVersion()` on every `saveToActiveProject()`); project-relative path resolution. A `projectconfig` hook (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session. Hosts `ReaSamplerSession::pruneDryRun()` (read-only orphan enumeration via M4 project-relative resolution) and `pruneOrphanSet()` (full-set query for the R3 delete path); supplies `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core. +- `bank_panel` — docked LICE-drawn grid: thumbnails, audition, multi-select, keyboard navigation; three-zone layout (L4/L5/L6): **top toolbar** = visible bar in cluster order Capture (Capture Item, Capture Track) → Maintenance (Re-capture) → Placement (Insert, Insert Conform) via `action_bar`, each button showing a single-row short label, firing via `NamedCommandLookup` + `Main_OnCommand`; a right-anchored **More (⋯) overflow menu button** (`overflow_menu`) opens a `TrackPopupMenu` listing Batch Items / Batch Razor / Capture RT / Cancel RT; **hover tooltips** (`tooltip`) show the full action name with the `ReaSampler:` prefix stripped plus the live keybinding ("`phrase — binding`" when bound, bare phrase when unbound), sourced from the registered action phrase + `kbd_getTextFromCmd`; toolbar height 28 px; **bottom toolbar** = four opposite-mode tag buttons (Item: Arrange / Item: Design / Track: Arrange / Track: Design — live only for the opposite of the active mode, `Disabled` otherwise via `mode_enable`) + set-apart Show Both (`VIEW_SHOW_BOTH`); Toggle and Activate-Arrange/Activate-Design buttons removed (actions stay registered); grouping `clusterGap` 24 / `buttonGap` 4; **footer** = narrow `[Arrange|Design]` toggle · per-mode count · Tail button (cycles tail on click, proper kit button states) · set-apart `warn` Prune (rightmost) via `footer_bar`; entire panel draws through the L1 kit by palette role with hover on interactive elements, single `KitColor→LICE_pixel` boundary via `draw_kit`; OS drag-out hook (M11) initiating an OS-level copy drag when the pointer leaves the panel client rect, via `drag_out` + `drag_out_win`. L7 adds: **grid renders in sparse slot order** (decorative empty-gap cells for unoccupied slots; every cell↔sample consumer remapped to id-based occupied-ordinal space — selection, keyboard nav with gap-skip arrows, audition, multi-select, delete/re-capture resolution, drags); **drop dispatch** (reorder / Alt-replace / move-copy) with one-Ctrl-Z undo, per-slot drop highlight (`accent/hot`, doubled outline for replace), SWELL stock cursor cues via `SetCursor` per the `card_drag` resolved gesture; **metadata overlay** (bars.beats bottom-left Micro, s.ms bottom-right ValueMono, `TextDim`, decorative); **selection = normal cell + `accent/tertiary` purple border**, inversion removed, focus ring distinct. +- `persist` — project ext state (`SetProjExtState` / `GetProjExtState`, namespace `"reasampler"`) ↔ `BankBook` JSON (`"banks"` key — L7: per-`Bank` `SlotMap` rides inside this blob; a pre-L7 blob with no slot data migrates via `reconcileSlots()` to dense insertion order on load) + `ViewModeModel` JSON (`"view_state"` key) + `TailSetting` JSON (`"tail_setting"` key) + `OwnedManifest` JSON (`"owned_files"` key) + writing-version stamp (`"version"` key, written via `stampVersion()` on every `saveToActiveProject()`); project-relative path resolution. A `projectconfig` hook (`BeginLoadProjectState(isUndo)`) triggers a deferred session reload on undo/redo so Ctrl-Z/redo visibly restores book/view/tail/manifest in-session. Hosts `ReaSamplerSession::pruneDryRun()` (read-only orphan enumeration via M4 project-relative resolution) and `pruneOrphanSet()` (full-set query for the R3 delete path); supplies `referencedPaths()` + `owned().paths()` to the `prune_reconcile` pure core. - `view` — Design View shell: reads the folder tree via `view_tree`, snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (`B_SHOWINTCP`/`B_SHOWINMIXER`/`B_MAINSEND`/`I_FXEN` + per-FX offline) and derived visibility on parents; restores from snapshot. Never touches master or `B_MUTE`/`I_SOLO`. - `track_guid` — shared `MediaTrack*` → canonical GUID-string formatter; single source of truth for membership keys used by both the view shell and the actions layer. - `provenance_shell` — FX-chain identity queries via `TrackFX_*` / `TakeFX_*` APIs; collects source-item paths and parent-detection inputs to feed the pure `provenance` fingerprint builder. Stamps `Sample.provenance` on capture when every resolving source item maps by exact normalized path (case-folded on Windows) to exactly one bank sample; ambiguous/mixed cases record nothing conservatively. diff --git a/COMPLETED.md b/COMPLETED.md index fea0e00..72e9329 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -1423,3 +1423,61 @@ targets, no capture/placement behavior change. **Notes/decisions:** - Icons were considered and deferred (not implemented in this pass). + +--- + +## L7 — capture ordering, card metadata, and selection styling +**Goal:** Three grid-facing improvements to the dock panel, drawn through the L1 kit in the +settled DS-2 palette. (1) **Persisted deterministic capture order + drag-drop reorder + sparse +placement:** each bank (and the pool) carries an explicit, persisted per-sample order via a +per-`Bank` id→slot `SlotMap` in `bank_book`; a card may sit in a slot that leaves earlier slots +empty (gaps preserved; trailing empty tail trimmed for scroll extent). (2) **Decorative metadata +over the peaks:** each card overlays capture length as **bars.beats.subdivisions (bottom-left)** +and **seconds.ms (bottom-right)** in the kit's micro / value-mono type class, `text/dim`. (3) +**Selection restyle:** a selected card drops the inverted accent-fill and instead draws the +*normal* cell + an **`accent/tertiary` (pastel purple `#C2AAE8`) border**. No new capture / +placement behavior; the "capture ≠ placement" principle is untouched. + +- [x] **`SlotMap` in `bank_book`:** gap-preserving per-`Bank` id→slot map — persisted + deterministic display order; interior gaps preserved / trailing tail trimmed; JSON rides + inside the existing `"banks"` blob; pre-L7 migration seeds dense insertion order via + `reconcileSlots()` on the load path. `bank_model` / `Sample` untouched by position (position + is a per-bank display concern). +- [x] **`BankBook` mutators:** `reorderSample` (insert-before-shift; same-slot = no-op), + `replaceSample` (occupant index-removal via the standard remove path + pool-guard inheritance; + no-op on reject), `orderedSampleIds`, `reconcileSlots`. All gap-preserving; deterministic; + CTest-covered. +- [x] **`Sample` meter stamp (ONE sanctioned `Sample` change):** `captureTimeSigNum` / + `captureTimeSigDenom` added to `Sample` + JSON round-trip; stamped on both capture paths and + refreshed on re-capture via `TimeMap_GetTimeSigAtTime` (API confirmed at build). Old samples + with no stamp fall back gracefully (blank musical read-out). +- [x] **`card_drag` pure module:** gesture precedence — leave-client → OS drag-out; + other-bank → move/copy; same-bank → reorder / Alt-over-occupied → replace. Cursor-cue map + returned as the pure resolved gesture. Sparse `computeSlotRects` / `hitTestSlot`. SWELL stock + cursors chosen at build: Reorder→`IDC_SIZEALL`, Move→`IDC_HAND`, Copy→`IDC_UPARROW`, + Replace→`IDC_SIZEWE`. New CTest target `card_drag_tests`. +- [x] **`card_meta` pure module:** bars.beats.subdivisions from the stamped tempo + meter; + seconds.milliseconds rounded. Both blank when the sample is unstamped. New CTest target + `card_meta_tests`. +- [x] **`bank_panel` sparse-grid render:** grid renders in sparse slot order; decorative + empty-gap cells drawn for unoccupied slots; every cell↔sample consumer remapped to + id-based occupied-ordinal space (selection, keyboard nav — arrows skip gaps, audition, + multi-select, delete / re-capture resolution, drags). +- [x] **`bank_panel` drop dispatch:** reorder / Alt-replace / move-copy drop with one-Ctrl-Z + undo via the existing batched undo pattern; per-slot drop highlight (`accent/hot`, doubled + outline for replace); SWELL stock cursor cues via `SetCursor` per the pure resolved gesture. +- [x] **`bank_panel` metadata overlay:** bars.beats bottom-left (Micro), s.ms bottom-right + (ValueMono), `TextDim`, decorative / non-interactive. +- [x] **Selection restyle:** normal cell + `accent/tertiary` purple border; inversion removed; + focus ring distinct from the selection border. + +**Notes/decisions:** +- **M9 overlap (awareness note — unchanged intent).** M9 (capture-to-slot-N / insert-slot-N, + MIDI-bindable, MPC-style) remains **explicitly deferred (Daniel, 2026-07-26).** The + interchangeable-slot substrate L7 builds still eases a future M9 revival but L7 adds **no** + slot-numbered capture/insert actions and **no** MIDI bindings. The "plain vs. M9-shaped" + sub-fork is closed: plain gap-preserving substrate (F2). +- **Build-time choices confirmed:** `TimeMap_GetTimeSigAtTime` confirmed at build for the + meter stamp; SWELL stock cursors chosen (no custom cursor load/synthesis required); + gap navigation = skip gaps (arrow keys skip empty slots); same-slot reorder = no-op. +- New CTest targets `card_drag_tests`, `card_meta_tests`. diff --git a/CONTEXT.md b/CONTEXT.md index 07b0cb5..3da3e09 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1688,9 +1688,8 @@ id→slot map) is settled at build; the *contract* below holds either way. slot** (append). It never fills an earlier gap automatically — a gap is a user's deliberate layout, not a hole to be plugged. - **Delete / remove / prune:** removing a sample **leaves its slot empty** (does not re-pack) so - every other sample keeps its position. Define at build whether a purely-empty trailing tail is - trimmed for content-height purposes (the recommendation: trim trailing empties for scroll - extent, keep interior gaps). + every other sample keeps its position. Trailing empty tail is trimmed for scroll-extent + purposes; interior gaps are preserved (confirmed at build). - **Reorder:** the user drags a card to a target slot within its bank; the pure reorder mutator moves that sample's position to the target slot, gap-preserving. **Drop-into-empty-slot places there; drop-onto-occupied inserts-before and shifts the tail** (matching common file-manager @@ -1739,8 +1738,8 @@ kit draw is shell. requires a **tempo + time-signature** reference. `Sample` already carries `captureTempo` (BPM at capture) and `lengthBeats`, but **no time-signature.** **Decision: capture-time stamp** — add `captureTimeSigNum` / `captureTimeSigDenom` to `Sample` + its JSON round-trip, stamped on the -capture path (read the project meter at capture via the REAPER meter API — verify -`TimeMap_GetTimeSigAtTime` / equivalent against the SDK at build). bars.beats.subdivisions renders +capture path (read the project meter at capture via `TimeMap_GetTimeSigAtTime` — confirmed +at build against the SDK). bars.beats.subdivisions renders from the stamped tempo + meter, **stable under later project tempo/meter changes** — a bank sample outlives the project state it was captured under, matching the existing `captureTempo` stamp philosophy. This is a **capture-path write beyond draw work** (its own checkbox in the PLAN, now @@ -1812,8 +1811,8 @@ precisely: pool's rules forbid. The consistent rule the model enforces: **the replace's index-removal step is the same operation as remove-from-bank, and it must pass the same pool-privilege guard that remove already applies — if remove-from-pool would be rejected for that sample, Alt+Replace over - it is rejected too** (the drop falls back to a no-op or the default insert-shift; settle the - exact rejection UX at build). No special pool-only replace path; one rule, guarded by the + it is rejected too** (the drop falls back to a no-op; the pool-privilege guard is reused + as-is — confirmed at build). No special pool-only replace path; one rule, guarded by the existing pool invariants. **Drop-result cursor cues (SETTLED 2026-07-27 — REAPER-idiomatic special cursors).** During a @@ -1828,12 +1827,13 @@ distinct action cursors. The cue set: occupied slot** (i.e. only when precedence resolves to the Alt+replace case). It must not appear over an empty slot or when Alt is not held. -**Shell mechanism:** the shell sets the cursor via Win32/SWELL `SetCursor` (REAPER ships its own -action-cursor idiom as the visual reference — match its feel; load or synthesize kit-consistent -cursors at build). **The *decision* of which cue applies stays in the pure gesture/disambiguation -helper** — the same helper that resolves precedence returns the resolved gesture (reorder / move / -copy / os-drag-out / replace), and the shell maps that pure result to a cursor. No cue logic in -the shell; the shell only owns the `SetCursor` call and the cursor resources. +**Shell mechanism:** the shell sets the cursor via Win32/SWELL `SetCursor`. SWELL stock cursors +were chosen at build (Reorder→`IDC_SIZEALL`, Move→`IDC_HAND`, Copy→`IDC_UPARROW`, +Replace→`IDC_SIZEWE`; no custom cursor load/synthesis required). **The *decision* of which cue +applies stays in the pure gesture/disambiguation helper** — the same helper that resolves +precedence returns the resolved gesture (reorder / move / copy / os-drag-out / replace), and the +shell maps that pure result to a cursor. No cue logic in the shell; the shell only owns the +`SetCursor` call and the cursor resources. ### Pure/shell discipline (L7) @@ -1856,8 +1856,8 @@ editor (`IPlugView` LICE surface), the S6 embed strip, and the keyboard strip li Phase S, which is **not on dev yet** (it exists on the phase-s worktree). L3 cannot be built on dev until Phase S's editor/embed surfaces (≈ S1 / S6 / S10) merge to dev — **the Phase L team must not chase these files on dev; they are not there.** Until then L3 is a -planned, blocked point; L1, L2, L4, and **L5 have all landed** — see `COMPLETED.md`. L4 and L5 -are independent of the L3 gate. +planned, blocked point; L1, L2, L4, L5, L6, and **L7 have all landed** — see `COMPLETED.md`. +L4, L5, L6, and L7 are independent of the L3 gate. **Coordination contract (load-bearing):** Phase S's S10–S13 build their interaction UX with the **current** drawing and **adopt the L1 kit when it is available — they are NOT gated on diff --git a/PLAN.md b/PLAN.md index 6b40444..65dbea1 100644 --- a/PLAN.md +++ b/PLAN.md @@ -285,7 +285,8 @@ build:** the `IReaperUIEmbedInterface` contract + embed message/lifecycle agains > moves it to `COMPLETED.md`. > > **L1 (shared LICE drawing kit — the foundation), L2 (dock-panel layout redesign), L4 -> (dock-panel button layout enhancement), and L5 (dock-panel button refinements) have landed** — +> (dock-panel button layout enhancement), L5 (dock-panel button refinements), L6 (toolbar +> polish), and L7 (capture ordering, card metadata, and selection styling) have landed** — > `theme`/palette module, `component_geometry` geometry/hit-test helpers, `draw_kit` shell, GDI > `DrawText` retirement in `bank_panel` (L1); `action_bar` pure task-grouped layout module, full > M11-aware button inventory placed by task cluster, `bank_panel` redesigned through the L1 kit @@ -294,8 +295,11 @@ build:** the `IReaperUIEmbedInterface` contract + embed message/lifecycle agains > `Switching` in `action_bar` (L4); top-bar overflow menu (`overflow_menu` pure module), > custom LICE-kit hover-delay tooltips (`tooltip` pure module), opposite-mode Item/Track tag > buttons + Show Both, Toggle + Activate-Arrange/Design buttons removed, grouping spacing -> widened (`mode_enable` pure module) (L5). See `COMPLETED.md`. **L3 remains** — L3 gated on -> Phase S landing on dev. +> widened (`mode_enable` pure module) (L5); single-row button faces, keybinding in tooltip, +> Cancel RT moved to overflow, top-bar cluster order tidied (L6); per-bank `SlotMap` +> (id→slot) in `bank_book`, sparse-grid rendering, `card_drag` + `card_meta` pure modules, +> `captureTimeSigNum`/`captureTimeSigDenom` on `Sample`, tertiary-border selection (L7). See +> `COMPLETED.md`. **L3 remains** — L3 gated on Phase S landing on dev. > > **This section is self-contained for a team without Phase S context.** Where a point > touches a Phase S surface (the VST editor, the embed strip, the keyboard strip), the @@ -372,139 +376,6 @@ work. state — never a pulse, per the speed constraint) as the signature surface; VST3 class UID unchanged. (Gated.) -## L7 — capture ordering, card metadata, and selection styling (ungated, after L6) -**Goal:** Three grid-facing improvements to the dock panel, drawn through the L1 kit in the -settled DS-2 palette. (1) **Persisted deterministic capture order + drag-drop reorder + sparse -placement:** each bank (and the pool) carries an explicit, persisted per-sample order, changed -by dragging cards within the grid; a card may sit in a grid slot that leaves gaps (an empty -first row above an occupied second row). (2) **Decorative metadata over the peaks:** each card -overlays capture length as **bars.beats.subdivisions in the bottom-LEFT** and **seconds.ms in -the bottom-RIGHT**, subtle/legible over the waveform in the kit's micro / value-mono type -classes. (3) **Selection restyle:** a selected card drops the inverted accent-fill and instead -draws the *normal* cell + an **`accent/tertiary` (pastel purple `#C2AAE8`) border**; hover, -focus, and drag-highlight stay visually distinct. **No new capture/placement behavior** — this -is model + layout + draw only; the "capture ≠ placement" principle is untouched. - -> **This is a persisted-model change, not pure layout.** Feature (1) adds an explicit -> per-sample ordinal/slot to the persisted state and its JSON round-trip + a migration for -> existing projects — scoped honestly below, model work separated from draw work. - -> **Forks — all SETTLED 2026-07-27 (see CONTEXT.md §L7 for full detail):** -> - **F1 — SETTLED: capture-time stamp.** Add `captureTimeSigNum` / `captureTimeSigDenom` to -> `Sample`, stamped on the capture path alongside the existing `captureTempo`; -> bars.beats.subdivisions renders from the stamped tempo + meter, stable under later project -> changes. Live-meter-at-draw-time rejected (would drift). The capture-path stamp checkbox -> below is **settled, not flagged.** -> - **F2 — SETTLED: plain interchangeable-slot substrate, NOT fixed MPC-style slots.** The -> carrier is a **per-bank id→slot map** (gap-preserving interchangeable slots): a slot is just a -> display position an occupant sits in — **no slot identities, no numbered/addressable slots, no -> slot actions, no MIDI-bindable slot numbers, no capture-to-slot-N.** The "plain vs. M9-shaped" -> sub-fork is closed (plain). M9 (capture-to-slot-N / insert-slot-N, MIDI-bindable, MPC-style) -> stays **explicitly deferred (Daniel, 2026-07-26);** the substrate still eases a future M9 -> revival but L7 adds no M9 actions or bindings. -> - **F3 — SETTLED + EXTENDED.** Drag precedence: leave-client → OS drag-out; else tab/other-bank -> → move/copy (Ctrl = copy); else same-bank grid → reorder-to-slot. **Extensions:** -> (a) **drop-result cursor cues** — the cursor indicates the drop result via REAPER-idiomatic -> special cursors (reorder / move / copy / OS-drag-out / replace); the shell sets it via -> Win32/SWELL `SetCursor`, but the *decision* of which cue applies stays in the pure gesture -> helper. (b) **Alt+drop-onto-occupied = REPLACE** — default occupied-slot drop stays -> insert-and-shift; holding Alt replaces the occupant (index-only removal, existing -> remove-from-bank semantics — file stays on disk, prune governs bytes, `hashReferencedElsewhere` -> handles last-reference). **Pool rule:** Alt+Replace in the un-evacuable pool is allowed only -> when the same pool-privilege guard that `remove` applies would permit removing that sample — -> one rule, no pool-only path; the replace cursor cue shows only when Alt is actually held over -> an occupied slot. - -**Verify (in DAW):** capture several samples; drag a card to a new position within its bank — -the order persists across project save/reload and Ctrl-Z restores the prior order; drag the -first card below the second so the first row is empty — the gap holds across reload; each card -shows bars.beats.subdivisions bottom-left and s.ms bottom-right, legible over the peaks; a -selected card shows the normal cell with a purple (`accent/tertiary`) border — no inversion — -and hover / focus / drag-target remain distinct; during any drag the cursor shows the correct -special cue (reorder within the bank, move/copy over a tab/other bank, OS drag-out past the client -edge); Alt+drop over an occupied card **replaces** the occupant (the replaced sample leaves that -bank's grid but its file survives on disk; if it was the last reference, prune later reclaims it), -and the replace cursor appears only while Alt is held over an occupied slot; dragging a card to -another bank's tab still moves/copies (not reorder), and dragging out of the panel still starts an -OS file drag; an existing project (saved before L7) loads cleanly with cards in their prior -insertion order. -**Depends on:** L1 (the kit) for all draw; sequences **after L6** (both rework the same -`bank_panel` grid). **Ungated by Phase S** (the dock panel is on dev); independent of the L3 -gate. No `capture` / `insert` behavior change; no palette/font decision re-opened. - -**(a) Persisted order + sparse-position model (pure, hard-tested) — model work, separate from draw:** -- [ ] Add an explicit per-sample **display position** to the persisted bank state — a - **per-`Bank` id→slot map in `bank_book`** (gap-preserving interchangeable slots, per F2 - settled). `bank_model` / `Sample` stay untouched (position is a per-bank display concern, not a - `Sample` property — a copy of a sample into two banks may sit at different slots). **No slot - identities / numbered addressable slots** — a slot is just a position an occupant sits in. -- [ ] JSON round-trip for the position data (`serialize`/`deserialize` remain lossless: - `deserialize(serialize(x)) == x` including positions). -- [ ] **Migration for existing projects** (old blobs with no position data): load cleanly with a - **default = current insertion order, densely packed** (no gaps). Spec the default explicitly so - a pre-L7 project is visually identical on first load post-L7. -- [ ] **Gap semantics on insert / delete / prune** (pure, tested): a new capture takes the next - free slot after the last occupied one (append); a delete/remove/prune **leaves a gap** (does - not re-pack) so positions stay stable. **Confirmed defaults:** trailing empty tail trimmed for - scroll-extent purposes; interior gaps preserved. -- [ ] **Reorder mutator** (pure): move sample `id` to target slot within its bank, gap-preserving; - deterministic; unit-tested. Drop-into-empty places there; **drop-onto-occupied (default, no - modifier) inserts-before-and-shifts-tail.** -- [ ] **Alt-replace mutator** (pure): Alt+drop over an occupied slot **replaces the occupant** — - index-only removal of the occupant from THAT bank (reuse the existing remove-from-bank - semantics: file stays on disk, owned-manifest/prune govern bytes, `hashReferencedElsewhere` - handles the last-reference case), then the dragged sample takes the vacated slot (position - preserved). **Pool guard:** if the sample sits in the un-evacuable pool, the replace's - index-removal step passes the **same pool-privilege guard that `remove` applies** — if - remove-from-pool would be rejected for that sample, Alt+Replace over it is rejected (falls back - to no-op / default insert-shift; settle rejection UX at build). One rule, no pool-only path. - Deterministic; unit-tested. **No new deletion authority — never touches disk.** -- [ ] Undo integration: a reorder **or a replace** is one Ctrl-Z (batched undo point in the - actions/shell layer, matching the existing bank-verb undo discipline). - -**(b) Reorder-drag gesture + slot hit-test (pure geometry + shell wiring):** -- [ ] **Sparse-aware grid layout** (pure, extend `bank_grid`): map per-slot positions (with gaps) - to `CellRect`s — an empty slot is drawn empty (or skipped), not back-filled. Slot ↔ pixel-rect - math + point→slot hit-test stay pure and unit-tested (mirror `mode_switch`/`bank_grid`). -- [ ] **Drag-disambiguation** (F3): drop within the same bank's grid = reorder-to-slot (empty = - place; occupied+no-modifier = insert-shift; **occupied+Alt = replace**); drop on a tab / other - bank = move/copy (unchanged, Ctrl = copy); leave client = OS drag-out (unchanged). Precedence - encoded in a **pure decision helper** (mirror `drag_out::decideGesture`); shell reads live - pointer + region + client rect + **modifier state (Alt)** and calls it. The helper returns the - **resolved gesture** (reorder / move / copy / os-drag-out / replace) so the shell can map it to - a cursor cue. -- [ ] **Drop-result cursor cues** (F3 extension): the shell sets a REAPER-idiomatic special cursor - per the pure resolved-gesture (reorder / move / copy / OS-drag-out / replace) via Win32/SWELL - `SetCursor`; the **replace** cursor shows only when Alt is actually held over an occupied slot. - **Cue-decision logic stays in the pure helper;** the shell owns only the `SetCursor` call and - the cursor resources (load/synthesize kit-consistent cursors at build; REAPER's own action - cursors as the visual reference). -- [ ] Shell wiring: on a same-bank reorder drop, resolve the target slot via the pure hit-test and - call the reorder mutator (or, when Alt is held over an occupied slot, the **Alt-replace - mutator**); drop-target slot highlight during the drag (distinct from the existing region/tab - drop highlights, and — for a pending replace — distinct from the pending-reorder highlight). - -**(c) Metadata overlay (pure formatting + kit draw):** -- [ ] **Pure formatters, unit-tested:** `bars.beats.subdivisions` from `lengthSeconds` + - `captureTempo` + capture-time signature (F1) and `seconds.milliseconds` from `lengthSeconds`. - Deterministic string output; edge cases (zero length, missing tempo → graceful blank/fallback). -- [ ] **Capture-time signature stamp (F1, settled — capture-path write beyond draw work):** add - `captureTimeSigNum` / `captureTimeSigDenom` to `Sample` + its JSON round-trip, stamped on the - capture path (read the project meter at capture — verify `TimeMap_GetTimeSigAtTime` / equivalent - against the SDK at build). Old samples with no stamp fall back gracefully in the formatter - (blank musical read-out, or a documented assumed 4/4). -- [ ] **Kit draw:** overlay the two strings on the card — bars.beats bottom-LEFT, s.ms - bottom-RIGHT — via the kit `text()` in the micro / value-mono class, `text/dim` (or a subtle - shadowed variant for legibility over the waveform); **decorative, non-interactive** (no - hit-test, no hover). Respects the speed constraint (no animation). - -**(d) Selection restyle (tertiary border):** -- [ ] `drawThumbnail`: a selected card draws the **normal cell fill** (Rest/Hover surface, not - `InteractionState::Active`) + an **`accent/tertiary` border** (pastel purple `#C2AAE8`); the - waveform draws in its normal accent color (drop the inverted `bg/base` wave). Focus ring, hover - state, and drag-target highlight stay **distinct** from the selection border (keep them - coherent — spec the four states' visual separation in CONTEXT.md §L7). - ## Phase L — sequencing ``` L1 (shared kit) ──► L2 (dock-panel layout redesign) [LANDED] @@ -512,10 +383,10 @@ L1 (shared kit) ──► L2 (dock-panel layout redesign) [LANDED] └─────────► L4 (dock-panel button layout enhancement) [LANDED] └────► L5 (dock-panel button refinements; ungated, after L4) [LANDED] └────► L6 (toolbar polish; ungated, after L5) [LANDED] - └────► L7 (capture ordering + card metadata + selection styling; ungated, after L6) + └────► L7 (capture ordering + card metadata + selection styling; ungated, after L6) [LANDED] ``` -L1, L2, L4, L5, and L6 have landed. L7 is the next ungated dock-panel work. L3 waits for -Phase S to reach dev. Phase S feature work runs in parallel, ungated by Phase L. +L1, L2, L4, L5, L6, and L7 have all landed. L3 waits for Phase S to reach dev. Phase S +feature work runs in parallel, ungated by Phase L. ## Phase L — must-verify-before-build - **LICE design-kit surfaces (L1)** — `LICE_GradRect`, `LICE_RoundRect`, AA @@ -540,16 +411,11 @@ Phase S to reach dev. Phase S feature work runs in parallel, ungated by Phase L. pure module; sourced from the registered action phrase, prefix stripped at draw time); item-move and track-tag action ids confirmed; active-mode read confirmed via the same `view().activeModeId()` the footer toggle uses. -- **L7 model + tempo surface (L7)** — forks F1/F2/F3 **all settled 2026-07-27** (see the §L7 - forks block above and CONTEXT.md §L7): position carrier = per-`Bank` id→slot map in `bank_book` - (plain interchangeable substrate, `bank_model`/`Sample` untouched); F1 = capture-time signature - stamp; F3 = drag precedence + REAPER-idiomatic cursor cues + Alt-replace-over-occupied with the - stated pool guard. **Remaining build-time verification (SDK/API, not product decisions):** - (1) the REAPER meter-read API for the F1 stamp (`TimeMap_GetTimeSigAtTime` or equivalent) — - verify signature/argument order against the SDK; (2) the Win32/SWELL `SetCursor` path and cursor - resource loading/synthesis for the drop-result cues — confirm the cursor idiom against SWELL and - REAPER's own action cursors; (3) confirm the existing remove-from-bank pool-privilege guard is - reusable as-is for the Alt-replace index-removal step. +- **L7 model + tempo surface (L7)** — **RESOLVED at build (L7 landed).** Forks F1/F2/F3 all + confirmed: `TimeMap_GetTimeSigAtTime` confirmed at build for the meter stamp; SWELL stock + cursors chosen for drop-result cues (Reorder→IDC_SIZEALL, Move→IDC_HAND, Copy→IDC_UPARROW, + Replace→IDC_SIZEWE); existing pool-privilege guard reused as-is for Alt-replace. Gap navigation + in the grid = skip gaps (arrow keys skip empty slots). See `COMPLETED.md` §L7. ---