Skip to content

Per-tag wiki prompts - #228

Merged
kenforthewin merged 4 commits into
mainfrom
feat/per-tag-wiki-prompts
Aug 8, 2026
Merged

Per-tag wiki prompts#228
kenforthewin merged 4 commits into
mainfrom
feat/per-tag-wiki-prompts

Conversation

@kenforthewin

Copy link
Copy Markdown
Owner

Summary

The most-repeated community request (asked 5× on Discord): a tag can now carry its own wiki generation/update prompts, overriding the global Settings → Prompts values for that tag's article only. Right-click a tag → Wiki Prompt… — the named use cases (a "Todo Summary" wiki over unchecked tasks, a "Diary" wiki over daily notes, per-meeting summary wikis) all work by writing one prompt on one tag.

Design

  • Precedence, resolved once in build_wiki_strategy_context (the single funnel both strategies and the update path already flow through): tag override → global custom prompt → built-in default. The update chain takes the tag's generation prompt as its middle term so a deliberately-shaped article keeps its shape through incremental updates.
  • Prompts stay off the Tag payload (the tree ships hundreds of rows; prompts run to kilobytes) — fetched on demand via GET/PUT /api/tags/{id}/wiki-prompts, blank-clears-override normalization server-side.
  • Storage: nullable columns on tags — SQLite V25 (transactional, crash-safe) + Postgres 026 (additive, fleet-gated). migrate push copies them. On Postgres the columns are db_id-scoped, i.e. genuinely per-tenant.
  • Agentic strategy: the research phase appends the custom prompt to its curation instructions so it selects the material the synthesis prompt will need.

Adversarial review

Four independent review passes (migrations/storage, wiki semantics, API/frontend, test adequacy) ran against the feature commit; every confirmed finding is fixed in the second commit. Highlights:

  • V25 crash window (reproduced): two autocommitted ALTERs behind a first-column-only guard could permanently strand the schema — now one transaction, plus a debug_assert pinning user_version to LATEST_VERSION after the ladder.
  • Precedence chain fully pinned: the e2e asserts on the system prompt the wiremock provider actually receives, for both halves (tag-wins and global-fallback) and for the update path — each verified to fail under the specific mutation it guards.
  • CI gap closed: the Postgres job never ran e2e_wiki, e2e_tags_settings, or migrate_tests — their _postgres arms reported ok without executing. All three are now in the workflow; every previously-dormant arm was verified green against a real pg16 first. The frontend job also gains tsc --noEmit.
  • Modal hardening: load state is keyed by tag id, so a failed fetch can never render one tag's prompts under another tag's title, and Save is gated on a successful load (PUT is a full replace — a blind save could otherwise clear prompts the user never saw).
  • Also: the SQLite prompt read moved off the write mutex onto the read pool; PG db_id fencing, route auth, and 404-body consistency are each pinned by new tests; whitespace-only global prompts no longer shadow the built-in default.

Known limitations (deliberate)

  • Entry point is the tag context menu only — unreachable on touch, like Rename/Delete (pre-existing platform gap; a wiki-reader affordance is a natural follow-up).
  • The section-ops update machinery has no delete op, so a "Todo Summary" wiki can't shed completed items incrementally — full regeneration does that (pre-existing).
  • The PUT body accepts unknown fields per the pinned missing-field-clears contract; no deny_unknown_fields.

Test plan

  • cargo test -p atomic-core -p atomic-server — 36 suites green (SQLite).
  • All PG-gated suites green against dockerized pg16: storage (83), pipeline (22), multi-db, migrate (5), server e2e incl. the 24 newly-executing wiki/tags arms.
  • npm test (87) and npx tsc --noEmit clean.
  • Storage round-trips, precedence, db_id fencing, auth, and 404 bodies each covered on both backends.

🤖 Generated with Claude Code

kenforthewin and others added 4 commits August 3, 2026 21:55
A tag can now carry its own wiki generation/update prompts, resolved
tag → global setting → built-in default at the single strategy-context
funnel, so both strategies and the incremental-update path inherit the
override with no changes. Prompts stay off the Tag payload (the tree
ships hundreds of rows; prompts run to kilobytes) and are fetched on
demand by the new GET/PUT /api/tags/{id}/wiki-prompts routes and the
tag context menu's Wiki Prompt modal. SQLite V25 + Postgres 026 add
the columns; migrate push copies them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration V25 wraps its two ALTERs and version stamp in one transaction
(a crash between autocommitted ALTERs re-entered past the single-column
guard and stranded the schema at 25 without wiki_update_prompt); a
debug_assert now pins user_version to LATEST_VERSION after the ladder.
The SQLite prompt read moves off the write mutex onto the read pool.
Update-prompt resolution gains the tag's generation prompt as its middle
term so a deliberately-shaped article keeps its shape through incremental
updates, and the agentic research phase now sees the override it was
curating against. The modal keys its load state by tag id so a failed
fetch can never render one tag's prompts under another's title, and Save
is gated on a successful load. CI newly executes e2e_wiki,
e2e_tags_settings, and migrate_tests under Postgres (all dormant arms
verified green against pg16 first) and typechecks the frontend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tag tree's context menu was the only way in. Article and suggestion
cards gain a hover/touch ⋮ menu (the suggestion case matters: shaping an
article before its first generation is the primary flow), and the reader
header gets a Prompt button beside Regenerate — deliberately not gated
on version-viewing, since reading a bad version is a common reason to go
edit the instructions. The card ⋮ also makes the feature reachable on
touch for the first time. WikiHeader turned out to be dead code (nothing
renders it — the reader's titleActions cluster is the real header) and
is deleted rather than maintained as a phantom twin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The menu portals its DOM to <body>, but React events bubble along the
component tree — a caller rendering the menu inside a clickable card or
row (WikiCard, latently ReportRow) had item clicks also fire the card's
own onClick, opening the reader over the just-opened modal. Stopping
propagation at the menu root fixes every inline caller at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kenforthewin
kenforthewin merged commit 23d9441 into main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant