[[project_v5_ui_requirements]]: "any action in the UI can be assigned to a
slot... full wheel or half wheel... half wheel positioned left or right...
both draggable... default: middle mouse button, anywhere."
app/legacy/actions.js: ported the composer tree's tiny action registry
(app/shell/actions.js, an ES module, unreferenced from boot) as a classic
global window.FunPackActions -- offerAction({id,label,icon,run}), offered(),
run(id). Whatever announces itself is what the wheel (or anything later --
a command palette, a keymap) can show.
app/legacy/wheel.js: middle-mouse (anywhere) opens a radial menu of
whatever's currently on it. Full wheel spaces items evenly around 360°;
half wheel spaces them over a 180° arc on the chosen side, with drag
locked to the vertical axis only (full wheel drags both axes) per the
spec's "half wheel moves up/down, full wheel up/down and left/right" line.
Dragging repositions the CURRENTLY OPEN wheel only, not persisted -- read
"draggable" as "don't get stuck somewhere awkward this time" rather than a
remembered dock position, since the spec doesn't say which and this is the
simpler reading. Customize dialog (reuses pinned_buttons.js's existing
.pin-modal/.pin-slot CSS) toggles shape, side, and which offered actions
are on the wheel. pinned_buttons.js is NOT removed -- the spec says this
supersedes it, but retiring a working control felt like a separate
decision from adding a new one; both coexist for now, confirmed to not
conflict (separate storage keys, no shared DOM, never both open at once).
app/legacy/wheel_actions_seed.js: seeds 9 starter actions (Generate,
Generate selected, Render, Auto Montage, Undo, Redo, Models & Pipeline,
Engine settings, Sampler quick-access) so the wheel isn't empty on first
use.
Two rounds of extensive_testing, plus a bug I found myself in live
testing before either round: the half wheel's fixed radius, sized for a
full wheel's 360° spread, packed the same item count into half the
angular room and produced heavily overlapping items -- fixed by growing
the radius only as far as needed to keep adjacent items from touching,
confirmed visually before/after.
Round 1: no functional break found in outside-click-vs-item-click
ordering, listener leaks on repeated opens, or coexistence with
pinned_buttons.js.
Round 2: found the real defect -- enabledIds()'s first-call snapshot
("default to everything offered so far") could run in the gap between
wheel.js executing and wheel_actions_seed.js finishing (classic <script>
tags block sequentially, and this window widens on a slow/cold-cache
load), permanently persisting an empty or partial action list with no
error, no indication -- the user would just experience "the wheel doesn't
work" and have no way to know why. Fixed by deferring the snapshot to the
window `load` event (guaranteed after every script has run); before that,
enabledIds() shows whatever's offered so far live without writing it
down as the permanent default. Also cleaned up a dead if/else in the
half-wheel angle math flagged as a "looks like a bug that got half-fixed"
smell.
Live-verified after both fixes: real physical drag (manual mousedown/
mousemove/mouseup sequences, not just JS calls) confirms half-wheel drag
is genuinely locked to the y-axis (x stays exactly put) while full-wheel
drag moves both, and neither drag opens Customize -- only a press that
never crosses the drag threshold does. npm test: 691 pass, 1 pre-existing
unrelated flake (shell/tests/projects.test.js:285).
No test file exists yet for actions.js/wheel.js/wheel_actions_seed.js --
a real, named test gap, not fixed here. Real hardware middle-click and
cross-browser autoscroll-blocking are also unverified (browser automation
has no native middle-click) -- declared, not chased further without
physical hardware.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>