docs(skills): extract house recipes as Claude Code skills for SSP templating
8 template-generic skills in .claude/skills/ (new-feature, new-context, value-object, form-machine, bff-endpoint, mutation-command, ui-component, new-ssp), condensed from CLAUDE.md/ARCHITECTURE/fp-tea/ADRs and pointing at this repo's worked examples. CLAUDE.md gains a pointer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
.claude/skills/mutation-command/SKILL.md
Normal file
58
.claude/skills/mutation-command/SKILL.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: mutation-command
|
||||
description: Add a write/submit operation — infrastructure adapter method plus an application command factory returning Result, keeping the reducer pure. Use for any POST/PUT/DELETE a form or action triggers.
|
||||
---
|
||||
|
||||
# Mutation command
|
||||
|
||||
Reducer = "what the new state is"; command = "go do it, then say what happened".
|
||||
The UI holds an application command, never the network client (lint-enforced).
|
||||
|
||||
## Skeleton
|
||||
|
||||
**Adapter method** (`<context>/infrastructure/<name>.adapter.ts`) — takes the
|
||||
machine's `Valid` type, returns the server's answer, no parse (server is authority):
|
||||
|
||||
```ts
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ChangeRequestAdapter {
|
||||
private client = inject(ApiClient);
|
||||
async changeRequest(data: Valid): Promise<string> { /* → server reference */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Command factory** (`<context>/application/submit-<name>.ts`) — binds the adapter in
|
||||
an injection context; `runSubmit` is the one try/catch + ProblemDetails mapping:
|
||||
|
||||
```ts
|
||||
export function createSubmitChangeRequest() {
|
||||
const adapter = inject(ChangeRequestAdapter);
|
||||
return (data: Valid): Promise<Result<string, string>> =>
|
||||
runSubmit(() => adapter.changeRequest(data), SUBMIT_FAILED);
|
||||
}
|
||||
```
|
||||
|
||||
**Caller** (organism, field initializer — same shape as `createStore`): on the
|
||||
machine's `Submitting` state, run the command, then dispatch
|
||||
`SubmitConfirmed` / `SubmitFailed { error }`. Never throw into the template.
|
||||
|
||||
## Optimistic updates on shared state
|
||||
|
||||
When a root store's view must reflect the write before the server confirms:
|
||||
`begin*` (flip pending) → `confirm*` (clear + `resource.reload()`) / `rollback*`
|
||||
(undo). See `src/app/registratie/application/big-profile.store.ts`.
|
||||
|
||||
## Worked examples
|
||||
|
||||
- `src/app/registratie/infrastructure/change-request.adapter.ts`
|
||||
- `src/app/registratie/application/submit-change-request.ts`
|
||||
- `src/app/registratie/application/draft-sync.ts` — `createDraftSync(...)` bundles
|
||||
draft persistence + submit for wizard flows; prefer it when the form has a draft.
|
||||
- `src/app/shared/application/submit.ts` — `runSubmit`, `SUBMIT_FAILED`.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
npm test && npm run lint && npm run build
|
||||
# exercise the failure path in the browser: ?scenario=error on the submitting page
|
||||
```
|
||||
Reference in New Issue
Block a user