Shared UI primitives: createAsyncAction and one errorMessage #117
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#117
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by an architecture review (deepening opportunities — turning shallow modules into deep ones). Rescoped during triage: the third primitive originally proposed here, a shared
createDragReorderdrag-DOM hook, was split out to #124 because its contract needs a design pass; this issue keeps the two mechanical extractions.Problem — pure duplication in the web editor (Solid.js):
errorMessage(err: unknown): stringexists as an export of the mutations store (which PagePane already imports) and is re-declared verbatim in five more components (MediaModule, PageSettings, LinkPicker, TreeBrowser, MediaPicker). Deleting any local copy just makes it reappear at its siblings — fails the deletion test.busyanderrorsignals plus asubmitthat does setBusy → try one api call → catch setError(errorMessage(err)) → finally setBusy(false).Solution: a
createAsyncAction(fn)primitive exposing{ busy, error, run }, and a single canonical home forerrorMessagethat every component imports.Benefits: mostly locality and consistency; modest but cheap, and it thins the panes ahead of (or alongside) the PagePane/TreePane controller extraction.
dominik.polakovics referenced this issue2026-07-19 01:13:49 +02:00
Shared UI primitives: createAsyncAction, one errorMessage, createDragReorderto Shared UI primitives: createAsyncAction and one errorMessageAgent Brief
Category: enhancement
Summary: Extract two shared web-editor primitives — one canonical
errorMessagehelper and acreateAsyncActionasync-shell primitive — and adopt them everywhere the pattern is currently copy-pasted.Current behavior:
The web editor (Solid.js, under
web/) duplicates two small patterns:errorMessage(err: unknown): string(returnserr.messageforErrorinstances,String(err)otherwise) is exported from the mutations store and also re-declared verbatim as a local function in five components: MediaModule, PageSettings, LinkPicker, TreeBrowser, MediaPicker. PagePane already imports the shared export.busyanderrorsignals and asubmitfunction with the same shape: optional client-side validation →setBusy(true)/setError("")→awaitone api call → on success call a parent callback →catchsetssetError(errorMessage(err))→finallysetBusy(false).Desired behavior:
errorMessageexists in the web source, in a home that doesn't create odd import directions (a dedicated shared module is fine, as is keeping the existing store export — pick whichever reads best and re-point every consumer). No component declares its own copy.createAsyncActionprimitive wraps an async function and exposes reactivebusyanderroraccessors plus arunmethod.runsets busy, clears the previous error, awaits the wrapped function, recordserrorMessage(err)on failure, and always clears busy. Concurrentruncalls while busy should be a no-op (matching what the disabled submit buttons already enforce).run, or the dialog keeps that check before invokingrun.Key interfaces:
errorMessage(err: unknown): string— behavior unchanged, single definition.createAsyncAction— new primitive; suggested shapecreateAsyncAction<Args extends unknown[]>(fn: (...args: Args) => Promise<void>)returning{ busy: Accessor<boolean>, error: Accessor<string>, run: (...args: Args) => Promise<void>, setError?: ... }. Exact signature is the agent's call; it must stay a thin Solid primitive (signals + one function), not a framework.onClose,onCreated, etc.) must not change.Acceptance criteria:
grep -rn "function errorMessage" web/srcyields exactly one hit; all former call sites import it.createAsyncActionhas unit tests (the web test suite underweb/src/test/uses vitest) covering: busy true during a pending run and false after; error set from a rejecting fn viaerrorMessage; error cleared on the next run; re-entrantrunwhile busy is a no-op.busy/errorsignals or try/catch submit shell.go test ./...pass (buildweb/distfirst; toolchains come fromnix shell nixpkgs#go/nixpkgs#nodejsper the repo's dev-env notes).Out of scope:
createDragReorder) — split to #124.