Create categories and tags in place from every assignment picker

Category and tag inputs across the transaction editor, Analyse
corrections, and merchant defaults now mint missing entries without a
detour through the registry pages. A bare name lands under the kind's
root, "Parent / Name" targets that parent, and typing an existing name
selects it instead of duplicating. Enter only creates when nothing
matches, server rejections surface inline in the dropdown, and
assignment pickers offer leaf categories only — the shape the server
validates.

Mutations now return the accepted state so callers can select the id
the server just minted, and the revision-keyed remounts on Transactions
and the registry pages are gone: they closed the open modal and threw
away pending edits the moment any in-modal creation committed.
This commit is contained in:
Lars Nolden
2026-09-14 11:50:50 +02:00
parent f9e829e6ba
commit 1b3d7b22bb
8 changed files with 384 additions and 90 deletions
+22 -18
View File
@@ -21,7 +21,7 @@ import type {
} from "./api";
import { categoryPath, request } from "./api";
import {
CategoryOptions,
CategoryCombobox,
Empty,
ErrorMessage,
Field,
@@ -550,17 +550,15 @@ function RegistryEditor({
</select>
</Field>
<Field label="Parent category">
<select
<CategoryCombobox
data={data}
kind={kind}
exclude={[...descendants]}
emptyLabel="No parent (root)"
mutate={mutate}
value={parent}
onChange={(e) => setParent(e.target.value)}
>
<option value="">No parent (root)</option>
<CategoryOptions
data={data}
kind={kind}
exclude={[...descendants]}
/>
</select>
onChange={setParent}
/>
</Field>
<p className="muted">
Changing the parent moves this category and its entire subtree.
@@ -590,15 +588,21 @@ function RegistryEditor({
Use these defaults when this merchant is recognized
</label>
<Field label="Default category">
<select
<CategoryCombobox
data={data}
leavesOnly
emptyLabel="No default category"
mutate={mutate}
value={category}
onChange={(e) => setCategory(e.target.value)}
>
<option value="">No default category</option>
<CategoryOptions data={data} />
</select>
onChange={setCategory}
/>
</Field>
<TagPicker data={data} value={tags} onChange={setTags} />
<TagPicker
data={data}
value={tags}
onChange={setTags}
mutate={mutate}
/>
<p className="muted">
Defaults are only used when explicitly enabled. Editing defaults
does not rewrite existing transactions.