feat: bootstrap design kit and vaporwave demo baseline
This commit is contained in:
14
.gitattributes
vendored
Normal file
14
.gitattributes
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Common binary assets
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.pdf binary
|
||||||
|
*.woff binary
|
||||||
|
*.woff2 binary
|
||||||
|
*.ttf binary
|
||||||
|
*.otf binary
|
||||||
|
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
bin/
|
||||||
|
dist/
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
.DS_Store
|
||||||
|
designsync
|
||||||
|
demo-server
|
||||||
|
.gocache_*/
|
||||||
15
AGENTS.md
Normal file
15
AGENTS.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# UI Design Code — Agent Instructions
|
||||||
|
|
||||||
|
Read and follow `bible/README.md` as the architecture source of truth.
|
||||||
|
|
||||||
|
This repository is a submodule-first design kit for Go web projects. Keep shared assets in
|
||||||
|
`kit/` generic and reusable; project branding belongs in host repositories.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Architecture and integration contracts live in `bible/` (English only).
|
||||||
|
- Significant decisions go to `bible/decisions/10-decisions.md`.
|
||||||
|
- Public bundle contracts live in `exports/`; keep them in sync with `kit/`.
|
||||||
|
- Prefer updating shared templates/patterns over adding host-specific logic here.
|
||||||
|
- Do not broaden the repository scope beyond the currently approved area unless the user
|
||||||
|
explicitly requests it; when in doubt, stay focused on the current scope.
|
||||||
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
17
CLAUDE.md
Normal file
17
CLAUDE.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# UI Design Code — Instructions for Claude
|
||||||
|
|
||||||
|
Read and follow the project Bible before making any changes:
|
||||||
|
|
||||||
|
**[`bible/README.md`](bible/README.md)**
|
||||||
|
|
||||||
|
The Bible is the single source of truth for architecture, public integration contracts,
|
||||||
|
bundle manifests, and reusable pattern conventions.
|
||||||
|
|
||||||
|
Every significant architectural decision must be recorded in
|
||||||
|
[`bible/decisions/10-decisions.md`](bible/decisions/10-decisions.md).
|
||||||
|
|
||||||
|
If a change affects `kit/`, `exports/`, or `tools/designsync`, update the relevant Bible
|
||||||
|
docs and `CHANGELOG.md` in the same change set.
|
||||||
|
|
||||||
|
Do not expand the repository scope beyond the currently approved work area unless the user
|
||||||
|
explicitly requests that expansion.
|
||||||
41
README.md
Normal file
41
README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# UI Design Code
|
||||||
|
|
||||||
|
Submodule-first design-code kit for Go web applications and AI coding agents.
|
||||||
|
|
||||||
|
This repository is meant to be added to host projects as a git submodule and used to
|
||||||
|
copy/sync canonical documentation, AI instruction templates, scaffolds, and UI pattern
|
||||||
|
building blocks.
|
||||||
|
|
||||||
|
## Intended Usage
|
||||||
|
|
||||||
|
1. Add as submodule (recommended path: `tools/ui-design-code`)
|
||||||
|
2. Use `tools/designsync` to list/plan/apply bundles into the host repo
|
||||||
|
3. Review changes and commit in the host repo
|
||||||
|
|
||||||
|
## Public Surface (stable)
|
||||||
|
|
||||||
|
- `kit/`
|
||||||
|
- `exports/`
|
||||||
|
- `tools/designsync/`
|
||||||
|
- `README.md`
|
||||||
|
- `VERSIONING.md`
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
|
||||||
|
`demo/` is a runnable reference app and may change faster.
|
||||||
|
|
||||||
|
## Quick Start (this repo)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./...
|
||||||
|
go build ./tools/designsync
|
||||||
|
|
||||||
|
cd demo
|
||||||
|
go test ./...
|
||||||
|
go run ./cmd/demo-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Architecture source of truth: `bible/README.md`
|
||||||
|
- AI instructions: `CLAUDE.md`, `AGENTS.md`
|
||||||
|
|
||||||
24
VERSIONING.md
Normal file
24
VERSIONING.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
56
bible/README.md
Normal file
56
bible/README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# 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 |
|
||||||
|
|
||||||
|
### 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.
|
||||||
41
bible/architecture/demo-runtime-flows.md
Normal file
41
bible/architecture/demo-runtime-flows.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 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
|
||||||
31
bible/architecture/legacy-aqua-freeze.md
Normal file
31
bible/architecture/legacy-aqua-freeze.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 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 and shared styling is **Aqua-first (macOS Aqua-inspired)**.
|
||||||
|
- This document remains as 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.
|
||||||
44
bible/architecture/submodule-integration-contract.md
Normal file
44
bible/architecture/submodule-integration-contract.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
33
bible/architecture/system-overview.md
Normal file
33
bible/architecture/system-overview.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
46
bible/architecture/ui-pattern-catalog.md
Normal file
46
bible/architecture/ui-pattern-catalog.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# UI Pattern Catalog
|
||||||
|
|
||||||
|
Canonical pattern taxonomy for reusable Go web UI patterns.
|
||||||
|
|
||||||
|
Visual baseline for the canonical demo and scaffold is Vapor Soft (light) / Vapor Night (dark),
|
||||||
|
with system color-scheme detection as the active mode selection mechanism. The hidden style
|
||||||
|
playground remains a comparative laboratory and not part of the main demo navigation. A frozen
|
||||||
|
Aqua snapshot bundle is still kept as a legacy archive for reference.
|
||||||
|
|
||||||
|
## 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 |
|
||||||
|
|
||||||
|
## 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 a two-mode baseline (`Vapor Soft` and `Vapor Night`) and should
|
||||||
|
not expose ad-hoc per-page visual toggles in the main demo shell.
|
||||||
|
- 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.
|
||||||
106
bible/decisions/10-decisions.md
Normal file
106
bible/decisions/10-decisions.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
**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.
|
||||||
17
bible/decisions/README.md
Normal file
17
bible/decisions/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 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)
|
||||||
29
bible/governance/documentation-policy.md
Normal file
29
bible/governance/documentation-policy.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 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.
|
||||||
18
bible/synthesis/common-invariants.md
Normal file
18
bible/synthesis/common-invariants.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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.
|
||||||
8
bible/synthesis/normalization-matrix.md
Normal file
8
bible/synthesis/normalization-matrix.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 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` |
|
||||||
15
bible/synthesis/source-repos.md
Normal file
15
bible/synthesis/source-repos.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# 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.
|
||||||
47
bible/synthesis/ui-pattern-coverage-matrix.md
Normal file
47
bible/synthesis/ui-pattern-coverage-matrix.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 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
|
||||||
12
demo/Makefile
Normal file
12
demo/Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
run:
|
||||||
|
go run ./cmd/demo-server
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build ./cmd/demo-server
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w $$(find . -name '*.go' -type f)
|
||||||
|
|
||||||
7
demo/README.md
Normal file
7
demo/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# 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`.
|
||||||
|
|
||||||
4
demo/go.mod
Normal file
4
demo/go.mod
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module git.mchus.pro/mchus/ui-design-code-demo
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
1189
demo/internal/web/patterns_more.go
Normal file
1189
demo/internal/web/patterns_more.go
Normal file
File diff suppressed because it is too large
Load Diff
340
demo/internal/web/server.go
Normal file
340
demo/internal/web/server.go
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
292
demo/internal/web/server_test.go
Normal file
292
demo/internal/web/server_test.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
6
demo/web/embed.go
Normal file
6
demo/web/embed.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed templates/* static/*
|
||||||
|
var Assets embed.FS
|
||||||
4547
demo/web/static/css/app.css
Normal file
4547
demo/web/static/css/app.css
Normal file
File diff suppressed because it is too large
Load Diff
2
demo/web/static/js/app.js
Normal file
2
demo/web/static/js/app.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
document.documentElement.classList.add("js");
|
||||||
|
document.documentElement.setAttribute("data-theme-mode", "auto");
|
||||||
311
demo/web/templates/base.html
Normal file
311
demo/web/templates/base.html
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
{{ define "demo_doc_start" }}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<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>
|
||||||
|
{{ 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 }}
|
||||||
114
demo/web/templates/controls_pattern.html
Normal file
114
demo/web/templates/controls_pattern.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{{ 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">
|
||||||
|
<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="bulk-bar">
|
||||||
|
<a class="btn btn-secondary" href="{{ .SelectVisibleURL }}">Select visible</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .SelectFilteredURL }}">Select filtered</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .ClearVisibleURL }}">Clear visible</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .ClearFilteredURL }}">Clear filtered</a>
|
||||||
|
<a class="btn btn-primary" href="{{ .OpenEditSelectedURL }}">Edit selected</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .OpenDeleteSelectedURL }}">Remove selected</a>
|
||||||
|
<a class="btn btn-ghost" href="{{ .ClearSelectionURL }}">Clear selection</a>
|
||||||
|
<details class="inline-menu">
|
||||||
|
<summary class="btn btn-secondary">More actions</summary>
|
||||||
|
<div class="inline-menu-list">
|
||||||
|
<a class="menu-item" href="{{ .BulkReviewURL }}">Mark for review</a>
|
||||||
|
<a class="menu-item" href="{{ .BulkExportURL }}">Export selected</a>
|
||||||
|
<a class="menu-item" href="{{ .BulkRetrySyncURL }}">Retry sync</a>
|
||||||
|
<a class="menu-item danger" href="{{ .BulkArchiveURL }}">Archive</a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table class="ui-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Select</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Rows }}
|
||||||
|
<tr>
|
||||||
|
<td><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">
|
||||||
|
<a class="text-link" href="{{ .EditURL }}">Edit</a>
|
||||||
|
<a class="text-link" href="{{ .RemoveURL }}">Remove</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 }}
|
||||||
95
demo/web/templates/forms_pattern.html
Normal file
95
demo/web/templates/forms_pattern.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
{{ 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 }}
|
||||||
99
demo/web/templates/io_pattern.html
Normal file
99
demo/web/templates/io_pattern.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{{ 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 }}
|
||||||
74
demo/web/templates/modal_pattern.html
Normal file
74
demo/web/templates/modal_pattern.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{{ 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 }}
|
||||||
114
demo/web/templates/operator_tools_pattern.html
Normal file
114
demo/web/templates/operator_tools_pattern.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{{ 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="segmented">
|
||||||
|
<a class="segment {{ if eq .Scope "assets" }}active{{ end }}" href="{{ index .ScopeURLs "assets" }}">Assets</a>
|
||||||
|
<a class="segment {{ if eq .Scope "components" }}active{{ end }}" href="{{ index .ScopeURLs "components" }}">Components</a>
|
||||||
|
<a class="segment {{ if eq .Scope "imports" }}active{{ end }}" href="{{ index .ScopeURLs "imports" }}">Imports</a>
|
||||||
|
<a class="segment {{ if eq .Scope "maintenance" }}active{{ end }}" href="{{ index .ScopeURLs "maintenance" }}">Maintenance</a>
|
||||||
|
<a class="segment {{ if eq .Scope "all" }}active{{ end }}" href="{{ index .ScopeURLs "all" }}">All</a>
|
||||||
|
</div>
|
||||||
|
<div class="segmented" style="margin-top:10px;">
|
||||||
|
<a class="segment {{ if eq .Queue "all" }}active{{ end }}" href="{{ index .QueueURLs "all" }}">All queue states</a>
|
||||||
|
<a class="segment {{ if eq .Queue "queued" }}active{{ end }}" href="{{ index .QueueURLs "queued" }}">Queued</a>
|
||||||
|
<a class="segment {{ if eq .Queue "running" }}active{{ end }}" href="{{ index .QueueURLs "running" }}">Running</a>
|
||||||
|
<a class="segment {{ if eq .Queue "failed" }}active{{ end }}" href="{{ index .QueueURLs "failed" }}">Failed</a>
|
||||||
|
<a class="segment {{ if eq .Queue "done" }}active{{ end }}" href="{{ index .QueueURLs "done" }}">Done</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-subsection panel-subsection-divider" id="operator-actions">
|
||||||
|
<div class="panel-head">
|
||||||
|
<h2>Operator Tooling Actions</h2>
|
||||||
|
<div class="meta">Batch controls must keep scope/filter context explicit</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="bulk-bar">
|
||||||
|
<a class="btn btn-secondary" href="{{ .SelectVisibleURL }}">Select visible</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .ClearVisibleURL }}">Clear visible</a>
|
||||||
|
<a class="btn btn-ghost" href="{{ .ClearSelectionURL }}">Clear selection</a>
|
||||||
|
<a class="btn btn-primary" href="{{ .RunSelectedURL }}">Run selected</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .RetrySelectedURL }}">Retry selected</a>
|
||||||
|
<a class="btn btn-danger" href="{{ .CancelSelectedURL }}">Cancel selected</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .OpenReviewModalURL }}">Open confirm modal</a>
|
||||||
|
</div>
|
||||||
|
<div class="button-demo-row" style="margin-top:12px;">
|
||||||
|
<a class="btn btn-secondary" href="{{ .ImportPreviewURL }}">Import batch preview</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .ExportFilteredURL }}">Export filtered</a>
|
||||||
|
<a class="btn btn-secondary" href="{{ .ExportSelectedURL }}">Export selected</a>
|
||||||
|
</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>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table class="ui-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>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>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Rows }}
|
||||||
|
<tr>
|
||||||
|
<td><a class="check-toggle {{ if .Selected }}checked{{ end }}" href="{{ .ToggleURL }}" aria-label="Toggle {{ .ID }}">{{ if .Selected }}☑{{ else }}☐{{ end }}</a></td>
|
||||||
|
<td><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">
|
||||||
|
<a class="text-link" href="{{ .RetryURL }}">Retry</a>
|
||||||
|
<a class="text-link" href="{{ .CancelURL }}">Cancel</a>
|
||||||
|
<a class="text-link" href="{{ .ExportURL }}">Export</a>
|
||||||
|
<a class="text-link" href="{{ .InspectURL }}">Inspect</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 }}
|
||||||
177
demo/web/templates/style_playground_pattern.html
Normal file
177
demo/web/templates/style_playground_pattern.html
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
{{ 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 }}
|
||||||
118
demo/web/templates/timeline_pattern.html
Normal file
118
demo/web/templates/timeline_pattern.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
{{ 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 }}
|
||||||
14
exports/bundles/ai-rules.yaml
Normal file
14
exports/bundles/ai-rules.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
9
exports/bundles/bible-core.yaml
Normal file
9
exports/bundles/bible-core.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
18
exports/bundles/go-web-skeleton.yaml
Normal file
18
exports/bundles/go-web-skeleton.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
10
exports/bundles/ui-pattern-controls.yaml
Normal file
10
exports/bundles/ui-pattern-controls.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
10
exports/bundles/ui-pattern-forms.yaml
Normal file
10
exports/bundles/ui-pattern-forms.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
10
exports/bundles/ui-pattern-io.yaml
Normal file
10
exports/bundles/ui-pattern-io.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
9
exports/bundles/ui-pattern-modal.yaml
Normal file
9
exports/bundles/ui-pattern-modal.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
9
exports/bundles/ui-pattern-operator-tools.yaml
Normal file
9
exports/bundles/ui-pattern-operator-tools.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
9
exports/bundles/ui-pattern-table.yaml
Normal file
9
exports/bundles/ui-pattern-table.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
10
exports/bundles/ui-pattern-timeline.yaml
Normal file
10
exports/bundles/ui-pattern-timeline.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
9
exports/bundles/ui-theme-aqua-legacy.yaml
Normal file
9
exports/bundles/ui-theme-aqua-legacy.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"id": "ui-theme-aqua-legacy",
|
||||||
|
"version": 1,
|
||||||
|
"description": "Legacy snapshot bundle of the Aqua-style visual exploration (reference only, not active baseline).",
|
||||||
|
"conflict_policy": "merge-manual",
|
||||||
|
"entries": [
|
||||||
|
{ "from": "patterns/theme-aqua-legacy", "to": "docs/ui-themes/aqua-legacy", "mode": "dir" }
|
||||||
|
]
|
||||||
|
}
|
||||||
15
exports/index.yaml
Normal file
15
exports/index.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"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-aqua-legacy", "manifest": "exports/bundles/ui-theme-aqua-legacy.yaml" }
|
||||||
|
]
|
||||||
|
}
|
||||||
39
exports/schema/bundle-manifest.schema.yaml
Normal file
39
exports/schema/bundle-manifest.schema.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
$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] }
|
||||||
|
|
||||||
11
kit/ai/claude/CLAUDE.template.md
Normal file
11
kit/ai/claude/CLAUDE.template.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# {{ .project_name }} — Instructions for Claude
|
||||||
|
|
||||||
|
Read and follow the project Bible before making any changes:
|
||||||
|
|
||||||
|
**[`bible/README.md`](bible/README.md)**
|
||||||
|
|
||||||
|
The Bible is the single source of truth for architecture, data models, API contracts, and UI
|
||||||
|
pattern conventions.
|
||||||
|
|
||||||
|
Every significant architectural decision must be recorded in the appropriate Bible decision log.
|
||||||
|
|
||||||
10
kit/ai/codex/AGENTS.template.md
Normal file
10
kit/ai/codex/AGENTS.template.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# {{ .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.
|
||||||
|
|
||||||
11
kit/ai/shared/ARCH_DOC_POLICY.md
Normal file
11
kit/ai/shared/ARCH_DOC_POLICY.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
20
kit/docs/bible-skeleton/README.md
Normal file
20
kit/docs/bible-skeleton/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
10
kit/docs/bible-skeleton/architecture/api-surface.md
Normal file
10
kit/docs/bible-skeleton/architecture/api-surface.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
11
kit/docs/bible-skeleton/architecture/runtime-flows.md
Normal file
11
kit/docs/bible-skeleton/architecture/runtime-flows.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Runtime Flows
|
||||||
|
|
||||||
|
Document the critical runtime flows and failure invariants.
|
||||||
|
|
||||||
|
Recommended sections:
|
||||||
|
|
||||||
|
- Startup flow
|
||||||
|
- Request handling flow
|
||||||
|
- Background tasks
|
||||||
|
- Failure / retry / cancellation behavior
|
||||||
|
|
||||||
32
kit/docs/bible-skeleton/architecture/system-overview.md
Normal file
32
kit/docs/bible-skeleton/architecture/system-overview.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 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 ./...`
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
18
kit/docs/bible-skeleton/decisions/README.md
Normal file
18
kit/docs/bible-skeleton/decisions/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
21
kit/docs/bible-skeleton/governance/documentation-policy.md
Normal file
21
kit/docs/bible-skeleton/governance/documentation-policy.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
7
kit/examples/integration/gin-admin-app/README.md
Normal file
7
kit/examples/integration/gin-admin-app/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# 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
|
||||||
9
kit/examples/integration/greenfield-app/README.md
Normal file
9
kit/examples/integration/greenfield-app/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Greenfield App Integration Notes (Example)
|
||||||
|
|
||||||
|
Suggested use:
|
||||||
|
|
||||||
|
- apply `ai-rules`
|
||||||
|
- apply `bible-core` (or merge selectively if a Bible already exists)
|
||||||
|
- copy selected UI patterns into new modules/pages
|
||||||
|
|
||||||
|
Keep domain-specific business rules in the host project's own Bible.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# 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
|
||||||
13
kit/patterns/controls-selection/README.md
Normal file
13
kit/patterns/controls-selection/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Controls + Selection Pattern
|
||||||
|
|
||||||
|
Canonical interactive controls for server-rendered admin/list pages:
|
||||||
|
|
||||||
|
- button hierarchy (primary / secondary / danger / quiet / disabled)
|
||||||
|
- segmented filters
|
||||||
|
- row checkboxes + select-all-visible semantics
|
||||||
|
- bulk-action bar with explicit preview/confirm steps
|
||||||
|
- status badges for list rows and workflow state
|
||||||
|
|
||||||
|
This pattern standardizes control language and interaction shape while leaving branding and
|
||||||
|
domain terminology to the host project.
|
||||||
|
|
||||||
39
kit/patterns/controls-selection/contract.md
Normal file
39
kit/patterns/controls-selection/contract.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Contract: Controls + Selection
|
||||||
|
|
||||||
|
## Buttons
|
||||||
|
|
||||||
|
- Use a small stable button taxonomy: `primary`, `secondary`, `ghost`, `danger`, `disabled`.
|
||||||
|
- The canonical visual baseline for demo/scaffold examples is the active dual baseline used in the
|
||||||
|
demo/scaffold (`Vapor Soft` / `Vapor Night`, system-selected). A frozen Aqua snapshot bundle may
|
||||||
|
be kept for archival reference, but examples should follow the active baseline.
|
||||||
|
- Destructive actions (`archive`, `delete`, `remove`) must use danger styling and explicit labels.
|
||||||
|
- Button text should describe the action outcome, not implementation detail.
|
||||||
|
- Buttons are text-first; icons are optional and must not replace labels on primary/danger actions.
|
||||||
|
- Base examples must show both `disabled` and `loading` states.
|
||||||
|
|
||||||
|
## Checkbox Selection
|
||||||
|
|
||||||
|
- Row checkboxes support bulk actions from a shared action bar.
|
||||||
|
- Header checkbox semantics must be explicit:
|
||||||
|
- select visible rows only, or
|
||||||
|
- select all rows in query scope (must be clearly labeled)
|
||||||
|
- Selection summary must show count (`N selected`) near bulk actions.
|
||||||
|
- In paginated views, the UI should distinguish selection on the current page from selection across the filtered/query scope.
|
||||||
|
- Selection state should survive pagination/filter navigation via explicit state (query params, server session, or another deterministic mechanism).
|
||||||
|
- Same-page interactions should preserve reading position (module anchor pattern is preferred in canonical server-rendered flows).
|
||||||
|
|
||||||
|
## Segmented Filters
|
||||||
|
|
||||||
|
- Segment/toggle filters are allowed for small enumerations (status, active/archived).
|
||||||
|
- Segmented controls are grouped joined-buttons:
|
||||||
|
- one shared outer shell,
|
||||||
|
- rounded corners only on the first/last segment,
|
||||||
|
- straight internal separators between middle segments.
|
||||||
|
- Active segment color may vary by context (for example blue preset selector vs dark status scope tabs), but geometry must remain consistent.
|
||||||
|
- Segment changes should preserve other active filters where possible.
|
||||||
|
|
||||||
|
## Bulk Action Safety
|
||||||
|
|
||||||
|
- Bulk action click should preview count and target scope before execution.
|
||||||
|
- Destructive bulk actions require confirmation.
|
||||||
|
- Dense bulk action bars may move rare actions into an explicit overflow menu (`More actions`) while keeping primary actions visible.
|
||||||
12
kit/patterns/forms-validation/README.md
Normal file
12
kit/patterns/forms-validation/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Forms + Validation Pattern
|
||||||
|
|
||||||
|
Canonical patterns for server-rendered form workflows in Go web applications:
|
||||||
|
|
||||||
|
- tabbed / mode-switched forms
|
||||||
|
- datalist/autocomplete suggestions
|
||||||
|
- inline validation messages
|
||||||
|
- review / confirm step before submit
|
||||||
|
- explicit reset and error handling states
|
||||||
|
|
||||||
|
This pattern standardizes interaction flow and validation UX, not domain-specific field sets.
|
||||||
|
|
||||||
34
kit/patterns/forms-validation/contract.md
Normal file
34
kit/patterns/forms-validation/contract.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Contract: Forms + Validation + Suggestions
|
||||||
|
|
||||||
|
## Form Structure
|
||||||
|
|
||||||
|
- Group fields semantically and keep labels explicit.
|
||||||
|
- Inputs requiring suggestions may use `datalist` or equivalent autocomplete UI.
|
||||||
|
- Suggestion sources must represent the full relevant scope (not only visible rows from paginated tables).
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Surface validation errors inline near fields and in a form-level summary when helpful.
|
||||||
|
- Validation messages must be human-readable and action-oriented.
|
||||||
|
- Do not hide required-field errors behind generic submit failures.
|
||||||
|
|
||||||
|
## Multi-Step Flow
|
||||||
|
|
||||||
|
Recommended stages:
|
||||||
|
|
||||||
|
1. `edit`
|
||||||
|
2. `review`
|
||||||
|
3. `confirm/submit`
|
||||||
|
4. `result`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Users must be able to return from review to edit without losing entered values.
|
||||||
|
- Destructive or irreversible actions require explicit confirmation.
|
||||||
|
- Query- or state-driven step transitions should be deterministic and testable.
|
||||||
|
|
||||||
|
## File Inputs in Forms
|
||||||
|
|
||||||
|
- If a workflow includes upload, the file control should be clearly labeled and the supported formats explicit.
|
||||||
|
- Import parsing/preview may be delegated to a dedicated import/export pattern, but the form contract must remain clear.
|
||||||
|
|
||||||
13
kit/patterns/import-export/README.md
Normal file
13
kit/patterns/import-export/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Import / Export Pattern
|
||||||
|
|
||||||
|
Canonical file transfer UX patterns for Go web applications:
|
||||||
|
|
||||||
|
- file import forms (CSV/JSON and similar)
|
||||||
|
- validation preview tables before confirm
|
||||||
|
- confirm step with human-readable summary
|
||||||
|
- export controls (format + scope + options)
|
||||||
|
- predictable file download behavior and filenames
|
||||||
|
|
||||||
|
This pattern covers UI and UX contracts. Business-specific validation and file schemas remain in
|
||||||
|
the host project's own architecture docs.
|
||||||
|
|
||||||
32
kit/patterns/import-export/contract.md
Normal file
32
kit/patterns/import-export/contract.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Contract: Import / Export Workflows
|
||||||
|
|
||||||
|
## Import Workflow
|
||||||
|
|
||||||
|
Recommended stages:
|
||||||
|
|
||||||
|
1. `Upload`
|
||||||
|
2. `Preview / Validate`
|
||||||
|
3. `Confirm`
|
||||||
|
4. `Execute`
|
||||||
|
5. `Result summary`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Validation preview must be human-readable (table/list), not raw JSON only.
|
||||||
|
- Warnings and errors should be shown per row and in aggregate summary.
|
||||||
|
- Confirm step should clearly communicate scope and side effects.
|
||||||
|
|
||||||
|
## Export Workflow
|
||||||
|
|
||||||
|
- User must explicitly choose export scope (`selected`, `filtered`, `all`) when ambiguity exists.
|
||||||
|
- Export format should be explicit (`csv`, `json`, etc.).
|
||||||
|
- Download response should set:
|
||||||
|
- `Content-Type`
|
||||||
|
- `Content-Disposition`
|
||||||
|
- If CSV targets spreadsheet users, document delimiter and BOM policy.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- Import errors should map to clear user-facing messages.
|
||||||
|
- Export errors after streaming starts must degrade gracefully (human-readable fallback).
|
||||||
|
|
||||||
9
kit/patterns/modal-workflows/README.md
Normal file
9
kit/patterns/modal-workflows/README.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Modal Workflow Pattern
|
||||||
|
|
||||||
|
This pattern package captures modal-based create/edit/remove workflows for server-rendered Go
|
||||||
|
web UIs.
|
||||||
|
|
||||||
|
Synthesis sources:
|
||||||
|
|
||||||
|
- detailed enterprise UI interaction contracts
|
||||||
|
- operational admin workflows in Go web apps
|
||||||
9
kit/patterns/modal-workflows/contract.md
Normal file
9
kit/patterns/modal-workflows/contract.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Contract: Modal Workflows
|
||||||
|
|
||||||
|
## Shared Rules
|
||||||
|
|
||||||
|
- Destructive actions require explicit confirmation.
|
||||||
|
- Validation and backend errors are rendered in human-readable form.
|
||||||
|
- UI state transitions are explicit (`open` / `submit` / `success` / `error` / `cancel`).
|
||||||
|
- API contracts and UI copy should be documented in the host project's Bible.
|
||||||
|
|
||||||
13
kit/patterns/operator-tools/README.md
Normal file
13
kit/patterns/operator-tools/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 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.
|
||||||
36
kit/patterns/operator-tools/contract.md
Normal file
36
kit/patterns/operator-tools/contract.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Contract: Operator Tools Dashboard
|
||||||
|
|
||||||
|
## 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.
|
||||||
4
kit/patterns/status-indicators/README.md
Normal file
4
kit/patterns/status-indicators/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Status Indicators Pattern (Planned)
|
||||||
|
|
||||||
|
Reserved for health badges, sync status indicators, and severity labels.
|
||||||
|
|
||||||
7
kit/patterns/table-pagination/README.md
Normal file
7
kit/patterns/table-pagination/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# 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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Placeholder template for pagination helpers.
|
||||||
|
// Adapt from existing host codebase conventions (net/http or Gin).
|
||||||
|
|
||||||
27
kit/patterns/table-pagination/contract.md
Normal file
27
kit/patterns/table-pagination/contract.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 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.
|
||||||
2
kit/patterns/table-pagination/static/table.css
Normal file
2
kit/patterns/table-pagination/static/table.css
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/* Placeholder styles for table pagination pattern. */
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<!-- Placeholder table + pager partial. -->
|
||||||
|
|
||||||
14
kit/patterns/theme-aqua-legacy/README.md
Normal file
14
kit/patterns/theme-aqua-legacy/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# macOS Aqua Theme Snapshot (Legacy)
|
||||||
|
|
||||||
|
Status: legacy reference only (not active baseline)
|
||||||
|
|
||||||
|
This directory preserves the Aqua-style visual exploration snapshot that was developed during demo
|
||||||
|
refinement. It is stored as a reusable archive bundle so it can be referenced later without
|
||||||
|
driving the active baseline.
|
||||||
|
|
||||||
|
Active visual baseline for the demo/scaffold is Aqua-first. This package is a frozen snapshot for archival/reference use.
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
- `demo-aqua-freeze.css` — frozen snapshot of the demo stylesheet at the time of the freeze
|
||||||
|
- `notes.md` — summary of what was explored and how to treat the snapshot
|
||||||
2944
kit/patterns/theme-aqua-legacy/demo-aqua-freeze.css
Normal file
2944
kit/patterns/theme-aqua-legacy/demo-aqua-freeze.css
Normal file
File diff suppressed because it is too large
Load Diff
22
kit/patterns/theme-aqua-legacy/notes.md
Normal file
22
kit/patterns/theme-aqua-legacy/notes.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Aqua Legacy Snapshot Notes
|
||||||
|
|
||||||
|
This snapshot is kept for historical reference and selective reuse.
|
||||||
|
|
||||||
|
## What it captures
|
||||||
|
|
||||||
|
- Aqua-style control surfaces and button hierarchy
|
||||||
|
- segmented controls and tab-like grouped buttons
|
||||||
|
- modal window chrome experiments (titlebar + close button)
|
||||||
|
- table/filter visual refinements and dot-pager styling
|
||||||
|
- style-playground Aqua preset refinements
|
||||||
|
|
||||||
|
## What it is not
|
||||||
|
|
||||||
|
- Not a required dependency for host projects
|
||||||
|
- Not the live source of truth for the current Aqua baseline (the live baseline is in demo/scaffold CSS)
|
||||||
|
- Not the target for normal iterative styling work unless explicitly requested
|
||||||
|
|
||||||
|
## Current policy
|
||||||
|
|
||||||
|
- Active baseline styling remains Aqua-first in the live demo/scaffold assets.
|
||||||
|
- Use this snapshot only when explicitly needed for archival comparison or extraction.
|
||||||
12
kit/patterns/timeline-cards/README.md
Normal file
12
kit/patterns/timeline-cards/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
29
kit/patterns/timeline-cards/contract.md
Normal file
29
kit/patterns/timeline-cards/contract.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
12
kit/scaffolds/go-nethttp-web/Makefile
Normal file
12
kit/scaffolds/go-nethttp-web/Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
run:
|
||||||
|
go run ./cmd/demo-server
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build ./cmd/demo-server
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w $$(find . -name '*.go' -type f)
|
||||||
|
|
||||||
10
kit/scaffolds/go-nethttp-web/README.md
Normal file
10
kit/scaffolds/go-nethttp-web/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
23
kit/scaffolds/go-nethttp-web/internal/web/handlers.go.tmpl
Normal file
23
kit/scaffolds/go-nethttp-web/internal/web/handlers.go.tmpl
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
37
kit/scaffolds/go-nethttp-web/internal/web/server.go.tmpl
Normal file
37
kit/scaffolds/go-nethttp-web/internal/web/server.go.tmpl
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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))))
|
||||||
|
}
|
||||||
|
|
||||||
27
kit/scaffolds/go-nethttp-web/internal/web/templates.go.tmpl
Normal file
27
kit/scaffolds/go-nethttp-web/internal/web/templates.go.tmpl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
9
kit/scaffolds/go-nethttp-web/web/embed.go
Normal file
9
kit/scaffolds/go-nethttp-web/web/embed.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// Assets contains templates and static files.
|
||||||
|
//
|
||||||
|
//go:embed templates/* static/*
|
||||||
|
var Assets embed.FS
|
||||||
|
|
||||||
46
kit/scaffolds/go-nethttp-web/web/static/css/app.css
Normal file
46
kit/scaffolds/go-nethttp-web/web/static/css/app.css
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
: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; }
|
||||||
2
kit/scaffolds/go-nethttp-web/web/static/js/app.js
Normal file
2
kit/scaffolds/go-nethttp-web/web/static/js/app.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
document.documentElement.classList.add("js");
|
||||||
|
|
||||||
22
kit/scaffolds/go-nethttp-web/web/templates/base.html
Normal file
22
kit/scaffolds/go-nethttp-web/web/templates/base.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{{ define "base.html" }}
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<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 }}
|
||||||
|
|
||||||
11
kit/scaffolds/go-nethttp-web/web/templates/index.html
Normal file
11
kit/scaffolds/go-nethttp-web/web/templates/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{{ 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 }}
|
||||||
|
|
||||||
Reference in New Issue
Block a user