Rename kit/ to rules/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 16:57:06 +03:00
parent 168e4b852a
commit d9204f2210
16 changed files with 0 additions and 0 deletions

View 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.

View 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.

View 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.

View 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.

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

@@ -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 (13 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 // 0100
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: 13 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

View 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.

View 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.

View 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)
```

View 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.

View 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.

View 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

View 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.

View 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`

View 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 (300500 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 51100 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.