Compare commits
25 Commits
v0.1.0
...
5a69e0bba8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a69e0bba8 | ||
|
|
d2e11b8bdd | ||
|
|
f55bd84668 | ||
|
|
61ed2717d0 | ||
|
|
548eb70d55 | ||
|
|
72e10622ba | ||
|
|
0e61346d20 | ||
| a38c35ce2d | |||
| c73ece6c7c | |||
| 456c1f022c | |||
| 34b457d654 | |||
| 472c8a6918 | |||
| 91a1cc182d | |||
| af4d0f353b | |||
| 66c38f5a60 | |||
| e020c9b234 | |||
| 0c829182a1 | |||
| ed8ba09226 | |||
| b30b7ee739 | |||
| 1288d825d9 | |||
| d9204f2210 | |||
| 168e4b852a | |||
| 34d813d7ce | |||
| a37aec8790 | |||
| 40d1c303bb |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,8 +1,2 @@
|
||||
bin/
|
||||
dist/
|
||||
*.exe
|
||||
*.test
|
||||
.DS_Store
|
||||
designsync
|
||||
demo-server
|
||||
.gocache_*/
|
||||
|
||||
19
AGENTS.md
19
AGENTS.md
@@ -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.
|
||||
|
||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -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.
|
||||
21
CLAUDE.md
21
CLAUDE.md
@@ -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.
|
||||
|
||||
66
README.md
66
README.md
@@ -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/`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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` |
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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`.
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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 1–10 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 1–3 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
})();
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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] }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ./...`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -1,4 +0,0 @@
|
||||
# Status Indicators Pattern (Planned)
|
||||
|
||||
Reserved for health badges, sync status indicators, and severity labels.
|
||||
|
||||
@@ -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.
|
||||
@@ -1,3 +0,0 @@
|
||||
// Placeholder template for pagination helpers.
|
||||
// Adapt from existing host codebase conventions (net/http or Gin).
|
||||
|
||||
@@ -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 X–Y 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.
|
||||
@@ -1,2 +0,0 @@
|
||||
/* Placeholder styles for table pagination pattern. */
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<!-- Placeholder table + pager partial. -->
|
||||
|
||||
@@ -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
@@ -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.
|
||||
@@ -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 |
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
@@ -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 |
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]`
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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))))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package web
|
||||
|
||||
import "embed"
|
||||
|
||||
// Assets contains templates and static files.
|
||||
//
|
||||
//go:embed templates/* static/*
|
||||
var Assets embed.FS
|
||||
|
||||
@@ -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; }
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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`.
|
||||
55
rules/ai/claude/CLAUDE.template.md
Normal file
55
rules/ai/claude/CLAUDE.template.md
Normal 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: "51–100 из 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.
|
||||
142
rules/patterns/alpine-livecd/contract.md
Normal file
142
rules/patterns/alpine-livecd/contract.md
Normal 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 10–30 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.
|
||||
157
rules/patterns/app-binary/contract.md
Normal file
157
rules/patterns/app-binary/contract.md
Normal 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
Reference in New Issue
Block a user