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

View File

@@ -1,57 +1,55 @@
# {{ .project_name }} — Instructions for Claude # {{ .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)** ## Project Architecture
Read `bible-local/` — project-specific architecture.
The Bible is the single source of truth for architecture, data models, API contracts, and UI Every architectural decision specific to this project must be recorded in `bible-local/`.
pattern conventions. Every significant architectural decision must be recorded in the Bible
decision log before or alongside the code change.
--- ---
## Shared Engineering Rules ## Quick Reference (full contracts in `bible/kit/patterns/`)
The following rules apply to ALL changes in this project. ### Go Code Style (`go-code-style/contract.md`)
They are maintained centrally in `tools/ui-design-code/kit/patterns/`. - 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 ### Logging (`go-logging/contract.md`)
See [`tools/ui-design-code/kit/patterns/go-code-style/contract.md`](tools/ui-design-code/kit/patterns/go-code-style/contract.md) - `slog`, stdout/stderr only. Never `console.log` as substitute for server logging.
- Handler → Service → Repository layering - Always log: startup, task start/finish/error, export row counts, ingest results, any 500.
- 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 ### Database (`go-database/contract.md`)
See [`tools/ui-design-code/kit/patterns/go-logging/contract.md`](tools/ui-design-code/kit/patterns/go-logging/contract.md) - **CRITICAL**: never run SQL on the same tx while iterating a cursor. Two-phase: read all → close → write.
- Use `slog`, log to binary stdout/stderr only - Soft delete via `is_active = false`.
- Never use `console.log` as a substitute for server-side logging - Fail-fast DB ping before starting HTTP server.
- Always log: startup, background task start/finish/error, export row counts, ingest results - No N+1: use JOINs or batch `WHERE id IN (...)`.
- GORM: `gorm:"-"` = fully ignored; `gorm:"-:migration"` = skip migration only.
### Database ### REST API (`go-api/contract.md`)
See [`tools/ui-design-code/kit/patterns/go-database/contract.md`](tools/ui-design-code/kit/patterns/go-database/contract.md) - Plural nouns: `/api/assets`, `/api/components`.
- **CRITICAL**: never execute SQL on the same tx while iterating a cursor — two-phase only - Never `200 OK` for errors — use `422` for validation, `404`, `500`.
- Soft delete via `is_active = false`, not physical deletes - Error body: `{"error": "message", "fields": {"field": "reason"}}`.
- Fail-fast DB check before starting the HTTP server - List response always includes `total_count`, `page`, `per_page`, `total_pages`.
- No N+1 queries: use JOINs or batch `IN` queries - `/health` and `/api/db-status` required in every app.
### Background Tasks ### Background Tasks (`go-background-tasks/contract.md`)
See [`tools/ui-design-code/kit/patterns/go-background-tasks/contract.md`](tools/ui-design-code/kit/patterns/go-background-tasks/contract.md) - Slow ops (>300ms): POST → `{task_id}` → client polls `/api/tasks/:id`.
- All slow operations: POST → task_id → client polls `/api/tasks/:id` - No SSE. Polling only. Return `202 Accepted`.
- No SSE — polling only
- Return `202 Accepted` when task is created
### Tables, Filtering, Pagination ### Tables, Filtering, Pagination (`table-management/contract.md`)
See [`tools/ui-design-code/kit/patterns/table-management/contract.md`](tools/ui-design-code/kit/patterns/table-management/contract.md) - Server-side only. Filter state in URL params. Filter resets to page 1.
- Server-side filtering and pagination only - Display: "51100 из 342".
- 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"
### CSV Export ### Modals (`modal-workflows/contract.md`)
See [`tools/ui-design-code/kit/patterns/import-export/contract.md`](tools/ui-design-code/kit/patterns/import-export/contract.md) - States: open → submitting → success | error.
- UTF-8 BOM required (`\xEF\xBB\xBF`) - Destructive actions require confirmation modal naming the target.
- Semicolon delimiter (`;`), not comma - Never close on error. Use `422` for validation errors in htmx flows.
- Decimal separator: comma (`1 234,56`), not period
- Dates as `DD.MM.YYYY` ### CSV Export (`import-export/contract.md`)
- `csv.Writer` with `Comma = ';'` - 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.

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.

View File

@@ -71,8 +71,53 @@ fmt.Sprintf("%.2f", price)
- Escape embedded double-quotes by doubling them: `""`. - Escape embedded double-quotes by doubling them: `""`.
- Use `encoding/csv` with `csv.Writer` and set `csv.Writer.Comma = ';'`; it handles quoting automatically. - 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 ## Error Handling
- Import errors should map to clear user-facing messages. - 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.

View File

@@ -1,9 +1,56 @@
# Contract: Modal Workflows # Contract: Modal Workflows
## Shared Rules ## State Machine
- Destructive actions require explicit confirmation. Every modal has exactly these states:
- 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.
```
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.