Fill gaps in shared pattern contracts

- modal-workflows: full state machine, htmx pattern, validation rules
- go-api: REST conventions, URL naming, status codes, error format, list response
- import-export: streaming export 3-layer architecture with Go example
- CLAUDE.template.md: updated to include modals and REST API references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 16:47:52 +03:00
parent 40d1c303bb
commit a37aec8790
4 changed files with 231 additions and 50 deletions
+42 -44
View File
@@ -1,57 +1,55 @@
# {{ .project_name }} — Instructions for Claude
Read and follow the project Bible before making any changes:
## Shared Engineering Rules
Read `bible/` — shared rules for all projects (CSV, logging, DB, tables, background tasks, code style).
Start with `bible/kit/patterns/` for specific contracts.
**[`bible/README.md`](bible/README.md)**
The Bible is the single source of truth for architecture, data models, API contracts, and UI
pattern conventions. Every significant architectural decision must be recorded in the Bible
decision log before or alongside the code change.
## Project Architecture
Read `bible-local/` — project-specific architecture.
Every architectural decision specific to this project must be recorded in `bible-local/`.
---
## Shared Engineering Rules
## Quick Reference (full contracts in `bible/kit/patterns/`)
The following rules apply to ALL changes in this project.
They are maintained centrally in `tools/ui-design-code/kit/patterns/`.
### Go Code Style (`go-code-style/contract.md`)
- Handler → Service → Repository. No SQL in handlers, no HTTP writes in services.
- Errors: `fmt.Errorf("context: %w", err)`. Never discard with `_`.
- `gofmt` before every commit.
- Thresholds and status logic on the server — UI only reflects what server returns.
### Go Code Style
See [`tools/ui-design-code/kit/patterns/go-code-style/contract.md`](tools/ui-design-code/kit/patterns/go-code-style/contract.md)
- Handler → Service → Repository layering
- Error wrapping with `fmt.Errorf("...: %w", err)`
- Startup sequence: connect DB → migrate → start server
- Business logic and status thresholds live on the server, not in JS
### Logging (`go-logging/contract.md`)
- `slog`, stdout/stderr only. Never `console.log` as substitute for server logging.
- Always log: startup, task start/finish/error, export row counts, ingest results, any 500.
### Logging
See [`tools/ui-design-code/kit/patterns/go-logging/contract.md`](tools/ui-design-code/kit/patterns/go-logging/contract.md)
- Use `slog`, log to binary stdout/stderr only
- Never use `console.log` as a substitute for server-side logging
- Always log: startup, background task start/finish/error, export row counts, ingest results
### Database (`go-database/contract.md`)
- **CRITICAL**: never run SQL on the same tx while iterating a cursor. Two-phase: read all → close → write.
- Soft delete via `is_active = false`.
- Fail-fast DB ping before starting HTTP server.
- No N+1: use JOINs or batch `WHERE id IN (...)`.
- GORM: `gorm:"-"` = fully ignored; `gorm:"-:migration"` = skip migration only.
### Database
See [`tools/ui-design-code/kit/patterns/go-database/contract.md`](tools/ui-design-code/kit/patterns/go-database/contract.md)
- **CRITICAL**: never execute SQL on the same tx while iterating a cursor — two-phase only
- Soft delete via `is_active = false`, not physical deletes
- Fail-fast DB check before starting the HTTP server
- No N+1 queries: use JOINs or batch `IN` queries
### REST API (`go-api/contract.md`)
- Plural nouns: `/api/assets`, `/api/components`.
- Never `200 OK` for errors — use `422` for validation, `404`, `500`.
- Error body: `{"error": "message", "fields": {"field": "reason"}}`.
- List response always includes `total_count`, `page`, `per_page`, `total_pages`.
- `/health` and `/api/db-status` required in every app.
### Background Tasks
See [`tools/ui-design-code/kit/patterns/go-background-tasks/contract.md`](tools/ui-design-code/kit/patterns/go-background-tasks/contract.md)
- All slow operations: POST → task_id → client polls `/api/tasks/:id`
- No SSE — polling only
- Return `202 Accepted` when task is created
### Background Tasks (`go-background-tasks/contract.md`)
- Slow ops (>300ms): POST → `{task_id}` → client polls `/api/tasks/:id`.
- No SSE. Polling only. Return `202 Accepted`.
### Tables, Filtering, Pagination
See [`tools/ui-design-code/kit/patterns/table-management/contract.md`](tools/ui-design-code/kit/patterns/table-management/contract.md)
- Server-side filtering and pagination only
- Filter state in URL query params; applying a filter resets to page 1
- Response must include `total_count`, `page`, `per_page`, `total_pages`
- Display format: "51100 из 342"
### Tables, Filtering, Pagination (`table-management/contract.md`)
- Server-side only. Filter state in URL params. Filter resets to page 1.
- Display: "51100 из 342".
### CSV Export
See [`tools/ui-design-code/kit/patterns/import-export/contract.md`](tools/ui-design-code/kit/patterns/import-export/contract.md)
- UTF-8 BOM required (`\xEF\xBB\xBF`)
- Semicolon delimiter (`;`), not comma
- Decimal separator: comma (`1 234,56`), not period
- Dates as `DD.MM.YYYY`
- `csv.Writer` with `Comma = ';'`
### Modals (`modal-workflows/contract.md`)
- States: open → submitting → success | error.
- Destructive actions require confirmation modal naming the target.
- Never close on error. Use `422` for validation errors in htmx flows.
### CSV Export (`import-export/contract.md`)
- BOM: `\xEF\xBB\xBF`. Delimiter: `;`. Decimal: `,` (`1 234,56`). Dates: `DD.MM.YYYY`.
- Stream via callback — never load all rows into memory.
- Always call `w.Flush()` after the loop.
+91
View File
@@ -0,0 +1,91 @@
# Contract: REST API Conventions (Go Web Applications)
## URL Naming
- Resources are plural nouns: `/api/assets`, `/api/components`, `/api/pricelists`.
- Nested resources use parent path: `/api/assets/:id/components`.
- Actions that are not CRUD use a verb suffix: `/api/pricelists/:id/export-csv`,
`/api/tasks/:id/cancel`, `/api/sync/push`.
- No verbs in resource paths: use `/api/assets` + DELETE, not `/api/delete-asset`.
## HTTP Methods and Status Codes
| Operation | Method | Success | Notes |
|-----------|--------|---------|-------|
| List | GET | 200 | |
| Get one | GET | 200 / 404 | |
| Create | POST | 201 | Return created resource |
| Update | PUT / PATCH | 200 | Return updated resource |
| Delete / Archive | DELETE | 200 or 204 | |
| Async action | POST | 202 | Return `{task_id}` |
| Validation error | POST/PUT | 422 | Return field errors |
| Server error | any | 500 | Log full error server-side |
| Not found | any | 404 | |
| Unauthorized | any | 401 | |
- Never return `200 OK` for validation errors — use `422 Unprocessable Entity`.
- Never return `200 OK` with `{"error": "..."}` in the body — use the correct status code.
## Error Response Format
All non-2xx responses return a consistent JSON body:
```json
{
"error": "human-readable message",
"fields": {
"serial_number": "Serial number is required",
"price": "Must be greater than 0"
}
}
```
- `error` — always present, describes what went wrong.
- `fields` — optional, present only for validation errors (422). Keys match form field names.
- Never expose raw Go error strings, stack traces, or SQL errors to the client.
## List Response Format
```json
{
"items": [...],
"total_count": 342,
"page": 2,
"per_page": 50,
"total_pages": 7
}
```
- Always include pagination metadata even if the client did not request it.
- `items` is always an array — never `null`, use `[]` for empty.
## Request Conventions
- Accept `application/json` for API endpoints.
- HTML form submissions (htmx) use `application/x-www-form-urlencoded` or `multipart/form-data`.
- File uploads use `multipart/form-data`.
- Query parameters for filtering and pagination: `?page=1&per_page=50&search=abc&status=active`.
## Health and Utility Endpoints
Every application must expose:
```
GET /health → 200 {"status": "ok"}
GET /api/db-status → 200 {"ok": true} or 500 {"ok": false, "error": "..."}
```
- `/health` must respond even if DB is down (for load balancer probes).
- `/api/db-status` checks the actual DB connection and returns its state.
## Async Actions
For long-running operations return immediately with a task reference:
```
POST /api/pricelists/create → 202 {"task_id": "abc123"}
GET /api/tasks/abc123 → 200 {"status": "running", "progress": 42, "message": "Processing..."}
GET /api/tasks/abc123 → 200 {"status": "success", "result": {...}}
```
See `go-background-tasks/contract.md` for full task contract.
+46 -1
View File
@@ -71,8 +71,53 @@ fmt.Sprintf("%.2f", price)
- Escape embedded double-quotes by doubling them: `""`.
- Use `encoding/csv` with `csv.Writer` and set `csv.Writer.Comma = ';'`; it handles quoting automatically.
## Streaming Export Architecture (Go)
For exports with potentially large row counts use a 3-layer streaming pattern.
Never load all rows into memory before writing — stream directly to the response writer.
```
Handler → sets HTTP headers + writes BOM → calls Service
Service → delegates to Repository with a row callback
Repository → queries in batches → calls callback per row
Handler/Service → csv.Writer.Flush() after all rows
```
```go
// Handler
func ExportCSV(c *gin.Context) {
c.Header("Content-Type", "text/csv; charset=utf-8")
c.Header("Content-Disposition", `attachment; filename="export.csv"`)
c.Writer.Write([]byte{0xEF, 0xBB, 0xBF}) // BOM
w := csv.NewWriter(c.Writer)
w.Comma = ';'
w.Write([]string{"ID", "Name", "Price"}) // header row
err := svc.StreamRows(ctx, filters, func(row Row) error {
return w.Write([]string{row.ID, row.Name, formatPrice(row.Price)})
})
w.Flush()
if err != nil {
// headers already sent — log only, cannot change status
slog.Error("csv export failed mid-stream", "err", err)
}
}
// Repository — batch fetch with callback
func (r *Repo) StreamRows(ctx, filters, fn func(Row) error) error {
rows, err := r.db.QueryContext(ctx, query, args...)
// ... scan and call fn(row) for each row
}
```
- Use `JOIN` in the repository query to avoid N+1 per row.
- Batch size is optional; streaming row-by-row is fine for most datasets.
- Always call `w.Flush()` after the loop — `csv.Writer` buffers internally.
## Error Handling
- Import errors should map to clear user-facing messages.
- Export errors after streaming starts must degrade gracefully (human-readable fallback).
- Export errors after streaming starts must be logged server-side only — HTTP headers are already
sent and the status code cannot be changed mid-stream.
+52 -5
View File
@@ -1,9 +1,56 @@
# Contract: Modal Workflows
## Shared Rules
## State Machine
- Destructive actions require explicit confirmation.
- Validation and backend errors are rendered in human-readable form.
- UI state transitions are explicit (`open` / `submit` / `success` / `error` / `cancel`).
- API contracts and UI copy should be documented in the host project's Bible.
Every modal has exactly these states:
```
closed → open → submitting → success | error
cancel → closed
```
- `open`: form visible, submit enabled.
- `submitting`: form disabled, spinner on submit button, no double-submit possible.
- `success`: close modal, show toast, refresh affected data.
- `error`: stay open, show error message inline, re-enable form.
- `cancel`: close without changes, no confirmation needed unless form is dirty.
## Rules
- Destructive actions (delete, archive, bulk remove) require a separate confirmation modal — not
just a disabled button. Confirmation modal must name the target: "Delete component ABC-123?"
- Never close a modal automatically on error — keep it open so the user can retry or copy the error.
- Submit button text must describe the action: "Save", "Delete", "Archive" — not "OK" or "Confirm".
- Modal title must match the action: "Edit Component", "Delete Asset" — not generic "Modal".
- Escape key and clicking the backdrop close the modal (unless in `submitting` state).
## Validation
- Validate on submit server-side. Client-side validation is optional progressive enhancement only.
- Show field-level errors inline below each field.
- Show a form-level error summary at the top if multiple fields fail.
- Error messages must be human-readable and action-oriented: "Serial number is required" — not
"serial_number: cannot be null".
## htmx Pattern (server-rendered modals)
```
POST /api/entity → 200 OK + HX-Trigger: "entitySaved" (success)
→ 422 Unprocessable + partial HTML (validation error, re-render form)
→ 500 + error message (server error)
```
- On success: server sends `HX-Trigger` header, JS listener closes modal and refreshes list.
- On validation error: server re-renders the form partial with inline errors (422).
- On server error: show generic error toast, log full error server-side.
- Do not use `200 OK` for validation errors — use `422` so htmx can differentiate.
## Multi-Step Modals
Use only when the workflow genuinely requires staged input (e.g. import preview → confirm).
- Show a step indicator (Step 1 of 3).
- Back button must restore previous step values.
- Final confirm step must summarise what will happen before the destructive/irreversible action.
- Single-step edits must NOT be split into multi-step without good reason.