Files
Paul Lipscomb f7ac34141a Check in Claude session memory as CLAUDE_MEMORY.md, imported from CLAUDE.md
Auto-memory is normally stored outside the repo, keyed to the working
directory's absolute path, so it doesn't follow a clone to a new machine.
This snapshot travels with the repo instead, so Claude has the same
project/feedback context when opened from a different machine (e.g. the
Windows VM being set up for the extension-reaper-macos port).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 18:55:42 -04:00

232 lines
12 KiB
Markdown

# Carried-over session memory
This file is a portable dump of Claude's per-machine auto-memory for this
project (normally stored outside the repo, keyed to the absolute path of the
working directory — so it doesn't survive a clone to a new machine on its
own). This copy is checked into the repo so it travels with `git clone`/`git
pull` instead of staying stuck on one machine.
Auto-generated 2026-07-22. May drift from live memory over time — treat as a
snapshot, not a live sync.
---
## How Paul wants to collaborate (feedback)
### Confirm before changes
Always talk through the diagnosis/plan and get explicit confirmation before
making code changes — do not implement on the first pass, even for what
looks like a clear-cut bug.
- Stated explicitly (2026-07-06): "from here on out, always talk to me
before we make changes." A standing rule, not one-off.
- Stating a plan and executing it *in the same turn* does not count as
confirming — waiting for an actual reply is required, especially for
open/free-choice sub-decisions (a button label, a variable name, which of
several equally-valid options to pick) that come up mid-task.
- If Paul says something like "just do it" for a specific request, that
authorizes that one instance, not a standing reversal of the rule.
### Bullet-point replies
Explain diagnoses/what's-happening in short, clear bullet points — not
paragraph prose.
- Said explicitly during iOS FocusToggleWidget color debugging: "I'm going
to need you to start simplifying your discussions... respond to me in
clear bullet points about what's actually happening."
- Recurrence: bullets with bold headers still drifted into multi-sentence
paragraphs underneath. The failure mode isn't lack of bullets, it's
bullets containing paragraph-length reasoning. One short sentence per
bullet, no exceptions — split into more, shorter bullets instead of
letting one run long.
- Second recurrence: when Paul states the correct technical answer himself,
respond with a short confirmation ("yes, correct" + at most one
clarifying detail) — don't re-explain it back to him in different words.
Treat his own correct restatement as a signal the concept has landed.
### ReaLearn is reference-only
`plugin-reaper-relearn` (vendored helgoboss/helgobox ReaLearn) is for
reading/insight only — never edit, patch, or extend it directly.
- Paul: "the realearn stuff is its own project and we dont mod it at all...
if we need to read from it to gain some insight ok other than that no."
- All new proprietary REAPER integration work happens in
`extension-reaper-macos` instead.
- Useful reference patterns inside ReaLearn: `main/src/domain/targets/*.rs`
target-plugin pattern, `mouse_target.rs` OS-level mouse emulation via
`enigo`, `reaper-high`/`reaper-medium` (reaper-rs) usage — studied, not
depended on.
---
## Ongoing project context
### extension-reaper-macos / envelope automation pipeline
Paul is building `extension-reaper-macos` as his own proprietary, owned
compiled REAPER extension — not scoped to one feature.
- Language: C/C++ against the real Cockos SDK directly (Rust/reaper-rs was
floated and explicitly dropped: "REAPER's actual native SDK... no wrapper
layer, no crate dependency graph").
- Layout: `vendor/reaper-sdk/sdk/` (unmodified headers from
justinfrankel/reaper-sdk), `vendor/reaper-sdk/WDL/` (full justinfrankel/WDL
checkout, header types only — nothing compiled/linked from it),
`src/main.cpp` (entrypoint, owns `REAPERAPI_IMPLEMENT`), one
subfolder per module (`hello/`, `tracking/`, `socket/`).
- Confirmed working 2026-07-15: bare-minimum skeleton built, installed,
loads in REAPER, prints console confirmation.
- Since then: extended to read/write automation items (`D_BASELINE` nudge,
`tracking.cpp`), and a UDP socket listener (`socket.cpp`) wired to the
desktop app so fader moves nudge the selected automation item's baseline
live.
**Origin feature:** click an automation item on a REAPER envelope lane to
select it, then move a fader in the desktop app (eventually the tablet
remote) to raise/lower ALL envelope points in that item together — maps to
REAPER's own `D_BASELINE` property (same as ctrl-drag natively), not
per-point editing.
**Design decisions:**
- Mouse-emulation (replaying a physical Y-axis drag) was explicitly
rejected in favor of calling REAPER's API directly.
- This extension owns its own socket listener rather than going through
REAPER's OSC-control-surface config or ReaScript/Action-List-learn.
- Open question, unresolved: does this extension eventually *replace* the
existing OSC in/out to REAPER (track/preset name, transport, position via
`osc_sender.py`/`osc_receiver.py`), or live alongside it for things OSC
can't reach?
- Also undecided: relative (nudge) vs absolute fader mapping for baseline
value.
**Cross-platform build (this is the live thread — Windows port in
progress):**
- macOS: `.dylib`, `clang++ -dynamiclib -arch arm64` via `build.sh`. Needs a
real Mac — can't be reliably/legally cross-compiled from another host.
Output now goes to `build/macos/`, copied to
`~/Library/Application Support/REAPER/UserPlugins/`.
- Windows: needs a `.dll`. `main.cpp`/`tracking.cpp` are portable as-is (pure
REAPER SDK C++, and `REAPER_PLUGIN_DLL_EXPORT` is already `_WIN32`-aware in
the vendored SDK header). `socket.cpp` is the one file that needs a real
port — it uses raw POSIX sockets
(`sys/socket.h`/`netinet/in.h`/`unistd.h`/`fcntl.h`,
`recvfrom`/`fcntl`/`close`) which don't exist on Windows; needs a Winsock2
path (`WSAStartup`/`WSACleanup`, `closesocket`, `ioctlsocket` instead of
`fcntl`, link `ws2_32`). Also need a Windows-side build script (MSVC
`cl.exe /LD` or MinGW `g++ -shared`) producing a `.dll`, copied to
`%APPDATA%\REAPER\UserPlugins\`.
- Dev environment: Paul is setting up a Windows ARM64 VM (Parallels, Apple
Silicon host) rather than full x86 emulation (tried QEMU/UTM full x86
emulation first — extremely slow, no hardware acceleration for a foreign
instruction set). ARM64 Windows runs natively/fast on Apple Silicon;
x64 compiler + x64 REAPER run inside it via Windows' own built-in
x64-on-ARM app emulation, which is fast enough for real dev work. REAPER
also ships a native ARM64 Windows build if x64 ends up unnecessary.
- A self-hosted Gitea instance (separate box) was floated for CI: Linux
`.so` natively there, Windows `.dll` via `x86_64-w64-mingw32-g++`
cross-compile from that same Linux box — viable while the extension has
no REAPER C++ virtual-class interfaces crossing the plugin boundary
(re-check if things like `PCM_source`/`ProjectStateContext` get used,
where the SDK's MSVC-ABI requirement actually bites).
### Hardware CC binding backlog
Backlog for the hw_cc/hw_channel binding system in `main.py`,
`faderwidget.py`, `focusfaderwidget.py`, `focustogglewidget.py`:
1. **Assignment collision detection** — no validation today for two widgets
(faders/toggles/transports/preset_browsers/focus_faders/focus_toggles)
being bound to the same hw_cc + hw_channel. Not yet designed — open
question is where the check should live (Learn-time vs. preset-wide
audit) and what UI signals it.
Raised 2026-07-06 after building JL Cooper touch-sense support (CH15/CH16
same-CC convention) and Focus widget Hardware/Feedback checkboxes — those
made binding conflicts more likely without adding detection.
### Planned features backlog (2026-06-29, ordered)
1. Momentary on Toggle Widget — Mom/Tog mode on ToggleWidget, mirrors
TransportWidget
2. Add Rows — add new fader/toggle rows to layout at runtime
3. Duplicate (Create New Gen ID) — duplicate a widget/row with a fresh
generated ID
4. Big Title — large display title element
5. Enable/Disable Preset Browser Row
6. Re-order Rows — drag or button-based row reordering
---
## iOS remote client (companion project, same session lineage)
These entries came out of sessions rooted in this project directory but
concern `remote-client-ios` / `virtual-controller-ios-app`. Kept here since
that's where they were captured.
### Tablet direction (confirmed)
The WebSocket + vanilla-JS web surface is the **permanent** direction for
performance control — not TouchOSC, not a native app. POC validated the
workflow: one-time arrange per preset, auto-switches with preset, instant JS
load, works on any device browser.
Stack: PyQt6 app owns all routing/presets/MIDI/OSC; `QWebSocketServer` on
:8765; HTTP server on :8080 serving `/client`; vanilla JS client, no
frameworks; layout saved per preset UUID in `presets.json` under
`"tablet_layout"`. Native app (Android/iOS) explicitly off the table unless
revisited.
### iOS FaderWidget architecture (settled, do not revisit)
Rebuilt from scratch using raw `touchesBegan`/`touchesMoved` overrides
instead of UISlider or UIScrollView — UISlider needs precise thumb
targeting, UIScrollView's pan gesture recognizer fought a drag gesture
recognizer (`touchesCancelled` froze the fader after one increment). Raw
touch tracking fires at 120hz with zero latency and works anywhere on the
widget surface.
Key decisions:
- Value mapping: `value = (1 - pt.y / activeHeight) * 127`, normalized
against `bounds.height`.
- Dead zones: 90pt top/bottom (`padHeight = 90`).
- REL mode (default): drag relative to touch start, no value jump. ABS
mode: touch position maps directly to value.
- Gradient fill: `CAGradientLayer`, transparent black (bottom) to color at
45% alpha (top), grows upward with value.
- Haptics: `UIImpactFeedbackGenerator(.medium)` on hitting 0 or 127
(edge-detected via `lastHapticValue`).
- Dedup via `lastSentValue`.
- Gesture fix: `addDragGesture` sets `cancelsTouchesInView = false` so pan
doesn't steal touches; `arrangeMode` flag guards touch handlers during
drag; pinch-to-resize removed for faders.
### iOS remote backlog
**Next up:** review pass on the `dest_id` system — Paul flagged things to
go over, "kinda works, things to go over."
Shipped: `remote_checkbox`/`dest_spin` per widget (own `grp_remote` frame),
Bonjour discovery (desktop advertises `_vcremote._tcp.local.` via
`zeroconf`, iOS browses via `NetServiceBrowser`), Feedback widget (Channel
Focus-scoped, not the old global one), `ToggleWidget` off-state now uses
`color.darkened(by: 0.6)` instead of a hardcoded gray, pinch-to-resize +
grid-snap in arrange mode.
Remaining:
1. **Bonjour discovery bug** — desktop-side confirmed correct (registers,
resolves, correct IP, Local Network permission on, same Wi-Fi), but never
resolves from a *borrowed* test iPad — possibly MDM/managed-device or
router client isolation. **Retest on Paul's own iPad before assuming the
code is broken.**
2. Review pass on dest_id system (see "Next up" above).
3. All-edge snap — `snapFrame` written but unused; call sites still use
`snapValue` on origin only.
4. Preset save bug — first/last presets not persisting.
5. Model all iOS JSON transactions (outer envelope still raw
`JSONSerialization`).
6. Model all Python outgoing data — dataclasses in `ws_server.py`
broadcasts.
7. Clean up `layout` arg — `broadcast_preset` takes it but doesn't send it.
8. Initial sync/ack handshake on connect.
9. Disallow presets without UUID (enforce server-side before broadcast).
10. Desktop activity ring — flash on desktop widget when it receives a
value from iPad.
11. **No live color-sync broadcast** (deprioritized) — color only travels
inside a full `"preset"` snapshot, never standalone like
label/value/feedback. Regressed at some point per Paul; not worth
chasing until other things are done.