Compare commits

...

24 Commits

Author SHA1 Message Date
Mikhail Chusavitin
d2e11b8bdd Strengthen backup and secret handling contracts 2026-03-07 22:03:49 +03:00
Mikhail Chusavitin
f55bd84668 Require application-owned backups for migrations 2026-03-07 21:56:51 +03:00
Mikhail Chusavitin
61ed2717d0 Extract shared backup management contract 2026-03-07 21:49:07 +03:00
Mikhail Chusavitin
548eb70d55 Add mandatory DB backup rule 2026-03-07 21:39:50 +03:00
Mikhail Chusavitin
72e10622ba Add BOM decomposition contract 2026-03-07 15:08:45 +03:00
Mikhail Chusavitin
0e61346d20 feat: add KISS and task-discipline contracts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:07:47 +03:00
a38c35ce2d docs: add three LiveCD/embedded patterns from bee project
- alpine-livecd: mkimage profile rules, apkovl mechanics, workdir caching,
  squashfs compression, NIC firmware, long build survival via screen
- vendor-installer-verification: checksum-before-download, cache validation,
  version URL verification before writing build scripts
- unattended-boot-services: OpenRC invariants for headless environments,
  network-independent SSH, persistent DHCP, graceful degradation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 18:18:22 +03:00
c73ece6c7c feat(app-binary): add host deployment path convention /appdata/<appname>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 14:07:54 +03:00
456c1f022c feat(release-signing): add Ed25519 multi-key release signing contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 10:27:21 +03:00
34b457d654 Add git sync check contract 2026-03-01 22:34:30 +03:00
472c8a6918 Add no-hardcoded-vendors contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 22:21:13 +03:00
91a1cc182d Add identifier normalization contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 22:07:19 +03:00
af4d0f353b Add batch file upload ADR contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 18:22:42 +03:00
66c38f5a60 Add app binary contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:45:53 +03:00
e020c9b234 Add testing policy contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:33:52 +03:00
0c829182a1 Add Version: 1.0 to all contracts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:16:50 +03:00
ed8ba09226 Add module versioning contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:15:32 +03:00
b30b7ee739 Add project bible rules and update README
- rules/patterns/go-project-bible/contract.md: required files, ADL format,
  runtime-flows rules, what not to duplicate, how to keep current
- README.md: rewritten to reflect actual repo purpose and structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 17:04:41 +03:00
1288d825d9 Update paths kit/ → rules/ in CLAUDE.md, AGENTS.md, template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 16:57:48 +03:00
d9204f2210 Rename kit/ to rules/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 16:57:06 +03:00
168e4b852a Clean up .gitignore 2026-03-01 16:56:00 +03:00
34d813d7ce Remove legacy infrastructure, keep only shared rule contracts
Deleted: demo app, designsync/exports bundle system, bible meta-docs,
theme CSS, scaffolds, examples, empty pattern placeholders, go.mod,
CHANGELOG, VERSIONING

Kept: kit/patterns/*/contract.md (all engineering rules),
kit/ai/claude/CLAUDE.template.md, CLAUDE.md, AGENTS.md, README.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 16:55:11 +03:00
a37aec8790 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>
2026-03-01 16:47:52 +03:00
40d1c303bb Add shared engineering rule contracts
- go-logging: slog, server-side only, structured attributes
- go-database: MySQL cursor safety, soft delete, GORM tags, fail-fast, N+1 prevention
- go-background-tasks: Task Manager pattern, polling, no SSE
- go-code-style: layering, error wrapping, startup sequence, config, templating
- import-export: CSV Excel-compatible rules (BOM, semicolon, decimal comma, DD.MM.YYYY)
- table-management: filtering and pagination rules added
- CLAUDE.template.md: updated to reference all shared contracts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 16:39:39 +03:00
130 changed files with 2577 additions and 18243 deletions

6
.gitignore vendored
View File

@@ -1,8 +1,2 @@
bin/
dist/
*.exe
*.test
.DS_Store
designsync
demo-server
.gocache_*/

View File

@@ -1,15 +1,8 @@
# UI Design Code — Agent Instructions
# Bible — Instructions for Codex
Read and follow `bible/README.md` as the architecture source of truth.
This repository is the shared engineering rules library for all projects.
This repository is a submodule-first design kit for Go web projects. Keep shared assets in
`kit/` generic and reusable; project branding belongs in host repositories.
## Rules
- Architecture and integration contracts live in `bible/` (English only).
- Significant decisions go to `bible/decisions/10-decisions.md`.
- Public bundle contracts live in `exports/`; keep them in sync with `kit/`.
- Prefer updating shared templates/patterns over adding host-specific logic here.
- Do not broaden the repository scope beyond the currently approved area unless the user
explicitly requests it; when in doubt, stay focused on the current scope.
Rules live in `rules/patterns/` as `contract.md` files. When adding or updating a rule:
- Find the relevant existing contract and edit it, or create a new `rules/patterns/<topic>/contract.md`.
- Do not create rules outside `rules/patterns/`.
- Do not expand scope beyond engineering rules and patterns.

View File

@@ -1,27 +0,0 @@
# Changelog
All notable changes to the public design kit surface are documented here.
## Unreleased
### Added
- Submodule-first repository layout (`kit/`, `exports/`, `tools/designsync`, `demo/`)
- Initial bundle manifests (`ai-rules`, `bible-core`, `go-web-skeleton`, `ui-pattern-table`, `ui-pattern-modal`)
- `designsync` MVP CLI (`list`, `validate`, `plan`, `apply`)
- Demo app (`demo/`) with bundle catalog UI
- Design single-source index: `bible/architecture/design-canon-map.md`
- Active reusable Vapor theme module in `kit/patterns/theme-vapor` (CSS + icon sprite)
- New export bundle: `ui-theme-vapor`
- Vapor shell palette catalog with 9 preset IDs for header/accent colors and tokenized shell height
(`kit/patterns/theme-vapor/palette-catalog.md`)
### Changed
- Canonical baseline docs aligned to `Vapor Soft` / `Vapor Night` as active demo/scaffold style.
- Aqua module/docs clarified as legacy archive/reference (not active baseline).
- Table-management docs clarified as theme-agnostic geometry/semantics contract.
- Vapor shell CSS now supports reusable preset selection via `data-vapor-shell` and shared
`--shell-*` tokens across default, auto-dark, and explicit `data-theme` modes.
- Demo/scaffold runtime now includes a deterministic vapor preset selection strategy:
`?vapor_shell``localStorage["vapor_shell"]` → markup default.

View File

@@ -1,17 +1,8 @@
# UI Design Code — Instructions for Claude
# Bible — Instructions for Claude
Read and follow the project Bible before making any changes:
This repository is the shared engineering rules library for all projects.
**[`bible/README.md`](bible/README.md)**
The Bible is the single source of truth for architecture, public integration contracts,
bundle manifests, and reusable pattern conventions.
Every significant architectural decision must be recorded in
[`bible/decisions/10-decisions.md`](bible/decisions/10-decisions.md).
If a change affects `kit/`, `exports/`, or `tools/designsync`, update the relevant Bible
docs and `CHANGELOG.md` in the same change set.
Do not expand the repository scope beyond the currently approved work area unless the user
explicitly requests that expansion.
Rules live in `rules/patterns/` as `contract.md` files. When adding or updating a rule:
- Find the relevant existing contract and edit it, or create a new `rules/patterns/<topic>/contract.md`.
- Do not create rules outside `rules/patterns/`.
- Do not expand scope beyond engineering rules and patterns.

View File

@@ -1,41 +1,45 @@
# UI Design Code
# Bible
Submodule-first design-code kit for Go web applications and AI coding agents.
Shared engineering rules library for Go web projects.
This repository is meant to be added to host projects as a git submodule and used to
copy/sync canonical documentation, AI instruction templates, scaffolds, and UI pattern
building blocks.
Add as a git submodule to any project — agents (Claude, Codex) will read the rules automatically.
## Intended Usage
1. Add as submodule (recommended path: `tools/ui-design-code`)
2. Use `tools/designsync` to list/plan/apply bundles into the host repo
3. Review changes and commit in the host repo
## Public Surface (stable)
- `kit/`
- `exports/`
- `tools/designsync/`
- `README.md`
- `VERSIONING.md`
- `CHANGELOG.md`
`demo/` is a runnable reference app and may change faster.
## Quick Start (this repo)
## Usage
```bash
go test ./...
go build ./tools/designsync
# Add to a project
git submodule add https://git.mchus.pro/mchus/bible.git bible
cd demo
go test ./...
go run ./cmd/demo-server
# Update to latest rules
git submodule update --remote bible
```
## Documentation
## Structure
- Architecture source of truth: `bible/README.md`
- AI instructions: `CLAUDE.md`, `AGENTS.md`
```
rules/patterns/ — shared engineering rule contracts
go-logging/ — slog, server-side only
go-database/ — cursor safety, soft delete, GORM, N+1
go-api/ — REST conventions, error format, status codes
go-background-tasks/ — Task Manager pattern, polling
go-code-style/ — layering, error wrapping, startup sequence
go-project-bible/ — how to write and maintain a project bible
bom-decomposition/ — one BOM row to many component/LOT mappings
import-export/ — CSV Excel-compatible format, streaming export
table-management/ — toolbar, filtering, pagination
modal-workflows/ — state machine, htmx pattern, confirmation
forms-validation/ — validation, multi-step flows
controls-selection/ — buttons, checkboxes, segmented filters
rules/ai/claude/
CLAUDE.template.md — base CLAUDE.md template for new projects
```
## Project Setup
Each project needs:
- `bible/` — this submodule
- `bible-local/` — project-specific architecture (data model, API, ADL)
- `CLAUDE.md` + `AGENTS.md` — point agents to both
See `rules/ai/claude/CLAUDE.template.md` for a ready-made template.
See `rules/patterns/go-project-bible/contract.md` for what goes in `bible-local/`.

View File

@@ -1,24 +0,0 @@
# Versioning Policy
This repository uses `v0.x.y` tags while the public bundle contracts are stabilizing.
## Public Compatibility Surface
Breaking changes are changes to:
- `kit/` file paths or template variables
- `exports/` manifest schema or bundle ids
- `tools/designsync` CLI behavior/flags
## Tagging Rules (`v0.x.y`)
- `y` (patch): fixes, docs updates, non-breaking improvements
- `x` (minor): any breaking public-surface change during `0.x`
## Release Checklist
1. Update `CHANGELOG.md`
2. Validate manifests (`designsync validate`)
3. Run tests (`go test ./...` and `cd demo && go test ./...`)
4. Tag release

View File

@@ -1,57 +0,0 @@
# UI Design Code Bible
> **Documentation language:** English only. All maintained architecture documentation must be
> written in English.
>
> **Architectural decisions:** Every significant architectural decision **must** be recorded in
> [`decisions/10-decisions.md`](decisions/10-decisions.md) before or alongside the code change.
>
> **Single source of truth:** Architecture and technical design documentation belongs in `bible/`.
> Keep `README.md`, `CLAUDE.md`, and `AGENTS.md` minimal to avoid duplicate documentation.
This directory is the single source of truth for UI Design Code architecture, bundle contracts,
and reusable pattern conventions. It is structured so both humans and AI assistants can navigate
it quickly.
---
## Reading Map (Hierarchical)
### 1. Foundations (read first)
| File | What it covers |
|------|----------------|
| [architecture/system-overview.md](architecture/system-overview.md) | Product purpose, scope, repository composition |
| [architecture/submodule-integration-contract.md](architecture/submodule-integration-contract.md) | How host repositories consume this kit |
| [architecture/ui-pattern-catalog.md](architecture/ui-pattern-catalog.md) | Canonical pattern taxonomy and bundle status |
| [architecture/design-canon-map.md](architecture/design-canon-map.md) | Single-source map for canonical UI contracts and assets |
### 2. Runtime & Delivery
| File | What it covers |
|------|----------------|
| [architecture/demo-runtime-flows.md](architecture/demo-runtime-flows.md) | Demo app routes, render flow, failure behavior |
| [governance/documentation-policy.md](governance/documentation-policy.md) | Rules for maintaining architecture docs |
### 3. Governance (always current)
| File | What it covers |
|------|----------------|
| [decisions/10-decisions.md](decisions/10-decisions.md) | Architectural Decision Log (ADL) |
| [synthesis/source-repos.md](synthesis/source-repos.md) | Source repositories and Bible entrypoints |
| [synthesis/common-invariants.md](synthesis/common-invariants.md) | Shared rules extracted from source repos |
| [synthesis/normalization-matrix.md](synthesis/normalization-matrix.md) | Source → canonical convention mapping |
| [synthesis/ui-pattern-coverage-matrix.md](synthesis/ui-pattern-coverage-matrix.md) | UI pattern checklist and coverage status |
---
## Quick Orientation for AI Assistants
- Public reusable artifacts: `kit/`
- Bundle manifests: `exports/bundles/`
- Bundle index: `exports/index.yaml`
- Sync/apply tool: `tools/designsync/`
- Runnable reference app: `demo/`
Read order for most changes: system overview → submodule contract → affected bundle docs →
decision log.

View File

@@ -1,41 +0,0 @@
# Demo Runtime Flows
## Demo App Purpose
The demo app (`demo/`) is a runnable reference for shared UI patterns. It is not the public
integration surface for host repositories.
## HTTP Routes (initial)
- `GET /` - pattern bundle catalog page
- `GET /healthz` - health endpoint
- `GET /static/*` - static assets
## Demo Shell Visual Mode (Current Baseline)
- The main demo shell uses a dual visual baseline:
- `Vapor Soft` for light/system-light mode
- `Vapor Night` for dark/system-dark mode
- Theme selection in the main demo shell is system-driven (`prefers-color-scheme`) only.
- The style playground route may exist for internal experimentation, but it is not part of the
primary demo navigation/catalog when the baseline is under active refinement.
## Page Render Flow (`GET /`)
1. Request hits `net/http` server mux
2. Handler prepares bundle/pattern catalog view model
3. Embedded templates render HTML response
4. Static CSS/JS loaded from embedded assets
## Interaction Flow Guardrail (Server-Rendered UI)
- Query-driven interactions (filters, pagination, segmented tabs, bulk actions, modal open/step state)
must preserve the user's reading position on the page.
- Canonical mechanism in this repo: anchor-to-module links/forms (for example `#table-filters`,
`#table-list`, `#controls-selection`, `#timeline-drilldown`).
- JS scroll restoration is optional and not required for the base contracts.
## Failure Behavior
- Template parse/render errors return `500`
- No DB calls or external services in bootstrap demo

View File

@@ -1,42 +0,0 @@
# Design Canon Map (Single Source Index)
This file defines where each reusable design element is specified exactly once.
Use it to prevent duplicated contracts and drift across demo, kit, and exports.
## Active Visual Baseline
- Active baseline: `Vapor Soft` / `Vapor Night` (system-driven mode selection).
- Canonical runtime statement: `bible/architecture/demo-runtime-flows.md`.
- Aqua assets are archive-only: `bible/architecture/legacy-aqua-freeze.md`.
## Canonical Component Contracts
- Table management geometry + semantics (toolbar groups, icon semantics, select/actions columns):
`kit/patterns/table-management/contract.md`
- Controls + selection additions:
`kit/patterns/controls-selection/contract.md`
- Operator tools additions:
`kit/patterns/operator-tools/contract.md`
- Table pagination/filter contract:
`kit/patterns/table-pagination/contract.md`
- Forms + validation:
`kit/patterns/forms-validation/contract.md`
- Import/export flow:
`kit/patterns/import-export/contract.md`
- Modal workflow:
`kit/patterns/modal-workflows/contract.md`
## Canonical Asset Sources
- Active icon sprite source: `kit/patterns/theme-vapor/templates/icon_sprite.html`
- Active stylesheet source: `kit/patterns/theme-vapor/static/vapor.css`
- Shell palette catalog source: `kit/patterns/theme-vapor/palette-catalog.md`
- Demo runtime icon usage mirror: `demo/web/templates/base.html`
- Demo runtime stylesheet mirror: `demo/web/static/css/app.css`
## Reuse Rules
- Do not redefine shared toolbar/table geometry in pattern-specific contracts.
- Do not redefine icon semantics outside `table-management/contract.md`.
- When a pattern needs exceptions, document only additive overrides in its own contract.
- Keep bundle manifests in `exports/bundles/` aligned with canonical contract locations.

View File

@@ -1,32 +0,0 @@
# Legacy Aqua Freeze (Reference Only)
## Status
This document records the outcome of the Aqua-style exploration and its freeze status.
- Status: `legacy`
- Active use: `archive reference`
- Purpose: preserve the explored style decisions as a reusable reference snapshot bundle
## Locked Decisions (Aqua Exploration)
- Aqua-inspired component language was explored across buttons, segmented controls, tables, filter controls, modal chrome, and status badges.
- The exploration introduced a consistent control-surface vocabulary and modal/window chrome experiments.
- The exploration is preserved as an archive snapshot for reference and rollback safety.
## Current Direction
- Active visual baseline for the demo/scaffold is **Vapor Soft / Vapor Night**.
- Aqua artifacts remain archived in this module and are not the active baseline.
- This document remains an archive record of the freeze/snapshot event and stored artifacts.
## Storage / Bundle
- Legacy Aqua snapshot bundle: `ui-theme-aqua-legacy`
- Bundle manifest: `exports/bundles/ui-theme-aqua-legacy.yaml`
- Snapshot assets and notes: `kit/patterns/theme-aqua-legacy/`
## Usage Policy
- The snapshot may be used as a legacy reference, migration source, or comparison artifact when explicitly needed.
- The active baseline is maintained in the live demo/scaffold styles, not in this frozen snapshot file.

View File

@@ -1,44 +0,0 @@
# Submodule Integration Contract
## Canonical Consumption Model
Host repositories consume this project as a git submodule and copy/sync selected artifacts
from the kit into their own repository.
Direct runtime dependency on submodule paths is not the default integration pattern.
## Recommended Host Placement
- `tools/ui-design-code`
- `third_party/ui-design-code`
## Stable Public Surface
Host repositories may rely on:
- `kit/`
- `exports/`
- `tools/designsync/`
- `VERSIONING.md`
- `CHANGELOG.md`
`demo/` is reference-only and not part of the stable integration contract.
## Bundles
Bundle definitions live in `exports/bundles/*.yaml` and are indexed by `exports/index.yaml`.
Each bundle must define:
- stable `id`
- version
- source paths under `kit/`
- target mapping rules
- conflict policy
- template variables (if any)
## Safety Rules
- Bundle manifests must not write outside the declared host target root.
- `designsync` must reject path traversal in manifest source/target paths.

View File

@@ -1,33 +0,0 @@
# System Overview
## Product
UI Design Code is a submodule-first design kit for Go web applications and AI coding agents.
It provides:
- canonical documentation skeletons ("Bible")
- AI instruction templates
- reusable scaffolds and UI pattern building blocks
- a sync/apply tool (`designsync`) for host repositories
- a runnable demo app for pattern reference
## Primary Consumers
- Host Go repositories using this repo as a git submodule
- AI coding agents (Codex, Claude Code, etc.)
- Developers maintaining shared UI/architecture conventions
## Runtime Composition
- Root module: tools and bundle management (`tools/designsync`)
- `kit/`: copy/sync source artifacts for host repos
- `exports/`: machine-readable bundle manifests
- `demo/`: separate Go module for browsing patterns in action
## Non-Goals (Phase 0 / bootstrap)
- No production-ready shared Go UI framework
- No host-project-specific business rules or branding
- No database/runtime dependencies for the demo

View File

@@ -1,57 +0,0 @@
# UI Pattern Catalog
Canonical pattern taxonomy for reusable Go web UI patterns.
Visual baseline for the canonical demo and scaffold is Vapor:
- `Vapor Soft` for light mode
- `Vapor Night` for dark mode
Pattern-level interaction contracts inherit the active baseline component language rather than
redefining primitives locally. The Aqua bundle remains archive/reference only.
## Pattern Families
- Table + filters + pagination
- Modal workflows (create/edit/remove/confirm)
- Status indicators and health badges
- Timeline cards and grouped history views
- Forms with validation and server round-trips
- Operator/admin tools dashboards (queue + batch actions)
## Initial Bundle Status
| Pattern | Bundle | Status |
|---|---|---|
| Table + pagination | `ui-pattern-table` | placeholder contract + files |
| Controls + selection | `ui-pattern-controls` | contract + files |
| Forms + validation + suggestions | `ui-pattern-forms` | contract + files |
| Operator tools dashboard | `ui-pattern-operator-tools` | contract + files |
| Modal workflow | `ui-pattern-modal` | contract + files |
| Import / export | `ui-pattern-io` | contract + files |
| Status indicators | bundled inside controls/table demos | partial |
| Timeline cards | `ui-pattern-timeline` | contract + files |
## Theme Bundles
| Theme | Bundle | Status |
|---|---|---|
| Vapor (active baseline) | `ui-theme-vapor` | active |
| Aqua legacy snapshot | `ui-theme-aqua-legacy` | archive/reference |
## Synthesis Direction
Initial canonical behaviors are synthesized from multiple existing Go web applications with:
- detailed UI interaction contracts
- operational admin workflows
- lean `net/http` + template runtime structures
## Component Baseline Notes
- Buttons, segmented controls, status badges, filter inputs, and table surfaces share one baseline component language.
- Canonical demo visuals target the active Vapor baseline (`Vapor Soft` / `Vapor Night`).
- Segmented controls use joined-button geometry (shared shell, rounded outer edges only, straight internal separators).
- Context-specific active-state color is allowed (for example dark scope tabs vs blue theme preset selector) without changing segmented geometry.
- Table-management behavior and toolbar icon semantics are defined once in
`kit/patterns/table-management/contract.md`.

View File

@@ -1,148 +0,0 @@
# Architectural Decision Log
## ADL-001 — Submodule-first repository layout
**Date:** 2026-02-23
**Context:** The repository must be consumable by multiple host projects with minimal coupling.
**Decision:** The root repository is the reusable kit surface (`kit/`, `exports/`, `tools/designsync`),
while the runnable reference app lives in `demo/` as a separate module.
**Consequences:**
- Host repositories can pin and sync shared artifacts without depending on demo runtime code.
- Public compatibility boundaries are clearer.
- Documentation and changelog discipline are required for bundle contracts.
## ADL-002 — Copy/sync is the default integration model
**Date:** 2026-02-23
**Context:** Host repositories need stable, reviewable changes and should avoid hidden runtime
dependency on submodule internals.
**Decision:** Canonical integration is copy/sync from submodule bundles using `designsync`.
**Consequences:**
- Host repos review concrete file changes in their own tree.
- Runtime coupling to submodule paths is avoided.
- Bundle manifests and sync tooling become a critical public interface.
## ADL-003 — Scope expansion requires explicit user direction
**Date:** 2026-02-23
**Context:** The repository can easily grow from UI/UX design code into broader application
architecture standards, which risks losing focus and slowing delivery.
**Decision:** Keep the repository focused on the currently approved scope. Do not expand into new
design-code domains unless the user explicitly requests that expansion.
**Consequences:**
- Work remains concentrated on current deliverables and consistency.
- New domains are added intentionally instead of opportunistically.
- Agents should prefer improving existing patterns/docs over inventing adjacent scope.
## ADL-004 — Aqua style work is frozen as legacy; active rewrite direction is Win9x
Superseded by ADL-005.
**Date:** 2026-02-23
**Context:** A substantial Aqua-inspired visual exploration was implemented during demo refinement.
The result is useful as a reference archive, but it should not continue to drive the active baseline.
The next visual rewrite direction is a simpler classic Windows 95-2000 (Win9x-style) baseline.
**Decision:** Freeze Aqua-related visual work as a legacy snapshot bundle. Keep it documented for
reference, but do not treat it as the active baseline for ongoing demo/scaffold styling. Continue
visual work on the Win9x rewrite path.
**Consequences:**
- Aqua styles remain available for legacy reference and extraction.
- New styling changes should target the Win9x baseline instead of extending Aqua.
- Documentation must clearly mark Aqua as legacy / not active to avoid ambiguity.
## ADL-005 — Restore Aqua as the active visual baseline
Superseded by ADL-006.
**Date:** 2026-02-23
**Context:** The temporary Win9x-wide rewrite pass was evaluated and rejected for the current demo
catalog. The team decided to continue from the existing Aqua-based visual work.
**Decision:** Restore Aqua-first styling as the active baseline for the demo/scaffold and keep the
Win9x attempt as a discarded experiment. Preserve the Aqua snapshot bundle/docs as archive
artifacts, but continue active iteration on the live Aqua baseline.
**Consequences:**
- Demo pages continue to evolve on top of Aqua-first styles.
- Legacy bundle artifacts remain available for archival reference.
- Documentation should describe Aqua as active baseline and avoid marking Win9x as current direction.
## ADL-006 — Main demo shell baseline is Vapor Soft / Vapor Night with system-only mode selection
**Date:** 2026-02-23
**Context:** The demo catalog visual work moved from Aqua-first experimentation to a new Vaporwave
inspired UI direction intended to remain usable for day-long operation. Manual day/night toggles in
the shell caused state conflicts and inconsistent rendering relative to system color-scheme.
**Decision:** Use a dual baseline in the main demo shell:
- `Vapor Soft` for light mode
- `Vapor Night` for dark mode
Mode selection follows system settings only (`prefers-color-scheme`). The style playground may
remain available as an internal route for visual experimentation, but it is hidden from the main
demo navigation/catalog while the baseline is actively refined.
**Consequences:**
- The main demo shell keeps one stable visual contract while still supporting light/dark operation.
- Manual theme override UI/state is removed from the main shell to avoid visual mismatches.
- Vapor-themed background composition (including dark-mode retro-grid variant) becomes part of the
canonical demo presentation and must remain subordinate to UI readability.
## ADL-007 — Canonical design contracts must resolve through a single-source map
**Date:** 2026-02-28
**Context:** Table-management and operator/controls patterns evolved quickly during iterative demo
refinement. Without an explicit source map, repeated definitions across contracts/documents risk
semantic drift (toolbar groups, icon semantics, active baseline references).
**Decision:** Maintain one explicit single-source index for design contracts and assets in
`bible/architecture/design-canon-map.md`, with Vapor as active baseline and Aqua marked archive-only.
Pattern-specific contracts must reference shared base contracts and document only additive overrides.
**Consequences:**
- Contract ownership is explicit and reviewable in one place.
- Repeated component semantics are less likely to diverge across patterns.
- Release preparation can validate canonical sources first, then dependent pattern docs/manifests.
## ADL-008 — Vapor shell uses preset-driven header/accent palette contract
**Date:** 2026-02-28
**Context:** Host repositories that consume this kit need a stable way to keep the app header
height and color treatment consistent with Vapor aesthetics. Ad-hoc local overrides caused drift in
header height and non-Vapor accent choices.
**Decision:** Introduce reusable shell palette presets in `theme-vapor` with stable preset IDs and
document them in `kit/patterns/theme-vapor/palette-catalog.md`. Presets are selected by
`data-vapor-shell` on the root element and resolve shared shell tokens for light/dark backgrounds,
accent gradients, accent borders, and shell height. Runtime selection precedence is standardized as
query `vapor_shell` → local storage key `vapor_shell` → markup default preset.
**Consequences:**
- Host projects can switch shell look without modifying component markup.
- Header height is tokenized (`--shell-height`) and no longer depends on one-off CSS edits.
- Palette ID compatibility becomes part of the reusable visual contract for `ui-theme-vapor`.

View File

@@ -1,17 +0,0 @@
# Architecture Decision Records
Use this directory to capture architecture decisions and significant updates.
## Required
- Every architecture decision must be recorded in the Bible.
- If a decision replaces an older one, update the older document in the same change.
- Keep entries short, explicit, and linked to the affected architecture files.
## Minimal Entry Template
- Date (`YYYY-MM-DD`)
- Decision
- Context
- Consequences
- Supersedes (if any)

View File

@@ -1,29 +0,0 @@
# Documentation Policy
## Purpose
This policy defines how architectural knowledge is captured and maintained.
## Mandatory Rules
- Record every architecture decision in the Bible before or together with implementation.
- Use English for all architecture documentation.
- Keep only current architecture in active sections.
- When a solution is replaced, update or remove obsolete guidance in the same change.
- Keep architecture details centralized in `bible/`; top-level docs should only reference it.
## Change Workflow
1. Update the relevant file(s) in `bible/architecture/` or `bible/synthesis/`.
2. If behavior changed, add or update a decision note in `bible/decisions/10-decisions.md`.
3. Update `CHANGELOG.md` when public bundle/tooling contracts change.
4. Remove duplicated or outdated statements from `README.md`, `CLAUDE.md`, and `AGENTS.md`.
## Scope Expansion Guardrail
- Do not expand the design-code scope beyond the currently approved work area by default.
- Expansion into new domains (for example API contracts, data/model architecture, observability,
security, or backend service patterns) requires an explicit user request.
- If the request is ambiguous, keep focus on the current scope and ask for confirmation before
broadening the repository mission.
- Prefer depth and consistency in the current approved scope over breadth.

View File

@@ -1,18 +0,0 @@
# Common Invariants Across Source Repos
## Documentation
- Architecture docs are authoritative and must be kept current with code changes.
- English-only Bible documentation is enforced (or being enforced).
- AI instruction files point to the Bible entrypoint.
## Engineering Practices
- Go is the implementation language.
- Web UI is server-rendered or template-driven.
- Explicit operational/build commands are documented for AI agents.
## Design-Kit Implication
This repository standardizes documentation and reusable pattern contracts while leaving room for
host-project branding and business rules.

View File

@@ -1,8 +0,0 @@
# Normalization Matrix (Initial)
| Source Variant | Source Convention | Canonical Rule in This Repo |
|---|---|---|
| Variant A | `bible/README.md` | Root `bible/README.md` as source of truth |
| Variant B | `bible/BIBLE.md` | Root `bible/README.md` entrypoint (English docs) |
| Variant C | `docs/bible/README.md` | Normalize to root `bible/` for new repos |
| Mature UI Contract Repo | Detailed UI interaction contracts | Use pattern contracts in `kit/patterns/*/contract.md` |

View File

@@ -1,15 +0,0 @@
# Reference Applications (Anonymized)
The initial design-code synthesis draws from several existing Go web applications that use a
Bible-style architecture documentation system.
## Observed Variants
- Bible entrypoint at `bible/README.md`
- Bible entrypoint at `bible/BIBLE.md`
- Bible entrypoint at `docs/bible/README.md`
## Key Observation
All source applications treat the Bible (or equivalent folder) as the architecture source of
truth, but folder layout and UI/runtime stacks differ.

View File

@@ -1,47 +0,0 @@
# UI Pattern Coverage Matrix (Reference Apps → Design Kit)
This file tracks UI/UX pattern extraction from reference Go web applications into this
repository's current scope (UI/UX + interaction contracts + demo pages + reusable pattern docs).
## Scope Reminder
- This matrix covers only the currently approved design-code scope.
- It does **not** expand into backend architecture, API contract frameworks, or observability.
## Pattern Checklist
| Pattern Family | Reference Evidence (types of usage observed) | Demo Coverage | Kit Contract / Bundle | Status |
|---|---|---|---|---|
| Table lists | multiple list/detail/admin tables | `/patterns/table`, `/patterns/controls` | `ui-pattern-table` | covered (core) |
| Pagination | shared pagination style, prev/next/page links | `/patterns/table` | `ui-pattern-table` | covered (core) |
| Server-side filters | header filters, URL query semantics | `/patterns/table` | `ui-pattern-table` | covered (core) |
| Datalist/autocomplete filters | datalist-backed header filters and suggestions | `/patterns/forms` | `ui-pattern-forms` | covered |
| Buttons hierarchy | primary/secondary/danger/quiet controls | `/patterns/controls` | `ui-pattern-controls` | covered |
| Checkboxes + selection | row select, select visible, bulk actions | `/patterns/controls` | `ui-pattern-controls` | covered |
| Segmented/tabs | active/archived, mode switches, tab buttons | `/patterns/controls`, `/patterns/forms` | `ui-pattern-controls`, `ui-pattern-forms` | covered |
| Status badges/indicators | entity status, source/status labels, warning chips | `/patterns/controls`, `/patterns/timeline` | `ui-pattern-controls` | covered (core) |
| Modals (single-step) | create/edit/remove dialogs | `/patterns/modals` | `ui-pattern-modal` | covered |
| Modals (multi-step confirm) | review/confirm/submit flows | `/patterns/modals`, `/patterns/forms` | `ui-pattern-modal`, `ui-pattern-forms` | covered |
| Import workflow | file input, preview, confirm | `/patterns/io`, `/patterns/forms` | `ui-pattern-io`, `ui-pattern-forms` | covered |
| Export workflow | explicit scope/format, CSV download | `/patterns/io` | `ui-pattern-io` | covered |
| CSV export compatibility details | BOM + delimiter for spreadsheet UX | `/patterns/io/export.csv` | `ui-pattern-io` | covered |
| Timeline cards | grouped by day/action/source | `/patterns/timeline` | `ui-pattern-timeline` | covered |
| Timeline drilldown | single drilldown panel/modal, event detail | `/patterns/timeline` | `ui-pattern-timeline` | covered |
| Empty states | table/list/filter empty states | multiple demo pages | multiple bundles | covered |
| Inline validation messages | form-level and field-level messages | `/patterns/forms` | `ui-pattern-forms` | covered |
| File upload controls | `<input type="file">` and import affordances | `/patterns/forms`, `/patterns/io` | `ui-pattern-io`, `ui-pattern-forms` | covered |
| Global selection across paginated pages | persistent selection across pages/storage | `/patterns/controls` (paginated selection + select visible/filtered + preserved `sel`) | `ui-pattern-controls` | covered |
| Advanced admin tools / repair dashboards | complex operator tooling with many tables | `/patterns/operator-tools` (canonicalized operator dashboard) | `ui-pattern-operator-tools` | covered (simplified canonical) |
## Notes on Intentional Simplifications
- Some complex operator workflows are represented as simplified demos to keep the repository
focused on reusable interaction contracts rather than domain-specific UI logic.
- Global selection is demonstrated via query-driven selected IDs preserved across paginated views.
- Client-side persistence (for example local storage) is intentionally not required for the canonical contract.
## Next In-Scope Improvements (Optional, not automatic)
- Stronger global-selection demo (cross-page or local-storage persistence)
- Dedicated "operator dashboard tools" pattern page (still within UI/UX scope)
- Richer datalist/autocomplete disambiguation candidate flow

View File

@@ -1,12 +0,0 @@
run:
go run ./cmd/demo-server
build:
go build ./cmd/demo-server
test:
go test ./...
fmt:
gofmt -w $$(find . -name '*.go' -type f)

View File

@@ -1,9 +0,0 @@
# UI Design Code Demo
Runnable reference app for shared UI patterns and bundle catalog.
This app is not the public submodule contract; host repositories should consume `kit/` via
`tools/designsync`.
Active shared theme assets for host integration are published in `kit/patterns/theme-vapor`
and exported as bundle `ui-theme-vapor`.

View File

@@ -1,4 +0,0 @@
module git.mchus.pro/mchus/ui-design-code-demo
go 1.24.0

File diff suppressed because it is too large Load Diff

View File

@@ -1,340 +0,0 @@
package web
import (
"fmt"
"html/template"
"io/fs"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
appweb "git.mchus.pro/mchus/ui-design-code-demo/web"
)
type Server struct {
mux *http.ServeMux
tmpl *template.Template
}
type PatternCard struct {
ID string
Name string
Bundle string
Link string
Status string
Summary string
}
type IndexViewData struct {
Title string
CurrentPath string
Bundles []PatternCard
Patterns []PatternCard
}
type tableDemoRow struct {
ID int
Name string
Category string
Status string
Owner string
Updated string
}
type tableDemoFilters struct {
Query string
Category string
Status string
}
type tableDemoPager struct {
Page int
PerPage int
TotalItems int
TotalPages int
From int
To int
PrevURL string
NextURL string
Links []tableDemoPageLink
}
type tableDemoPageLink struct {
Label string
URL string
Current bool
Ellipsis bool
}
type tableDemoPageData struct {
Title string
CurrentPath string
Rows []tableDemoRow
Filters tableDemoFilters
Pager tableDemoPager
PerPage int
PerPageOpts []int
Categories []string
Statuses []string
}
func NewServer() (*Server, error) {
tmpl, err := template.New("demo").Funcs(template.FuncMap{
"withQuery": withQuery,
"dict": dict,
}).ParseFS(appweb.Assets, "templates/*.html")
if err != nil {
return nil, err
}
s := &Server{mux: http.NewServeMux(), tmpl: tmpl}
s.registerRoutes()
return s, nil
}
func (s *Server) Handler() http.Handler { return s.mux }
func (s *Server) registerRoutes() {
s.mux.HandleFunc("/", s.handleIndex)
s.mux.HandleFunc("/patterns/table", s.handleTablePattern)
s.mux.HandleFunc("/patterns/controls", s.handleControlsPattern)
s.mux.HandleFunc("/patterns/modals", s.handleModalPattern)
s.mux.HandleFunc("/patterns/io", s.handleIOPattern)
s.mux.HandleFunc("/patterns/io/export.csv", s.handleIOExportCSV)
s.mux.HandleFunc("/patterns/forms", s.handleFormsPattern)
s.mux.HandleFunc("/patterns/style-playground", s.handleStylePlaygroundPattern)
s.mux.HandleFunc("/patterns/operator-tools", s.handleOperatorToolsPattern)
s.mux.HandleFunc("/patterns/timeline", s.handleTimelinePattern)
s.mux.HandleFunc("/healthz", s.handleHealthz)
staticFS, err := fs.Sub(appweb.Assets, "static")
if err == nil {
s.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
}
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
data := IndexViewData{
Title: "UI Design Code",
CurrentPath: "/",
Bundles: []PatternCard{
{ID: "ai-rules", Name: "AI Rules", Bundle: "ai-rules", Status: "ready", Summary: "CLAUDE/AGENTS templates + shared architecture doc policy."},
{ID: "bible-core", Name: "Bible Core", Bundle: "bible-core", Status: "ready", Summary: "Canonical Bible skeleton for Go web projects using AI coding agents."},
{ID: "go-web-skeleton", Name: "Go Web Skeleton", Bundle: "go-web-skeleton", Status: "ready", Summary: "Minimal net/http + templates scaffold for host repos."},
},
Patterns: []PatternCard{
{ID: "table-pagination", Name: "Table + Pagination", Bundle: "ui-pattern-table", Link: "/patterns/table", Status: "ready", Summary: "Server-side filters + pagination contract for canonical list pages."},
{ID: "controls-selection", Name: "Controls + Selection", Bundle: "ui-pattern-controls", Link: "/patterns/controls", Status: "ready", Summary: "Buttons, checkboxes, bulk-selection, segmented actions, status chips."},
{ID: "modal-workflows", Name: "Modal Workflows", Bundle: "ui-pattern-modal", Link: "/patterns/modals", Status: "ready", Summary: "Create/edit/remove + confirm modal workflow contracts for admin and detail pages."},
{ID: "import-export", Name: "Import / Export", Bundle: "ui-pattern-io", Link: "/patterns/io", Status: "ready", Summary: "File import forms, preview/confirm UX, CSV export controls and download endpoint."},
{ID: "forms-validation", Name: "Forms + Validation", Bundle: "ui-pattern-forms", Link: "/patterns/forms", Status: "ready", Summary: "Inline validation, datalist suggestions, tabs/steps, confirm-before-submit forms."},
{ID: "operator-tools", Name: "Operator Tools", Bundle: "ui-pattern-operator-tools", Link: "/patterns/operator-tools", Status: "ready", Summary: "Complex operator/admin dashboards: queue tables, batch actions, safety checks, and run states."},
{ID: "timeline-cards", Name: "Timeline Cards", Bundle: "ui-pattern-timeline", Link: "/patterns/timeline", Status: "ready", Summary: "Grouped timeline cards with drilldown modal and in-card filtering."},
},
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, "base.html", data); err != nil {
http.Error(w, "template error", http.StatusInternalServerError)
}
}
func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
func (s *Server) handleTablePattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/table" {
http.NotFound(w, r)
return
}
filters := tableDemoFilters{
Query: strings.TrimSpace(r.URL.Query().Get("q")),
Category: strings.TrimSpace(r.URL.Query().Get("category")),
Status: strings.TrimSpace(r.URL.Query().Get("status")),
}
allRows := demoTableRows()
filtered := make([]tableDemoRow, 0, len(allRows))
for _, row := range allRows {
if !matchesTableFilters(row, filters) {
continue
}
filtered = append(filtered, row)
}
page := parsePositiveInt(r.URL.Query().Get("page"), 1)
perPage := parseAllowedInt(r.URL.Query().Get("per_page"), 10, []int{5, 10, 20})
rowsPage, pager := paginateTableRows(r.URL, filtered, page, perPage)
data := tableDemoPageData{
Title: "Table + Pagination Pattern",
CurrentPath: "/patterns/table",
Rows: rowsPage,
Filters: filters,
Pager: pager,
PerPage: perPage,
PerPageOpts: []int{5, 10, 20},
Categories: []string{"Compute", "Networking", "Power", "Storage"},
Statuses: []string{"ready", "warning", "review"},
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, "table_pattern.html", data); err != nil {
http.Error(w, "template error", http.StatusInternalServerError)
}
}
func matchesTableFilters(row tableDemoRow, f tableDemoFilters) bool {
if f.Query != "" {
q := strings.ToLower(f.Query)
haystack := strings.ToLower(strings.Join([]string{row.Name, row.Category, row.Status, row.Owner}, " "))
if !strings.Contains(haystack, q) {
return false
}
}
if f.Category != "" && !strings.EqualFold(row.Category, f.Category) {
return false
}
if f.Status != "" && !strings.EqualFold(row.Status, f.Status) {
return false
}
return true
}
func paginateTableRows(u *url.URL, rows []tableDemoRow, page, perPage int) ([]tableDemoRow, tableDemoPager) {
totalItems := len(rows)
totalPages := 1
if totalItems > 0 {
totalPages = (totalItems + perPage - 1) / perPage
}
if page < 1 {
page = 1
}
if page > totalPages {
page = 1
}
start := 0
end := 0
from := 0
to := 0
pageRows := []tableDemoRow{}
if totalItems > 0 {
start = (page - 1) * perPage
end = start + perPage
if end > totalItems {
end = totalItems
}
from = start + 1
to = end
pageRows = rows[start:end]
}
pager := tableDemoPager{
Page: page,
PerPage: perPage,
TotalItems: totalItems,
TotalPages: totalPages,
From: from,
To: to,
Links: buildPageLinks(u, page, totalPages),
}
return pageRows, pager
}
func pageURL(u *url.URL, page, totalPages int) string {
if totalPages < 1 {
totalPages = 1
}
if page < 1 || page > totalPages {
return ""
}
clone := *u
q := clone.Query()
q.Set("page", strconv.Itoa(page))
clone.RawQuery = q.Encode()
return clone.String()
}
func buildPageLinks(u *url.URL, current, totalPages int) []tableDemoPageLink {
if totalPages <= 1 {
return nil
}
if totalPages <= 4 {
out := make([]tableDemoPageLink, 0, totalPages)
for p := 1; p <= totalPages; p++ {
out = append(out, tableDemoPageLink{
Label: strconv.Itoa(p),
URL: pageURL(u, p, totalPages),
Current: p == current,
})
}
return out
}
pages := map[int]bool{
1: true, 2: true,
totalPages: true, totalPages - 1: true,
current: true, current - 1: true, current + 1: true,
}
keys := make([]int, 0, len(pages))
for p := range pages {
if p < 1 || p > totalPages {
continue
}
keys = append(keys, p)
}
sort.Ints(keys)
out := make([]tableDemoPageLink, 0, len(keys)+2)
prev := 0
for _, p := range keys {
if prev != 0 && p-prev > 1 {
out = append(out, tableDemoPageLink{Label: "...", Ellipsis: true})
}
out = append(out, tableDemoPageLink{
Label: strconv.Itoa(p),
URL: pageURL(u, p, totalPages),
Current: p == current,
})
prev = p
}
return out
}
func parsePositiveInt(v string, fallback int) int {
n, err := strconv.Atoi(v)
if err != nil || n < 1 {
return fallback
}
return n
}
func parseAllowedInt(v string, fallback int, allowed []int) int {
n := parsePositiveInt(v, fallback)
for _, candidate := range allowed {
if n == candidate {
return n
}
}
return fallback
}
func demoTableRows() []tableDemoRow {
rows := make([]tableDemoRow, 0, 36)
categories := []string{"Compute", "Networking", "Storage", "Power"}
statuses := []string{"ready", "warning", "review"}
owners := []string{"Ops", "Infra", "QA", "Procurement"}
for i := 1; i <= 36; i++ {
rows = append(rows, tableDemoRow{
ID: i,
Name: fmt.Sprintf("Component Spec %02d", i),
Category: categories[(i-1)%len(categories)],
Status: statuses[(i-1)%len(statuses)],
Owner: owners[(i-1)%len(owners)],
Updated: fmt.Sprintf("2026-02-%02d", (i%23)+1),
})
}
// Seed a few targeted names to make filter behavior testable and demonstrative.
rows[4].Name = "Rack Controller Alpha"
rows[11].Name = "Rack Controller Beta"
rows[20].Name = "Rack Controller Gamma"
return rows
}

View File

@@ -1,292 +0,0 @@
package web
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestRoutes(t *testing.T) {
srv, err := NewServer()
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Run("index", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
if !strings.Contains(rr.Body.String(), "UI Design Code") {
t.Fatalf("unexpected body: %s", rr.Body.String())
}
})
t.Run("healthz", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
})
t.Run("static", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/static/css/app.css", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
})
t.Run("table pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/table", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Canonical List Page") {
t.Fatalf("missing table demo content in body")
}
if !strings.Contains(body, "Showing 110 of 36") {
t.Fatalf("missing summary: %s", body)
}
})
t.Run("controls pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=warning", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Selection Table") {
t.Fatalf("missing controls page content")
}
})
t.Run("controls selection toggle persists in query links", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=all&selection_action=toggle&id=2", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "1 selected") {
t.Fatalf("expected selected counter, got body")
}
if !strings.Contains(body, "sel=2") {
t.Fatalf("expected selected id in follow-up links")
}
})
t.Run("controls select visible", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=warning&selection_action=select_visible", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "4 selected") {
t.Fatalf("expected all visible selected")
}
})
t.Run("modal pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/modals?open=edit&stage=confirm&sel=1", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Confirmation Summary") || !strings.Contains(body, "Single-modal workflow progression") {
t.Fatalf("missing modal page markers")
}
})
t.Run("io pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/io?import_mode=confirm&export_ready=1", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Import / Export Pattern") || !strings.Contains(body, "Download CSV") {
t.Fatalf("missing io page markers")
}
})
t.Run("forms pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/forms?mode=register&step=review", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Forms") || !strings.Contains(body, "Validation Pattern") || !strings.Contains(body, "Review Summary") {
t.Fatalf("missing forms page markers")
}
if !strings.Contains(body, "Server serial is required") {
t.Fatalf("expected validation message")
}
})
t.Run("style playground pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/style-playground?style=slate", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Style Playground") || !strings.Contains(body, "Theme Presets") {
t.Fatalf("missing style playground markers")
}
if !strings.Contains(body, "theme-slate") {
t.Fatalf("expected theme class in body")
}
})
t.Run("style playground extra presets", func(t *testing.T) {
for _, tc := range []struct {
name string
style string
expectMark string
}{
{name: "y2k silver", style: "y2k-silver", expectMark: "theme-y2k-silver"},
{name: "vaporwave alias", style: "vaporwave", expectMark: "theme-vaporwave"},
{name: "vaporwave soft", style: "vaporwave-soft", expectMark: "theme-vaporwave"},
{name: "vaporwave night", style: "vaporwave-night", expectMark: "theme-vaporwave-night"},
{name: "aqua", style: "aqua", expectMark: "theme-aqua"},
{name: "win9x", style: "win9x", expectMark: "theme-win9x"},
} {
t.Run(tc.name, func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/style-playground?style="+tc.style, nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, tc.expectMark) {
t.Fatalf("expected theme marker %q", tc.expectMark)
}
if !strings.Contains(body, "Y2K Silver") || !strings.Contains(body, "Vapor Soft") || !strings.Contains(body, "Vapor Night") || !strings.Contains(body, "Aqua") || !strings.Contains(body, "Win9x") {
t.Fatalf("expected preset tabs in switcher")
}
})
}
})
t.Run("operator tools pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/operator-tools?scope=components&queue=failed", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Operator Tools Pattern") || !strings.Contains(body, "Operations Queue") {
t.Fatalf("missing operator tools page markers")
}
})
t.Run("timeline pattern page", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/timeline?open=c1", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Timeline Cards Pattern") || !strings.Contains(body, "Open details") {
t.Fatalf("missing timeline page markers")
}
if !strings.Contains(body, "Event Detail") {
t.Fatalf("expected drilldown panel when card is open")
}
})
t.Run("io export csv endpoint", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/io/export.csv?scope=selected", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
if got := rr.Header().Get("Content-Type"); !strings.Contains(got, "text/csv") {
t.Fatalf("content-type=%q", got)
}
if got := rr.Header().Get("Content-Disposition"); !strings.Contains(got, "items.csv") {
t.Fatalf("content-disposition=%q", got)
}
body := rr.Body.Bytes()
if len(body) < 3 || !bytes.Equal(body[:3], []byte{0xEF, 0xBB, 0xBF}) {
t.Fatalf("missing UTF-8 BOM")
}
if !strings.Contains(string(body), "Code;Name;Category;Status;Qty") {
t.Fatalf("unexpected csv header")
}
})
t.Run("table pattern filters before pagination", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/table?q=Rack&page=99", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "Showing 13 of 3") {
t.Fatalf("expected filtered summary, got: %s", body)
}
if !strings.Contains(body, "Rack Controller Alpha") {
t.Fatalf("expected filtered rows")
}
if strings.Contains(body, "Component Spec 36") {
t.Fatalf("unexpected unrelated row present")
}
})
t.Run("table pattern keeps filter params in pager links", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/table?status=ready", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "status=ready") {
t.Fatalf("expected status filter in pagination links")
}
})
t.Run("table pattern empty result", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/patterns/table?q=does-not-exist", nil)
srv.Handler().ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d", rr.Code)
}
body := rr.Body.String()
if !strings.Contains(body, "No rows match current filters.") {
t.Fatalf("expected empty-state message")
}
if !strings.Contains(body, "Showing 0 of 0") {
t.Fatalf("expected zero summary")
}
})
}

View File

@@ -1,6 +0,0 @@
package web
import "embed"
//go:embed templates/* static/*
var Assets embed.FS

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
(() => {
const root = document.documentElement;
const storageKey = "vapor_shell";
const queryKey = "vapor_shell";
const fallbackPreset = "miami-sunset";
const allowed = new Set([
"miami-sunset",
"neon-grid",
"laser-flamingo",
"synth-lagoon",
"mall-soft",
"hologram-sky",
"peach-drive",
"ultraviolet-plaza",
"cyber-mint",
]);
const normalizePreset = (value) => {
const trimmed = (value || "").trim().toLowerCase();
if (!trimmed || !allowed.has(trimmed)) {
return "";
}
return trimmed;
};
const setPreset = (value) => {
const preset = normalizePreset(value);
if (!preset) {
return false;
}
root.setAttribute("data-vapor-shell", preset);
return true;
};
root.classList.add("js");
root.setAttribute("data-theme-mode", "auto");
const url = new URL(window.location.href);
const fromQuery = normalizePreset(url.searchParams.get(queryKey));
if (fromQuery) {
setPreset(fromQuery);
try {
window.localStorage.setItem(storageKey, fromQuery);
} catch (_err) {
// Keep behavior deterministic even when storage is blocked.
}
return;
}
const fromMarkup = normalizePreset(root.getAttribute("data-vapor-shell"));
if (fromMarkup) {
return;
}
try {
const fromStorage = normalizePreset(window.localStorage.getItem(storageKey));
if (fromStorage) {
setPreset(fromStorage);
return;
}
} catch (_err) {
// Fallback handled below.
}
setPreset(fallbackPreset);
})();

View File

@@ -1,413 +0,0 @@
{{ define "demo_doc_start" }}
<!doctype html>
<html lang="en" data-vapor-shell="miami-sunset">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .Title }}</title>
<link rel="stylesheet" href="/static/css/app.css">
</head>
<body>
<svg class="icon-sprite" aria-hidden="true" focusable="false" width="0" height="0">
<symbol id="ico-select-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8.2l2 2.1 4-4.3"></path>
</symbol>
<symbol id="ico-select-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11l-4.2 4.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M11.4 10.2h3M12.9 8.7v3"></path>
</symbol>
<symbol id="ico-clear-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8h6"></path>
</symbol>
<symbol id="ico-clear-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11L9.3 7.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M10.5 10.2l3 3M13.5 10.2l-3 3"></path>
</symbol>
<symbol id="ico-clear-selection" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 5l6 6M11 5l-6 6"></path>
</symbol>
<symbol id="ico-run" viewBox="0 0 16 16">
<path d="M5 3.5l7 4.5-7 4.5z"></path>
</symbol>
<symbol id="ico-edit" viewBox="0 0 16 16">
<path d="M3 13l1.2-3.6L10 3.6l2.4 2.4-5.8 5.8L3 13z"></path>
<path d="M8.8 4.8l2.4 2.4"></path>
</symbol>
<symbol id="ico-remove" viewBox="0 0 16 16">
<path d="M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6"></path>
</symbol>
<symbol id="ico-cancel" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M5.5 5.5l5 5M10.5 5.5l-5 5"></path>
</symbol>
<symbol id="ico-archive" viewBox="0 0 16 16">
<rect x="2.5" y="3" width="11" height="2.8" rx="1"></rect>
<path d="M3.5 5.8V13h9V5.8"></path>
<path d="M6.2 8h3.6"></path>
</symbol>
<symbol id="ico-import" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 1.8v6.2"></path>
<path d="M5.8 5.8L8 8l2.2-2.2"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 8V1.8"></path>
<path d="M5.8 4L8 1.8 10.2 4"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export-filtered" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<path d="M10 5.1h5.2L13.1 7.5v3.1l-1.3.7V7.5z"></path>
</symbol>
<symbol id="ico-export-selected" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<rect x="10.1" y="5.2" width="5" height="5" rx="0.6"></rect>
<path d="M11.2 6.3l2.8 2.8M14 6.3l-2.8 2.8"></path>
</symbol>
<symbol id="ico-retry" viewBox="0 0 16 16">
<path d="M12.2 6A4.8 4.8 0 0 0 4.8 4.4"></path>
<path d="M5.6 2.9L4.1 4.6 5.9 5.8"></path>
<path d="M3.8 10A4.8 4.8 0 0 0 11.2 11.6"></path>
<path d="M10.4 13.1L11.9 11.4 10.1 10.2"></path>
</symbol>
<symbol id="ico-review" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M8 7.2v3.6"></path>
<path d="M8 5.1h.01"></path>
</symbol>
<symbol id="ico-mark-review" viewBox="0 0 16 16">
<path d="M8 2.6l5.2 2v3.7c0 2.3-1.5 4.4-5.2 5.7-3.7-1.3-5.2-3.4-5.2-5.7V4.6l5.2-2z"></path>
<path d="M8 6.1v3.2"></path>
<path d="M8 11.1h.01"></path>
</symbol>
<symbol id="ico-confirm" viewBox="0 0 16 16">
<path d="M8 2.5l5 2v3.8c0 2.2-1.3 4.1-5 5.3-3.7-1.2-5-3.1-5-5.3V4.5l5-2z"></path>
<path d="M5.7 8.3l1.6 1.7 3-3.1"></path>
</symbol>
<symbol id="ico-inspect" viewBox="0 0 16 16">
<circle cx="7" cy="7" r="3.8"></circle>
<path d="M9.8 9.8l3.2 3.2"></path>
</symbol>
<symbol id="ico-scope" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="4.2" height="4.2"></rect>
<rect x="9.3" y="2.5" width="4.2" height="4.2"></rect>
<rect x="2.5" y="9.3" width="4.2" height="4.2"></rect>
<rect x="9.3" y="9.3" width="4.2" height="4.2"></rect>
</symbol>
<symbol id="ico-queue" viewBox="0 0 16 16">
<path d="M5.4 4h8.1M5.4 8h8.1M5.4 12h8.1"></path>
<circle cx="3.2" cy="4" r="0.7"></circle>
<circle cx="3.2" cy="8" r="0.7"></circle>
<circle cx="3.2" cy="12" r="0.7"></circle>
</symbol>
</svg>
{{ template "demo_nav" . }}
{{ template "demo_app_shell" . }}
<main class="page">
{{ end }}
{{ define "demo_doc_end" }}
</main>
<script src="/static/js/app.js" defer></script>
</body>
</html>
{{ end }}
{{ define "demo_masthead" }}
<header class="masthead">
<p class="label">{{ index . "label" }}</p>
<h1>{{ index . "title" }}</h1>
<p class="lead">{{ index . "lead" }}</p>
{{ if index . "back_url" }}
<p class="meta" style="margin-top:10px;"><a class="text-link" href="{{ index . "back_url" }}">{{ index . "back_text" }}</a></p>
{{ end }}
{{ if index . "links_html" }}
{{ index . "links_html" }}
{{ end }}
</header>
{{ end }}
{{ define "demo_app_shell" }}
<header class="app-shell" id="app-shell">
<div class="app-shell-inner">
<div>
<p class="app-shell-kicker">Demo Application Shell</p>
<h1 class="app-shell-title">Universal Operations Console</h1>
<p class="app-shell-subtitle">Reference shell for server-rendered Go web apps using shared design-code contracts.</p>
</div>
<div class="app-shell-statuses" aria-label="Application status">
<span class="shell-pill shell-pill-env">ENV: demo</span>
<span class="shell-pill shell-pill-db">DB: connected</span>
<span class="shell-pill shell-pill-user">operator@demo · admin</span>
</div>
</div>
</header>
{{ end }}
{{ define "base.html" }}
{{ template "demo_doc_start" . }}
<header class="masthead" id="home-overview">
<p class="label">Submodule-First Design Kit</p>
<h1>{{ .Title }}</h1>
<p class="lead">A universal design-code workspace for Go web applications: reusable rules, demo-first UI patterns, and canonical development contracts for AI-assisted implementation.</p>
<div class="button-demo-row" style="margin-top:12px;">
{{ range .Patterns }}
{{ if .Link }}
<a class="chip-link" href="{{ .Link }}">{{ .Name }}</a>
{{ end }}
{{ end }}
</div>
</header>
<section class="panel" id="home-design-approach">
<div class="panel-head"><h2>Design Approach</h2></div>
<div class="grid">
<article class="card">
<h3>Contract First</h3>
<p>UI behavior is defined as explicit contracts (filters, pagination, modal steps, timeline grouping) before implementation details. Demo pages act as executable specs.</p>
</article>
<article class="card">
<h3>Server-Rendered by Default</h3>
<p>Patterns target Go server-rendered apps first (net/http or Gin with templates). Interactivity is additive and must preserve deterministic URL/query contracts.</p>
</article>
<article class="card">
<h3>Reusable, Not Branded</h3>
<p>Shared patterns standardize behavior and structure while leaving visual branding, domain terminology, and business logic to each host project.</p>
</article>
</div>
</section>
<section class="panel" id="home-workflow">
<div class="panel-head"><h2>Development Workflow Standard</h2></div>
<div class="timeline-cards">
<article class="timeline-card">
<div class="timeline-card-top"><h3>1. Describe the contract</h3></div>
<p class="meta">Update Bible + pattern contract first: routes, query params, states, edge cases, and UI semantics.</p>
</article>
<article class="timeline-card">
<div class="timeline-card-top"><h3>2. Implement in demo</h3></div>
<p class="meta">Build the pattern in the demo app as a live reference page with realistic state transitions and test coverage.</p>
</article>
<article class="timeline-card">
<div class="timeline-card-top"><h3>3. Publish as bundle</h3></div>
<p class="meta">Encode reusable docs and templates in the design kit and expose them as bundles for host repositories.</p>
</article>
<article class="timeline-card">
<div class="timeline-card-top"><h3>4. Apply in host repos</h3></div>
<p class="meta">Use the sync workflow to plan and apply changes, then adapt domain-specific rules without breaking canonical UI contracts.</p>
</article>
</div>
</section>
<section class="panel" id="home-standardizes">
<div class="panel-head"><h2>What the Demo Standardizes</h2></div>
<div class="chip-row">
<span class="chip">URL-driven filters</span>
<span class="chip">Server-side pagination</span>
<span class="chip">Bulk selection semantics</span>
<span class="chip">Modal state machines</span>
<span class="chip">Import preview / confirm</span>
<span class="chip">CSV export behavior</span>
<span class="chip">Operator tooling dashboards</span>
<span class="chip">Timeline card grouping</span>
<span class="chip">Drilldown UX</span>
</div>
</section>
<section class="panel" id="home-anti-patterns">
<div class="panel-head"><h2>Anti-Patterns (Do Not Implement)</h2></div>
<div class="grid">
<article class="card">
<h3>Page-local filters on paginated tables</h3>
<p>Do not filter only the currently rendered page slice. Filters must apply to the full dataset/query scope before pagination.</p>
</article>
<article class="card">
<h3>Nested modals</h3>
<p>Do not open one modal from another modal. Use a single modal state machine with explicit stages (edit, confirm, done).</p>
</article>
<article class="card">
<h3>Implicit export scope</h3>
<p>Do not export without clear scope selection when ambiguity exists (selected, filtered, all). Make the scope explicit in UI and request.</p>
</article>
<article class="card">
<h3>Undocumented UI contracts</h3>
<p>Do not implement new interaction behavior without updating the design code (Bible, pattern contract, and demo reference page).</p>
</article>
</div>
</section>
<section class="panel" id="home-bundles">
<div class="panel-head">
<h2>Bundles</h2>
<a href="/healthz" class="pill">healthz</a>
</div>
<div class="grid">
{{ range .Bundles }}
<article class="card">
<div class="row">
<h3>{{ .Name }}</h3>
<span class="status status-{{ .Status }}">{{ .Status }}</span>
</div>
<p>{{ .Summary }}</p>
<p class="meta"><code>{{ .Bundle }}</code></p>
{{ if .Link }}
<p class="meta"><a class="text-link" href="{{ .Link }}">Open demo</a></p>
{{ end }}
</article>
{{ end }}
</div>
</section>
<section class="panel" id="home-roadmap">
<div class="panel-head">
<h2>Pattern Roadmap</h2>
</div>
<div class="grid">
{{ range .Patterns }}
<article class="card">
<div class="row">
<h3>{{ .Name }}</h3>
<span class="status status-{{ .Status }}">{{ .Status }}</span>
</div>
<p>{{ .Summary }}</p>
<p class="meta"><code>{{ .Bundle }}</code></p>
</article>
{{ end }}
</div>
</section>
{{ template "demo_doc_end" . }}
{{ end }}
{{ define "demo_nav" }}
<nav class="demo-topnav" aria-label="Demo navigation">
<div class="demo-topnav-inner">
<a class="demo-topnav-brand {{ if eq .CurrentPath "/" }}active{{ end }}" href="/">Demo Catalog</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/table" }}active{{ end }}" href="/patterns/table">Table</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/controls" }}active{{ end }}" href="/patterns/controls">Controls</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/modals" }}active{{ end }}" href="/patterns/modals">Modals</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/io" }}active{{ end }}" href="/patterns/io">Import/Export</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/forms" }}active{{ end }}" href="/patterns/forms">Forms</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/operator-tools" }}active{{ end }}" href="/patterns/operator-tools">Operator Tools</a>
<a class="demo-topnav-link {{ if eq .CurrentPath "/patterns/timeline" }}active{{ end }}" href="/patterns/timeline">Timeline</a>
</div>
</nav>
{{ end }}
{{ define "table_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Server-side filtering and pagination. Filters apply to the full dataset before pagination." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel panel-composite" id="table-module">
<div class="panel-subsection" id="table-filters">
<div class="panel-head">
<h2>Filters</h2>
<div class="button-demo-row" style="margin-top:0;">
<a href="/patterns/table#table-filters" class="btn btn-ghost btn-pair">Reset</a>
<button class="btn btn-primary btn-pair" form="table-filters-form" type="submit">Apply</button>
</div>
</div>
<form id="table-filters-form" class="filters" method="get" action="/patterns/table#table-filters">
<label>
Search
<input type="text" name="q" value="{{ .Filters.Query }}" placeholder="name / owner / status" />
</label>
<label>
Category
<select name="category">
<option value="">All</option>
{{ range .Categories }}
<option value="{{ . }}" {{ if eq $.Filters.Category . }}selected{{ end }}>{{ . }}</option>
{{ end }}
</select>
</label>
<label>
Status
<select name="status">
<option value="">All</option>
{{ range .Statuses }}
<option value="{{ . }}" {{ if eq $.Filters.Status . }}selected{{ end }}>{{ . }}</option>
{{ end }}
</select>
</label>
<label>
Rows per page
<select name="per_page">
{{ range .PerPageOpts }}
<option value="{{ . }}" {{ if eq $.PerPage . }}selected{{ end }}>{{ . }}</option>
{{ end }}
</select>
</label>
</form>
</div>
<div class="panel-subsection panel-subsection-divider" id="table-list">
<div class="panel-head">
<h2>Canonical List Page</h2>
<div class="meta">
{{ if gt .Pager.TotalItems 0 }}
Showing {{ .Pager.From }}{{ .Pager.To }} of {{ .Pager.TotalItems }}
{{ else }}
Showing 0 of 0
{{ end }}
</div>
</div>
<div class="table-wrap">
<table class="ui-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Category</th>
<th class="status-col">Status</th>
<th>Owner</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{{ if .Rows }}
{{ range .Rows }}
<tr>
<td>{{ .ID }}</td>
<td>{{ .Name }}</td>
<td>{{ .Category }}</td>
<td class="status-col"><span class="status status-{{ .Status }}">{{ .Status }}</span></td>
<td>{{ .Owner }}</td>
<td>{{ .Updated }}</td>
</tr>
{{ end }}
{{ else }}
<tr>
<td colspan="6" class="empty-cell">No rows match current filters.</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .Pager.TotalPages 1 }}
<nav class="pager pager-dots" aria-label="Pagination">
{{ range .Pager.Links }}
{{ if .Ellipsis }}
<span class="ellipsis" aria-hidden="true"></span>
{{ else if .Current }}
<a class="current" aria-current="page" href="{{ .URL }}#table-list" aria-label="Page {{ .Label }}, current">{{ .Label }}</a>
{{ else }}
<a href="{{ .URL }}#table-list" aria-label="Go to page {{ .Label }}">{{ .Label }}</a>
{{ end }}
{{ end }}
</nav>
{{ end }}
</div>
</section>
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,135 +0,0 @@
{{ define "controls_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Canonical actions, segmented filters, row selection and bulk-action control bar." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel" id="controls-buttons">
<div class="panel-head"><h2>Buttons</h2></div>
<div class="button-demo-row">
<button class="btn btn-primary" type="button">Apply changes</button>
<button class="btn btn-secondary" type="button">Review selection</button>
<button class="btn btn-danger" type="button">Archive selected</button>
<button class="btn btn-ghost" type="button">Reset filters</button>
<button class="btn" type="button" disabled>Disabled</button>
{{ if .SimulateLoading }}
<a class="btn btn-secondary is-loading" aria-disabled="true" href="{{ .ClearLoadingURL }}">Loading…</a>
{{ else }}
<a class="btn btn-secondary" href="{{ .SimulateLoadingURL }}">Simulate loading</a>
{{ end }}
</div>
<div class="button-demo-row">
<a class="btn btn-primary" href="/patterns/io?export_ready=1">Export</a>
<a class="btn btn-secondary" href="/patterns/io">Import</a>
<a class="btn btn-ghost" href="/patterns/modals?open=edit&stage=edit#modal-open-states">Open modal</a>
</div>
</section>
<section class="panel panel-composite" id="controls-workbench">
<div class="panel-subsection" id="controls-segments">
<div class="panel-head">
<h2>Segmented Status Filter</h2>
<div class="meta">{{ .Pager.TotalItems }} filtered · page {{ .Pager.Page }}/{{ .Pager.TotalPages }} · {{ .SelectedCount }} selected</div>
</div>
<div class="segmented segmented-wide" role="toolbar" aria-label="Segmented status filter">
<a class="segment {{ if eq .Segment "all" }}active{{ end }}" href="{{ index .SegmentURLs "all" }}">All ({{ index .SegmentedCounts "all" }})</a>
<a class="segment {{ if eq .Segment "ready" }}active{{ end }}" href="{{ index .SegmentURLs "ready" }}">Ready ({{ index .SegmentedCounts "ready" }})</a>
<a class="segment {{ if eq .Segment "warning" }}active{{ end }}" href="{{ index .SegmentURLs "warning" }}">Warning ({{ index .SegmentedCounts "warning" }})</a>
<a class="segment {{ if eq .Segment "review" }}active{{ end }}" href="{{ index .SegmentURLs "review" }}">Review ({{ index .SegmentedCounts "review" }})</a>
</div>
</div>
<div class="panel-subsection panel-subsection-divider" id="controls-selection">
<div class="panel-head">
<h2>Selection Table</h2>
<div class="meta">Checkbox rows + bulk action preview + global selection across pages</div>
</div>
{{ if .ActionMessage }}<div class="notice">{{ .ActionMessage }}</div>{{ end }}
<p class="meta" style="margin-bottom:12px;">
Visible on this page: {{ .VisibleCount }} · Selected on this page: {{ .SelectedVisible }}{{ if gt .SelectedHidden 0 }} · Selected on other page(s): {{ .SelectedHidden }}{{ end }}
</p>
<div class="table-toolbar" role="toolbar" aria-label="Selection controls">
<div class="toolbar-group" role="group" aria-label="Selection">
<div class="toolbar-group-title">Selection</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .SelectVisibleURL }}" title="Select visible" aria-label="Select visible"><svg class="tool-svg" aria-hidden="true"><use href="#ico-select-visible"></use></svg></a>
<a class="tool-icon-btn" href="{{ .SelectFilteredURL }}" title="Select filtered" aria-label="Select filtered"><svg class="tool-svg" aria-hidden="true"><use href="#ico-select-filtered"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ClearVisibleURL }}" title="Clear visible" aria-label="Clear visible"><svg class="tool-svg" aria-hidden="true"><use href="#ico-clear-visible"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ClearFilteredURL }}" title="Clear filtered" aria-label="Clear filtered"><svg class="tool-svg" aria-hidden="true"><use href="#ico-clear-filtered"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Actions">
<div class="toolbar-group-title">Actions</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn tool-icon-btn-primary" href="{{ .OpenEditSelectedURL }}" title="Edit selected" aria-label="Edit selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-edit"></use></svg></a>
<a class="tool-icon-btn tool-icon-btn-danger" href="{{ .OpenDeleteSelectedURL }}" title="Remove selected" aria-label="Remove selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-remove"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Import and export">
<div class="toolbar-group-title">Import/Export</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="/patterns/io" title="Import" aria-label="Import"><svg class="tool-svg" aria-hidden="true"><use href="#ico-import"></use></svg></a>
<a class="tool-icon-btn" href="{{ .BulkExportURL }}" title="Export selected" aria-label="Export selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-export-selected"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Task actions">
<div class="toolbar-group-title">Task Actions</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .BulkReviewURL }}" title="Mark for review" aria-label="Mark for review"><svg class="tool-svg" aria-hidden="true"><use href="#ico-review"></use></svg></a>
<a class="tool-icon-btn" href="{{ .BulkRetrySyncURL }}" title="Retry sync" aria-label="Retry sync"><svg class="tool-svg" aria-hidden="true"><use href="#ico-retry"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Misc">
<div class="toolbar-group-title">Misc</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .ClearSelectionURL }}" title="Clear selection" aria-label="Clear selection"><svg class="tool-svg" aria-hidden="true"><use href="#ico-clear-selection"></use></svg></a>
<a class="tool-icon-btn tool-icon-btn-danger" href="{{ .BulkArchiveURL }}" title="Archive" aria-label="Archive"><svg class="tool-svg" aria-hidden="true"><use href="#ico-remove"></use></svg></a>
</div>
</div>
</div>
<div class="table-wrap">
<table class="ui-table">
<thead>
<tr>
<th class="select-col" aria-label="Select"></th>
<th>Name</th>
<th>Category</th>
<th>Status</th>
<th class="actions-col" aria-label="Actions"></th>
</tr>
</thead>
<tbody>
{{ range .Rows }}
<tr>
<td class="select-col"><a class="check-toggle {{ if .Selected }}checked{{ end }}" href="{{ .ToggleURL }}" aria-label="Toggle row {{ .ID }}">{{ if .Selected }}☑{{ else }}☐{{ end }}</a></td>
<td>{{ .Name }}</td>
<td>{{ .Type }}</td>
<td><span class="status status-{{ .Status }}">{{ .Status }}</span></td>
<td class="action-cell action-icons actions-col">
<a class="icon-link" href="{{ .EditURL }}" title="Edit" aria-label="Edit"><svg class="tool-svg" aria-hidden="true"><use href="#ico-edit"></use></svg></a>
<a class="icon-link icon-link-danger" href="{{ .RemoveURL }}" title="Remove" aria-label="Remove"><svg class="tool-svg" aria-hidden="true"><use href="#ico-remove"></use></svg></a>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if gt .Pager.TotalPages 1 }}
<nav class="pager pager-dots" aria-label="Pagination">
{{ range .Pager.Links }}
{{ if .Ellipsis }}
<span class="ellipsis" aria-hidden="true"></span>
{{ else if .Current }}
<a class="current" aria-current="page" href="{{ .URL }}" aria-label="Page {{ .Label }}, current">{{ .Label }}</a>
{{ else }}
<a href="{{ .URL }}" aria-label="Go to page {{ .Label }}">{{ .Label }}</a>
{{ end }}
{{ end }}
</nav>
{{ end }}
</div>
</section>
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,95 +0,0 @@
{{ define "forms_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Tabbed/step-based forms with datalist suggestions, inline validation, and explicit review/confirm workflow." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel" id="forms-mode">
<div class="panel-head"><h2>Mode Switch (Tabs)</h2></div>
<div class="segmented">
<a class="segment {{ if eq .Mode "register" }}active{{ end }}" href="{{ index .ModeURLs "register" }}">Manual register</a>
<a class="segment {{ if eq .Mode "import" }}active{{ end }}" href="{{ index .ModeURLs "import" }}">Import-assisted</a>
</div>
<p class="meta" style="margin-top:10px;">Tabs preserve entered values while changing workflow mode.</p>
</section>
<section class="panel" id="forms-steps">
<div class="panel-head"><h2>Step Flow</h2></div>
<div class="segmented">
<a class="segment {{ if eq .Step "edit" }}active{{ end }}" href="{{ index .StepURLs "edit" }}">Edit</a>
<a class="segment {{ if eq .Step "review" }}active{{ end }}" href="{{ index .StepURLs "review" }}">Review</a>
<a class="segment {{ if eq .Step "confirm" }}active{{ end }}" href="{{ index .StepURLs "confirm" }}">Done</a>
</div>
{{ if .Message }}<div class="notice" style="margin-top:12px;">{{ .Message }}</div>{{ end }}
</section>
<section class="panel" id="forms-contract">
<div class="panel-head"><h2>Form Contract Demo</h2></div>
<form class="forms-grid" method="get" action="/patterns/forms#forms-contract">
<input type="hidden" name="mode" value="{{ .Mode }}">
<input type="hidden" name="step" value="review">
<label>Server serial {{ if index .FieldErrors "server_serial" }}<span class="field-error">{{ index .FieldErrors "server_serial" }}</span>{{ end }}
<input class="{{ if index .FieldErrors "server_serial" }}input-error{{ end }}" name="server_serial" value="{{ .ServerSerial }}" list="forms-server-list" placeholder="SRV-001">
<datalist id="forms-server-list">{{ range .ServerOptions }}<option value="{{ . }}"></option>{{ end }}</datalist>
</label>
<label>Location / slot {{ if index .FieldErrors "location" }}<span class="field-error">{{ index .FieldErrors "location" }}</span>{{ end }}
<input class="{{ if index .FieldErrors "location" }}input-error{{ end }}" name="location" value="{{ .Location }}" list="forms-location-list" placeholder="AOC#1">
<datalist id="forms-location-list">{{ range .LocationOptions }}<option value="{{ . }}"></option>{{ end }}</datalist>
</label>
<label>Component serial {{ if index .FieldErrors "component_serial" }}<span class="field-error">{{ index .FieldErrors "component_serial" }}</span>{{ end }}
<input class="{{ if index .FieldErrors "component_serial" }}input-error{{ end }}" name="component_serial" value="{{ .ComponentSerial }}" list="forms-component-list" placeholder="NIC-AX210-001">
<datalist id="forms-component-list">{{ range .ComponentOptions }}<option value="{{ . }}"></option>{{ end }}</datalist>
</label>
<label>Event date {{ if index .FieldErrors "event_date" }}<span class="field-error">{{ index .FieldErrors "event_date" }}</span>{{ end }}
<input class="{{ if index .FieldErrors "event_date" }}input-error{{ end }}" type="date" name="event_date" value="{{ .EventDate }}">
</label>
<label class="full-row">Details
<textarea name="details" rows="3" placeholder="Optional human-readable note">{{ .Details }}</textarea>
</label>
<label class="full-row">Import file (demo control)
<input type="file" name="upload" accept=".csv,text/csv,application/json">
<span class="meta">UI-only demo control: actual upload/parse flow is demonstrated in Import/Export pattern.</span>
</label>
<label class="checkbox-row full-row"><input type="checkbox" checked> Show suggestions from full query scope (not current page only)</label>
<label class="checkbox-row full-row"><input type="checkbox"> Allow force action on ambiguous match (requires explicit review)</label>
<div class="button-demo-row full-row">
<button class="btn btn-primary" type="submit">Review</button>
<a class="btn btn-secondary" href="{{ index .StepURLs "edit" }}">Stay in edit</a>
<a class="btn btn-ghost" href="/patterns/forms#forms-contract">Reset</a>
</div>
</form>
</section>
{{ if or (eq .Step "review") (eq .Step "confirm") }}
<section class="panel" id="forms-review">
<div class="panel-head"><h2>{{ if eq .Step "confirm" }}Result Summary{{ else }}Review Summary{{ end }}</h2></div>
<div class="table-wrap">
<table class="ui-table">
<tbody>
<tr><th style="width:30%;">Mode</th><td>{{ .Mode }}</td></tr>
<tr><th>Server serial</th><td>{{ if .ServerSerial }}{{ .ServerSerial }}{{ else }}<span class="meta"></span>{{ end }}</td></tr>
<tr><th>Location</th><td>{{ if .Location }}{{ .Location }}{{ else }}<span class="meta"></span>{{ end }}</td></tr>
<tr><th>Component serial</th><td>{{ if .ComponentSerial }}{{ .ComponentSerial }}{{ else }}<span class="meta"></span>{{ end }}</td></tr>
<tr><th>Date</th><td>{{ .EventDate }}</td></tr>
<tr><th>Details</th><td>{{ if .Details }}{{ .Details }}{{ else }}<span class="meta">No note</span>{{ end }}</td></tr>
</tbody>
</table>
</div>
<div class="button-demo-row" style="margin-top:12px;">
{{ if eq .Step "review" }}
<a class="btn btn-secondary" href="{{ index .StepURLs "edit" }}">Back to edit</a>
<a class="btn btn-primary" href="{{ index .StepURLs "confirm" }}">Confirm</a>
{{ else }}
<a class="btn btn-primary" href="{{ index .StepURLs "edit" }}">Start new</a>
{{ end }}
</div>
</section>
{{ end }}
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,99 +0,0 @@
{{ define "io_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Canonical file transfer UX: import preview/confirm and export with explicit scope/format selection." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel" id="io-import">
<div class="panel-head"><h2>Import Workflow</h2></div>
<div class="notice">{{ .ImportMessage }}</div>
<form class="filters" method="get" action="/patterns/io#io-import">
<input type="hidden" name="import_mode" value="{{ .ImportMode }}">
<label>Source file
<input type="text" name="file" value="{{ .FileName }}" placeholder="items.csv">
</label>
<label>Step
<select name="import_mode">
<option value="preview" {{ if eq .ImportMode "preview" }}selected{{ end }}>Preview</option>
<option value="confirm" {{ if eq .ImportMode "confirm" }}selected{{ end }}>Confirm</option>
</select>
</label>
<label>Validation profile
<select>
<option selected>strict</option>
<option>lenient</option>
</select>
</label>
<div class="filter-actions">
<button class="btn btn-primary btn-pair" type="submit">Render state</button>
</div>
</form>
<div class="table-wrap" style="margin-top:12px;">
<table class="ui-table">
<thead>
<tr>
<th>Row</th>
<th>Code</th>
<th>Name</th>
<th>Qty</th>
<th>Validation</th>
</tr>
</thead>
<tbody>
{{ range .PreviewRows }}
<tr>
<td>{{ .RowNo }}</td>
<td>{{ .ItemCode }}</td>
<td>{{ .Name }}</td>
<td>{{ .Qty }}</td>
<td><span class="status status-{{ if eq .Status "error" }}warning{{ else }}ready{{ end }}">{{ .Status }}</span></td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<div class="button-demo-row" style="margin-top:12px;">
{{ if eq .ImportMode "preview" }}
<a class="btn btn-primary" href="/patterns/io?import_mode=confirm&file={{ .FileName }}#io-import">Review Import</a>
{{ else }}
<a class="btn btn-secondary" href="/patterns/io?import_mode=preview&file={{ .FileName }}#io-import">Back to preview</a>
<a class="btn btn-primary" href="/patterns/io?import_mode=preview&file={{ .FileName }}#io-import">Confirm & Import (demo)</a>
{{ end }}
</div>
</section>
<section class="panel" id="io-export">
<div class="panel-head"><h2>Export Workflow</h2></div>
<div class="notice">{{ .ExportMessage }}</div>
<form class="filters" method="get" action="/patterns/io#io-export">
<input type="hidden" name="export_ready" value="1">
<label>Format
<select name="format">
<option value="csv" {{ if eq .ExportFormat "csv" }}selected{{ end }}>CSV</option>
<option value="json" {{ if eq .ExportFormat "json" }}selected{{ end }}>JSON (planned)</option>
</select>
</label>
<label>Scope
<select name="scope">
<option value="filtered" {{ if eq .ExportScope "filtered" }}selected{{ end }}>Filtered rows</option>
<option value="selected" {{ if eq .ExportScope "selected" }}selected{{ end }}>Selected rows</option>
<option value="all" {{ if eq .ExportScope "all" }}selected{{ end }}>All rows</option>
</select>
</label>
<label>Include headers
<select>
<option selected>Yes</option>
<option>No</option>
</select>
</label>
<div class="filter-actions">
<button class="btn btn-primary btn-pair" type="submit">Prepare export</button>
</div>
</form>
<div class="button-demo-row" style="margin-top:12px;">
<a class="btn btn-primary" href="/patterns/io/export.csv?scope={{ .ExportScope }}">Download CSV</a>
<a class="btn btn-ghost" href="/patterns/io#io-export">Reset</a>
</div>
<p class="meta" style="margin-top:10px;">Demo export endpoint includes UTF-8 BOM and semicolon delimiter to illustrate spreadsheet compatibility patterns.</p>
</section>
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,74 +0,0 @@
{{ define "modal_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Single-modal workflow progression: edit → confirm → complete, with explicit cancel/close paths." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel" id="modal-open-states">
<div class="panel-head"><h2>Open States</h2></div>
<div class="button-demo-row">
<a class="btn btn-primary" href="/patterns/modals?open=edit&stage=edit&sel=1&sel=3#modal-open-states">Open Edit Modal</a>
<a class="btn btn-secondary" href="/patterns/modals?open=delete&stage=confirm&sel=2#modal-open-states">Open Confirm Modal</a>
<a class="btn btn-ghost" href="/patterns/modals#modal-open-states">No modal open</a>
</div>
<div class="notice">{{ .Message }}</div>
</section>
<section class="panel" id="modal-context">
<div class="panel-head"><h2>Page Context</h2></div>
<div class="card">
<div class="row"><h3>Selected items</h3><span class="status status-{{ if .SelectedIDs }}ready{{ else }}review{{ end }}">{{ if .SelectedIDs }}{{ len .SelectedIDs }} selected{{ else }}none{{ end }}</span></div>
<p class="meta">{{ if .SelectedIDs }}IDs: {{ range $i, $id := .SelectedIDs }}{{ if $i }}, {{ end }}{{ $id }}{{ end }}{{ else }}Select rows on the controls page to carry context into modal demos.{{ end }}</p>
</div>
</section>
{{ if .Open }}
<div class="demo-modal-backdrop">
<section class="demo-modal">
<div class="demo-modal-titlebar">
<a class="demo-modal-close-dot" href="/patterns/modals#modal-open-states" aria-label="Close modal"></a>
<div class="demo-modal-title">{{ if eq .Open "delete" }}Confirm Destructive Action{{ else }}Edit Items{{ end }}</div>
</div>
<div class="demo-modal-body">
<p class="meta">{{ .Message }}</p>
{{ if eq .Stage "edit" }}
<div class="modal-grid">
<label>Display name <input type="text" value="Example group change"></label>
<label>Status after action
<select>
<option>ready</option>
<option selected>review</option>
<option>warning</option>
</select>
</label>
<label class="checkbox-row"><input type="checkbox" checked> Apply to all selected items</label>
<label class="checkbox-row"><input type="checkbox"> Clear optional field values</label>
</div>
<div class="button-demo-row">
<a class="btn btn-secondary" href="/patterns/modals#modal-open-states">Cancel</a>
<a class="btn btn-primary" href="/patterns/modals?open={{ .Open }}&stage=confirm{{ range .SelectedIDs }}&sel={{ . }}{{ end }}#modal-open-states">Review Changes</a>
</div>
{{ else if eq .Stage "confirm" }}
<div class="card">
<h3 style="margin:0;">Confirmation Summary</h3>
<ul>
<li>Selected rows are listed and reviewed before submit.</li>
<li>Destructive actions require explicit confirmation wording.</li>
<li>No nested modals; stay within one modal state machine.</li>
</ul>
</div>
<div class="button-demo-row">
<a class="btn btn-secondary" href="/patterns/modals?open={{ .Open }}&stage=edit{{ range .SelectedIDs }}&sel={{ . }}{{ end }}#modal-open-states">Back</a>
<a class="btn {{ if eq .Open "delete" }}btn-danger{{ else }}btn-primary{{ end }}" href="/patterns/modals?open={{ .Open }}&stage=done{{ range .SelectedIDs }}&sel={{ . }}{{ end }}#modal-open-states">Confirm</a>
</div>
{{ else }}
<div class="notice success">Action completed. Show human-readable summary and next actions.</div>
<div class="button-demo-row">
<a class="btn btn-primary" href="/patterns/modals#modal-open-states">Done</a>
</div>
{{ end }}
</div>
</section>
</div>
{{ end }}
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,135 +0,0 @@
{{ define "operator_tools_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Universal operator/admin dashboard pattern: tool queue, batch actions, safety checks, import/export shortcuts, and explicit confirmation paths." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel panel-composite" id="operator-workbench">
<div class="panel-subsection" id="operator-filters">
<div class="panel-head">
<h2>Tool Scope Tabs</h2>
<div class="meta">{{ .VisibleCount }} visible jobs · {{ .SelectedCount }} selected</div>
</div>
<div class="operator-toolbar operator-scope-toolbar" role="toolbar" aria-label="Scope and queue filters">
<div class="tool-group" role="group" aria-label="Tool scope">
<a class="tool-btn tool-tab {{ if eq .Scope "assets" }}active{{ end }}" href="{{ index .ScopeURLs "assets" }}">Assets</a>
<a class="tool-btn tool-tab {{ if eq .Scope "components" }}active{{ end }}" href="{{ index .ScopeURLs "components" }}">Components</a>
<a class="tool-btn tool-tab {{ if eq .Scope "imports" }}active{{ end }}" href="{{ index .ScopeURLs "imports" }}">Imports</a>
<a class="tool-btn tool-tab {{ if eq .Scope "maintenance" }}active{{ end }}" href="{{ index .ScopeURLs "maintenance" }}">Maintenance</a>
<a class="tool-btn tool-tab {{ if eq .Scope "all" }}active{{ end }}" href="{{ index .ScopeURLs "all" }}">All</a>
</div>
<div class="tool-group" role="group" aria-label="Queue status">
<a class="tool-btn tool-tab {{ if eq .Queue "all" }}active{{ end }}" href="{{ index .QueueURLs "all" }}">All queue states</a>
<a class="tool-btn tool-tab {{ if eq .Queue "queued" }}active{{ end }}" href="{{ index .QueueURLs "queued" }}">Queued</a>
<a class="tool-btn tool-tab {{ if eq .Queue "running" }}active{{ end }}" href="{{ index .QueueURLs "running" }}">Running</a>
<a class="tool-btn tool-tab {{ if eq .Queue "failed" }}active{{ end }}" href="{{ index .QueueURLs "failed" }}">Failed</a>
<a class="tool-btn tool-tab {{ if eq .Queue "done" }}active{{ end }}" href="{{ index .QueueURLs "done" }}">Done</a>
</div>
</div>
</div>
<div class="panel-subsection panel-subsection-divider" id="operator-queue">
<div class="panel-head">
<h2>Operations Queue</h2>
<div class="meta">Complex dashboards may include multiple tables; standardize row actions and statuses first.</div>
</div>
{{ if .ActionMessage }}<div class="notice">{{ .ActionMessage }}</div>{{ end }}
<p class="meta" style="margin-bottom:12px;">
Selected on this view: {{ .SelectedVisible }}{{ if gt .SelectionOutside 0 }} · Selected outside current filter: {{ .SelectionOutside }}{{ end }}
</p>
<div class="meta" style="margin-bottom:12px;">Batch controls must keep scope/filter context explicit.</div>
<div class="table-toolbar" role="toolbar" aria-label="Operator tooling actions">
<div class="toolbar-group" role="group" aria-label="Selection">
<div class="toolbar-group-title">Selection</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .SelectVisibleURL }}" title="Select visible" aria-label="Select visible"><svg class="tool-svg" aria-hidden="true"><use href="#ico-select-visible"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ClearVisibleURL }}" title="Clear visible" aria-label="Clear visible"><svg class="tool-svg" aria-hidden="true"><use href="#ico-clear-visible"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ClearSelectionURL }}" title="Clear selection" aria-label="Clear selection"><svg class="tool-svg" aria-hidden="true"><use href="#ico-clear-selection"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Actions">
<div class="toolbar-group-title">Actions</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn tool-icon-btn-primary" href="{{ .RunSelectedURL }}" title="Run selected" aria-label="Run selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-run"></use></svg></a>
<a class="tool-icon-btn tool-icon-btn-danger" href="{{ .CancelSelectedURL }}" title="Cancel selected" aria-label="Cancel selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-cancel"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Import and export">
<div class="toolbar-group-title">Import/Export</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .ImportPreviewURL }}" title="Import batch preview" aria-label="Import batch preview"><svg class="tool-svg" aria-hidden="true"><use href="#ico-import"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ExportFilteredURL }}" title="Export filtered" aria-label="Export filtered"><svg class="tool-svg" aria-hidden="true"><use href="#ico-export-filtered"></use></svg></a>
<a class="tool-icon-btn" href="{{ .ExportSelectedURL }}" title="Export selected" aria-label="Export selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-export-selected"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Task actions">
<div class="toolbar-group-title">Task Actions</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ .RetrySelectedURL }}" title="Retry selected" aria-label="Retry selected"><svg class="tool-svg" aria-hidden="true"><use href="#ico-retry"></use></svg></a>
<a class="tool-icon-btn" href="{{ .OpenReviewModalURL }}" title="Open confirm modal" aria-label="Open confirm modal"><svg class="tool-svg" aria-hidden="true"><use href="#ico-confirm"></use></svg></a>
</div>
</div>
<div class="toolbar-group" role="group" aria-label="Misc">
<div class="toolbar-group-title">Misc</div>
<div class="toolbar-group-buttons">
<a class="tool-icon-btn" href="{{ index .ScopeURLs "all" }}" title="All scope" aria-label="All scope"><svg class="tool-svg" aria-hidden="true"><use href="#ico-scope"></use></svg></a>
<a class="tool-icon-btn" href="{{ index .QueueURLs "all" }}" title="All queue states" aria-label="All queue states"><svg class="tool-svg" aria-hidden="true"><use href="#ico-queue"></use></svg></a>
</div>
</div>
</div>
<div class="table-wrap">
<table class="ui-table">
<thead>
<tr>
<th class="select-col" aria-label="Select"></th>
<th>Job ID</th>
<th>Tool</th>
<th>Scope</th>
<th>Mode</th>
<th>Status</th>
<th>Owner</th>
<th>Started</th>
<th class="actions-col" aria-label="Actions"></th>
</tr>
</thead>
<tbody>
{{ range .Rows }}
<tr>
<td class="select-col"><a class="check-toggle {{ if .Selected }}checked{{ end }}" href="{{ .ToggleURL }}" aria-label="Toggle {{ .ID }}">{{ if .Selected }}☑{{ else }}☐{{ end }}</a></td>
<td class="job-id-cell"><code>{{ .ID }}</code></td>
<td>{{ .Tool }}</td>
<td>{{ .Scope }}</td>
<td>{{ .Mode }}</td>
<td><span class="status status-{{ .Status }}">{{ .Status }}</span></td>
<td>{{ .Owner }}</td>
<td>{{ .StartedAt }}</td>
<td class="action-cell action-icons actions-col">
<a class="icon-link" href="{{ .RetryURL }}" title="Retry" aria-label="Retry"><svg class="tool-svg" aria-hidden="true"><use href="#ico-retry"></use></svg></a>
<a class="icon-link icon-link-danger" href="{{ .CancelURL }}" title="Cancel" aria-label="Cancel"><svg class="tool-svg" aria-hidden="true"><use href="#ico-cancel"></use></svg></a>
<a class="icon-link" href="{{ .ExportURL }}" title="Export" aria-label="Export"><svg class="tool-svg" aria-hidden="true"><use href="#ico-export"></use></svg></a>
<a class="icon-link" href="{{ .InspectURL }}" title="Inspect" aria-label="Inspect"><svg class="tool-svg" aria-hidden="true"><use href="#ico-inspect"></use></svg></a>
</td>
</tr>
{{ else }}
<tr><td colspan="9"><span class="meta">No queued items for this scope/filter combination.</span></td></tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</section>
<section class="panel" id="operator-safety">
<div class="panel-head"><h2>Safety Checklist</h2></div>
<ul class="meta">
{{ range .SafetyChecklist }}
<li>{{ . }}</li>
{{ end }}
</ul>
<div class="panel-head" style="margin-top:14px;"><h2>Recent Operator Notes</h2></div>
<ul class="meta">
{{ range .RecentActivityNotes }}
<li>{{ . }}</li>
{{ end }}
</ul>
</section>
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,177 +0,0 @@
{{ define "style_playground_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Experiment with visual directions on identical UI modules. Behavior contracts stay the same; only presentation changes." "back_url" "/" "back_text" "← Back to catalog") }}
<div class="style-playground {{ .StyleClass }}">
<section class="panel" id="style-presets">
<div class="panel-head">
<h2>Theme Presets</h2>
<div class="meta">Current: {{ .StyleLabel }}</div>
</div>
<div class="segmented status-filter-tabs status-filter-tabs-blue">
<a class="segment {{ if eq .Style "linen" }}active{{ end }}" href="{{ index .StyleURLs "linen" }}">Linen</a>
<a class="segment {{ if eq .Style "slate" }}active{{ end }}" href="{{ index .StyleURLs "slate" }}">Slate</a>
<a class="segment {{ if eq .Style "signal" }}active{{ end }}" href="{{ index .StyleURLs "signal" }}">Signal</a>
<a class="segment {{ if eq .Style "y2k-silver" }}active{{ end }}" href="{{ index .StyleURLs "y2k-silver" }}">Y2K Silver</a>
<a class="segment {{ if eq .Style "vaporwave-soft" }}active{{ end }}" href="{{ index .StyleURLs "vaporwave-soft" }}">Vapor Soft</a>
<a class="segment {{ if eq .Style "vaporwave-night" }}active{{ end }}" href="{{ index .StyleURLs "vaporwave-night" }}">Vapor Night</a>
<a class="segment {{ if eq .Style "aqua" }}active{{ end }}" href="{{ index .StyleURLs "aqua" }}">Aqua</a>
<a class="segment {{ if eq .Style "win9x" }}active{{ end }}" href="{{ index .StyleURLs "win9x" }}">Win9x</a>
</div>
<p class="meta" style="margin-top:10px;">Use this page to compare visual directions on identical UI modules. Behavior contracts stay the same; only presentation changes.</p>
</section>
<section class="panel" id="style-components">
<div class="panel-head">
<h2>Component Preview</h2>
<div class="button-demo-row" style="margin-top:0;">
{{ if .LoadingDemo }}
<a class="btn btn-ghost" href="{{ .ClearLoadingURL }}">Stop loading demo</a>
{{ else }}
<a class="btn btn-secondary" href="{{ .LoadingDemoURL }}">Simulate loading state</a>
{{ end }}
</div>
</div>
<div class="grid">
<article class="card">
<h3 style="margin-top:0;">Buttons</h3>
<div class="button-demo-row">
<button class="btn btn-primary" type="button">Apply</button>
<button class="btn btn-secondary" type="button">Review</button>
<button class="btn btn-ghost" type="button">Reset</button>
<button class="btn btn-danger" type="button">Archive</button>
{{ if .LoadingDemo }}
<a class="btn btn-secondary is-loading" aria-disabled="true" href="{{ .ClearLoadingURL }}">Loading…</a>
{{ else }}
<button class="btn" type="button" disabled>Disabled</button>
{{ end }}
</div>
</article>
<article class="card">
<h3 style="margin-top:0;">Status + Chips</h3>
<div class="chip-row">
<span class="chip">URL-driven state</span>
<span class="chip">Server-rendered</span>
<span class="chip">Anchor restore</span>
</div>
<div class="button-demo-row" style="margin-top:12px;">
<span class="status status-ready">ready</span>
<span class="status status-warning">warning</span>
<span class="status status-review">review</span>
<span class="status status-failed">failed</span>
</div>
</article>
</div>
</section>
<section class="panel" id="style-status-filter">
<div class="panel-head">
<h2>Segmented Status Filter</h2>
<div class="meta">12 filtered • page 1/3 • 0 selected</div>
</div>
<div class="status-filter-shell">
<div class="segmented status-filter-tabs status-filter-tabs-dark" role="tablist" aria-label="Status filter">
<button class="segment active" type="button" role="tab" aria-selected="true">All (12)</button>
<button class="segment" type="button" role="tab" aria-selected="false">Ready (4)</button>
<button class="segment" type="button" role="tab" aria-selected="false">Warning (4)</button>
<button class="segment" type="button" role="tab" aria-selected="false">Review (4)</button>
</div>
</div>
</section>
<section class="panel" id="style-filters">
<div class="panel-head">
<h2>Compact Filter Module</h2>
<div class="button-demo-row" style="margin-top:0;">
<button class="btn btn-primary btn-pair" type="button">Apply</button>
<button class="btn btn-ghost btn-pair" type="button">Reset</button>
</div>
</div>
<form class="filters" action="/patterns/style-playground#style-filters" method="get">
<input type="hidden" name="style" value="{{ .Style }}">
<label>
Search
<input type="text" name="q" value="rack / owner / status">
</label>
<label>
Category
<select name="category">
<option selected>All</option>
<option>Compute</option>
<option>Networking</option>
</select>
</label>
<label>
Status
<select name="status">
<option selected>All</option>
<option>ready</option>
<option>warning</option>
<option>review</option>
</select>
</label>
<label>
Rows per page
<select name="per_page">
<option>5</option>
<option selected>10</option>
<option>20</option>
</select>
</label>
</form>
</section>
<section class="panel" id="style-surface">
<div class="panel-head">
<h2>Surface + Table Readability</h2>
<div class="meta">Same content under a different visual direction</div>
</div>
<div class="table-wrap">
<table class="ui-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Category</th>
<th class="status-col">Status</th>
<th>Owner</th>
</tr>
</thead>
<tbody>
<tr>
<td>12</td>
<td>Rack Controller Alpha</td>
<td>Compute</td>
<td class="status-col"><span class="status status-ready">ready</span></td>
<td>Ops</td>
</tr>
<tr>
<td>13</td>
<td>Patch Panel Group</td>
<td>Networking</td>
<td class="status-col"><span class="status status-warning">warning</span></td>
<td>Infra</td>
</tr>
<tr>
<td>14</td>
<td>Mapping Repair Queue</td>
<td>Storage</td>
<td class="status-col"><span class="status status-review">review</span></td>
<td>QA</td>
</tr>
</tbody>
</table>
</div>
<div class="pager pager-dots" style="margin-top:12px;" aria-label="Pagination preview">
<a class="current" aria-current="page" href="{{ index .StyleURLs .Style }}#style-surface" aria-label="Page 1, current">1</a>
<a href="{{ index .StyleURLs .Style }}#style-surface" aria-label="Go to page 2">2</a>
<span class="ellipsis" aria-hidden="true"></span>
<a href="{{ index .StyleURLs .Style }}#style-surface" aria-label="Go to page 7">7</a>
<a href="{{ index .StyleURLs .Style }}#style-surface" aria-label="Go to page 8">8</a>
</div>
</section>
</div>
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,118 +0,0 @@
{{ define "timeline_pattern.html" }}
{{ template "demo_doc_start" . }}
{{ template "demo_masthead" (dict "label" "Pattern Demo" "title" .Title "lead" "Grouped timeline cards by day with source/action filters and single drilldown modal." "back_url" "/" "back_text" "← Back to catalog") }}
<section class="panel panel-composite" id="timeline-module">
<div class="panel-subsection" id="timeline-filters">
<div class="panel-head"><h2>Timeline Filters</h2></div>
<div class="segmented" style="margin-bottom:10px;">
<a class="segment {{ if eq .ActionFilter "" }}active{{ end }}" href="{{ index .ActionURLs "" }}">All actions</a>
<a class="segment {{ if eq .ActionFilter "installation" }}active{{ end }}" href="{{ index .ActionURLs "installation" }}">Installation</a>
<a class="segment {{ if eq .ActionFilter "removal" }}active{{ end }}" href="{{ index .ActionURLs "removal" }}">Removal</a>
<a class="segment {{ if eq .ActionFilter "edit" }}active{{ end }}" href="{{ index .ActionURLs "edit" }}">Edit</a>
</div>
<div class="segmented">
<a class="segment {{ if eq .SourceFilter "" }}active{{ end }}" href="{{ index .SourceURLs "" }}">All sources</a>
<a class="segment {{ if eq .SourceFilter "manual" }}active{{ end }}" href="{{ index .SourceURLs "manual" }}">Manual</a>
<a class="segment {{ if eq .SourceFilter "ingest" }}active{{ end }}" href="{{ index .SourceURLs "ingest" }}">Ingest</a>
<a class="segment {{ if eq .SourceFilter "system" }}active{{ end }}" href="{{ index .SourceURLs "system" }}">System</a>
</div>
</div>
<div class="panel-subsection panel-subsection-divider" id="timeline-cards">
<div class="panel-head"><h2>Grouped Cards</h2></div>
{{ if .Cards }}
<div class="timeline-cards">
{{ range .Cards }}
<article class="timeline-card">
<div class="timeline-card-top">
<div>
<p class="timeline-day">{{ .Day }}</p>
<h3>{{ .Title }}</h3>
</div>
<div class="timeline-meta">
<span class="status status-review">{{ .Action }}</span>
<span class="status status-{{ if eq .Source "manual" }}ready{{ else if eq .Source "ingest" }}warning{{ else }}review{{ end }}">{{ .Source }}</span>
</div>
</div>
<div class="timeline-summary-grid">
<div>
<div class="meta">Models / Types</div>
<div class="chip-row">{{ range .SummaryLeft }}<span class="chip">{{ . }}</span>{{ end }}</div>
</div>
<div>
<div class="meta">Slots / Scope</div>
<div class="chip-row">{{ range .SummaryRight }}<span class="chip">{{ . }}</span>{{ end }}</div>
</div>
</div>
<div class="timeline-card-actions">
<span class="meta">{{ .Count }} event(s)</span>
<a class="btn btn-secondary" href="{{ .OpenURL }}">Open details</a>
</div>
</article>
{{ end }}
</div>
{{ else }}
<div class="notice">No timeline cards match current filters.</div>
{{ end }}
</div>
</section>
{{ if .OpenCard }}
<div class="demo-modal-backdrop">
<section class="demo-modal timeline-modal">
<div class="demo-modal-titlebar">
<a class="demo-modal-close-dot" href="{{ .ClearCardURL }}" aria-label="Close modal"></a>
<div class="demo-modal-title">{{ .OpenCard.Title }}</div>
</div>
<div class="demo-modal-body">
<p class="meta">{{ .OpenCard.Day }} · {{ .OpenCard.Source }} · {{ .OpenCard.Action }}</p>
<form id="timeline-drilldown" class="filters" method="get" action="/patterns/timeline#timeline-drilldown" style="margin-top:12px;">
<input type="hidden" name="action" value="{{ .ActionFilter }}">
<input type="hidden" name="source" value="{{ .SourceFilter }}">
<input type="hidden" name="open" value="{{ .OpenCard.ID }}">
<label>Filter events in card
<input type="search" name="q" value="{{ .CardSearch }}" placeholder="serial / slot / model / source">
</label>
<div></div><div></div>
<div class="filter-actions">
<button class="btn btn-primary btn-pair" type="submit">Apply</button>
<a class="btn btn-ghost btn-pair" href="{{ .ClearCardURL }}">Reset</a>
</div>
</form>
<div class="timeline-drill-grid">
<div class="timeline-drill-list">
{{ if .OpenCard.Items }}
{{ range .OpenCard.Items }}
<a class="timeline-item {{ if and $.ActiveEvent (eq $.ActiveEvent.ID .ID) }}active{{ end }}" href="/patterns/timeline?action={{ $.ActionFilter }}&source={{ $.SourceFilter }}&open={{ $.OpenCard.ID }}{{ if $.CardSearch }}&q={{ $.CardSearch }}{{ end }}&event={{ .ID }}#timeline-drilldown">
<div class="timeline-item-title">{{ .Action }} · {{ .At }}</div>
<div class="timeline-item-meta">{{ .Entity }} · {{ .Target }}</div>
<div class="timeline-item-meta">{{ .Slot }} · {{ .Device }} · {{ .Source }}</div>
</a>
{{ end }}
{{ else }}
<div class="notice">No events match the card filter.</div>
{{ end }}
</div>
<div class="timeline-drill-detail">
{{ if .ActiveEvent }}
<h3 style="margin-top:0;">Event Detail</h3>
<div class="meta">When: {{ .ActiveEvent.At }}</div>
<div class="meta">Action: {{ .ActiveEvent.Action }}</div>
<div class="meta">Source: {{ .ActiveEvent.Source }}</div>
<div class="meta">Entity: {{ .ActiveEvent.Entity }}</div>
<div class="meta">Target: {{ .ActiveEvent.Target }}</div>
<div class="meta">Slot / Device: {{ .ActiveEvent.Slot }} · {{ .ActiveEvent.Device }}</div>
<div class="notice" style="margin-top:12px;">{{ .ActiveEvent.Detail }}</div>
{{ else }}
<div class="meta">Select an event to view details.</div>
{{ end }}
</div>
</div>
</div>
</section>
</div>
{{ end }}
{{ template "demo_doc_end" . }}
{{ end }}

View File

@@ -1,14 +0,0 @@
{
"id": "ai-rules",
"version": 1,
"description": "Canonical AI instruction templates and shared architecture doc policy notes.",
"conflict_policy": "merge-manual",
"variables": [
{ "name": "project_name", "required": false, "default": "Project" }
],
"entries": [
{ "from": "ai/claude/CLAUDE.template.md", "to": "CLAUDE.md", "template": true, "mode": "file" },
{ "from": "ai/codex/AGENTS.template.md", "to": "AGENTS.md", "template": true, "mode": "file" },
{ "from": "ai/shared/ARCH_DOC_POLICY.md", "to": "docs/ARCH_DOC_POLICY.md", "template": false, "mode": "file" }
]
}

View File

@@ -1,9 +0,0 @@
{
"id": "bible-core",
"version": 1,
"description": "Canonical Bible skeleton for Go web projects using AI coding agents.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "docs/bible-skeleton", "to": "bible", "mode": "dir" }
]
}

View File

@@ -1,18 +0,0 @@
{
"id": "go-web-skeleton",
"version": 1,
"description": "Minimal net/http + html/template web skeleton for Go projects.",
"conflict_policy": "merge-manual",
"variables": [
{ "name": "module_path", "required": true },
{ "name": "binary_name", "required": false, "default": "app" },
{ "name": "project_name", "required": false, "default": "Demo" }
],
"entries": [
{ "from": "scaffolds/go-nethttp-web/cmd/demo-server/main.go.tmpl", "to": "cmd/{{ .binary_name }}/main.go", "template": true, "mode": "file" },
{ "from": "scaffolds/go-nethttp-web/internal/web", "to": "internal/web", "mode": "dir" },
{ "from": "scaffolds/go-nethttp-web/web", "to": "web", "mode": "dir" },
{ "from": "scaffolds/go-nethttp-web/Makefile", "to": "Makefile", "mode": "file" },
{ "from": "scaffolds/go-nethttp-web/README.md", "to": "docs/ui-design-skeleton.md", "mode": "file" }
]
}

View File

@@ -1,10 +0,0 @@
{
"id": "ui-pattern-controls",
"version": 1,
"description": "Buttons, checkboxes, segmented filters, and bulk-selection control patterns.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/controls-selection", "to": "docs/ui-patterns/controls-selection", "mode": "dir" },
{ "from": "patterns/table-management", "to": "docs/ui-patterns/table-management", "mode": "dir" }
]
}

View File

@@ -1,10 +0,0 @@
{
"id": "ui-pattern-forms",
"version": 1,
"description": "Form validation, datalist suggestions, and review/confirm workflow patterns.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/forms-validation", "to": "docs/ui-patterns/forms-validation", "mode": "dir" }
]
}

View File

@@ -1,10 +0,0 @@
{
"id": "ui-pattern-io",
"version": 1,
"description": "Import/export workflow patterns for file upload preview and downloads.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/import-export", "to": "docs/ui-patterns/import-export", "mode": "dir" }
]
}

View File

@@ -1,9 +0,0 @@
{
"id": "ui-pattern-modal",
"version": 1,
"description": "Modal workflow pattern docs and starter templates.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/modal-workflows", "to": "docs/ui-patterns/modal-workflows", "mode": "dir" }
]
}

View File

@@ -1,10 +0,0 @@
{
"id": "ui-pattern-operator-tools",
"version": 1,
"description": "Universal operator/admin dashboard pattern: queue tables, batch actions, and safety guardrails.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/operator-tools", "to": "docs/ui-patterns/operator-tools", "mode": "dir" },
{ "from": "patterns/table-management", "to": "docs/ui-patterns/table-management", "mode": "dir" }
]
}

View File

@@ -1,9 +0,0 @@
{
"id": "ui-pattern-table",
"version": 1,
"description": "Table + server-side filter + pagination pattern docs and starter templates.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/table-pagination", "to": "docs/ui-patterns/table-pagination", "mode": "dir" }
]
}

View File

@@ -1,10 +0,0 @@
{
"id": "ui-pattern-timeline",
"version": 1,
"description": "Timeline cards with grouped summaries and drilldown modal patterns.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/timeline-cards", "to": "docs/ui-patterns/timeline-cards", "mode": "dir" }
]
}

View File

@@ -1,9 +0,0 @@
{
"id": "ui-theme-aqua-legacy",
"version": 1,
"description": "Legacy Aqua theme snapshot bundle for reference/migration, including icon sprite and component chrome archive.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/theme-aqua-legacy", "to": "docs/ui-themes/aqua-legacy", "mode": "dir" }
]
}

View File

@@ -1,9 +0,0 @@
{
"id": "ui-theme-vapor",
"version": 1,
"description": "Active Vapor theme layer for shared UI primitives, including icon sprite and reusable component chrome.",
"conflict_policy": "merge-manual",
"entries": [
{ "from": "patterns/theme-vapor", "to": "docs/ui-themes/vapor", "mode": "dir" }
]
}

View File

@@ -1,16 +0,0 @@
{
"bundles": [
{ "id": "ai-rules", "manifest": "exports/bundles/ai-rules.yaml" },
{ "id": "bible-core", "manifest": "exports/bundles/bible-core.yaml" },
{ "id": "go-web-skeleton", "manifest": "exports/bundles/go-web-skeleton.yaml" },
{ "id": "ui-pattern-controls", "manifest": "exports/bundles/ui-pattern-controls.yaml" },
{ "id": "ui-pattern-forms", "manifest": "exports/bundles/ui-pattern-forms.yaml" },
{ "id": "ui-pattern-io", "manifest": "exports/bundles/ui-pattern-io.yaml" },
{ "id": "ui-pattern-operator-tools", "manifest": "exports/bundles/ui-pattern-operator-tools.yaml" },
{ "id": "ui-pattern-table", "manifest": "exports/bundles/ui-pattern-table.yaml" },
{ "id": "ui-pattern-modal", "manifest": "exports/bundles/ui-pattern-modal.yaml" },
{ "id": "ui-pattern-timeline", "manifest": "exports/bundles/ui-pattern-timeline.yaml" },
{ "id": "ui-theme-vapor", "manifest": "exports/bundles/ui-theme-vapor.yaml" },
{ "id": "ui-theme-aqua-legacy", "manifest": "exports/bundles/ui-theme-aqua-legacy.yaml" }
]
}

View File

@@ -1,39 +0,0 @@
$schema: "informational"
title: UI Design Code Bundle Manifest (informal schema)
type: object
required:
- id
- version
- description
- entries
properties:
id:
type: string
version:
type: integer
description:
type: string
conflict_policy:
type: string
enum: [overwrite, skip, merge-manual]
variables:
type: array
items:
type: object
required: [name]
properties:
name: { type: string }
required: { type: boolean }
default: {}
entries:
type: array
items:
type: object
required: [from, to]
properties:
from: { type: string, description: "path under kit/" }
to: { type: string, description: "path under host repo root" }
template: { type: boolean }
mode: { type: string, enum: [file, dir] }
conflict_policy: { type: string, enum: [overwrite, skip, merge-manual] }

3
go.mod
View File

@@ -1,3 +0,0 @@
module git.mchus.pro/mchus/ui-design-code
go 1.24.0

View File

@@ -1,11 +0,0 @@
# {{ .project_name }} — Instructions for Claude
Read and follow the project Bible before making any changes:
**[`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 appropriate Bible decision log.

View File

@@ -1,10 +0,0 @@
# {{ .project_name }} — Agent Instructions
Read and follow `bible/README.md` as the architecture source of truth.
## Rules
- Keep architecture documentation in English.
- Update Bible docs in the same change set as architecture/code contract changes.
- Keep README and agent docs minimal; avoid duplicating Bible architecture content.

View File

@@ -1,11 +0,0 @@
# Architecture Documentation Policy
This project uses a Bible-style architecture documentation folder as the single source of truth.
## Mandatory Rules
- Record architecture decisions before or together with implementation.
- Use English for architecture documentation.
- Remove or update obsolete architectural guidance in the same change.
- Keep non-Bible docs concise and reference the Bible instead of duplicating it.

View File

@@ -1,20 +0,0 @@
# Project Bible
The Bible is the single source of truth for this project's architecture.
## Documentation Rules
- All architecture decisions must be recorded in this Bible.
- The documentation language is English only.
- If a decision is superseded, update the Bible immediately and remove or mark obsolete content.
- Do not duplicate architecture documentation in `README.md`, `CLAUDE.md`, or other files.
## Structure
- `governance/documentation-policy.md` - mandatory rules for maintaining architecture docs.
- `architecture/system-overview.md` - current scope, boundaries, and high-level system composition.
- `architecture/api-surface.md` - HTTP API and UI route contracts.
- `architecture/runtime-flows.md` - critical runtime behavior and failure invariants.
- `architecture/ui-information-architecture.md` - required UI structure and semantics.
- `decisions/README.md` - how to capture new and updated architecture decisions.

View File

@@ -1,10 +0,0 @@
# API Surface
Document all HTTP endpoints and UI routes that are part of the active contract.
## Rules
- Keep request/response shapes explicit.
- Record compatibility aliases and deprecations.
- Update this file when route behavior changes.

View File

@@ -1,11 +0,0 @@
# Runtime Flows
Document the critical runtime flows and failure invariants.
Recommended sections:
- Startup flow
- Request handling flow
- Background tasks
- Failure / retry / cancellation behavior

View File

@@ -1,32 +0,0 @@
# System Overview
## Product
Describe the product in one paragraph.
## Active Scope
- List current in-scope modules/features
- List user-visible surfaces
- List operational responsibilities
## Explicitly Removed / Out of Scope
- List legacy or excluded modules to avoid accidental restoration
## Runtime Composition
- HTTP server package(s)
- Domain/service orchestration
- Persistence layer(s)
- Migrations / background workers (if any)
## Local Execution
- `make run`
- `go run ./cmd/<binary>`
## Verification
- `go test ./...`

View File

@@ -1,7 +0,0 @@
# UI Information Architecture
Document page structure, required section order, UI interaction contracts, and list/filter/
pagination behavior.
Use explicit route names and action contracts so AI agents can implement changes consistently.

View File

@@ -1,18 +0,0 @@
# Architecture Decision Records
Use this directory to capture architecture decisions and significant updates.
## Required
- Every architecture decision must be recorded in the Bible.
- If a decision replaces an older one, update the older document in the same change.
- Keep entries short, explicit, and linked to the affected architecture files.
## Minimal Entry Template
- Date (`YYYY-MM-DD`)
- Decision
- Context
- Consequences
- Supersedes (if any)

View File

@@ -1,21 +0,0 @@
# Documentation Policy
## Purpose
This policy defines how architectural knowledge is captured and maintained.
## Mandatory Rules
- Record every architecture decision in the Bible before or together with implementation.
- Use English for all architecture documentation.
- Keep only current architecture in active sections.
- When a solution is replaced, update or remove obsolete guidance in the same change.
- Keep architecture details centralized in `bible/`; other top-level docs should only reference it.
## Change Workflow
1. Update the relevant file(s) in `bible/architecture/`.
2. If behavior changed, add or update a decision note in `bible/decisions/`.
3. Remove duplicated or outdated statements from non-Bible docs.
4. Validate consistency against current code paths.

View File

@@ -1,7 +0,0 @@
# Gin Admin App Integration Notes (Example)
Suggested use:
- use `ai-rules` templates only if they do not conflict with existing agent instructions
- reuse UI pattern bundles for admin tables and modal workflows
- keep domain-specific hard rules in project-local Bible docs

View File

@@ -1,10 +0,0 @@
# Greenfield App Integration Notes (Example)
Suggested use:
- apply `ai-rules`
- apply `bible-core` (or merge selectively if a Bible already exists)
- apply `ui-theme-vapor` for active visual baseline (CSS + icon sprite)
- copy selected UI patterns into new modules/pages
Keep domain-specific business rules in the host project's own Bible.

View File

@@ -1,9 +0,0 @@
# net/http Enterprise App Integration Notes (Example)
This example assumes the host repo already has a mature Bible structure and detailed UI contracts.
Use this kit primarily for:
- shared AI instruction wording
- pattern bundles that codify existing host UI contracts
- future cross-project standardization docs

View File

@@ -1,32 +0,0 @@
# 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 should set:
- `Content-Type`
- `Content-Disposition`
- If CSV targets spreadsheet users, document delimiter and BOM policy.
## Error Handling
- Import errors should map to clear user-facing messages.
- Export errors after streaming starts must degrade gracefully (human-readable fallback).

View File

@@ -1,9 +0,0 @@
# Contract: Modal Workflows
## Shared Rules
- 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.

View File

@@ -1,13 +0,0 @@
# Operator Tools Pattern
Universal pattern for complex operator/admin dashboards in Go web applications.
This pattern standardizes:
- scope tabs and queue/status filters
- operations queue tables with row actions
- batch actions with explicit selection
- import/export shortcuts near operator workflows
- safety checklists and confirmation routing
See `contract.md` for behavior rules.

View File

@@ -1,43 +0,0 @@
# Contract: Operator Tools Dashboard
## 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
operator-specific behavior.
## Purpose
Provide a canonical structure for high-density operator/admin screens without encoding
domain-specific terminology or business rules.
## Required UI Regions
- Scope tabs (or equivalent segmented navigation)
- Queue/status filters
- Batch action bar with explicit selection count
- Primary queue table/list with stable row actions
- Safety/guardrail region (checklist, warnings, runbook notes)
## Selection and Batch Actions
- Batch actions must require explicit selection; never infer hidden rows by default.
- The UI must display selection counts for the current view and, if applicable, outside the current view/filter.
- Destructive or high-impact actions must route through an explicit confirmation step (modal or dedicated confirm state).
## Status and Queue Semantics
- Queue/status labels must be consistent across filters, row badges, and summaries.
- Failed items must remain easy to filter and retry without losing current scope context.
- Running/queued states should be visually distinct from done/failed states.
## Import / Export Placement
- Import preview and export actions should be discoverable near operator workflows.
- Export scope must remain explicit (`selected`, `filtered`, `all`) when ambiguity exists.
## Reuse Boundary
- Keep the pattern generic: host projects provide their own tool names, domain fields, and backend execution logic.
- This contract standardizes interaction semantics and layout zones, not business workflows.

View File

@@ -1,4 +0,0 @@
# Status Indicators Pattern (Planned)
Reserved for health badges, sync status indicators, and severity labels.

View File

@@ -1,7 +0,0 @@
# Table + Pagination Pattern
This pattern package captures a shared contract for table-based UI pages with server-side
filters and pagination.
It is derived from mature server-rendered admin UIs and list-page conventions across multiple
Go web codebases.

View File

@@ -1,3 +0,0 @@
// Placeholder template for pagination helpers.
// Adapt from existing host codebase conventions (net/http or Gin).

View File

@@ -1,27 +0,0 @@
# Contract: Table + Server-Side Filters + Pagination
## Required Behavior
- Filters apply to the full query scope before pagination (not page-local filtering).
- Pagination state is controlled by URL query parameters.
- Changing a filter resets the page parameter to page `1`.
- UI shows a summary (`Showing XY of N`) and an explicit active page state in pagination controls.
- Canonical demo visual style uses a centered dot pager while preserving page semantics via URL parameters and accessible labels.
- Demo/base contract includes a `rows per page` control.
- Status badge columns should be centered when the column semantics are status-only values.
- Small modules (no internal scroll, no more than 2 actions) should place actions in the module header row.
- Shared table/filter controls should inherit the repository baseline component theme
(currently Vapor Soft / Vapor Night in the canonical demo/scaffold) rather than redefining
per-pattern primitives.
## Recommended Query Parameters
- `page` for single-list pages
- `per_page` when the page-size control is user-visible
- section-specific params (for example `active_page`, `chronology_page`) for multi-list pages
## Interaction Guardrails
- Same-page filter/pagination interactions should preserve user reading position (for example via module anchors such as `#table-filters` / `#table-list`).
- Table status badges should remain readable under the shared theme baseline (including tinted badge variants when a host project opts into a legacy or alternate visual preset).
- Dot pager interactions should remain keyboard accessible and expose page meaning via labels/`aria-current`, even if numbers are visually hidden.

View File

@@ -1,2 +0,0 @@
/* Placeholder styles for table pagination pattern. */

View File

@@ -1,2 +0,0 @@
<!-- Placeholder table + pager partial. -->

View File

@@ -1,18 +0,0 @@
# Aqua Theme Module (Legacy Archive Layer)
Status: legacy archive/reference layer (not the active demo/scaffold baseline).
This directory preserves the Aqua visual language archive for the design code:
- global visual tokens and component surfaces
- reusable control geometry (buttons, segmented controls, table chrome)
- icon rendering baseline (stroke style + sprite integration)
Active pattern contracts should target the current baseline and may reference this module only
for migration/comparison purposes.
Contents:
- `demo-aqua-freeze.css` — frozen Aqua stylesheet snapshot
- `templates/icon_sprite.html` — canonical icon sprite for reusable action semantics
- `notes.md` — scope and usage policy for this theme module

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +0,0 @@
# Aqua Theme Notes
This module is a reusable Aqua style archive for design-code consumers.
## What it captures
- Aqua-style control surfaces and button hierarchy
- segmented controls and grouped tab geometry
- table/filter/toolbars visual primitives
- icon rendering baseline and shared icon sprite usage
- modal and status component style language
## How to use
- Treat this module as a legacy reference snapshot, not the active baseline.
- Pattern contracts should inherit the active baseline first and use this module only when a host
project explicitly opts into Aqua-like styling.
- Do not move new baseline decisions here.
## Boundary
- This module standardizes visual language only.
- Domain behavior, labels, and business workflow semantics belong to pattern contracts.

View File

@@ -1,103 +0,0 @@
<!-- Canonical Aqua icon sprite for shared UI actions -->
<svg class="icon-sprite" aria-hidden="true" focusable="false" width="0" height="0">
<symbol id="ico-select-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8.2l2 2.1 4-4.3"></path>
</symbol>
<symbol id="ico-select-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11l-4.2 4.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M11.4 10.2h3M12.9 8.7v3"></path>
</symbol>
<symbol id="ico-clear-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8h6"></path>
</symbol>
<symbol id="ico-clear-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11L9.3 7.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M10.5 10.2l3 3M13.5 10.2l-3 3"></path>
</symbol>
<symbol id="ico-clear-selection" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 5l6 6M11 5l-6 6"></path>
</symbol>
<symbol id="ico-run" viewBox="0 0 16 16">
<path d="M5 3.5l7 4.5-7 4.5z"></path>
</symbol>
<symbol id="ico-edit" viewBox="0 0 16 16">
<path d="M3 13l1.2-3.6L10 3.6l2.4 2.4-5.8 5.8L3 13z"></path>
<path d="M8.8 4.8l2.4 2.4"></path>
</symbol>
<symbol id="ico-remove" viewBox="0 0 16 16">
<path d="M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6"></path>
</symbol>
<symbol id="ico-cancel" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M5.5 5.5l5 5M10.5 5.5l-5 5"></path>
</symbol>
<symbol id="ico-archive" viewBox="0 0 16 16">
<rect x="2.5" y="3" width="11" height="2.8" rx="1"></rect>
<path d="M3.5 5.8V13h9V5.8"></path>
<path d="M6.2 8h3.6"></path>
</symbol>
<symbol id="ico-import" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 1.8v6.2"></path>
<path d="M5.8 5.8L8 8l2.2-2.2"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 8V1.8"></path>
<path d="M5.8 4L8 1.8 10.2 4"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export-filtered" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<path d="M10 5.1h5.2L13.1 7.5v3.1l-1.3.7V7.5z"></path>
</symbol>
<symbol id="ico-export-selected" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<rect x="10.1" y="5.2" width="5" height="5" rx="0.6"></rect>
<path d="M11.2 6.3l2.8 2.8M14 6.3l-2.8 2.8"></path>
</symbol>
<symbol id="ico-retry" viewBox="0 0 16 16">
<path d="M12.2 6A4.8 4.8 0 0 0 4.8 4.4"></path>
<path d="M5.6 2.9L4.1 4.6 5.9 5.8"></path>
<path d="M3.8 10A4.8 4.8 0 0 0 11.2 11.6"></path>
<path d="M10.4 13.1L11.9 11.4 10.1 10.2"></path>
</symbol>
<symbol id="ico-review" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M8 7.2v3.6"></path>
<path d="M8 5.1h.01"></path>
</symbol>
<symbol id="ico-mark-review" viewBox="0 0 16 16">
<path d="M8 2.6l5.2 2v3.7c0 2.3-1.5 4.4-5.2 5.7-3.7-1.3-5.2-3.4-5.2-5.7V4.6l5.2-2z"></path>
<path d="M8 6.1v3.2"></path>
<path d="M8 11.1h.01"></path>
</symbol>
<symbol id="ico-confirm" viewBox="0 0 16 16">
<path d="M8 2.5l5 2v3.8c0 2.2-1.3 4.1-5 5.3-3.7-1.2-5-3.1-5-5.3V4.5l5-2z"></path>
<path d="M5.7 8.3l1.6 1.7 3-3.1"></path>
</symbol>
<symbol id="ico-inspect" viewBox="0 0 16 16">
<circle cx="7" cy="7" r="3.8"></circle>
<path d="M9.8 9.8l3.2 3.2"></path>
</symbol>
<symbol id="ico-scope" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="4.2" height="4.2"></rect>
<rect x="9.3" y="2.5" width="4.2" height="4.2"></rect>
<rect x="2.5" y="9.3" width="4.2" height="4.2"></rect>
<rect x="9.3" y="9.3" width="4.2" height="4.2"></rect>
</symbol>
<symbol id="ico-queue" viewBox="0 0 16 16">
<path d="M5.4 4h8.1M5.4 8h8.1M5.4 12h8.1"></path>
<circle cx="3.2" cy="4" r="0.7"></circle>
<circle cx="3.2" cy="8" r="0.7"></circle>
<circle cx="3.2" cy="12" r="0.7"></circle>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,17 +0,0 @@
# Vapor Theme Module (Active Design Layer)
Status: active visual baseline for reusable UI primitives.
This module defines the shared Vapor visual language used by the demo and intended host-project
integration:
- baseline tokens and surface treatment
- reusable component geometry (buttons, segmented controls, table chrome)
- shared icon sprite for table-management and operator workflows
Contents:
- `static/vapor.css` - active stylesheet baseline
- `templates/icon_sprite.html` - active icon sprite
- `notes.md` - usage boundaries and integration rules
- `palette-catalog.md` - preset list for app-shell header and accent colors

View File

@@ -1,28 +0,0 @@
# Vapor Theme Notes
This module is the active reusable theme source for design-code consumers.
## What it captures
- Vapor control surfaces and button hierarchy
- segmented controls and grouped tab geometry
- table/filter/toolbar primitives
- icon rendering baseline and shared sprite usage
- modal and status component style language
- app-shell color/height presets for host-level branding fit within Vapor aesthetics
## How to use
- Use this module as the visual baseline for host projects that adopt the canonical demo style.
- Pattern contracts should reference shared behavior contracts and rely on this module for visual primitives.
- When baseline visuals change, update this module first to keep downstream integration deterministic.
- For shell color selection, use `palette-catalog.md` and set `data-vapor-shell` on `<html>`.
- For runtime switching, the recommended query key is `vapor_shell` (same IDs as the catalog).
- Keep host-specific brand overrides outside this module unless they are reusable presets.
## Boundary
- This module standardizes visual language only.
- Domain behavior, labels, and business workflow semantics belong to pattern contracts.
- Preset palette IDs are part of the reusable contract; host projects may choose one, but should not
rename/remove preset IDs in downstream copies without migration notes.

View File

@@ -1,50 +0,0 @@
# Vapor Shell Palette Catalog
This catalog defines reusable `app-shell` presets for host projects that consume `theme-vapor`.
Apply a preset on the root element:
```html
<html data-vapor-shell="miami-sunset">
```
Optional: host project can override shell height without changing preset IDs:
```css
:root { --shell-height: 72px; }
```
## Selection Approach
Recommended precedence for host integration:
1. `?vapor_shell=<preset-id>` query parameter (preview/share links)
2. `localStorage["vapor_shell"]` (remembered user choice)
3. `<html data-vapor-shell="...">` static default from server/templates
4. fallback preset: `miami-sunset`
The scaffold and demo JavaScript implement this precedence.
## Presets (Header + Accent)
| Preset ID | Header core | Accent core | Default shell height |
|---|---|---|---|
| `miami-sunset` | `#fbe5f8` | `#ff7ec7` | `66px` |
| `neon-grid` | `#f5eeff` | `#ff4fd8` | `64px` |
| `laser-flamingo` | `#ffe8f4` | `#ff6fae` | `70px` |
| `synth-lagoon` | `#e8f6ff` | `#67d7ff` | `62px` |
| `mall-soft` | `#fff1f8` | `#ffb5d7` | `68px` |
| `hologram-sky` | `#edf9ff` | `#5ce4ff` | `64px` |
| `peach-drive` | `#fff0e6` | `#ff9a6a` | `72px` |
| `ultraviolet-plaza` | `#f0e8ff` | `#b26dff` | `66px` |
| `cyber-mint` | `#ebfff7` | `#57f0c1` | `62px` |
## Token Surface
Each preset resolves these tokens in `static/vapor.css`:
- `--shell-height`
- `--shell-bg-light`, `--shell-bg-dark`
- `--shell-border-light`, `--shell-border-dark`
- `--shell-accent-light`, `--shell-accent-dark`
- `--shell-accent-line-light`, `--shell-accent-line-dark`

File diff suppressed because it is too large Load Diff

View File

@@ -1,102 +0,0 @@
<svg class="icon-sprite" aria-hidden="true" focusable="false" width="0" height="0">
<symbol id="ico-select-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8.2l2 2.1 4-4.3"></path>
</symbol>
<symbol id="ico-select-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11l-4.2 4.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M11.4 10.2h3M12.9 8.7v3"></path>
</symbol>
<symbol id="ico-clear-visible" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 8h6"></path>
</symbol>
<symbol id="ico-clear-filtered" viewBox="0 0 16 16">
<path d="M2.5 3h11L9.3 7.6v3.1l-2.6 1.5V7.6L2.5 3z"></path>
<path d="M10.5 10.2l3 3M13.5 10.2l-3 3"></path>
</symbol>
<symbol id="ico-clear-selection" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="11" height="11" rx="2"></rect>
<path d="M5 5l6 6M11 5l-6 6"></path>
</symbol>
<symbol id="ico-run" viewBox="0 0 16 16">
<path d="M5 3.5l7 4.5-7 4.5z"></path>
</symbol>
<symbol id="ico-edit" viewBox="0 0 16 16">
<path d="M3 13l1.2-3.6L10 3.6l2.4 2.4-5.8 5.8L3 13z"></path>
<path d="M8.8 4.8l2.4 2.4"></path>
</symbol>
<symbol id="ico-remove" viewBox="0 0 16 16">
<path d="M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6"></path>
</symbol>
<symbol id="ico-cancel" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M5.5 5.5l5 5M10.5 5.5l-5 5"></path>
</symbol>
<symbol id="ico-archive" viewBox="0 0 16 16">
<rect x="2.5" y="3" width="11" height="2.8" rx="1"></rect>
<path d="M3.5 5.8V13h9V5.8"></path>
<path d="M6.2 8h3.6"></path>
</symbol>
<symbol id="ico-import" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 1.8v6.2"></path>
<path d="M5.8 5.8L8 8l2.2-2.2"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export" viewBox="0 0 16 16">
<path d="M3 4.2v8.6h10V4.2"></path>
<path d="M8 8V1.8"></path>
<path d="M5.8 4L8 1.8 10.2 4"></path>
<path d="M5 10.2h6"></path>
</symbol>
<symbol id="ico-export-filtered" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<path d="M10 5.1h5.2L13.1 7.5v3.1l-1.3.7V7.5z"></path>
</symbol>
<symbol id="ico-export-selected" viewBox="0 0 16 16">
<path d="M2 4.2v8.6h10.8"></path>
<path d="M7 8V1.8"></path>
<path d="M4.8 4L7 1.8 9.2 4"></path>
<rect x="10.1" y="5.2" width="5" height="5" rx="0.6"></rect>
<path d="M11.2 6.3l2.8 2.8M14 6.3l-2.8 2.8"></path>
</symbol>
<symbol id="ico-retry" viewBox="0 0 16 16">
<path d="M12.2 6A4.8 4.8 0 0 0 4.8 4.4"></path>
<path d="M5.6 2.9L4.1 4.6 5.9 5.8"></path>
<path d="M3.8 10A4.8 4.8 0 0 0 11.2 11.6"></path>
<path d="M10.4 13.1L11.9 11.4 10.1 10.2"></path>
</symbol>
<symbol id="ico-review" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5.5"></circle>
<path d="M8 7.2v3.6"></path>
<path d="M8 5.1h.01"></path>
</symbol>
<symbol id="ico-mark-review" viewBox="0 0 16 16">
<path d="M8 2.6l5.2 2v3.7c0 2.3-1.5 4.4-5.2 5.7-3.7-1.3-5.2-3.4-5.2-5.7V4.6l5.2-2z"></path>
<path d="M8 6.1v3.2"></path>
<path d="M8 11.1h.01"></path>
</symbol>
<symbol id="ico-confirm" viewBox="0 0 16 16">
<path d="M8 2.5l5 2v3.8c0 2.2-1.3 4.1-5 5.3-3.7-1.2-5-3.1-5-5.3V4.5l5-2z"></path>
<path d="M5.7 8.3l1.6 1.7 3-3.1"></path>
</symbol>
<symbol id="ico-inspect" viewBox="0 0 16 16">
<circle cx="7" cy="7" r="3.8"></circle>
<path d="M9.8 9.8l3.2 3.2"></path>
</symbol>
<symbol id="ico-scope" viewBox="0 0 16 16">
<rect x="2.5" y="2.5" width="4.2" height="4.2"></rect>
<rect x="9.3" y="2.5" width="4.2" height="4.2"></rect>
<rect x="2.5" y="9.3" width="4.2" height="4.2"></rect>
<rect x="9.3" y="9.3" width="4.2" height="4.2"></rect>
</symbol>
<symbol id="ico-queue" viewBox="0 0 16 16">
<path d="M5.4 4h8.1M5.4 8h8.1M5.4 12h8.1"></path>
<circle cx="3.2" cy="4" r="0.7"></circle>
<circle cx="3.2" cy="8" r="0.7"></circle>
<circle cx="3.2" cy="12" r="0.7"></circle>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -1,12 +0,0 @@
# Timeline Cards Pattern
Canonical timeline UX for operational history views:
- cards grouped by day
- summary chips for aggregated actions
- source labels (`ingest`, `manual`, `system`, etc.)
- single drilldown modal (no nested modals)
- in-card search/filtering for event lists
This pattern is intended for history/audit/timeline surfaces in server-rendered Go web UIs.

View File

@@ -1,29 +0,0 @@
# Contract: Timeline Cards + Drilldown
## Card Grouping
- Render timeline as grouped cards (typically by day), not raw event rows.
- Correlated or visually equivalent events may be collapsed into one card with counts.
- Card summaries should be human-readable and avoid repeating page-scope identity labels.
## Filters
- Timeline filters are server-side when timeline is paginated or large.
- Filters should preserve grouping semantics and timezone behavior.
- Scope-invariant filters may be hidden on entity detail pages.
## Drilldown
- One card opens one drilldown modal/panel.
- Drilldown contains:
- event list (left/top)
- selected event details (right/bottom)
- No nested modal inside drilldown.
- Card-local search/filter may be applied within the drilldown.
## UX Rules
- Use human-readable source labels.
- Prefer action-oriented card titles (`Installed N components`, `Removed N components`).
- Empty states must explain whether filters removed all results.

View File

@@ -1,12 +0,0 @@
run:
go run ./cmd/demo-server
build:
go build ./cmd/demo-server
test:
go test ./...
fmt:
gofmt -w $$(find . -name '*.go' -type f)

View File

@@ -1,15 +0,0 @@
# Go net/http Web Skeleton
Minimal starter skeleton for a Go web app using:
- `net/http`
- `html/template`
- embedded templates and static assets
This scaffold is intended as a starting point that host repositories can adapt.
Vapor shell preset integration is included:
- default preset via `<html data-vapor-shell="miami-sunset">`
- runtime override via `?vapor_shell=<preset-id>`
- persisted selection via `localStorage["vapor_shell"]`

View File

@@ -1,23 +0,0 @@
package web
import (
"net/http"
)
type IndexViewData struct {
Title string
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
_ = s.render(w, "base.html", IndexViewData{Title: "Home"})
}
func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}

View File

@@ -1,37 +0,0 @@
package web
import (
"html/template"
"io/fs"
"net/http"
appweb "{{ .module_path }}/web"
)
type Server struct {
mux *http.ServeMux
tmpl *template.Template
}
func NewServer() (*Server, error) {
tmpl, err := parseTemplates()
if err != nil {
return nil, err
}
s := &Server{
mux: http.NewServeMux(),
tmpl: tmpl,
}
s.registerRoutes()
return s, nil
}
func (s *Server) Handler() http.Handler { return s.mux }
func (s *Server) registerRoutes() {
s.mux.HandleFunc("/", s.handleIndex)
s.mux.HandleFunc("/healthz", s.handleHealthz)
staticFS, _ := fs.Sub(appweb.Assets, "static")
s.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
}

View File

@@ -1,27 +0,0 @@
package web
import (
"fmt"
"html/template"
"net/http"
appweb "{{ .module_path }}/web"
)
func parseTemplates() (*template.Template, error) {
t, err := template.ParseFS(appweb.Assets, "templates/*.html")
if err != nil {
return nil, fmt.Errorf("parse templates: %w", err)
}
return t, nil
}
func (s *Server) render(w http.ResponseWriter, name string, data any) error {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, name, data); err != nil {
http.Error(w, "template error", http.StatusInternalServerError)
return fmt.Errorf("render %s: %w", name, err)
}
return nil
}

View File

@@ -1,9 +0,0 @@
package web
import "embed"
// Assets contains templates and static files.
//
//go:embed templates/* static/*
var Assets embed.FS

View File

@@ -1,46 +0,0 @@
:root {
--bg: #e6e8ee;
--ink: #1f2023;
--muted: #5d646f;
--card: linear-gradient(180deg, rgba(252,252,253,0.99), rgba(240, 244, 249, 0.97) 44%, rgba(228, 238, 248, 0.96) 78%, rgba(221, 234, 248, 0.96));
--line: #b9cbe0;
--accent: linear-gradient(180deg, #85b8ef 0%, #93c6fb 26%, #abd8ff 55%, #c7e8ff 82%, #def2ff 100%);
--accent-line: #88a4d4;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Lucida Grande", "Helvetica Neue", Helvetica, Arial, sans-serif;
background:
radial-gradient(760px 360px at 14% 6%, rgba(255,255,255,0.82), transparent 72%),
radial-gradient(760px 360px at 88% 8%, rgba(154, 203, 247, 0.16), transparent 74%),
repeating-linear-gradient(to bottom, rgba(255,255,255,0.05) 0 1px, rgba(170,182,198,0.03) 1px 3px),
linear-gradient(#eef1f6, #dde2ea);
color: var(--ink);
}
.shell {
max-width: 880px;
margin: 0 auto;
padding: 24px 16px 48px;
}
.hero { margin-bottom: 16px; }
.eyebrow {
margin: 0;
color: #5f6772;
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 12px;
}
h1 { margin: 6px 0 0; font-size: 32px; }
.card {
background:
linear-gradient(180deg, rgba(255,255,255,0.70), rgba(255,255,255,0.18) 28%, rgba(255,255,255,0.0) 29%),
repeating-linear-gradient(to bottom, rgba(255,255,255,0.26) 0 1px, rgba(188,205,223,0.10) 1px 3px),
var(--card);
border: 1px solid var(--line);
border-radius: 14px;
padding: 16px;
box-shadow: 0 10px 20px rgba(78, 107, 139, 0.09), inset 0 1px 0 rgba(255,255,255,0.96);
}
code { background: #eef3fb; padding: 1px 5px; border-radius: 6px; }

View File

@@ -1,65 +0,0 @@
(() => {
const root = document.documentElement;
const storageKey = "vapor_shell";
const queryKey = "vapor_shell";
const fallbackPreset = "miami-sunset";
const allowed = new Set([
"miami-sunset",
"neon-grid",
"laser-flamingo",
"synth-lagoon",
"mall-soft",
"hologram-sky",
"peach-drive",
"ultraviolet-plaza",
"cyber-mint",
]);
const normalizePreset = (value) => {
const trimmed = (value || "").trim().toLowerCase();
if (!trimmed || !allowed.has(trimmed)) {
return "";
}
return trimmed;
};
const setPreset = (value) => {
const preset = normalizePreset(value);
if (!preset) {
return false;
}
root.setAttribute("data-vapor-shell", preset);
return true;
};
root.classList.add("js");
const url = new URL(window.location.href);
const fromQuery = normalizePreset(url.searchParams.get(queryKey));
if (fromQuery) {
setPreset(fromQuery);
try {
window.localStorage.setItem(storageKey, fromQuery);
} catch (_err) {
// Keep behavior deterministic even when storage is blocked.
}
return;
}
const fromMarkup = normalizePreset(root.getAttribute("data-vapor-shell"));
if (fromMarkup) {
return;
}
try {
const fromStorage = normalizePreset(window.localStorage.getItem(storageKey));
if (fromStorage) {
setPreset(fromStorage);
return;
}
} catch (_err) {
// Fallback handled below.
}
setPreset(fallbackPreset);
})();

View File

@@ -1,21 +0,0 @@
{{ define "base.html" }}
<!doctype html>
<html lang="en" data-vapor-shell="miami-sunset">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .Title }}</title>
<link rel="stylesheet" href="/static/css/app.css">
</head>
<body>
<main class="shell">
<header class="hero">
<p class="eyebrow">Go Web Scaffold</p>
<h1>{{ .Title }}</h1>
</header>
{{ template "content" . }}
</main>
<script src="/static/js/app.js" defer></script>
</body>
</html>
{{ end }}

View File

@@ -1,11 +0,0 @@
{{ define "content" }}
<section class="card">
<p>Minimal net/http + html/template scaffold.</p>
<ul>
<li><code>GET /</code> page</li>
<li><code>GET /healthz</code> healthcheck</li>
<li><code>/static/*</code> embedded assets</li>
</ul>
</section>
{{ end }}

View File

@@ -1,31 +0,0 @@
# Release v0.1.0
Date: 2026-02-28
## Highlights
- Initial public release of the submodule-first UI design kit for Go web projects.
- Added bundle-driven distribution (`exports/`) and `designsync` workflow (`list`, `validate`,
`plan`, `apply`).
- Established active Vapor visual baseline and canonical architecture/source maps in `bible/`.
- Added reusable Vapor shell palette presets with tokenized header height and accent styling.
- Standardized host integration approach for Vapor preset selection:
`?vapor_shell` -> `localStorage["vapor_shell"]` -> `<html data-vapor-shell="...">`.
## Included Surface
- Reusable kit assets and patterns in `kit/`
- Bundle manifests and schema in `exports/`
- `designsync` tooling in `tools/designsync`
- Reference demo app in `demo/`
## Validation
- `./designsync validate` passed (12 bundles validated)
- `go test ./...` passed
- `cd demo && go test ./...` passed
## Upgrade Notes
- No previous tag exists; this is the baseline release.
- Host projects should pin submodule to tag `v0.1.0`.

View File

@@ -0,0 +1,55 @@
# {{ .project_name }} — Instructions for Claude
## Shared Engineering Rules
Read `bible/` — shared rules for all projects (CSV, logging, DB, tables, background tasks, code style).
Start with `bible/rules/patterns/` for specific contracts.
## Project Architecture
Read `bible-local/` — project-specific architecture.
Every architectural decision specific to this project must be recorded in `bible-local/`.
---
## Quick Reference (full contracts in `bible/rules/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.
### 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.
### 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.
### 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 (`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 (`table-management/contract.md`)
- Server-side only. Filter state in URL params. Filter resets to page 1.
- Display: "51100 из 342".
### 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.

View File

@@ -0,0 +1,142 @@
# Contract: Alpine LiveCD Build
Version: 1.0
## Purpose
Rules for building bootable Alpine Linux ISO images with custom overlays using `mkimage.sh`.
Applies to any project that needs a LiveCD: hardware audit, rescue environments, kiosks.
---
## mkimage Profile
Every project must have a profile file `mkimg.<name>.sh` defining:
```sh
profile_<name>() {
arch="x86_64" # REQUIRED — without this mkimage silently skips the profile
hostname="<hostname>"
apkovl="genapkovl-<name>.sh"
image_ext="iso"
output_format="iso"
kernel_flavors="lts"
initfs_cmdline="modules=loop,squashfs,sd-mod,usb-storage quiet"
initfs_features="ata base cdrom ext4 mmc nvme raid scsi squashfs usb virtio"
grub_mod="all_video disk part_gpt part_msdos linux normal configfile search search_label efi_gop fat iso9660 cat echo ls test true help gzio"
apks="alpine-base linux-lts linux-firmware-none ..."
}
```
**`arch` is mandatory.** If missing, mkimage silently builds nothing and exits 0.
---
## apkovl Mechanism
The apkovl is a `.tar.gz` overlay extracted by initramfs at boot, overlaying `/etc`, `/usr`, `/root`.
`genapkovl-<name>.sh` generates the tarball:
- Must be in the **CWD** when mkimage runs — not only in `~/.mkimage/`
- `~/.mkimage/` is searched for mkimg profiles only, not genapkovl scripts
```sh
# Copy both scripts to ~/.mkimage AND to CWD (typically /var/tmp)
cp "genapkovl-<name>.sh" ~/.mkimage/
cp "genapkovl-<name>.sh" /var/tmp/
cd /var/tmp
sh mkimage.sh --workdir /var/tmp/work ...
```
---
## Build Environment
**Always use `/var/tmp`, not `/tmp`:**
```sh
export TMPDIR=/var/tmp
cd /var/tmp
sh mkimage.sh ...
```
`/tmp` on Alpine builder VMs is typically a 1GB tmpfs. Kernel firmware squashfs alone exceeds this.
`/var/tmp` uses actual disk space.
---
## Workdir Caching
mkimage stores each ISO section in a hash-named subdirectory. Preserve expensive sections across builds:
```sh
# Delete everything EXCEPT cached sections
if [ -d /var/tmp/bee-iso-work ]; then
find /var/tmp/bee-iso-work -maxdepth 1 -mindepth 1 \
-not -name 'apks_*' \ # downloaded packages
-not -name 'kernel_*' \ # modloop squashfs
-not -name 'syslinux_*' \ # syslinux bootloader
-not -name 'grub_*' \ # grub EFI
-exec rm -rf {} +
fi
```
The apkovl section is always regenerated (contains project-specific config that changes per build).
---
## Squashfs Compression
Default compression is `xz` — slow but small. For RAM-loaded modloops, size rarely matters.
Use `lz4` for faster builds:
```sh
mkdir -p /etc/mkinitfs
grep -q 'MKSQUASHFS_OPTS' /etc/mkinitfs/mkinitfs.conf 2>/dev/null || \
echo 'MKSQUASHFS_OPTS="-comp lz4 -Xhc"' >> /etc/mkinitfs/mkinitfs.conf
```
Apply before running mkimage. Rebuilds modloop only when kernel version changes.
---
## Long Builds
NVIDIA driver downloads, kernel compiles, and package fetches can take 1030 minutes.
Run in a `screen` session so builds survive SSH disconnects:
```sh
apk add screen
screen -dmS build sh -c "sh build.sh > /var/log/build.log 2>&1"
tail -f /var/log/build.log
```
---
## NIC Firmware
`linux-firmware-none` (default) contains zero firmware files. Real hardware NICs often require firmware.
Include firmware packages matching expected hardware:
```
linux-firmware-intel # Intel NICs (X710, E810, etc.)
linux-firmware-mellanox # Mellanox/NVIDIA ConnectX
linux-firmware-bnx2x # Broadcom NetXtreme
linux-firmware-rtl_nic # Realtek
linux-firmware-other # catch-all
```
---
## Versioning
Pin all versions in a single `VERSIONS` file sourced by all build scripts:
```sh
ALPINE_VERSION=3.21
KERNEL_VERSION=6.12
GO_VERSION=1.23.6
NVIDIA_DRIVER_VERSION=590.48.01
```
Never hardcode versions inside build scripts.

View File

@@ -0,0 +1,157 @@
# Contract: Application Binary
Version: 1.0
## Purpose
Правила сборки, упаковки ресурсов и первого запуска Go-приложений.
---
## Расположение на хосте
> Это правило применяется когда ИИ самостоятельно разворачивает приложение или выполняет команды на build-машине (деплой, копирование файлов, запуск сервисов).
Бинарник приложения размещается в директории:
```
/appdata/<appname>/
```
где `<appname>` — имя приложения (строчными буквами, без пробелов).
Пример: приложение `myservice``/appdata/myservice/myservice`.
Все файлы, связанные с конкретным приложением (бинарник, вспомогательные скрипты запуска, `docker-compose.yml`), хранятся внутри этой директории. Конфиг и данные — по правилам секций ниже.
### Примеры внедрения
При деплое, копировании файлов или запуске сервисов ИИ **всегда по умолчанию** использует этот путь:
```bash
# Копирование бинарника
scp bin/myservice user@host:/appdata/myservice/myservice
# Копирование docker-compose
scp docker-compose.yml user@host:/appdata/myservice/docker-compose.yml
# Запуск на хосте
ssh user@host "cd /appdata/myservice && docker compose up -d"
```
```bash
# Создание директории если не существует
ssh user@host "mkdir -p /appdata/myservice"
```
Не предлагать альтернативные пути (`/opt/`, `/usr/local/bin/`, `~/`) — только `/appdata/<appname>/`.
---
## Бинарник
Бинарник самодостаточен — все ресурсы встроены через `//go:embed`:
- HTML-шаблоны
- Статика (JS, CSS, иконки)
- Шаблон конфиг-файла (`config.template.yaml`)
- Миграции БД
Никаких внешних папок рядом с бинарником не требуется для запуска.
---
## Конфиг-файл
Создаётся автоматически при первом запуске, если не существует.
### Расположение
| Режим приложения | Путь |
|---|---|
| Однопользовательское | `~/.config/<appname>/config.yaml` |
| Серверное / многопользовательское | `/etc/<appname>/config.yaml` или рядом с бинарником |
Приложение само определяет путь и создаёт директорию если её нет.
### Содержимое
Конфиг хранит:
- Настройки приложения (порт, язык, таймауты, feature flags)
- Параметры подключения к централизованной СУБД (host, port, user, password, dbname)
Конфиг **не хранит**:
- Данные пользователя
- Кеш или состояние
- Что-либо что относится к SQLite (см. ниже)
### Шаблон
Шаблон конфига встроен в бинарник. При создании файла шаблон копируется в целевой путь.
Шаблон содержит все ключи с комментариями и дефолтными значениями.
```yaml
# <appname> configuration
# Generated on first run. Edit as needed.
server:
port: 8080
database:
host: localhost
port: 5432
user: ""
password: ""
dbname: ""
# ... остальные настройки
```
---
## SQLite (однопользовательский режим)
Если приложение использует локальную SQLite:
- Файл хранится рядом с конфигом: `~/.config/<appname>/<appname>.db`
- Путь к файлу не выносится в конфиг — приложение вычисляет его из пути конфига
- SQLite **не хранит** параметры подключения к централизованной СУБД — только локальные данные приложения
---
## Первый запуск — алгоритм
```
Старт приложения
├── Конфиг существует? → Нет → создать директорию → скопировать шаблон → сообщить пользователю путь
│ → завершить с кодом 0
│ (пользователь заполняет конфиг)
└── Конфиг существует? → Да → валидировать → запустить приложение
```
При первом создании конфига приложение **не запускается** — выводит сообщение:
```
Config created: ~/.config/<appname>/config.yaml
Edit the file and restart the application.
```
---
## Сборка
Финальный бинарник собирается без CGO если это возможно (для SQLite — с CGO):
```
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/<appname> ./cmd/<appname>
```
С SQLite:
```
CGO_ENABLED=1 go build -ldflags="-s -w" -o bin/<appname> ./cmd/<appname>
```
Бинарник не зависит от рабочей директории запуска.

Some files were not shown because too many files have changed in this diff Show More