docs: spec Phase Q (Quality) — structural reorganization pillar
Product framing (docs/product/code-organization.md), PLAN.md $Phase Q (Q-W1..Q-W6), CONTEXT.md $Phase Q. Gated on tree quiescence; zero-hot-path-cost acceptance bar. Namespace Q settled; Q-2..Q-6 carry recommendations.
This commit is contained in:
@@ -406,3 +406,234 @@ for Phase S to reach dev. Phase S feature work runs in parallel, ungated by Phas
|
||||
- **M11 button inventory (L2)** — L2's layout depends on the *actual* set of buttons M11
|
||||
lands (action-trigger buttons + keybinding-help labels). Re-inventory against dev after
|
||||
M11 merges before finalizing the layout.
|
||||
|
||||
---
|
||||
|
||||
# Phase Q — Quality (structural reorganization; zero-runtime-cost)
|
||||
|
||||
> **New pillar, own lettered namespace, and — uniquely — the LAST structural pillar.** Phase
|
||||
> Q is a **pure structural refactor**: it reorganizes `src/` into a healthier shape (more
|
||||
> encapsulation, granular namespaces, `core/`/`shell/`/`app/` subdirectories) against a stated
|
||||
> quality bar — *"mtytel Vital is my code reference for quality"* — to bring the codebase
|
||||
> "into the realm of something I can stand to look at." It ships **no feature and changes no
|
||||
> behavior**: the test suite passing unchanged is the proof of correctness. Namespaced **`Q`
|
||||
> (Quality)** — M/D/B/R/V/S/L are all taken; `Q` names the *end* (the quality bar), the reorg
|
||||
> being the *means*. Authoritative spec: **CONTEXT.md §Phase Q — structural reorganization
|
||||
> (reorg spec)**. Product framing, the Vital-grounded target shape, the grep-verified SOLID
|
||||
> audit that is the evidence base, and the settled/recommended fork record (Q-1..Q-6):
|
||||
> `docs/product/code-organization.md`. When a point lands, doc-keeper moves it to
|
||||
> `COMPLETED.md`.
|
||||
>
|
||||
> **THE GATE (load-bearing — state first).** Phase Q is **gated on the tree being otherwise
|
||||
> quiescent.** It does not begin until **Phase S has merged to dev**, **Phase L (L2 + L3) has
|
||||
> merged to dev**, any **D2 residuals** are closed, and **M9** is landed-or-abandoned. *Why:*
|
||||
> Phase Q touches **nearly every file in `src/`** (relocate into subdirectories, re-namespace
|
||||
> every header, split the four largest TUs). Every large in-flight branch (Phase S on its
|
||||
> worktree, Phase L's `bank_panel`-touching L2/L3) is diffed against the *current flat layout*;
|
||||
> landing a rename-and-relocate-everything reorg mid-flight forces every open branch through the
|
||||
> worst conflict class (every hunk moved, every qualified reference changed) — a combinatorial
|
||||
> re-resolution, not a linear one. Phase Q is *last* precisely because it reshapes the ground
|
||||
> every other pillar stands on. Landing it early taxes every subsequent phase; landing it last
|
||||
> taxes nothing. **Do not begin any Q point until the gate is satisfied.**
|
||||
>
|
||||
> **Settled (Q-1, this-doc):** the phase is **`Q` (Quality)**; point-id family `Q1..Qn`, wave
|
||||
> prefixes `Q-W1..Q-W6`. **Recommended, Daniel's to call (Q-2..Q-6, see
|
||||
> `docs/product/code-organization.md` §6):** Q-2 JSON extraction in scope + first (rec: yes);
|
||||
> Q-3 directory shape `core/`/`shell/`/`app/` top-split with subsystem dirs beneath (rec: this
|
||||
> over pure-Vital subsystem-first — it makes the pure/shell invariant *structural*); Q-4
|
||||
> sub-namespace to match sub-directory (rec: both); Q-5 split god-modules to the audit's named
|
||||
> seams, no finer (rec: yes); Q-6 OCP registration-table as the final wave (rec: in, last).
|
||||
>
|
||||
> **HARD CONSTRAINT — performance (see CONTEXT.md §Phase Q, `docs/product/code-organization.md`
|
||||
> §3).** The reorg must cost **zero runtime.** On the three hot paths — `peaks` envelope
|
||||
> compute, audition/preview, the realtime-capture tick — **no added virtual dispatch, no
|
||||
> header→TU indirection, no changed call/inline or branch shape.** `computeEnvelope` stays a
|
||||
> free function on `const std::vector<float>&`; audition split stays a direct call-through;
|
||||
> the realtime idle tick stays a single pointer test; `FxBypassGuard` stays stack RAII. This is
|
||||
> an acceptance criterion on every point: *a split that would add a hot-path indirection is out
|
||||
> of scope — rework it or drop it.*
|
||||
>
|
||||
> **Every point is independently landable and CTest-green at EVERY step.** The CMake
|
||||
> per-module static-lib + per-module test-executable seams already draw the module boundaries;
|
||||
> a file move + namespace change is mechanically verifiable — `ctest --test-dir build` is green
|
||||
> or it isn't. **Green-CTest-at-every-point is an acceptance criterion.** Big-bang is rejected;
|
||||
> the reorg is risk-ordered waves (W1 safe opener → W2–W5 god-module splits → W6 OCP finish).
|
||||
|
||||
## Q-W1 — safe opener: extract `core/json` + impose the directory/namespace layout on clean modules
|
||||
**Goal:** The zero-god-module-risk opener. Two moves: (1) extract a pure **`core/json`** module
|
||||
(parser + serializer) and **delete the four hand-rolled `Parser`s** in `bank_model` /
|
||||
`bank_book` / `view_mode_model` / `owned_manifest` (the single largest DRY+SRP violation, and
|
||||
entirely off the hot paths); (2) impose the settled `core/`/`shell/`/`app/` directory layout +
|
||||
sub-namespaces (`reasampler::model`/`view`/`capture`/`audio`/`ui`/`reclaim`/`version`/`json`) on
|
||||
the **30 clean pure libs + the clean shells that need no splitting** — pure relocation, no logic
|
||||
change. Proves the wave discipline (relocate + encapsulate, CTest-green) before any god-module
|
||||
surgery. CONTEXT.md §Phase Q (json extraction; directory + namespace map).
|
||||
**Verify:** CTest green at every commit. The four duplicate `Parser`s are gone, replaced by one
|
||||
`core/json` consumed by all four models; round-trip serialization is byte-identical to before
|
||||
(no format change — a *structural* dedupe, not a behavior change). Every relocated clean module
|
||||
compiles and its test executable passes unmoved. `Sample` (model) vs `AudioSample` (audio) vs
|
||||
unified `Parser` (json) do not collide once sub-namespaced. No REAPER type crosses into any
|
||||
`core/` file; the CMake pure/shell enforcement still holds.
|
||||
**Depends on:** the GATE (tree quiescent). Nothing else in Phase Q.
|
||||
|
||||
- [ ] Extract `core/json` (pure parser + serializer: parseString/parseInt/parseKey/skipValue +
|
||||
escape, plus emit helpers); unify under `reasampler::json`; guard the `Parser` name against
|
||||
cross-lib collision. Off all hot paths — safe to abstract freely.
|
||||
- [ ] Rewire `bank_model`, `bank_book`, `view_mode_model`, `owned_manifest` onto `core/json`;
|
||||
**delete the four duplicate `Parser`s.** Round-trip output byte-identical (dedupe, not
|
||||
reformat).
|
||||
- [ ] Relocate the 30 clean pure libs into `core/{model,view,capture,audio,ui,reclaim,version,
|
||||
json}/` and the clean shells into `shell/{capture,panel,view,persist,actions}/`; move
|
||||
`main.cpp` to `app/`. Update `CMakeLists.txt` `src/` paths only (no target-graph change).
|
||||
- [ ] Apply sub-namespaces matching the directories on every relocated *clean* module (the
|
||||
god-modules re-namespace their own new TUs as they split, W2–W5). Resolve `Sample`/
|
||||
`AudioSample`/`Parser` homes.
|
||||
- [ ] Confirm CTest green + no hot-path change: `peaks`/audition/realtime-tick untouched by this
|
||||
wave (pure relocation of clean modules; `peaks` stays a free function).
|
||||
|
||||
## Q-W2 — split `bank_panel.cpp` (the biggest god-module, 2424 LOC)
|
||||
**Goal:** Split the largest god-module (8+ responsibilities) along the audit's named seams:
|
||||
`panel_render` / `panel_thumbnails` / `panel_audition` / `panel_input` / `panel_bank_ops` /
|
||||
`panel_window`. Split the fat `bank_panel.h` alongside (Interface Segregation). **Preserve the
|
||||
audition hot path as a direct call-through, never virtual.** `panel_bank_ops` becomes the single
|
||||
home for the bank-CRUD verbs that W4 will dedupe `actions.cpp` against. CONTEXT.md §Phase Q
|
||||
(bank_panel split seams; hot-path audition guardrail). See `docs/product/code-organization.md`
|
||||
§2.1, §5.
|
||||
**Verify:** CTest green at every commit. Each seam is its own TU under `shell/panel/`; the panel
|
||||
draws, thumbnails, auditions, handles input, does bank ops, and manages its window exactly as
|
||||
before (no behavior change — verify in DAW that the panel is visually and interactively
|
||||
unchanged). Audition/preview call path stays a **direct call-through** (no virtual dispatch, no
|
||||
added header→TU indirection on the preview path). The ~20-function public API is now segmented
|
||||
across the split headers.
|
||||
**Depends on:** Q-W1 (directory/namespace layout established). Independently landable.
|
||||
|
||||
- [ ] Split rendering (`draw*`/`paint*`) → `panel_render`; thumbnail compute+cache →
|
||||
`panel_thumbnails`.
|
||||
- [ ] Split the audio audition/preview engine → `panel_audition` — **direct call-through, not
|
||||
virtual; preview idle path unchanged.**
|
||||
- [ ] Split input handling (mouse/key/wheel) + new-content detection → `panel_input`; window
|
||||
lifecycle + OS drag-out/drop-target → `panel_window`.
|
||||
- [ ] Extract bank-CRUD verbs → `panel_bank_ops` (the future single owner; W4 dedupes
|
||||
`actions.cpp` against it). Split `bank_panel.h` into per-seam headers (I).
|
||||
- [ ] Verify in DAW: panel unchanged; CTest green; no hot-path indirection added.
|
||||
|
||||
## Q-W3 — split `main.cpp` (hoist orchestration; leave main = pointers + entry + dispatch)
|
||||
**Goal:** Reduce `main.cpp` (1762 LOC) to its actual job — API pointers + `ReaperPluginEntry` +
|
||||
dispatch (~the owns-pointers ~120 lines) — by hoisting: `capture_orchestrator` (`RunCapture` /
|
||||
`captureAndIndexOne` / `renderOffline` / batch/recapture/realtime `Run*`), `scope_resolve`
|
||||
(`resolveRange`/`resolveRazorRange`/`collectSelectedTracks` + provenance assembly inputs), and
|
||||
`realtime_lifecycle` (the realtime-capture state machine + globals + selection guards).
|
||||
**`FxBypassGuard` moves out but stays a stack RAII object (precision-critical); the realtime idle
|
||||
tick stays a single pointer test.** CONTEXT.md §Phase Q (main split seams; FxBypassGuard +
|
||||
realtime-tick guardrails). See `docs/product/code-organization.md` §2.1, §3.
|
||||
**Verify:** CTest green at every commit. Capture (offline + realtime + batch + recapture) behaves
|
||||
identically in DAW; the null test still nulls, bit-identical repeats still match (the precision
|
||||
invariants `FxBypassGuard` protects are unchanged); capture ≠ placement holds (no hoisted `Run*`
|
||||
path gains an `InsertMedia` call). The realtime idle fast-path is still a single pointer test.
|
||||
`main.cpp` is now pointers + entry + dispatch only.
|
||||
**Depends on:** Q-W1. Independent of Q-W2.
|
||||
|
||||
- [ ] Hoist capture orchestration → `capture_orchestrator` (`shell/capture/`); keep
|
||||
`FxBypassGuard` a **stack RAII** object as it moves (precision-invariant-critical).
|
||||
- [ ] Hoist scope/source resolution + provenance assembly inputs → `scope_resolve`.
|
||||
- [ ] Hoist the realtime-capture lifecycle state machine + globals + the two RAII selection
|
||||
guards → `realtime_lifecycle`; **idle tick stays a single pointer test.**
|
||||
- [ ] Leave `main.cpp` = API-pointer ownership + `ReaperPluginEntry` + dispatch; move to `app/`.
|
||||
- [ ] Verify in DAW: null test nulls, bit-identical repeats match, capture≠placement holds;
|
||||
CTest green; no realtime-tick branch-shape change.
|
||||
|
||||
## Q-W4 — split `actions.cpp` + dedupe bank verbs against `panel_bank_ops`
|
||||
**Goal:** Split the two unrelated command-id families in one TU (981 LOC) into
|
||||
`design_view_actions` / `bank_actions` / `prune_action`, and **dedupe** `actions.cpp`'s own
|
||||
`promptText`/`mintBankId` and bank verbs against the `panel_bank_ops` single-owner established in
|
||||
Q-W2. `prune_action` keeps the `doBankPruneFolder` deletion authority contract intact (routes to
|
||||
`persist`'s `prune_fs` after W5). CONTEXT.md §Phase Q (actions split seams; bank-verb dedupe).
|
||||
See `docs/product/code-organization.md` §2.1, §2.4.
|
||||
**Verify:** CTest green at every commit. Every action fires identically in DAW (Design View
|
||||
family; multi-bank create/rename/reorder/delete/evacuate/activate/move/copy/remove; prune). The
|
||||
bank-CRUD verbs have **one** implementation home (no `bank_panel`/`actions` duplication). Each
|
||||
bank verb still wraps its mutation in one batched undo point; the prune action still writes no
|
||||
ext state and opens no undo point. Command-id strings are **unchanged** (FOREVER-STABLE
|
||||
contract — a reorg must not touch a shipped command id).
|
||||
**Depends on:** Q-W2 (`panel_bank_ops` is the dedupe target). Independent of Q-W3.
|
||||
|
||||
- [ ] Split → `design_view_actions` (toggle/activate/tag/untag/showBoth/moveItems),
|
||||
`bank_actions` (bank CRUD family), `prune_action` (`doBankPruneFolder` — the single
|
||||
file-deletion action).
|
||||
- [ ] Dedupe `actions.cpp`'s `promptText`/`mintBankId` + bank verbs against `panel_bank_ops`
|
||||
(one owner); do **not** change any command-id string.
|
||||
- [ ] Verify in DAW: all action families fire unchanged; one bank op = one Ctrl-Z; prune still
|
||||
no-undo/no-ext-state; CTest green.
|
||||
|
||||
## Q-W5 — split `persist.cpp` (isolate the single file-deletion authority into `prune_fs`)
|
||||
**Goal:** Split `persist.cpp` (766 LOC, 5 responsibilities) into `session` (lifecycle+poll,
|
||||
`BeginLoadProjectState` reload hook), `ext_state_io` (the ext-state ↔ JSON serialization bridge +
|
||||
GUID minting + folder relocation), and **`prune_fs`** (prune scanning + `deleteOrphanFile` via
|
||||
`SHFileOperationW`). The split **concentrates** the byte-deleting authority into one obvious
|
||||
module — it must never spread it. CONTEXT.md §Phase Q (persist split seams; deletion-authority
|
||||
isolation). See `docs/product/code-organization.md` §2.1, §7.
|
||||
**Verify:** CTest green at every commit. Session save/load/undo-reload, ext-state round-trip,
|
||||
folder relocation, and prune deletion all behave identically in DAW. **File deletion lives in
|
||||
exactly one module (`prune_fs`)** — the single-file-deletion-authority invariant is *improved*
|
||||
(concentrated), never diluted. Relative-paths-only persistence is unchanged.
|
||||
**Depends on:** Q-W1. Best after Q-W4 (so `prune_action` routes cleanly to `prune_fs`), but
|
||||
independently landable.
|
||||
|
||||
- [ ] Split → `session` (lifecycle/poll + `projectconfig` reload hook), `ext_state_io`
|
||||
(serialization bridge + GUID minting + folder relocation).
|
||||
- [ ] Isolate prune scanning + `deleteOrphanFile` (`SHFileOperationW`) → **`prune_fs`** — the
|
||||
one file-deletion module; nothing else may delete bytes.
|
||||
- [ ] Verify in DAW: save/load/undo-reload/relocation/prune unchanged; deletion authority is one
|
||||
module; relative-paths-only holds; CTest green.
|
||||
|
||||
## Q-W6 — OCP registration-table + residual fat-header (I) splits
|
||||
**Goal:** Close the last SOLID wart: replace the ~350-line hand-written **non-table** action
|
||||
registration blocks (now isolated in `app/main.cpp` after Q-W3) with a **registration table**, so
|
||||
adding an action edits one place, not four parallel ones (OCP). Split any remaining fat headers
|
||||
(`capture.h`/`persist.h`) not already resolved by their TU splits (I). CONTEXT.md §Phase Q (OCP
|
||||
registration-table). See `docs/product/code-organization.md` §2.3, §6 (Q-6).
|
||||
**Verify:** CTest green at every commit. Every action still registers, appears in the Actions
|
||||
list, and fires via `hookcommand` exactly as before; command-id + display strings unchanged
|
||||
(FOREVER-STABLE, per-channel); unload still mirror-unregisters everything. Adding a hypothetical
|
||||
new action now touches the table only (demonstrated in review, not shipped). Remaining fat
|
||||
headers are segmented.
|
||||
**Depends on:** Q-W3 (registration code must be isolated first). Sequenced last; the most
|
||||
droppable point if the phase needs narrowing (Q-6).
|
||||
|
||||
- [ ] Convert the hand-written `Register("command_id"/"gaccel"/"hookcommand")` blocks to a
|
||||
data-driven registration table; unload mirror-unregisters from the same table.
|
||||
- [ ] Split residual fat headers (`capture.h`/`persist.h` and any other) alongside their TUs (I).
|
||||
- [ ] Verify: all actions register/fire/unregister unchanged; command-id strings untouched; CTest
|
||||
green.
|
||||
|
||||
## Phase Q — sequencing
|
||||
```
|
||||
GATE: Phase S + Phase L (L2+L3) + D2 residuals + M9 all merged/closed to dev (tree quiescent)
|
||||
│
|
||||
▼
|
||||
Q-W1 (safe opener: core/json extract + directory/namespace layout on clean modules)
|
||||
├─► Q-W2 (split bank_panel) ──► Q-W4 (split actions + dedupe bank verbs vs panel_bank_ops)
|
||||
├─► Q-W3 (split main.cpp; hoist orchestration) ──► Q-W6 (OCP registration-table + I splits)
|
||||
└─► Q-W5 (split persist; isolate prune_fs) [best after Q-W4]
|
||||
```
|
||||
W1 is the safe, high-leverage opener (all later waves assume the layout it establishes). The four
|
||||
god-module splits (W2–W5) are risk-ordered and mostly parallel-safe; W4 depends on W2's
|
||||
`panel_bank_ops`, W6 depends on W3's isolated registration code. Big-bang is rejected — every wave
|
||||
is independently landable and CTest-green.
|
||||
|
||||
## Phase Q — must-verify-before-build
|
||||
- **Hot-path call/inline shape** — before landing each split, confirm no virtual dispatch and no
|
||||
header→TU indirection was added on `peaks` envelope compute, audition/preview, or the realtime
|
||||
tick. `computeEnvelope` stays a free function on `const std::vector<float>&`;
|
||||
audition stays a direct call-through; the idle tick stays a single pointer test. (CONTEXT.md
|
||||
§Phase Q, `docs/product/code-organization.md` §3.)
|
||||
- **Command-id + display strings are FOREVER-STABLE** — a reorg must not change a shipped
|
||||
`command_id` string, action display name, ext-state namespace, or VST3 class UID. Re-namespacing
|
||||
C++ symbols is orthogonal to these on-the-wire/on-disk contract strings; keep them byte-identical
|
||||
(per-channel, per the Phase V V4 contract). (CONTEXT.md §Phase Q.)
|
||||
- **Name-collision sweep on sub-namespacing** — `Sample` (model) vs `AudioSample` (audio) vs the
|
||||
unified `Parser` (json), and any other cross-lib name that collides once flattened into
|
||||
granular namespaces. Resolve by each symbol's new subsystem home before landing W1. (audit §2.4;
|
||||
`docs/product/code-organization.md` §6 Q-4.)
|
||||
- **The GATE** — do not begin any Q point until Phase S + Phase L (L2/L3) + D2 residuals + M9 are
|
||||
merged/closed and the tree is quiescent. Re-confirm quiescence against dev before W1.
|
||||
|
||||
Reference in New Issue
Block a user