When creating a new note, picking a color from the palette had no
effect — the note saved with the default white background. Editing an
existing note worked because that path lives in a Dialog with no
auto-save guard.
Root cause
The inline Composer in artifacts/tx-os/src/pages/notes.tsx attaches a
document-level mousedown listener that auto-saves whenever the click
falls outside its container ref. The color and label menus are built
on Radix Popover, which renders content in a portal outside the
composer's DOM tree. So clicking a swatch fired:
1. document mousedown -> save() with the OLD color
2. swatch click -> setColor(new) (state already discarded)
Fix
The mousedown handler now ignores clicks whose target is inside any
Radix popper wrapper, Radix portal, or role="dialog" element. The
guard is narrow: legitimate outside clicks (page background, sidebar,
other notes) still trigger save() exactly as before. The same fix
covers the LabelMenu popover for free since it shares the abstraction.
Tests
- New tests/notes-composer-color.spec.mjs reproduces the original
failure mode: open composer, type title, pick the "red" swatch from
the popover, click outside, assert the POST /api/notes response has
color === "red".
- Existing notes-folders + new spec run green together (3 passed in
29.1s); tx-os tsc --noEmit clean.
Architect notes (followed inline / deferred)
- Suggested mirroring a labels-popover e2e — same code path is already
exercised; can be added later if regressions surface.
- composedPath() hardening — current target.closest() works for all
real Radix portals; not needed.