Rename kit/ to rules/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
13
rules/patterns/controls-selection/README.md
Normal file
13
rules/patterns/controls-selection/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Controls + Selection Pattern
|
||||
|
||||
Canonical interactive controls for server-rendered admin/list pages:
|
||||
|
||||
- button hierarchy (primary / secondary / danger / quiet / disabled)
|
||||
- segmented filters
|
||||
- row checkboxes + select-all-visible semantics
|
||||
- bulk-action bar with explicit preview/confirm steps
|
||||
- status badges for list rows and workflow state
|
||||
|
||||
This pattern standardizes control language and interaction shape while leaving branding and
|
||||
domain terminology to the host project.
|
||||
|
||||
46
rules/patterns/controls-selection/contract.md
Normal file
46
rules/patterns/controls-selection/contract.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Contract: Controls + Selection
|
||||
|
||||
## Shared Base
|
||||
|
||||
- This pattern inherits the shared `table-management` contract:
|
||||
`kit/patterns/table-management/contract.md`.
|
||||
- Reuse shared toolbar/table/icon geometry from the base contract first, then define only
|
||||
controls-specific additions below.
|
||||
|
||||
## Buttons
|
||||
|
||||
- Use a small stable button taxonomy: `primary`, `secondary`, `ghost`, `danger`, `disabled`.
|
||||
- The canonical visual baseline for demo/scaffold examples is the active dual baseline used in the
|
||||
demo/scaffold (`Vapor Soft` / `Vapor Night`, system-selected). A frozen Aqua snapshot bundle may
|
||||
be kept for archival reference, but examples should follow the active baseline.
|
||||
- Destructive actions (`archive`, `delete`, `remove`) must use danger styling and explicit labels.
|
||||
- Button text should describe the action outcome, not implementation detail.
|
||||
- Buttons are text-first; icons are optional and must not replace labels on primary/danger actions.
|
||||
- Base examples must show both `disabled` and `loading` states.
|
||||
|
||||
## Checkbox Selection
|
||||
|
||||
- Row checkboxes support bulk actions from a shared action bar.
|
||||
- Header checkbox semantics must be explicit:
|
||||
- select visible rows only, or
|
||||
- select all rows in query scope (must be clearly labeled)
|
||||
- Selection summary must show count (`N selected`) near bulk actions.
|
||||
- In paginated views, the UI should distinguish selection on the current page from selection across the filtered/query scope.
|
||||
- Selection state should survive pagination/filter navigation via explicit state (query params, server session, or another deterministic mechanism).
|
||||
- Same-page interactions should preserve reading position (module anchor pattern is preferred in canonical server-rendered flows).
|
||||
|
||||
## Segmented Filters
|
||||
|
||||
- Segment/toggle filters are allowed for small enumerations (status, active/archived).
|
||||
- Segmented controls are grouped joined-buttons:
|
||||
- one shared outer shell,
|
||||
- rounded corners only on the first/last segment,
|
||||
- straight internal separators between middle segments.
|
||||
- Active segment color may vary by context (for example blue preset selector vs dark status scope tabs), but geometry must remain consistent.
|
||||
- Segment changes should preserve other active filters where possible.
|
||||
|
||||
## Bulk Action Safety
|
||||
|
||||
- Bulk action click should preview count and target scope before execution.
|
||||
- Destructive bulk actions require confirmation.
|
||||
- Dense bulk action bars may move rare actions into an explicit overflow menu (`More actions`) while keeping primary actions visible.
|
||||
12
rules/patterns/forms-validation/README.md
Normal file
12
rules/patterns/forms-validation/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Forms + Validation Pattern
|
||||
|
||||
Canonical patterns for server-rendered form workflows in Go web applications:
|
||||
|
||||
- tabbed / mode-switched forms
|
||||
- datalist/autocomplete suggestions
|
||||
- inline validation messages
|
||||
- review / confirm step before submit
|
||||
- explicit reset and error handling states
|
||||
|
||||
This pattern standardizes interaction flow and validation UX, not domain-specific field sets.
|
||||
|
||||
34
rules/patterns/forms-validation/contract.md
Normal file
34
rules/patterns/forms-validation/contract.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Contract: Forms + Validation + Suggestions
|
||||
|
||||
## Form Structure
|
||||
|
||||
- Group fields semantically and keep labels explicit.
|
||||
- Inputs requiring suggestions may use `datalist` or equivalent autocomplete UI.
|
||||
- Suggestion sources must represent the full relevant scope (not only visible rows from paginated tables).
|
||||
|
||||
## Validation
|
||||
|
||||
- Surface validation errors inline near fields and in a form-level summary when helpful.
|
||||
- Validation messages must be human-readable and action-oriented.
|
||||
- Do not hide required-field errors behind generic submit failures.
|
||||
|
||||
## Multi-Step Flow
|
||||
|
||||
Recommended stages:
|
||||
|
||||
1. `edit`
|
||||
2. `review`
|
||||
3. `confirm/submit`
|
||||
4. `result`
|
||||
|
||||
Rules:
|
||||
|
||||
- Users must be able to return from review to edit without losing entered values.
|
||||
- Destructive or irreversible actions require explicit confirmation.
|
||||
- Query- or state-driven step transitions should be deterministic and testable.
|
||||
|
||||
## File Inputs in Forms
|
||||
|
||||
- If a workflow includes upload, the file control should be clearly labeled and the supported formats explicit.
|
||||
- Import parsing/preview may be delegated to a dedicated import/export pattern, but the form contract must remain clear.
|
||||
|
||||
91
rules/patterns/go-api/contract.md
Normal file
91
rules/patterns/go-api/contract.md
Normal 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.
|
||||
73
rules/patterns/go-background-tasks/contract.md
Normal file
73
rules/patterns/go-background-tasks/contract.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Contract: Background Tasks (Go Web Applications)
|
||||
|
||||
## Core Rule
|
||||
|
||||
All long-running operations (> ~300ms or uncertain duration) must run as background tasks.
|
||||
Never block an HTTP handler waiting for a slow operation to complete.
|
||||
|
||||
## Standard Flow
|
||||
|
||||
```
|
||||
POST /api/something → creates task → returns {task_id}
|
||||
GET /api/tasks/:id → client polls → returns status + progress
|
||||
UI shows spinner → on success: toast + refresh
|
||||
```
|
||||
|
||||
The client polls `/api/tasks/:id` on a fixed interval (1–3 s) until status is terminal.
|
||||
|
||||
## Task Status Values
|
||||
|
||||
| Status | Meaning |
|
||||
|-------------|---------|
|
||||
| `queued` | Created, not yet started |
|
||||
| `running` | In progress |
|
||||
| `success` | Completed successfully |
|
||||
| `failed` | Completed with error |
|
||||
| `canceled` | Explicitly canceled by user |
|
||||
|
||||
## Task Struct (canonical fields)
|
||||
|
||||
```go
|
||||
type Task struct {
|
||||
ID string
|
||||
Type string // e.g. "recalculate", "import", "export"
|
||||
Status string
|
||||
Progress int // 0–100
|
||||
Message string // human-readable current step
|
||||
Result any // populated on success (row count, file URL, etc.)
|
||||
Error string // populated on failure
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- Do NOT use SSE (Server-Sent Events) for task progress. Use polling.
|
||||
SSE requires persistent connections and complicates load balancers and proxies.
|
||||
- One task per operation. Do not batch unrelated operations into a single task.
|
||||
- Tasks must be stored in memory (or DB for persistence) with a TTL — clean up after completion.
|
||||
- Handler that creates the task returns `202 Accepted` with `{"task_id": "..."}`.
|
||||
- Log task start, finish, and error server-side (see go-logging contract).
|
||||
|
||||
## UI Contract
|
||||
|
||||
- Show a spinner/progress bar while `status == "running"`.
|
||||
- On `success`: show toast notification, refresh affected data.
|
||||
- On `failed`: show error message from `task.Error`.
|
||||
- Poll interval: 1–3 seconds. Stop polling on terminal status.
|
||||
- Do not disable the whole page during task execution — allow user to navigate away.
|
||||
|
||||
## What Qualifies as a Background Task
|
||||
|
||||
- CSV/file export with > ~500 rows
|
||||
- Bulk recalculation or recompute operations
|
||||
- External API calls (sync, collect, import from remote)
|
||||
- Any import/ingest that touches multiple DB tables
|
||||
- Database repair or cleanup operations
|
||||
|
||||
## What Does NOT Need a Background Task
|
||||
|
||||
- Simple CRUD operations (create/update/delete single record)
|
||||
- Filtered list queries
|
||||
- Single-record exports
|
||||
82
rules/patterns/go-code-style/contract.md
Normal file
82
rules/patterns/go-code-style/contract.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Contract: Go Code Style and Project Conventions
|
||||
|
||||
## Logging
|
||||
|
||||
See `kit/patterns/go-logging/contract.md` for full rules.
|
||||
|
||||
Summary: use `slog`, log to stdout/stderr (binary console), never to browser console.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Always wrap errors with context. Use `fmt.Errorf("...: %w", err)`.
|
||||
|
||||
```go
|
||||
// CORRECT
|
||||
if err := db.Save(&record).Error; err != nil {
|
||||
return fmt.Errorf("save component %s: %w", record.ID, err)
|
||||
}
|
||||
|
||||
// WRONG — loses context
|
||||
return err
|
||||
```
|
||||
|
||||
- Never silently discard errors with `_` in production paths.
|
||||
- Return errors up the call stack; log at the handler/task boundary, not deep in service code.
|
||||
|
||||
## Code Formatting
|
||||
|
||||
- Always run `gofmt` before committing. No exceptions.
|
||||
- No manual alignment of struct fields or variable assignments.
|
||||
|
||||
## HTTP Handler Structure
|
||||
|
||||
Handlers are thin. Business logic belongs in a service layer.
|
||||
|
||||
```
|
||||
Handler → validates input, calls service, writes response
|
||||
Service → business logic, calls repository
|
||||
Repository → SQL queries only, returns domain types
|
||||
```
|
||||
|
||||
- Handlers must not contain SQL queries.
|
||||
- Services must not write HTTP responses.
|
||||
- Repositories must not contain business rules.
|
||||
|
||||
## Startup Sequence (Go web app)
|
||||
|
||||
```
|
||||
1. Parse flags / load config
|
||||
2. Connect to DB — fail fast if unavailable (see go-database contract)
|
||||
3. Run migrations
|
||||
4. Initialize services and background workers
|
||||
5. Register routes
|
||||
6. Start HTTP server
|
||||
```
|
||||
|
||||
Never reverse steps 2 and 5. Never start serving before migrations complete.
|
||||
|
||||
## Configuration
|
||||
|
||||
- Config lives in a single `config.yaml` file, not scattered env vars.
|
||||
- Env vars may override config values but must be documented.
|
||||
- Never hardcode ports, DSNs, or file paths in application code.
|
||||
- Provide a `config.example.yaml` committed to the repo.
|
||||
- The actual `config.yaml` is gitignored.
|
||||
|
||||
## Template / UI Rendering
|
||||
|
||||
- Server-rendered HTML via Go templates is the default.
|
||||
- htmx for partial updates — no full SPA framework unless explicitly decided.
|
||||
- Template errors must return `500` and log the error server-side.
|
||||
- Never expose raw Go error messages to the end user in rendered HTML.
|
||||
|
||||
## Business Logic Placement
|
||||
|
||||
- Threshold computation, status derivation, and scoring live on the server.
|
||||
- The UI only reflects what the server returns — it does not recompute status client-side.
|
||||
- Example: "critical / warning / ok" badge color is determined by the handler, not by JS.
|
||||
|
||||
## Dependency Rules
|
||||
|
||||
- Prefer standard library. Add a dependency only when the stdlib alternative is significantly worse.
|
||||
- Document the reason for each non-stdlib dependency in a comment or ADL entry.
|
||||
110
rules/patterns/go-database/contract.md
Normal file
110
rules/patterns/go-database/contract.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Contract: Database Patterns (Go / MySQL / MariaDB)
|
||||
|
||||
## MySQL Transaction Cursor Safety (CRITICAL)
|
||||
|
||||
**Never execute SQL on the same transaction while iterating over a query result cursor.**
|
||||
|
||||
This is the most common source of `invalid connection` and `unexpected EOF` driver panics.
|
||||
|
||||
### Rule
|
||||
|
||||
Use a two-phase approach: read all rows first, close the cursor, then execute writes.
|
||||
|
||||
```go
|
||||
// WRONG — executes SQL inside rows.Next() loop on the same tx
|
||||
rows, _ := tx.Query("SELECT id FROM machines")
|
||||
for rows.Next() {
|
||||
var id string
|
||||
rows.Scan(&id)
|
||||
tx.Exec("UPDATE machines SET processed=1 WHERE id=?", id) // DEADLOCK / driver panic
|
||||
}
|
||||
|
||||
// CORRECT — collect IDs first, then write
|
||||
rows, _ := tx.Query("SELECT id FROM machines")
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
rows.Scan(&id)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
rows.Close() // explicit close before any write
|
||||
|
||||
for _, id := range ids {
|
||||
tx.Exec("UPDATE machines SET processed=1 WHERE id=?", id)
|
||||
}
|
||||
```
|
||||
|
||||
This applies to:
|
||||
- `database/sql` with manual transactions
|
||||
- GORM `db.Raw().Scan()` inside a `db.Transaction()` callback
|
||||
- Any loop that calls a repository method while a cursor is open
|
||||
|
||||
## Soft Delete / Archive Pattern
|
||||
|
||||
Do not use hard deletes for user-visible records. Use an archive flag.
|
||||
|
||||
```go
|
||||
// Schema: is_active bool DEFAULT true
|
||||
// "Delete" = set is_active = false
|
||||
// Restore = set is_active = true
|
||||
|
||||
// All list queries must filter:
|
||||
WHERE is_active = true
|
||||
```
|
||||
|
||||
- Never physically delete rows that have foreign key references or history.
|
||||
- Hard delete is only acceptable for orphaned/temporary data with no audit trail requirement.
|
||||
- Archive operations must be reversible from the UI.
|
||||
|
||||
## GORM Virtual Fields
|
||||
|
||||
Use the correct tag based on whether the field should exist in the DB schema:
|
||||
|
||||
```go
|
||||
// Field computed at runtime, column must NOT exist in DB (excludes from migrations AND queries)
|
||||
Count int `gorm:"-"`
|
||||
|
||||
// Field computed at query time via JOIN/SELECT, column must NOT be in migrations
|
||||
// but IS populated from query results
|
||||
DisplayName string `gorm:"-:migration"`
|
||||
```
|
||||
|
||||
- `gorm:"-"` — fully ignored: no migration, no read, no write.
|
||||
- `gorm:"-:migration"` — skip migration only; GORM will still read/write if the column exists.
|
||||
- Do not use `gorm:"-"` for JOIN-populated fields — the value will always be zero.
|
||||
|
||||
## Fail-Fast DB Check on Startup
|
||||
|
||||
Always verify the database connection before starting the HTTP server.
|
||||
|
||||
```go
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil || sqlDB.Ping() != nil {
|
||||
log.Fatal("database unavailable, refusing to start")
|
||||
}
|
||||
// then: run migrations, then: start gin/http server
|
||||
```
|
||||
|
||||
Never start serving traffic with an unverified DB connection. Fail loudly at boot.
|
||||
|
||||
## N+1 Query Prevention
|
||||
|
||||
Use JOINs or batch IN queries. Never query inside a loop over rows from another query.
|
||||
|
||||
```go
|
||||
// WRONG
|
||||
for _, pricelist := range pricelists {
|
||||
items, _ := repo.GetItems(pricelist.ID) // N queries
|
||||
}
|
||||
|
||||
// CORRECT
|
||||
items, _ := repo.GetItemsByPricelistIDs(ids) // 1 query with WHERE id IN (...)
|
||||
// then group in Go
|
||||
```
|
||||
|
||||
## Migration Policy
|
||||
|
||||
- Migrations are numbered sequentially and never modified after merge.
|
||||
- Each migration must be reversible where possible (document rollback in a comment).
|
||||
- Never rename a column in one migration step — add new, backfill, drop old across separate deploys.
|
||||
- Auto-apply migrations on startup is acceptable for internal tools; document if used.
|
||||
64
rules/patterns/go-logging/contract.md
Normal file
64
rules/patterns/go-logging/contract.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Contract: Logging (Go Web Applications)
|
||||
|
||||
## Core Rule
|
||||
|
||||
**All logging goes to the server binary's stdout/stderr — never to the browser console.**
|
||||
|
||||
- `console.log`, `console.error`, `console.warn` in JavaScript are for debugging only.
|
||||
Remove them before committing. They are not a substitute for server-side logging.
|
||||
- Diagnostic information (errors, slow queries, ingest results, background task status) must be
|
||||
logged server-side so it is visible in the terminal where the binary runs.
|
||||
|
||||
## Go Logging Standard
|
||||
|
||||
Use the standard library `log/slog` (Go 1.21+) or `log` for simpler projects.
|
||||
|
||||
```go
|
||||
// correct — structured, visible in binary console
|
||||
slog.Info("export started", "user", userID, "rows", count)
|
||||
slog.Error("db query failed", "err", err)
|
||||
|
||||
// wrong — goes to browser devtools, invisible in production
|
||||
// (JS template rendered in HTML)
|
||||
// console.log("export started")
|
||||
```
|
||||
|
||||
## Log Levels
|
||||
|
||||
| Level | When to use |
|
||||
|---------|-------------|
|
||||
| `Debug` | Detailed flow tracing; disabled in production builds |
|
||||
| `Info` | Normal operation milestones: server start, request handled, export done |
|
||||
| `Warn` | Recoverable anomaly: retry, fallback used, deprecated path |
|
||||
| `Error` | Operation failed; always include `"err", err` as a structured attribute |
|
||||
|
||||
## What to Always Log
|
||||
|
||||
- Server startup: address, port, config source.
|
||||
- Background task start/finish/error with task name and duration.
|
||||
- CSV/file export: scope, row count, duration.
|
||||
- Ingest/import: file name, rows accepted, rows rejected, error summary.
|
||||
- Any `500` response: handler name + error.
|
||||
|
||||
## What Not to Log
|
||||
|
||||
- Do not log full request bodies or CSV content — can contain PII or large payloads.
|
||||
- Do not log passwords, tokens, or secrets even partially.
|
||||
- Do not add `fmt.Println` for debugging and leave it in committed code.
|
||||
|
||||
## htmx / Browser-rendered Responses
|
||||
|
||||
- Handler errors that result in an htmx partial re-render must still log the error server-side.
|
||||
- Do not rely on the browser network tab as the only visibility into server errors.
|
||||
|
||||
## Format
|
||||
|
||||
- Use structured key-value attributes, not concatenated strings.
|
||||
|
||||
```go
|
||||
// correct
|
||||
slog.Error("sync failed", "component", comp.Serial, "err", err)
|
||||
|
||||
// wrong — hard to grep, hard to parse
|
||||
log.Printf("sync failed for component %s: %v", comp.Serial, err)
|
||||
```
|
||||
13
rules/patterns/import-export/README.md
Normal file
13
rules/patterns/import-export/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Import / Export Pattern
|
||||
|
||||
Canonical file transfer UX patterns for Go web applications:
|
||||
|
||||
- file import forms (CSV/JSON and similar)
|
||||
- validation preview tables before confirm
|
||||
- confirm step with human-readable summary
|
||||
- export controls (format + scope + options)
|
||||
- predictable file download behavior and filenames
|
||||
|
||||
This pattern covers UI and UX contracts. Business-specific validation and file schemas remain in
|
||||
the host project's own architecture docs.
|
||||
|
||||
123
rules/patterns/import-export/contract.md
Normal file
123
rules/patterns/import-export/contract.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Contract: Import / Export Workflows
|
||||
|
||||
## Import Workflow
|
||||
|
||||
Recommended stages:
|
||||
|
||||
1. `Upload`
|
||||
2. `Preview / Validate`
|
||||
3. `Confirm`
|
||||
4. `Execute`
|
||||
5. `Result summary`
|
||||
|
||||
Rules:
|
||||
|
||||
- Validation preview must be human-readable (table/list), not raw JSON only.
|
||||
- Warnings and errors should be shown per row and in aggregate summary.
|
||||
- Confirm step should clearly communicate scope and side effects.
|
||||
|
||||
## Export Workflow
|
||||
|
||||
- User must explicitly choose export scope (`selected`, `filtered`, `all`) when ambiguity exists.
|
||||
- Export format should be explicit (`csv`, `json`, etc.).
|
||||
- Download response must set:
|
||||
- `Content-Type: text/csv; charset=utf-8`
|
||||
- `Content-Disposition: attachment; filename="..."`
|
||||
|
||||
## CSV Format Rules (Excel-compatible)
|
||||
|
||||
These rules are **mandatory** whenever CSV is exported for spreadsheet users.
|
||||
|
||||
### Encoding and BOM
|
||||
|
||||
- Write UTF-8 BOM (`\xEF\xBB\xBF`) as the very first bytes of the response.
|
||||
- Without BOM, Excel on Windows opens UTF-8 CSV as ANSI and garbles Cyrillic/special characters.
|
||||
|
||||
```go
|
||||
w.Write([]byte{0xEF, 0xBB, 0xBF})
|
||||
```
|
||||
|
||||
### Delimiter
|
||||
|
||||
- Use **semicolon** (`;`) as the field delimiter, not comma.
|
||||
- Excel in Russian/European locale uses semicolon as the list separator.
|
||||
- Comma-delimited files open as a single column in these locales.
|
||||
|
||||
### Numbers
|
||||
|
||||
- Write decimal numbers with a **comma** as the decimal separator: `1 234,56` — not `1234.56`.
|
||||
- Excel in Russian locale does not recognize period as a decimal separator in numeric cells.
|
||||
- Format integers and floats explicitly; do not rely on Go's default `%v` or `strconv.FormatFloat`.
|
||||
- Use a thin non-breaking space (`\u202F`) or regular space as a thousands separator when the value
|
||||
benefits from readability (e.g. prices, quantities > 9999).
|
||||
|
||||
```go
|
||||
// correct
|
||||
fmt.Sprintf("%.2f", price) // then replace "." -> ","
|
||||
strings.ReplaceAll(fmt.Sprintf("%.2f", price), ".", ",")
|
||||
|
||||
// wrong — produces "1234.56", Excel treats it as text in RU locale
|
||||
fmt.Sprintf("%.2f", price)
|
||||
```
|
||||
|
||||
### Dates
|
||||
|
||||
- Write dates as `DD.MM.YYYY` — the format Excel in Russian locale parses as a date cell automatically.
|
||||
- Do not use ISO 8601 (`2006-01-02`) for user-facing CSV; it is not auto-recognized as a date in RU locale.
|
||||
|
||||
### Text quoting
|
||||
|
||||
- Wrap any field that contains the delimiter (`;`), a newline, or a double-quote in double quotes.
|
||||
- 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 be logged server-side only — HTTP headers are already
|
||||
sent and the status code cannot be changed mid-stream.
|
||||
|
||||
9
rules/patterns/modal-workflows/README.md
Normal file
9
rules/patterns/modal-workflows/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Modal Workflow Pattern
|
||||
|
||||
This pattern package captures modal-based create/edit/remove workflows for server-rendered Go
|
||||
web UIs.
|
||||
|
||||
Synthesis sources:
|
||||
|
||||
- detailed enterprise UI interaction contracts
|
||||
- operational admin workflows in Go web apps
|
||||
56
rules/patterns/modal-workflows/contract.md
Normal file
56
rules/patterns/modal-workflows/contract.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Contract: Modal Workflows
|
||||
|
||||
## State Machine
|
||||
|
||||
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.
|
||||
26
rules/patterns/table-management/README.md
Normal file
26
rules/patterns/table-management/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Table Management Pattern (Shared Module)
|
||||
|
||||
Shared reusable module for high-density table screens across patterns.
|
||||
|
||||
This module is the canonical source for:
|
||||
|
||||
- unified table toolbar layout (single container, grouped actions, vertical separators)
|
||||
- icon-first action buttons with deterministic semantics
|
||||
- select/actions narrow edge columns
|
||||
- toolbar+table visual seam behavior (single continuous block)
|
||||
- canonical SVG icon sprite for table actions (shared at theme layer)
|
||||
- canonical table-management visual/interaction seams independent of host branding
|
||||
|
||||
Use this module as the base interaction contract for:
|
||||
|
||||
- `controls-selection`
|
||||
- `operator-tools`
|
||||
|
||||
Pattern-specific contracts should only define additions or exceptions.
|
||||
|
||||
## Canonical Assets
|
||||
|
||||
- Behavior/layout contract: `contract.md`
|
||||
- Active icon sprite: `../theme-vapor/templates/icon_sprite.html`
|
||||
- Active stylesheet baseline: `../theme-vapor/static/vapor.css`
|
||||
- Legacy icon sprite archive: `../theme-aqua-legacy/templates/icon_sprite.html`
|
||||
108
rules/patterns/table-management/contract.md
Normal file
108
rules/patterns/table-management/contract.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Contract: Table Management (Shared)
|
||||
|
||||
## Scope
|
||||
|
||||
Defines one canonical, reusable interaction model for table-driven operator/admin screens.
|
||||
All patterns that expose table selection + bulk actions must inherit this contract.
|
||||
Visual styling is inherited from the active repository baseline (currently Vapor Soft / Vapor Night
|
||||
in demo/scaffold). This contract remains theme-agnostic and defines geometry/semantics only.
|
||||
|
||||
## Canonical Regions
|
||||
|
||||
1. Selection summary line (visible/selected counts).
|
||||
2. Unified table toolbar (single module container).
|
||||
3. Data table body.
|
||||
4. Pagination.
|
||||
|
||||
Toolbar and table must read as one continuous module:
|
||||
|
||||
- toolbar directly above table,
|
||||
- no double borders/rounded-corner seams at the join,
|
||||
- table horizontal overflow remains enabled when needed.
|
||||
|
||||
## Toolbar Geometry
|
||||
|
||||
- One outer toolbar container for all groups.
|
||||
- Groups are separated with vertical separators.
|
||||
- Group title + icon buttons are inline and compact.
|
||||
- Group order is fixed:
|
||||
1. `Selection`
|
||||
2. `Actions`
|
||||
3. `Import/Export`
|
||||
4. `Task Actions`
|
||||
5. `Misc`
|
||||
|
||||
## Button Model
|
||||
|
||||
- Buttons are icon-first inside toolbar groups.
|
||||
- Each button must expose deterministic semantics via `title` and `aria-label`.
|
||||
- Keep primary and destructive variants explicit via style tokens, not by icon ambiguity alone.
|
||||
|
||||
## Icon Semantics (One Action = One Icon)
|
||||
|
||||
Do not reuse the same icon for semantically different actions in this module.
|
||||
|
||||
Canonical mapping:
|
||||
|
||||
- `select visible` -> checked square
|
||||
- `select filtered` -> filter + plus
|
||||
- `clear visible` -> square + minus
|
||||
- `clear filtered` -> filter + close
|
||||
- `clear selection` -> clear selection icon (distinct from remove/cancel/archive)
|
||||
- `run` -> play/execute
|
||||
- `edit` -> pencil
|
||||
- `remove` -> remove/delete row
|
||||
- `cancel` -> cancel task (distinct from remove/archive)
|
||||
- `archive` -> archive box/tray
|
||||
- `import` -> arrow into container
|
||||
- `export` -> arrow out of container
|
||||
- `export filtered` -> export + filter marker
|
||||
- `export selected` -> export + checkbox marker
|
||||
- `retry/sync` -> circular refresh (two arrows along path)
|
||||
- `review` -> info/review marker
|
||||
- `confirm` -> confirm/check marker
|
||||
- `inspect` -> inspect/zoom marker
|
||||
|
||||
## Table Columns
|
||||
|
||||
- Left `select` column:
|
||||
- no text header label,
|
||||
- minimal fixed width,
|
||||
- centered checkbox control.
|
||||
- Right `actions` column:
|
||||
- no text header label,
|
||||
- minimal width,
|
||||
- right-aligned action icons.
|
||||
- Row action icons in `actions` column are intentionally smaller than toolbar icons.
|
||||
|
||||
## Filtering Rules
|
||||
|
||||
- Filters live above the table, in a dedicated filter bar or inline in the toolbar.
|
||||
- Every active filter must be visually indicated (highlighted field, chip, or badge count).
|
||||
- Applying a filter always resets pagination to page 1.
|
||||
- Filter state must be reflected in the URL as query parameters so the page is bookmarkable/shareable.
|
||||
- "Reset filters" clears all filter fields and reloads with no filter params.
|
||||
- Server-side filtering only — do not filter an already-loaded JS array client-side unless the full
|
||||
dataset is guaranteed small (< 500 rows) and never grows.
|
||||
- Filter inputs debounce text input (300–500 ms) before triggering a server request.
|
||||
|
||||
## Pagination Rules
|
||||
|
||||
- Pagination is server-side. Never load all rows and paginate client-side.
|
||||
- URL query parameters carry page state: `?page=2&per_page=50`.
|
||||
- `page` is 1-based.
|
||||
- `per_page` defaults to a fixed project constant (e.g. 50); user may change it from a fixed set
|
||||
(25 / 50 / 100).
|
||||
- The server response includes: `total_count`, `page`, `per_page`, `total_pages`.
|
||||
- Display: "Showing 51–100 of 342" — always show the range and total.
|
||||
- Prev/Next buttons are disabled (not hidden) at the boundary pages.
|
||||
- Direct page-number input is optional; if present it clamps to `[1, total_pages]` on blur.
|
||||
- Changing `per_page` resets to page 1.
|
||||
|
||||
## Reuse Rule
|
||||
|
||||
If a pattern needs table selection + bulk actions, it must:
|
||||
|
||||
1. Reuse this module unchanged by default.
|
||||
2. Document only additive overrides in the pattern-specific contract.
|
||||
3. Avoid redefining shared geometry/icon semantics locally.
|
||||
Reference in New Issue
Block a user