Skip to content

feat(admin): fill the category form from prose, then change it by talking - #279

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/ai-forms-categories
Aug 7, 2026
Merged

feat(admin): fill the category form from prose, then change it by talking#279
github-actions[bot] merged 1 commit into
mainfrom
feat/ai-forms-categories

Conversation

@catomean

@catomean catomean commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Second app on the fleet's AI-forms standard, after kivvi#38.

evig already had AI routes (extract, the advisors), but no form could be filled from a description, and nothing could be revised by talking to it.

Adopts @fleet/ai-forms, which owns the merge rule that makes refinement work: on a fill the user's typed input wins, on a refine the model wins for the fields it returns. useAiForm infers which from whether the form is empty — so editing an existing category refines by default, a new one gets filled, and the user never picks a mode.

What's here

  • src/config/ai-forms.ts — SSOT for what the assistant may write. Colour options come from UI_COLOR_PALETTE; nothing retyped.
  • src/app/api/ai/form-assist/route.ts — one route for every assisted form. Staff auth like the other advisors, and this app's own callWithFallback, so form assistance inherits the existing provider chain instead of adding a second policy. Field specs stay server-side: the client names a form, never fields.
  • src/components/ui/AiFormBar.tsx — generic over UseAiForm; the next form reuses it unchanged.
  • CategoryForm's useState<CategoryFormData> becomes the shared store. The form didn't grow — the state moved.

Two details worth the review time

The slug. It's aiExcluded — the model must not invent a URL segment, since changing one silently moves where published posts live. But a name the model writes still has to produce a slug, exactly as typing a name does, so onApplied regenerates it through the same generateSlug(). Without that, an AI-filled form saves with an empty slug and fails the required check.

The envelope. This route deliberately does not use the house { success, data } shape. It has exactly one client — useAiForm — and the hook reads the AssistResult shape directly (result.ok, result.values). Wrapping it typechecks fine and breaks at runtime, reporting every successful call as a failure. Found while writing it, not after.

Verified

tsc --noEmit clean · eslint 0 errors (48 pre-existing warnings in files this doesn't touch) · lint:umlauts clean. Translations for all eight locales. Build runs in CI.

🤖 Generated with Claude Code

…king

Second app on the fleet's AI-forms standard, after kivvi. evig already had AI
routes (extract, advisors), but no form here could be filled from a description,
and nothing could be revised by talking to it.

Adopts @fleet/ai-forms, which owns the merge rule that makes refinement work:
on a fill the user's typed input wins, on a refine the model wins for the fields
it returns. useAiForm infers which from whether the form is empty, so editing an
existing category refines by default and a new one gets filled — the user never
picks a mode.

- src/config/ai-forms.ts — SSOT for what the assistant may write. Colour options
  come from UI_COLOR_PALETTE; nothing is retyped.
- src/app/api/ai/form-assist/route.ts — one route for every assisted form, staff
  auth like the other advisors, and this app's own callWithFallback so form
  assistance inherits the existing provider chain rather than adding a second
  policy. Field specs stay server-side: the client names a form, never fields.
- src/components/ui/AiFormBar.tsx — generic over UseAiForm, so the next form
  reuses it unchanged.
- CategoryForm's useState<CategoryFormData> becomes the shared store. The form
  did not grow; the state moved.

Two details worth the review time:

The slug is aiExcluded — the model must not invent a URL segment, because
changing one silently moves where published posts live. But a name the model
writes still has to produce a slug, exactly as typing a name does, so onApplied
regenerates it via the same generateSlug(). Without that an AI-filled form would
save with an empty slug and fail the required check.

The route deliberately does NOT use the house { success, data } envelope. It has
exactly one client — useAiForm — and the hook reads the AssistResult shape
directly (result.ok, result.values). Wrapping it typechecks fine and breaks at
runtime, reporting every successful call as a failure. Found while writing it.

Translations added for all eight locales; lint:umlauts clean (ss, never ß).

Verified: tsc --noEmit clean, eslint 0 errors (48 pre-existing warnings in files
this does not touch), lint:umlauts clean. Build runs in CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions
github-actions Bot merged commit 60b27b2 into main Aug 7, 2026
8 checks passed
@github-actions
github-actions Bot deleted the feat/ai-forms-categories branch August 7, 2026 08:03
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
#283)

Three consecutive sweeps read #278 and #282 as MERGEABLE/CLEAN, announced the
merge, and were refused:

    X Pull request #278 is not mergeable:
      the base branch policy prohibits the merge.

There is no such policy. main has branch protection with every option
disabled, no rulesets, no CODEOWNERS, no required checks and no required
reviews. Throughout the same window the bot merged #225, #226, #279 and #280
without trouble — including #225/#226, which modify .github/workflows/, so
this is not the token's workflows scope either.

The refusal is gh's client-side precheck reading the mergeStateStatus that
GITHUB_TOKEN sees, which is not the one a PAT sees — nothing this script can
inspect its way around. So log both what we saw and delegate: native
auto-merge is GitHub performing the merge itself, and does not go through that
precheck. It is the escape hatch gh names in its own error message.

Still one car per sweep — the fallback is followed by the same `break`, so at
most one PR per sweep is handed over, and the green-base guard is unchanged.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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