SolidJS reactivity + UI state patterns for OpenWork
OpenWork’s UI is SolidJS: it updates via signals, not React-style rerenders.
Most “UI stuck” bugs are actually state coupling bugs (e.g. one global busy() disabling an unrelated action), not rerender issues.
This skill captures the patterns we want to consistently use in OpenWork.
pending state).createMemo() instead of duplicating booleans.When an operation can overlap with others (permissions, installs, background refresh), don’t reuse a global busy().
Use a dedicated signal per action:
const [replying, setReplying] = createSignal(false);
async function respond() {
if (replying()) return;
setReplying(true);
try {
await doTheThing();
} finally {
setReplying(false);
}
}
A single busy() boolean creates deadlocks:
busy(true)busy()Fix: permission UI must be disabled only by a permission-specific pending state.
If you read signals inside an async function and you need stable values, snapshot early:
const request = activePermission();
if (!request) return;
const requestID = request.id;
await respondPermission(requestID, "always");
Prefer createMemo() for computed disabled states:
const canSend = createMemo(() => prompt().trim().length > 0 && !busy());
setItems((current) => current.filter((x) => x.id !== id));
current in-place.await where values might have changed?