Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9d745aa
feat(input): Phase 0 — input rearchitecture infrastructure
grymmjack May 24, 2026
45c2b4d
feat(input): Phase 1a — register 88 keyboard bindings as legacy metadata
grymmjack May 24, 2026
d43e32f
feat(input): Phase 2a — first panel (status bar) migrated to REGION s…
grymmjack May 24, 2026
59da0b3
docs(memory): reference for input rearchitecture system
grymmjack May 24, 2026
1cf8a84
feat(input): Phase 2b.1 — 6 core panels migrated to REGION system
grymmjack May 24, 2026
952329e
feat(input): Phase 2b.2 — 8 more panels migrated to REGION system
grymmjack May 24, 2026
fa78a93
docs(input): Phase 7 — CLAUDE.md + .claude/instructions/input-system.md
grymmjack May 24, 2026
b1bd100
feat(input): Phase 3 + 4 — 72 mouse bindings registered, audit wheelD…
grymmjack May 24, 2026
ad8ce5a
refactor(brush): Phase 5a — adopt PIXEL_DOUBLE_AXIS + SAFE_FREEIMAGE
grymmjack May 24, 2026
c07d923
refactor(scale): Phase 5b — adopt PIXEL_DOUBLE_AXIS in CASE 331/333
grymmjack May 24, 2026
8e0a1b0
docs(memory): update reference_input_system with Phase 2b/3/4/5 commits
grymmjack May 24, 2026
e2a472c
refactor(helpers): Phase 5c — bulk SAFE_FREEIMAGE adoption (215 sites)
grymmjack May 24, 2026
6635665
refactor(helpers): Phase 5d — adopt SCENE_invalidate (13 same-line si…
grymmjack May 24, 2026
b4f164c
docs(memory): update reference with Phase 5c/5d (228 helper-adoption …
grymmjack May 24, 2026
fa3dce8
refactor(helpers): Phase 5e — collapse 4-line _FREEIMAGE blocks (38 s…
grymmjack May 24, 2026
20d75ee
refactor(helpers): Phase 5f — collapse 3-line _FREEIMAGE blocks (31 s…
grymmjack May 24, 2026
0d314f8
feat(input): Phase 5g — first dispatched=TRUE binding (F12 dev debug)
grymmjack May 24, 2026
d54a328
docs(memory): final update — 17 commits, 345 SAFE_FREEIMAGE adoption,…
grymmjack May 24, 2026
7d7a3b4
refactor(helpers): Phase 5h — defense-in-depth, all bare _FREEIMAGE →…
grymmjack May 24, 2026
8ba1990
docs(memory): final state — 100% SAFE_FREEIMAGE adoption (637 sites)
grymmjack May 24, 2026
ed741ed
docs(prep): PR description + manual QA checklist for input rearchitec…
grymmjack May 24, 2026
5df4900
docs(pr): clarify 'regex' refers to dev-time bulk-edit tool, not QB64-PE
grymmjack May 24, 2026
963f601
refactor(load): replace _FREEIMAGE with SAFE_FREEIMAGE in COACH_load_…
grymmjack May 24, 2026
26a0feb
fix(input): correct Phase 1a tool-key action IDs; add Q/K/O metadata
grymmjack May 24, 2026
12b7584
feat(input): add CASE 1701/1706/1707 + Shift+T binding for full tool …
grymmjack May 24, 2026
bc9e954
feat(input): Phase 6a-iii — flip 22 tool keys to dispatched=TRUE
grymmjack May 24, 2026
37350c1
feat(input): Phase 6b — flip opacity (1-0) and X-swap to dispatched=TRUE
grymmjack May 24, 2026
1b83204
feat(input): Phase 6c — flip brush size [ and ] to dispatched=TRUE
grymmjack May 24, 2026
b41d919
refactor(input): remove KEYBOARD_brush_size — fully migrated in 6c
grymmjack May 24, 2026
b387189
docs: update QA checklist, PR description, and memory for Phase 6a-c
grymmjack May 24, 2026
de909a4
feat(input): Phase 6d batch 1 — KEYBOARD_layers removed, 16 chords di…
grymmjack May 24, 2026
4e3f3c2
feat(input): Phase 6d batches 2+3 — Ctrl+B, Ctrl+R/Alt+R/Alt+Shift+G,…
grymmjack May 24, 2026
78e85fc
feat(input): Phase 6d batch 4 — clipboard/undo ops, KEYBOARD_handle_c…
grymmjack May 24, 2026
f9c940c
feat(input): Phase 6d batch 5 — file ops + zoom + Ctrl+Shift+Q
grymmjack May 24, 2026
3de3be9
feat(input): Phase 6e — chord bindings migrated (Z+digit, M+=, G+...)
grymmjack May 24, 2026
e4b66a7
feat(input): Phase 6 final — F-keys, Tab, backslash migrated; UI togg…
grymmjack May 24, 2026
b0e22b8
feat(input): Phase 6 final — grid toggles; KEYBOARD_handle_grid_contr…
grymmjack May 24, 2026
6539df4
feat(input): Phase 6 final — DEL/Backspace migrated; SUB removed
grymmjack May 24, 2026
7d2a6a0
refactor(input): remove empty KEYBOARD_handle_layer_shortcuts SUB
grymmjack May 24, 2026
005bbc2
docs: update QA checklist, PR description, memory for full Phase 6
grymmjack May 24, 2026
7d1c196
fix(input): tool keys M/Z/E/F/W can never fire — self-held context bi…
grymmjack May 25, 2026
3a38d24
docs(memory): capture self-blocking context bug lesson from Phase 6a-iii
grymmjack May 25, 2026
f2792af
fix(input): skip-list should only flag plain keys (requireMods=0)
grymmjack May 25, 2026
4f32fee
test(qa): fix 8 tests with inaccurate key bindings vs main DRAW behavior
grymmjack May 25, 2026
5f9b3af
test(qa): add 12 new tests for Phase 6 dispatcher coverage gaps
grymmjack May 25, 2026
4644c53
fix(input): double-fire when keycode has multiple bindings (M+Ctrl+M,…
grymmjack May 25, 2026
7babf68
docs(memory): capture QB64-PE NOT-is-bitwise lesson from 4644c53
grymmjack May 25, 2026
495849c
test(qa): tighten 5 tests after dispatcher single-fire confirmed
grymmjack May 25, 2026
3493f3c
fix(input): hoist double-fire dedup array to SHARED to avoid per-fram…
grymmjack May 25, 2026
6d56457
fix(input): replace ERASE-based dedup with version-counter (avoids \$…
grymmjack May 25, 2026
fbdcfd4
docs(memory): index the \$DYNAMIC + ERASE gotcha in MEMORY.md
grymmjack May 25, 2026
3571ffd
test(qa): tighten util-refimg dialog handling + smart-shapes cycle ti…
grymmjack May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .claude/agent-memory/main/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
- [Memory location](feedback_memory_location.md) — ALWAYS write DRAW memory to `.claude/agent-memory/main/` (version-controlled), NOT to `~/.claude/projects/...` (per-user)
- [Destructive in-place transforms](feedback_destructive_in_place_transforms.md) — new transform features (scale/flip/rotate) modify the layer in place; bundle layer + selection undo with HISTORY group; scale wand mask alongside content
- [Selection > brush > layer priority](feedback_selection_vs_brush_priority.md) — hotkey dispatch order when multiple targets are valid; require both ACTIVE flag and valid IMAGE handle for brush
- [Hotkey grep sweep before adding chords](feedback_hotkey_grep_sweep.md) — DRAW has no central dispatch; grep _KEYDOWN(<keycode>) and CASE "letter" across whole KEYBOARD.BM before adding any new chord
- [Hotkey grep sweep before adding chords](feedback_hotkey_grep_sweep.md) — DRAW has no central dispatch; grep _KEYDOWN(<keycode>) and CASE "letter" across whole KEYBOARD.BM before adding any new chord (NOTE: superseded by input rearchitecture once Phase 2+ complete)
- [Input system reference](reference_input_system.md) — INPUT/INPUT.BI/BM unified dispatch; CORE/HELPERS.BI/BM utilities; migration phase status; dev mode via --developer + inputs.log; spec in PLANS/REARCHITECTURE.md
- [Self-blocking context bug](feedback_self_blocking_context_bug.md) — chord-initiator tool keys (M/Z/E/F/W) must NOT forbid their own CTX_*_HELD bit; the context update runs before dispatch and would self-block on the very press edge
- [QB64-PE NOT is bitwise](feedback_qb64pe_not_is_bitwise.md) — `NOT 1` returns `-2` (truthy), not `0` (falsy). For non-boolean flags use `IF flag = 0 THEN` instead of `IF NOT flag THEN`. The naive `NOT` only works on values that are exactly 0 or -1.
- [QB64-PE $DYNAMIC + DIM SHARED doesn't allocate](feedback_qb64pe_dynamic_dim_shared.md) — under `$DYNAMIC`, module-level `DIM SHARED arr(0 TO N)` reserves the symbol but doesn't allocate. First read crashes "Subscript out of range". `ERASE` actively deallocates. Always init in `_init` via a fill loop; never `ERASE` a SHARED array you want to keep using.
46 changes: 46 additions & 0 deletions .claude/agent-memory/main/feedback_qb64pe_dynamic_dim_shared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: feedback-qb64pe-dynamic-dim-shared
description: With `$DYNAMIC` set, `DIM SHARED arr(0 TO N) AS T` at module level does NOT allocate the array. Reading any element before the first write crashes "Subscript out of range". `ERASE arr` actively deallocates it again. Force allocation in an init routine, and never ERASE if you want to keep using it.
metadata:
type: feedback
---

DRAW sets `$DYNAMIC` at the top of every `.BAS` file (CLAUDE.md), so
arrays default to dynamic allocation. Under `$DYNAMIC`:

- `DIM SHARED arr(0 TO 65535) AS LONG` at module level **does not
allocate** the array. The declaration only reserves the symbol.
- Reading `arr(0)` before any write crashes with
`Runtime error: Unhandled Error #9 — Subscript out of range`.
- The first WRITE to `arr(x)` triggers allocation of the whole array.
- `ERASE arr` is destructive under `$DYNAMIC` — it DEALLOCATES the
array. The next read crashes again.

**How to apply:**
- Allocate SHARED dynamic arrays in your `_init` SUB with an explicit
fill loop:
```
DIM i AS LONG
FOR i = 0 TO 65535
arr(i) = 0
NEXT i
```
- For per-frame "clear and reuse" patterns, do NOT use ERASE on a
SHARED array. Either:
- Track which slots were touched and clear only those; or
- Use a version-counter pattern (store the frame number written;
"set this frame" iff stored == current frame) and skip ERASE
entirely.

Caught in commit `<this commit>` after the SHARED-with-ERASE fix
(commit 3493f3c) for the dispatcher double-fire bug crashed every
test. The runtime error was at the array read site, but the root
cause was `ERASE INPUT_KC_ENQUEUED` deallocating the array on the
preceding line. Switched to a `LONG` version-counter array
(`INPUT_KC_ENQUEUED(kc) == INPUT_KC_FRAME` means "enqueued this
frame") plus an init-time fill loop.

Related:
- [[feedback-qb64pe-not-is-bitwise]] — sibling QB64-PE semantic
gotcha for `NOT`.
- [[reference-input-system]] for the input dispatcher specifically.
45 changes: 45 additions & 0 deletions .claude/agent-memory/main/feedback_qb64pe_not_is_bitwise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: feedback-qb64pe-not-is-bitwise
description: QB64-PE's NOT operator is bitwise, not boolean. `NOT 1` returns `-2` (still truthy!), so `IF NOT x THEN` only works correctly when `x` is exactly 0 or -1. For non-boolean flags, always write `IF x = 0 THEN` or `IF x THEN`.
metadata:
type: feedback
---

QB64-PE NOT is a bitwise complement, inherited from QuickBASIC. It does
NOT do boolean negation in the C/Python sense:

```
NOT 0 = -1 (truthy)
NOT 1 = -2 (truthy!)
NOT -1 = 0 (falsy)
NOT 5 = -6 (truthy)
```

So `IF NOT x THEN ...` does what you expect only when `x` can only be 0
or -1 (i.e., the result of a comparison like `x = TRUE`). For any flag
that takes integer values (a counter, a one-shot marker like 1 to mean
"yes"), `IF NOT x THEN ...` will succeed for almost every non-zero value.

**Why:** QB64 inherits QuickBASIC semantics where NOT is the bitwise
complement of an INTEGER. There is no separate boolean type.

**How to apply:** when checking a flag for "is this set?", use the
explicit comparison:
- `IF flag = 0 THEN` — "flag is not set"
- `IF flag <> 0 THEN` — "flag is set"
- `IF flag THEN` — "flag is set" (works because any non-zero is truthy)

NEVER use `IF NOT flag THEN` unless `flag` is the result of a comparison
that returns exactly 0 or -1.

Caught in `4644c53` — the double-fire fix in INPUT_detect_events used
`IF NOT kcEnqueued(kc&) THEN` first, which silently bypassed the dedup
every time (because NOT 1 = -2 ≠ 0 = truthy). Replaced with
`IF kcEnqueued(kc&) = 0 THEN` and the dedup worked correctly.

Pre-existing code in DRAW uses `IF NOT pressed%` only for STATIC INTEGER
flags that hold exactly TRUE (-1) or FALSE (0) — those work fine because
the values are always exactly -1 or 0. Be careful when introducing flags
that hold positive small values (1, 2, ...) — they break the idiom.

Related: [[reference-input-system]] for the input dispatcher specifically.
39 changes: 39 additions & 0 deletions .claude/agent-memory/main/feedback_self_blocking_context_bug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: feedback-self-blocking-context-bug
description: In context-aware dispatchers where the context table is updated before the dispatch pass, a binding must never include its own state-trigger bit in forbidCtx. Otherwise the binding self-blocks the moment its key transitions to down.
metadata:
type: feedback
---

When adding a binding for a key that *also* sets a context bit (chord
initiator: M/Z/E/F/W in DRAW), do NOT put that key's own CTX_*_HELD bit
in the binding's `forbidCtx`. The key will never fire.

**Why:** the DRAW main loop runs `INPUT_update_context` BEFORE
`INPUT_detect_events` and `INPUT_dispatch_frame`. On the very first
frame a chord-initiator key is pressed:
1. update_context sees the raw _KEYDOWN and sets CTX_<key>_HELD
2. detect_events sees the up→down edge and enqueues EVT_KEY_PRESS
3. dispatch_frame matches against the binding — CTX_<key>_HELD is
now TRUE, forbidCtx blocks → no fire, ever.

**How to apply:** when registering a binding for a chord-initiator
tool key, build a custom `chordHeldNo<X>` mask that excludes the
own bit. See `chordHeldNoM/Z/E/F/W` in INPUTS_register_all in
INPUT/INPUT.BM for the pattern. The chord bindings (Z+1..0, M+=, etc.)
are unaffected — they live on a different keycode and *require*
CTX_<X>_HELD, which is fine.

This is a class of bug specific to dispatchers where context updates
and binding matches happen in the same frame. Pure "level"-based
checks don't have this issue; the bug only appears with the combination
of (edge-triggered fire) + (context-gating) + (self-set context).

Caught and fixed in `7d1c196` after Phase 6a-iii flipped M/Z/E/F/W to
dispatched=TRUE. User reported "i cannot switch to the zoom tool with
z key" — symptom was the chord-initiator tools silently doing nothing.

Related memories:
- [[reference-input-system]] — full input dispatch architecture
- [[feedback-hotkey-grep-sweep]] — manual grep approach that motivated
the dispatcher rearchitecture
169 changes: 169 additions & 0 deletions .claude/agent-memory/main/reference_input_system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
name: reference-input-system
description: DRAW has a unified input dispatch system in INPUT/INPUT.BI/BM that replaces scattered IF/STATIC keyboard/mouse handlers. Bindings are declared in a table; events flow through a central dispatcher; conflicts are auto-detected in developer mode. Migration is phased — much of the legacy code still owns dispatch until converted.
metadata:
type: reference
---

DRAW migrated from input-driven dispatch (scattered IF/STATIC blocks in KEYBOARD.BM/MOUSE.BM polluting central handlers) to **event-driven dispatch** (HTML-style: regions register bounds, handlers register for events, central loop matches events to bindings). See [PLANS/REARCHITECTURE.md](PLANS/REARCHITECTURE.md) for the full ~1700-line spec.

## Files

- **[INPUT/INPUT.BI](INPUT/INPUT.BI)** — TYPEs (`INPUT_BIND`, `REGION_BOUNDS`, `INPUT_EVENT_OBJ`), CONSTs (`EVT_*` event types, `REGION_*` regions, `MOD_*` modifier bitmask, `CTX_*` 64-bit context flags, `ZORDER_*` z-order tiers), `DIM SHARED` state
- **[INPUT/INPUT.BM](INPUT/INPUT.BM)** — `INPUTS_init`, `INPUT_register*` family, `INPUT_update_context`, `INPUT_detect_events`, `INPUT_dispatch_frame`, `INPUT_audit`, `REGION_set_bounds/inactive/clear_all/hit_test`, `INPUTS_register_all` (Phase 1+ legacy metadata)
- **[INPUT/GAMEPAD.BM](INPUT/GAMEPAD.BM)** — stub for gamepad = mouse emulation (translates D-pad / sticks / buttons to MOUSE.RAW_X/Y/B1/B2/B3); not yet wired to real `_DEVICEINPUT`
- **[CORE/HELPERS.BI/BM](CORE/HELPERS.BI)** — cross-cutting utilities: `SAFE_FREEIMAGE`, `DEST/SOURCE/FONT_SAVE/RESTORE` (stack-based), `PIXEL_DOUBLE_AXIS`, `SCENE_invalidate`, `MODS_NOW%`, `MODS_only%`

## How to add a new hotkey or click handler

```qb64
' In INPUTS_register_all (INPUT.BM):
r% = INPUT_register_key%(<keycode>, <requireMods>, <forbidMods>, <requireCtx>, <forbidCtx>, <actionId>, TRUE, "label")
```

Wrappers: `INPUT_register_key%`, `INPUT_register_mouse%`, `INPUT_register_wheel%`, `INPUT_register_hover%`.

When `dispatched = TRUE`, the dispatcher fires `CMD_execute_action <actionId>` on match. When `dispatched = FALSE`, it's metadata for audit only; the legacy KEYBOARD.BM / MOUSE.BM handler still owns actual dispatch.

## Developer mode

Three sources OR'd together enable dev mode:
- CLI: `./DRAW.run --developer` or `--dev`
- CFG: `DEVELOPER_MODE=TRUE` in `~/.config/DRAW/DRAW.cfg`
- (Env var support reserved but not yet wired)

When active: `./inputs.log` is cleared on startup, then receives `[INIT]`, `[AUDIT]`, `[CONFLICT]`, `[FIRE]`, `[OVERFLOW]`, `[CONSISTENCY]`, `[GAMEPAD]` entries throughout the session. The `[AUDIT]` runs once at startup and reports every pair of bindings that could mathematically fire on the same input + context state.

## Migration status (commits on branch input-rearchitecture)

- `9d745aa` Phase 0 — infrastructure shipped (zero behavior change)
- `45c2b4d` Phase 1a — 88 keyboard bindings registered as `dispatched = FALSE` metadata (0 audit conflicts)
- `d43e32f` Phase 2a — first panel (status bar) migrated to REGION system
- `1cf8a84` Phase 2b.1 — 6 core panels migrated (edit bar, adv bar, toolbar, layer panel, palette strip, menubar)
- `952329e` Phase 2b.2 — 8 more panels migrated (drawer, organizer, charmap, preview, color mixer, browser, subtool flyout, text bar)
- `fa78a93` Phase 7 — CLAUDE.md + input-system.md instructions
- `b1bd100` Phase 3 + 4 — 72 mouse bindings as metadata, audit wheelDir fix (160 total bindings, 0 conflicts)
- `ad8ce5a` Phase 5a — PIXEL_DOUBLE_AXIS + SAFE_FREEIMAGE adoption in CUSTOM-BRUSH.BM
- `c07d923` Phase 5b — PIXEL_DOUBLE_AXIS adoption in COMMAND.BM CASE 331/333
- `8e0a1b0` Memory update with full commit list
- `e2a472c` Phase 5c — bulk SAFE_FREEIMAGE adoption (215 sites across 17 files)
- `6635665` Phase 5d — SCENE_invalidate adoption (13 same-line sites)
- `b4f164c` Memory update with Phase 5c/5d
- `fa3dce8` Phase 5e — collapse 4-line _FREEIMAGE blocks (38 sites, -114 LOC)
- `20d75ee` Phase 5f — collapse 3-line _FREEIMAGE blocks (31 sites, -58 LOC)
- `0d314f8` Phase 5g — first dispatched=TRUE binding (F12 dev debug proof of concept)

**Remaining work** (future sessions):
- Phase 1b: more keyboard registrations (brush size, Esc, arrows, F-keys 4-9)
- Phase 5 continued: ~297 remaining _FREEIMAGE sites have complex surrounding logic
(multi-statement IF blocks with extra conditions/statements) — per-site review needed
- ~50 multi-line SCENE_DIRTY+FRAME_IDLE pairs → SCENE_invalidate (varied patterns)
- ~10 manual MODIFIERS chains → MODS_only% (small win)
- ~8 _DEST/_SOURCE manual save-restore sites → DEST_SAVE/RESTORE
- Migrate dispatched=FALSE bindings to dispatched=TRUE one at a time
(must remove corresponding legacy inline handler at same time to avoid double-fire)
- Phase 8: manual QA against PLANS/TESTS/
- Phase 9: merge to main

**Current state (Phase 6 complete)**: 39 commits on branch, 206 bindings,
131 dispatched=TRUE (was 1 before P6), letter skip-list size 48,
0 audit conflicts.

`KEYBOARD.BM` SUB count dropped from 18 to 11. Removed entirely:
- `KEYBOARD_brush_size` (P6c)
- `KEYBOARD_layers` (P6d-1, 16 chord handlers)
- `KEYBOARD_handle_clipboard_operations` (P6d-4, 15 chord handlers)
- `KEYBOARD_handle_file_operations` (P6d-5, 5 chord handlers)
- `KEYBOARD_handle_zoom_shortcuts` (P6d-5, 3 chord handlers)
- `KEYBOARD_handle_z_zoom_presets` (P6e)
- `KEYBOARD_handle_marquee_expand_contract` (P6e)
- `KEYBOARD_handle_grid_controls` (P6 final)
- `KEYBOARD_handle_ui_toggles` (P6 final)
- `KEYBOARD_handle_delete_backspace` (P6 final)
- `KEYBOARD_handle_layer_shortcuts` (P6 final, was empty)

Remaining SUBs are all context-aware (tool-specific arrows, text-tool
editing, custom brush transforms, eraser hold/tap, recent files Alt+
1-0, command palette intercept) and don't fit the action-ID model
cleanly — they stay as legacy.

Phase 6 added two new infrastructure pieces in INPUT.BI/BM:
- `INPUT_LETTER_DISPATCHED(0 TO 127) AS INTEGER` — set by INPUT_register%
when a dispatched=TRUE EVT_KEY_PRESS binding lands on 0..127 keycode
(A-Z normalized to a-z). Lookup index = ASC(LCASE$(keypress$)).
- `INPUT_DISPATCH_DEPTH AS INTEGER` — depth counter, incremented at top
of CMD_execute_action, decremented at end. Lets KEYBOARD_tools and
KEYBOARD_colors early-exit when called from INKEY$ (depth=0) but
proceed when CMD_execute_action calls back into them (depth>0).
- New CTX_SS_DRAGGING bit for 3D dice / SS polygon dragging — digit
opacity bindings forbid it so digits feed dice type / polygon sides
instead of opacity during drag.

Phase 6 commits:
- `26a0feb` 6a-i — corrected Phase 1a action IDs (still FALSE)
- `12b7584` 6a-ii — added CASE 1701/1706/1707 + Shift+T binding
- `bc9e954` 6a-iii — flipped 22 tool keys to dispatched=TRUE
- `37350c1` 6b — flipped opacity 1-0 (501-510) and X-swap (517);
added CASE 517 handler that was registered but never implemented
- `1b83204` 6c — flipped [ and ] (601, 602)
- `b41d919` Removed KEYBOARD_brush_size SUB (fully migrated)
- `de909a4` 6d batch 1 — KEYBOARD_layers removed (Ctrl+L/Shift+N/
Shift+Del/Shift+D/Shift+R/PgUp/PgDn/Shift+[/]/Home/End/Alt+E/
Alt+Shift+E/G/Shift+G/Shift+U)
- `4e3f3c2` 6d batches 2+3 — Ctrl+B (new CASE 1101), Ctrl+R/Alt+R/
Alt+Shift+G, Ctrl+,/Shift+Del/Ctrl+D/Ctrl+M
- `78e85fc` 6d batch 4 — KEYBOARD_handle_clipboard_operations removed
(Ctrl+Z/Y/Shift+Z/C/Shift+C/Alt+C/X/Alt+X/V/Shift+V/A/Shift+I/H/E/T)
- `f9c940c` 6d batch 5 — KEYBOARD_handle_file_operations +
KEYBOARD_handle_zoom_shortcuts removed (Ctrl+N/O/S/Shift+S/
Alt+Shift+S/Shift+Q + Ctrl+0/=/-); new CASE 201 for Open
- `3de3be9` 6e — chord migrations (Z+1..0, M+=/-/++/_, G+R/Shift+R/
Ctrl+R/O/arrows, Ctrl+Shift+/). KEYBOARD_handle_z_zoom_presets +
KEYBOARD_handle_marquee_expand_contract removed. New actions
9001-9013 (G chord), 9100-9109 (Z chord). CTX_G_HELD switched to
use GRID_G_KEY_ARMED% for chord-sticky semantics.
- `e4b66a7` 6 final — F1-F8/Shift+F5/Tab/F10/F11/Ctrl+F11/\/| migrated;
KEYBOARD_handle_ui_toggles removed. New actions 8001-8011.
- `b0e22b8` 6 final — grid toggles ('/"/Ctrl+'/;//);
KEYBOARD_handle_grid_controls removed. New actions 8101-8105.
- `6539df4` 6 final — DEL/Backspace migrated;
KEYBOARD_handle_delete_backspace removed.
- `7d2a6a0` 6 final — empty KEYBOARD_handle_layer_shortcuts removed.

Branch is mergeable. The migrated keys are end-to-end dispatched through
the central dispatcher. Verify by running `./DRAW.run --developer`,
pressing any migrated key, then `cat inputs.log` — exactly one [FIRE]
line per press, action ID matches the binding label.

**Remaining work** (small follow-ups):
- Migrate `*` (random track, action 433) and `#` (border toggle, action
441) — needs CTX_MUSIC_ENABLED for `*` or in-action guard. KEYBOARD_colors
could then be removed (digits + X already skip-listed; only those
two remain).
- Migrate `?` (Shift+/ = command palette) — currently inline in
KEYBOARD_input_handler.
- Migrate `{` and `}` (music prev/next track, actions 428/427) — also
inline in KEYBOARD_input_handler.
- Migrate KEYBOARD_handle_recent_files (Alt+1-0) — would need 10
new actions.
- The context-aware KEYBOARD_handle_custom_brush (Home/End/PgUp/PgDn
with brush-vs-layer dispatch) stays inline by design.
- KEYBOARD_handle_text_tool stays inline (gated by TEXT.ACTIVE,
intentional context override).
- Other tool-specific arrow handlers (marquee, move, shape modifier)
stay inline.
- Phase 8: full QA against PLANS/TESTS/input-rearchitecture-qa.md
- Phase 9: merge to main

## Key invariants (don't violate)

1. **Every visible panel MUST call `REGION_set_bounds` in its render SUB.** Hidden panels stay inactive via `REGION_clear_all` at top of `SCREEN_render`.
2. **REGION_BOUNDS uses logical (pre-display-scale) pixels** to match `MOUSE.RAW_X/Y` coord space.
3. **Action handlers may read `INPUT_EVENT` shared state** — populated by dispatcher before the action ID is invoked. Valid only during the action call.
4. **First-match-wins via registration order** — register panels in z-order from top (modals → popups → panels → canvas) so the natural priority matches user expectation.
5. **Event-type CONST ranges are reserved** (1-9 KB, 10-29 mouse, 30-39 gamepad, 40-49 MIDI, 50-59 tablet, 60-69 touch) — don't renumber.

## Related memories

- [[feedback-hotkey-grep-sweep]] — the manual approach that drove the need for this rearchitecture. Once this is fully dispatched, that memory becomes obsolete (audit replaces manual grep).
- [[feedback-destructive-in-place-transforms]] — uses `HISTORY_*` helpers; will adopt `SCENE_invalidate` helper as Phase 5 migrates.
Loading
Loading