Remove legacy infrastructure, keep only shared rule contracts
Deleted: demo app, designsync/exports bundle system, bible meta-docs, theme CSS, scaffolds, examples, empty pattern placeholders, go.mod, CHANGELOG, VERSIONING Kept: kit/patterns/*/contract.md (all engineering rules), kit/ai/claude/CLAUDE.template.md, CLAUDE.md, AGENTS.md, README.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
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
|
Rules live in `kit/patterns/` as `contract.md` files. When adding or updating a rule:
|
||||||
`kit/` generic and reusable; project branding belongs in host repositories.
|
- Find the relevant existing contract and edit it, or create a new `kit/patterns/<topic>/contract.md`.
|
||||||
|
- Do not create rules outside `kit/patterns/`.
|
||||||
## Rules
|
- Do not expand scope beyond engineering rules and patterns.
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|||||||
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)**
|
Rules live in `kit/patterns/` as `contract.md` files. When adding or updating a rule:
|
||||||
|
- Find the relevant existing contract and edit it, or create a new `kit/patterns/<topic>/contract.md`.
|
||||||
The Bible is the single source of truth for architecture, public integration contracts,
|
- Do not create rules outside `kit/patterns/`.
|
||||||
bundle manifests, and reusable pattern conventions.
|
- Do not expand scope beyond engineering rules and patterns.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|||||||
@@ -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,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,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`.
|
|
||||||
Reference in New Issue
Block a user