Fix wave: dropdown keyboard/focus, appearance resets, inline key editors, content-sized slider
- MenuShell: after placement, focus lands in the search when the menu has one, else the body (tabIndex -1) — the lists' mount-time focus ran during the hidden measuring pass and silently failed, so no dropdown ever opened focused. The body owns the arrow machine for searchless menus: ArrowDown/Up cycle options with wrap (first/last entry from the body, native-select convention), Home/End, Enter activates, preventDefault so arrows never scroll the page. - Appearance: the CSS-var applier only ever setProperty'd — a later write omitting a var left the stale inline value forever (empty background never reset, serif toggle-back looked dead). appliedNamesRef tracks what was set; staleVarNames (pure, exported) returns what to removeProperty. - CommitInput: controlled — after a commit the field snaps to the stored record, so the clamp on a -5 prose size is visible (12), Esc reverts. - Keys: Add-provider dropdown closes on pick; the paste row is inline (label | input | Save | Cancel); existing-row editors go inline with an explicit Cancel beside Save/Delete — folding is visible, not Esc-only. - Graph context: readout is content-sized (tabular-nums) — no fixed-width dead gap when "off"; the dictation strip fills whatever space remains. Gates: tsc clean, 866/866 tests (new staleVarNames suite), lint 0, build ok. Live-verified via Playwright: dropdown focus+arrow cycles+wrap+Enter pick, background set→empty reset, serif round-trip, visible clamp to 12, provider pick closes menu, inline editor + Cancel + Esc fold, slider width 270→277px.
This commit is contained in:
@@ -95,6 +95,18 @@ export function appearanceVars(a: ReturnType<typeof resolveAppearance>): Record<
|
||||
return vars;
|
||||
}
|
||||
|
||||
/** Pure: the previously-applied names ABSENT from the next var set —
|
||||
* these get removeProperty so the factory stylesheet value returns.
|
||||
* (Owner repro, fix wave: resets and toggle-backs never reverted because
|
||||
* application only ever SET properties — the removal half was missing.)
|
||||
* Exported for tests. */
|
||||
export function staleVarNames(
|
||||
prevNames: readonly string[],
|
||||
nextVars: Record<string, string>,
|
||||
): string[] {
|
||||
return prevNames.filter((n) => !(n in nextVars));
|
||||
}
|
||||
|
||||
interface AppearanceContextValue {
|
||||
/** Resolved appearance; factory until the record arrives. */
|
||||
appearance: ReturnType<typeof resolveAppearance>;
|
||||
@@ -118,11 +130,25 @@ export function AppearanceProvider({ children }: { children: React.ReactNode })
|
||||
// The vars apply on every change — stringified as the change signal.
|
||||
const vars = useMemo(() => appearanceVars(resolveAppearance(delta)), [delta]);
|
||||
const key = useMemo(() => JSON.stringify(vars), [vars]);
|
||||
// REMOVAL is half the job (owner repro, fix wave): a later write that
|
||||
// OMITS a var (empty field = factory, serif toggle back on) must clear
|
||||
// the previously-set inline value or the stale one wins forever —
|
||||
// backgrounds never reset, toggles looked dead. Every name this effect
|
||||
// has ever set is tracked; names absent from the new set are removed so
|
||||
// the factory stylesheet value returns.
|
||||
const appliedNamesRef = useRef<Set<string>>(new Set());
|
||||
useEffect(() => {
|
||||
if (appliedRef.current === key) return;
|
||||
appliedRef.current = key;
|
||||
const root = document.documentElement;
|
||||
for (const [name, value] of Object.entries(vars)) root.style.setProperty(name, value);
|
||||
for (const name of staleVarNames([...appliedNamesRef.current], vars)) {
|
||||
root.style.removeProperty(name);
|
||||
appliedNamesRef.current.delete(name);
|
||||
}
|
||||
for (const [name, value] of Object.entries(vars)) {
|
||||
root.style.setProperty(name, value);
|
||||
appliedNamesRef.current.add(name);
|
||||
}
|
||||
}, [key, vars]);
|
||||
|
||||
// One fetch per app load: the record (sanitized server-side) carries the
|
||||
|
||||
Reference in New Issue
Block a user