Compare commits
1 Commits
dev
...
200-filame
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd17e5a5f4 |
@ -3,9 +3,6 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
dist/
|
||||
build/
|
||||
vendor/
|
||||
|
||||
8
.github/agents/copilot-instructions.md
vendored
8
.github/agents/copilot-instructions.md
vendored
@ -206,10 +206,6 @@ ## Active Technologies
|
||||
- PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `WorkspaceContext`, `OperateHubShell`, `EnsureFilamentTenantSelected`, `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, `TenantPageCategory`, and `ResolvesPanelTenantContext` (199-global-context-shell-contract)
|
||||
- PostgreSQL unchanged plus existing Laravel session keys `current_workspace_id`, `workspace_intended_url`, and `workspace_last_tenant_ids`; no schema change planned (199-global-context-shell-contract)
|
||||
- Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001` (200-filament-surface-rules)
|
||||
- Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`) + Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests (213-website-foundation-v0)
|
||||
- Static filesystem content, styles, and assets under `apps/website/src` and `apps/website/public`; no database (213-website-foundation-v0)
|
||||
- Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200 (201-enforcement-review-guardrails)
|
||||
- Repository-owned markdown and contract artifacts under `.specify/` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`; no product database persistence (201-enforcement-review-guardrails)
|
||||
|
||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||
|
||||
@ -244,8 +240,8 @@ ## Code Style
|
||||
PHP 8.4.15: Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 201-enforcement-review-guardrails: Added Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200
|
||||
- 213-website-foundation-v0: Added Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`) + Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests
|
||||
- 200-filament-surface-rules: Added Markdown governance artifacts in a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository + `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, adjacent Specs 196 through 199, existing UI rule IDs `UI-SURF-001`, `ACTSURF-001`, `UI-HARD-001`, `UI-EX-001`, `UI-FIL-001`, `DECIDE-001`, and `UX-001`
|
||||
- 199-global-context-shell-contract: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `WorkspaceContext`, `OperateHubShell`, `EnsureFilamentTenantSelected`, `WorkspaceRedirectResolver`, `WorkspaceIntendedUrl`, `TenantPageCategory`, and `ResolvesPanelTenantContext`
|
||||
- 212-test-authoring-guardrails: Added Markdown for repository governance artifacts, JSON Schema plus logical OpenAPI for planning contracts, and Bash-backed SpecKit scripts already present in the repo + `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `README.md`, and the existing Specs 206 through 211 governance vocabulary
|
||||
<!-- MANUAL ADDITIONS START -->
|
||||
<!-- MANUAL ADDITIONS END -->
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,9 +19,6 @@
|
||||
/apps/website/node_modules
|
||||
/.pnpm-store
|
||||
/apps/website/.astro
|
||||
/apps/website/playwright-report
|
||||
/apps/website/test-results
|
||||
/apps/website/blob-report
|
||||
dist/
|
||||
build/
|
||||
coverage/
|
||||
|
||||
@ -7,9 +7,6 @@ apps/platform/node_modules/
|
||||
apps/website/node_modules/
|
||||
apps/website/.astro/
|
||||
apps/website/dist/
|
||||
apps/website/playwright-report/
|
||||
apps/website/test-results/
|
||||
apps/website/blob-report/
|
||||
vendor/
|
||||
apps/platform/vendor/
|
||||
*.log
|
||||
|
||||
@ -11,46 +11,25 @@ ## Important
|
||||
- `tasks.md`
|
||||
- `checklists/requirements.md`
|
||||
- Runtime-changing or test-affecting work MUST carry actual test-purpose classification (`Unit`, `Feature`, `Heavy-Governance`, `Browser`), affected lanes, fixture/default cost risks, heavy-family changes, escalation decisions, and minimal validation commands through the active `spec.md`, `plan.md`, and `tasks.md`.
|
||||
- Review-oriented checklists MUST surface nativity, shared-family boundaries, state-layer ownership, exception spread, proof depth, and close-out targeting before merge.
|
||||
- `.specify/` is the operational workflow. `docs/ui/operator-ux-surface-standards.md` remains a rule/reference document, not a second checklist or review process.
|
||||
|
||||
## Author Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` in that order.
|
||||
|
||||
1. Fill the spec's UI / Surface Guardrail Impact section once. If there is no operator-facing surface change, write a concise `N/A` and do not invent extra prose downstream.
|
||||
2. Turn that classification into plan-level handling modes, repository-signal treatment, required proof depth, and the named active feature PR close-out entry.
|
||||
3. Carry the same terms into tasks so implementation, review, definition-of-done, exception documentation, and smoke coverage all point at the same guardrail decision.
|
||||
- Review-oriented checklists MUST surface lane fit, hidden defaults, heavy-family visibility, and runtime-budget follow-up before merge; lane upkeep belongs to the feature, not to a later cleanup pass.
|
||||
|
||||
## Review Entry Point
|
||||
|
||||
Use the active feature's `spec.md`, `plan.md`, and `tasks.md` together with the generated checklist based on `.specify/templates/checklist-template.md`.
|
||||
|
||||
1. Confirm the spec names the guardrail impact once: native/custom classification, shared-family relevance, state-layer ownership, exception need, and any low-impact `N/A` path.
|
||||
2. Confirm the plan turns that into handling modes, repository-signal treatment, required test or smoke depth, and the named active feature PR close-out entry.
|
||||
3. Apply the checklist and end with both one review outcome class (`blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`) and one workflow outcome (`keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`).
|
||||
4. If a guarded surface or exception remains in scope, ensure the active feature PR close-out entry records the final note rather than leaving the decision in scattered review comments.
|
||||
1. Confirm the spec names the affected validation lane(s) or a deliberate `N/A`, the test family impact, setup-cost impact, reviewer handoff, and any escalation outcome.
|
||||
2. Confirm the plan turns that into changed test types, narrowest proving commands, helper/default widening checks, and the note target for budget or trend drift.
|
||||
3. Apply the checklist and end with one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
|
||||
## Low-Impact Rule
|
||||
|
||||
- Docs-only or template-only work may answer `N/A` or `none`.
|
||||
- Do not force fake surface, lane, or exception prose when no operator-facing change or runtime impact exists.
|
||||
- Use the low-impact path to stay fast, not to hide a real guarded surface change.
|
||||
- Do not force fake lane prose when no runtime or suite impact exists.
|
||||
|
||||
## Escalation Rule
|
||||
|
||||
- Use `blocker` when fake-native drift, hidden host drift, unresolved state ownership, or missing exception boundaries remain.
|
||||
- Use `strong-warning` when the change can proceed only after the active workflow records the guardrail risk explicitly.
|
||||
- Use `documentation-required-exception` when default rules are intentionally relaxed and the bounded exception record is the remaining requirement.
|
||||
- Use `acceptable-special-case` only when the change already fits the declared guardrail contract.
|
||||
- Use `document-in-feature` for contained cost or drift that belongs in the active feature.
|
||||
- Use `follow-up-spec` only for recurring pain or structural lane or family changes.
|
||||
- Use `reject-or-split` when hidden test cost or wrong-lane scope is still unresolved.
|
||||
|
||||
## Close-Out Rule
|
||||
|
||||
- The named close-out target for guarded work is the active feature PR close-out entry `Guardrail / Exception / Smoke Coverage`.
|
||||
- That entry records the low-impact or representative scenario used, the outcome class, handling mode, workflow outcome, required tests or manual smoke, any exception boundary, duplicate-prompt notes, and any deferred automation.
|
||||
- If the change is genuinely low-impact, record a concise `N/A` note rather than fabricating guardrail spread.
|
||||
|
||||
The files `.specify/spec.md`, `.specify/plan.md`, `.specify/tasks.md` may exist as legacy references only.
|
||||
|
||||
@ -5,53 +5,39 @@ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
|
||||
**Feature**: [Link to spec.md or relevant documentation]
|
||||
|
||||
**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.
|
||||
If the checklist covers UI or surface work, use it to reach both one review
|
||||
outcome class (`blocker`, `strong-warning`,
|
||||
`documentation-required-exception`, or `acceptable-special-case`) and one
|
||||
workflow outcome (`keep`, `split`, `document-in-feature`,
|
||||
`follow-up-spec`, or `reject-or-split`). Low-impact docs-only or
|
||||
template-only work may mark runtime-only checks `N/A`, but should still
|
||||
leave one explicit workflow outcome and one note explaining why no
|
||||
guardrail spread exists.
|
||||
If the checklist covers runtime behavior or test-surface changes, use it to reach one explicit outcome: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
Low-impact docs-only or template-only work may mark runtime-only checks `N/A`, but should still leave one explicit outcome.
|
||||
|
||||
## Applicability And Low-Impact Gate
|
||||
## Lane Fit
|
||||
|
||||
- [ ] CHK001 The change explicitly says whether an operator-facing surface or guardrail workflow surface is affected; low-impact `N/A` handling is used once and not contradicted elsewhere.
|
||||
- [ ] CHK002 The spec, plan, and task artifacts carry forward the same native/custom classification, shared-family relevance, state-layer ownership, and exception need without inventing second wording.
|
||||
- [ ] CHK001 The chosen validation lane is the narrowest lane or lane mix that proves the change.
|
||||
- [ ] CHK002 The test stays in the smallest honest family (`Unit`, `Feature`, `Heavy-Governance`, `Browser`) and does not hide broader purpose behind a narrow label.
|
||||
|
||||
## Native, Shared-Family, And State Ownership
|
||||
## Breadth And Cost
|
||||
|
||||
- [ ] CHK003 The surface remains native/shared-primitives first; fake-native controls, GET-form page-body interactions, and simple-overview replacements are not treated as harmless customization.
|
||||
- [ ] CHK004 Any shared-detail or shared-family surface keeps one shared contract, and any host variation is either folded back into that contract or explicitly bounded as an exception.
|
||||
- [ ] CHK005 Shell, page, detail, and URL/query state owners are named once and do not collapse into one another.
|
||||
- [ ] CHK006 The likely next operator action and the primary inspect/open model stay coherent with the declared surface class.
|
||||
- [ ] CHK003 The changed or added test is no broader than the behavior it proves.
|
||||
- [ ] CHK004 Any database, Livewire, Filament, or browser surface is justified over a narrower alternative.
|
||||
- [ ] CHK005 Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is explicit and locally justified.
|
||||
|
||||
## Signals, Exceptions, And Test Depth
|
||||
## Validation And Drift
|
||||
|
||||
- [ ] CHK007 Any triggered repository signal is classified with one handling mode: `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`.
|
||||
- [ ] CHK008 Any deviation from default rules includes a bounded exception record naming the broken rule, product reason, standardized parts, spread-control rule, and the active feature PR close-out entry.
|
||||
- [ ] CHK009 The required surface test profile is explicit: `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, `exception-coded-surface`, or `standard-native-filament`.
|
||||
- [ ] CHK010 The chosen test family/lane and any manual smoke are the narrowest honest proof for the declared surface class, and `standard-native-filament` relief is used when no special contract exists.
|
||||
- [ ] CHK006 The minimal reviewer validation command is written explicitly and matches the declared lane.
|
||||
- [ ] CHK007 Any material budget, baseline, trend, or runtime-drift note is recorded in the active spec or PR.
|
||||
|
||||
## Review Outcome
|
||||
## Escalation Outcome
|
||||
|
||||
- [ ] CHK011 One review outcome class is chosen: `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`.
|
||||
- [ ] CHK012 One workflow outcome is chosen: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- [ ] CHK013 The final note location is explicit: the active feature PR close-out entry for guarded work, or a concise `N/A` note for low-impact changes.
|
||||
- [ ] CHK008 One explicit outcome is chosen: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- [ ] CHK009 New heavy families, new browser coverage, revived expensive defaults, or material lane-cost shifts are not left implicit.
|
||||
|
||||
## Notes
|
||||
|
||||
- `blocker`: the change conflicts with the declared surface contract or guardrail and cannot proceed as proposed.
|
||||
- `strong-warning`: the change may proceed only after the active workflow records the remaining guardrail risk explicitly.
|
||||
- `documentation-required-exception`: the change is valid only once a bounded exception and close-out note exist.
|
||||
- `acceptable-special-case`: the change is legitimate without extra escalation beyond ordinary documentation.
|
||||
- `keep`: the current scope, guardrail handling, and proof depth are justified.
|
||||
- `split`: the intent is valid, but the scope should narrow before merge.
|
||||
- `document-in-feature`: the change is acceptable, but the active feature must record the exception, signal handling, or proof notes explicitly.
|
||||
- `follow-up-spec`: the issue is recurring or structural and needs dedicated governance follow-up.
|
||||
- `reject-or-split`: hidden drift, unresolved exception spread, or wrong proof depth blocks merge as proposed.
|
||||
- `keep`: current lane, family, and setup are justified.
|
||||
- `split`: scope is valid, but the test or helper spread should be narrowed before merge.
|
||||
- `document-in-feature`: the change is acceptable, but the cost or drift must be recorded in the active spec or PR.
|
||||
- `follow-up-spec`: recurring pain or structural lane or family changes need dedicated governance work.
|
||||
- `reject-or-split`: hidden cost, wrong lane, or unjustified breadth blocks merge as proposed.
|
||||
- Check items off as completed: `[x]`
|
||||
- Add comments or findings inline
|
||||
- Link to relevant resources or documentation
|
||||
- Items are numbered sequentially for easy reference
|
||||
- Reviewer-facing checklists SHOULD stop merge when nativity, shared-family boundaries, state ownership, exception spread, test depth, or escalation handling is unclear.
|
||||
- Reviewer-facing runtime checklists SHOULD stop merge when lane fit, hidden cost, heavy-family drift, or escalation handling is unclear.
|
||||
|
||||
@ -28,21 +28,6 @@ ## Technical Context
|
||||
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
|
||||
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
|
||||
|
||||
## UI / Surface Guardrail Plan
|
||||
|
||||
> **Fill for operator-facing or guardrail-relevant workflow changes. Docs-only or template-only work may use concise `N/A`. Copy the spec classification forward; do not rename or expand it here.**
|
||||
|
||||
- **Guardrail scope**: [no operator-facing surface change / changed surfaces / workflow-only guardrail change]
|
||||
- **Native vs custom classification summary**: [native / custom / mixed / N/A]
|
||||
- **Shared-family relevance**: [none / list affected shared families]
|
||||
- **State layers in scope**: [shell / page / detail / URL-query / none]
|
||||
- **Handling modes by drift class or surface**: [hard-stop-candidate / review-mandatory / exception-required / report-only / N/A]
|
||||
- **Repository-signal treatment**: [report-only / review-mandatory / exception-required / future hard-stop candidate / N/A]
|
||||
- **Special surface test profiles**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Required tests or manual smoke**: [functional-core / state-contract / exception-fallback / manual-smoke / N/A]
|
||||
- **Exception path and spread control**: [none / describe the named exception boundary]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
@ -104,11 +89,6 @@ ## Constitution Check
|
||||
selection actions, navigation, and object actions; risky or rare
|
||||
actions are grouped and ordered by meaning/frequency/risk; any special
|
||||
type or workflow-hub exception is explicit and justified
|
||||
- UI review workflow: native/custom classification, shared-family
|
||||
relevance, state-layer ownership, repository-signal treatment,
|
||||
exception path, and the active feature PR close-out entry stay
|
||||
explicit without duplicating the same decision across spec, plan,
|
||||
tasks, checklist, and close-out surfaces
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
@ -121,12 +101,10 @@ ## Test Governance Check
|
||||
- **Fixture / helper / factory / seed / context cost risks**: [none / describe]
|
||||
- **Expensive defaults or shared helper growth introduced?**: [no / describe explicit opt-in path]
|
||||
- **Heavy-family additions, promotions, or visibility changes**: [none / describe]
|
||||
- **Surface-class relief / special coverage rule**: [standard-native relief / named special profile / N/A]
|
||||
- **Closing validation and reviewer handoff**: [What must be re-run, what reviewers should verify, and what exact proof command they should rely on]
|
||||
- **Budget / baseline / trend follow-up**: [none / describe]
|
||||
- **Review-stop questions**: [lane fit / breadth / hidden cost / heavy-family risk / escalation]
|
||||
- **Escalation path**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Why no dedicated follow-up spec is needed**: [Routine upkeep stays inside this feature unless recurring pain or structural lane changes justify a separate spec]
|
||||
|
||||
## Project Structure
|
||||
|
||||
@ -35,23 +35,11 @@ ## Spec Scope Fields *(mandatory)*
|
||||
- **Default filter behavior when tenant-context is active**: [e.g., prefilter to current tenant]
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: [Describe checks]
|
||||
|
||||
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
|
||||
|
||||
Use this section to classify UI and surface risk once. If the feature does
|
||||
not change an operator-facing surface, write `N/A - no operator-facing surface
|
||||
change` here and do not invent duplicate prose in the downstream surface tables.
|
||||
|
||||
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
||||
|---|---|---|---|---|---|---|
|
||||
| e.g. Tenant policies page | yes | Native Filament + shared primitives | none | page, detail | no | n/a |
|
||||
| e.g. Docs-only change | no | N/A | none | none | no | `N/A - repository workflow only` |
|
||||
|
||||
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
|
||||
|
||||
If this feature adds or materially changes an operator-facing surface,
|
||||
fill out one row per affected surface. This role is orthogonal to the
|
||||
Action Surface Class / Surface Type below. Reuse the exact surface names
|
||||
and classifications from the UI / Surface Guardrail Impact section above.
|
||||
Action Surface Class / Surface Type below.
|
||||
|
||||
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@ -62,8 +50,7 @@ ## UI/UX Surface Classification *(mandatory when operator-facing surfaces are ch
|
||||
If this feature adds or materially changes an operator-facing list, detail, queue, audit, config, or report surface,
|
||||
fill out one row per affected surface. Declare the broad Action Surface
|
||||
Class first, then the detailed Surface Type. Keep this table in sync
|
||||
with the Decision-First Surface Role section above and avoid renaming the
|
||||
same surface a second time.
|
||||
with the Decision-First Surface Role section above.
|
||||
|
||||
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
@ -111,12 +98,9 @@ ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
- **New or expanded test families**: [none / describe]
|
||||
- **Fixture / helper cost impact**: [none / describe new defaults, factories, seeds, helpers, browser setup, provider setup, workspace or membership context, session state, etc.]
|
||||
- **Heavy-family visibility / justification**: [none / explain any heavy-governance or browser addition and how it remains explicit in naming, lane choice, and review]
|
||||
- **Special surface test profile**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
|
||||
- **Standard-native relief or required special coverage**: [ordinary feature coverage only / describe required tests or smoke checks]
|
||||
- **Reviewer handoff**: [What reviewers must confirm about lane fit, hidden cost, heavy-family visibility, and the exact proof command]
|
||||
- **Budget / baseline / trend impact**: [none / expected drift + follow-up]
|
||||
- **Escalation needed**: [none / document-in-feature / follow-up-spec / reject-or-split]
|
||||
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
|
||||
- **Planned validation commands**: [Exact minimal commands reviewers should run]
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
@ -46,13 +46,6 @@ # Tasks: [FEATURE NAME]
|
||||
- using source/domain terms only where same-screen disambiguation is required,
|
||||
- aligning button labels, modal titles, run titles, notifications, and audit prose to the same domain vocabulary,
|
||||
- removing implementation-first wording from primary operator-facing copy.
|
||||
**UI / Surface Guardrails**: If this feature adds or changes operator-facing surfaces or the workflow that governs them, tasks MUST include:
|
||||
- carrying forward the spec's native/custom classification, shared-family relevance, state-layer ownership, and exception need into implementation work without renaming the same decision,
|
||||
- classifying any triggered repository signals with one handling mode (`hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`),
|
||||
- adding explicit review or definition-of-done work when a guarded surface class, repository signal, or exception path is involved,
|
||||
- adding required tests or manual smoke for `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, or `exception-coded-surface`, OR recording `standard-native-filament` relief when no special contract exists,
|
||||
- adding exception documentation and spread-control tasks whenever default surface rules are intentionally relaxed,
|
||||
- recording the active feature PR close-out entry with guardrail class, exception status, required tests/manual smoke, low-impact `N/A` use, and any deferred automation.
|
||||
**Operator Surfaces**: If this feature adds or materially refactors an operator-facing page or flow, tasks MUST include:
|
||||
- classifying each affected surface as Primary Decision, Secondary
|
||||
Context, or Tertiary Evidence / Diagnostics and keeping that role in
|
||||
@ -148,7 +141,6 @@ ## Test Governance Checklist
|
||||
- [ ] New or changed tests stay in the smallest honest family, and any heavy-governance or browser addition is explicit.
|
||||
- [ ] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
|
||||
- [ ] Planned validation commands cover the change without pulling in unrelated lane cost.
|
||||
- [ ] The declared surface test profile or `standard-native-filament` relief is explicit.
|
||||
- [ ] Any material budget, baseline, trend, or escalation note is recorded in the active spec or PR.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
@ -295,7 +287,6 @@ ## Phase N: Polish & Cross-Cutting Concerns
|
||||
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
||||
- [ ] TXXX Security hardening
|
||||
- [ ] TXXX Proportionality cleanup: remove or collapse superseded layers introduced during implementation
|
||||
- [ ] TXXX Record the active feature PR close-out entry with guardrail class, exception status, proof depth, and deferred automation
|
||||
- [ ] TXXX Run quickstart.md validation
|
||||
|
||||
---
|
||||
|
||||
@ -1,23 +1,9 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example';
|
||||
|
||||
export default defineConfig({
|
||||
output: 'static',
|
||||
site: publicSiteUrl,
|
||||
server: {
|
||||
host: true,
|
||||
port: 4321,
|
||||
},
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -9,18 +9,9 @@
|
||||
"scripts": {
|
||||
"dev": "astro dev --host 0.0.0.0 --port ${WEBSITE_PORT:-4321}",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --host 0.0.0.0",
|
||||
"test": "playwright test",
|
||||
"test:smoke": "playwright test"
|
||||
"preview": "astro preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.59.1",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@types/node": "^24.7.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const port = Number(process.env.WEBSITE_PORT ?? '4321');
|
||||
const baseURL = `http://127.0.0.1:${port}`;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests/smoke',
|
||||
fullyParallel: true,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [['list']],
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: `WEBSITE_PORT=${port} corepack pnpm dev`,
|
||||
port,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
});
|
||||
@ -1,3 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: /sitemap.xml
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
---
|
||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { AudienceRowContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: AudienceRowContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{item.audience}
|
||||
</p>
|
||||
<h3 class="mt-4 text-3xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
<ul class="mt-5 space-y-3 p-0">
|
||||
{
|
||||
item.bullets.map((bullet) => (
|
||||
<li class="list-none rounded-[1rem] bg-white/70 px-4 py-3 text-sm text-[var(--color-ink-800)]">
|
||||
{bullet}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{item.cta && (
|
||||
<div class="mt-6">
|
||||
<SecondaryCTA cta={item.cta} />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { CalloutContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
content: CalloutContent;
|
||||
}
|
||||
|
||||
const { content } = Astro.props;
|
||||
const variant = content.tone === 'accent' ? 'accent' : content.tone === 'subtle' ? 'subtle' : 'default';
|
||||
---
|
||||
|
||||
<Card variant={variant}>
|
||||
{content.eyebrow && (
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{content.eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{content.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{content.description}</p>
|
||||
</Card>
|
||||
@ -1,29 +0,0 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
points: string[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { cta, points, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Card variant="accent">
|
||||
<h3 class="m-0 text-3xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{title}</h3>
|
||||
<ul class="mt-5 space-y-3 p-0">
|
||||
{
|
||||
points.map((point) => (
|
||||
<li class="list-none rounded-[1rem] bg-white/72 px-4 py-3 text-sm text-[var(--color-ink-800)]">
|
||||
{point}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div class="mt-6">
|
||||
<Button href={cta.href} variant={cta.variant ?? 'primary'}>{cta.label}</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@ -1,18 +0,0 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Card>
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
Conversation focus
|
||||
</p>
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{title}</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{description}</p>
|
||||
</Card>
|
||||
@ -1,11 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<p class:list={['m-0 text-sm font-semibold uppercase tracking-[0.18em] text-[var(--color-brand)]', className]}>
|
||||
<slot />
|
||||
</p>
|
||||
@ -1,32 +0,0 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { FeatureItemContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: FeatureItemContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
{item.eyebrow && (
|
||||
<p class="m-0 text-xs font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{item.eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<h3 class="mt-4 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
{(item.meta || item.href) && (
|
||||
<div class="mt-5 flex flex-wrap items-center gap-3 text-sm">
|
||||
{item.meta && <span class="text-[var(--color-brand)]">{item.meta}</span>}
|
||||
{item.href && (
|
||||
<a class="font-semibold text-[var(--color-ink-900)] underline decoration-[rgba(17,36,58,0.18)] underline-offset-4" href={item.href}>
|
||||
Learn more
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
@ -1,11 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<h2 class:list={['m-0 font-[var(--font-display)] text-4xl leading-[0.98] tracking-[-0.03em] text-[var(--color-ink-900)] sm:text-5xl', className]}>
|
||||
<slot />
|
||||
</h2>
|
||||
@ -1,19 +0,0 @@
|
||||
---
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import type { IntegrationEntry } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: IntegrationEntry;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="rounded-[1.1rem] border border-[rgba(17,36,58,0.08)] bg-white/78 px-4 py-3 shadow-[var(--shadow-soft)]">
|
||||
<div class="flex items-center gap-3">
|
||||
<Badge tone="neutral">{item.category}</Badge>
|
||||
<p class="m-0 text-base font-semibold text-[var(--color-ink-900)]">{item.name}</p>
|
||||
</div>
|
||||
<p class="mt-3 max-w-72 text-sm leading-6 text-[var(--color-copy)]">{item.summary}</p>
|
||||
{item.note && <p class="mt-2 text-xs font-medium uppercase tracking-[0.14em] text-[var(--color-brand)]">{item.note}</p>}
|
||||
</div>
|
||||
@ -1,11 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
<p class:list={['m-0 text-base leading-8 text-[var(--color-copy)] sm:text-lg', className]}>
|
||||
<slot />
|
||||
</p>
|
||||
@ -1,18 +0,0 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { MetricItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: MetricItem;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card variant="subtle">
|
||||
<p class="m-0 text-3xl font-semibold tracking-[-0.04em] text-[var(--color-ink-900)]">{item.value}</p>
|
||||
<p class="mt-2 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-brand)]">
|
||||
{item.label}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">{item.description}</p>
|
||||
</Card>
|
||||
@ -1,12 +0,0 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
}
|
||||
|
||||
const { cta } = Astro.props;
|
||||
---
|
||||
|
||||
<Button href={cta.href} variant={cta.variant ?? 'primary'}>{cta.label}</Button>
|
||||
@ -1,29 +0,0 @@
|
||||
---
|
||||
import type { LegalSection } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
sections: LegalSection[];
|
||||
}
|
||||
|
||||
const { sections } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="space-y-6">
|
||||
{
|
||||
sections.map((section) => (
|
||||
<section class="rounded-[1.5rem] border border-[rgba(17,36,58,0.08)] bg-white/72 p-6 shadow-[var(--shadow-soft)]">
|
||||
<h2 class="m-0 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">
|
||||
{section.title}
|
||||
</h2>
|
||||
<div class="legal-prose mt-4">
|
||||
{section.body.map((paragraph) => <p>{paragraph}</p>)}
|
||||
{section.bullets && section.bullets.length > 0 && (
|
||||
<ul>
|
||||
{section.bullets.map((bullet) => <li>{bullet}</li>)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
@ -1,12 +0,0 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
cta: CtaLink;
|
||||
}
|
||||
|
||||
const { cta } = Astro.props;
|
||||
---
|
||||
|
||||
<Button href={cta.href} variant={cta.variant ?? 'secondary'}>{cta.label}</Button>
|
||||
@ -1,16 +0,0 @@
|
||||
---
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import type { TrustPrincipleContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
item: TrustPrincipleContent;
|
||||
}
|
||||
|
||||
const { item } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="h-full">
|
||||
<h3 class="m-0 text-2xl font-semibold tracking-[-0.03em] text-[var(--color-ink-900)]">{item.title}</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{item.description}</p>
|
||||
{item.note && <p class="mt-4 text-sm font-medium text-[var(--color-brand)]">{item.note}</p>}
|
||||
</Card>
|
||||
@ -1,59 +0,0 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import { contactCta, footerNavigationGroups, siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
const { currentPath: _currentPath } = Astro.props;
|
||||
const currentYear = new Date().getFullYear();
|
||||
---
|
||||
|
||||
<footer class="section-divider pt-10 sm:pt-12">
|
||||
<Container wide>
|
||||
<div class="grid gap-8 rounded-[2rem] bg-[rgba(255,255,255,0.58)] p-6 shadow-[var(--shadow-soft)] lg:grid-cols-[1.3fr,1fr] lg:p-8">
|
||||
<div class="space-y-5">
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-brand)]">
|
||||
{siteMetadata.siteName}
|
||||
</p>
|
||||
<h2 class="m-0 max-w-xl font-[var(--font-display)] text-3xl leading-[0.98] text-[var(--color-ink-900)] sm:text-4xl">
|
||||
A calmer public surface for teams that need governance clarity before they need another dashboard.
|
||||
</h2>
|
||||
<p class="m-0 max-w-xl text-base leading-7 text-[var(--color-copy)]">
|
||||
TenantAtlas keeps product explanation, trust framing, and next-step guidance readable without hiding the product model behind hype or placeholders.
|
||||
</p>
|
||||
<Button href={contactCta.href} variant="primary" size="sm">{contactCta.label}</Button>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 sm:grid-cols-3">
|
||||
{
|
||||
footerNavigationGroups.map((group) => (
|
||||
<div>
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-ink-900)]">
|
||||
{group.title}
|
||||
</p>
|
||||
<ul class="mt-4 space-y-3 p-0 text-sm text-[var(--color-copy)]">
|
||||
{group.items.map((item) => (
|
||||
<li class="list-none">
|
||||
<a class="transition hover:text-[var(--color-brand)]" href={item.href}>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 py-6 text-sm text-[var(--color-copy)] sm:flex-row sm:items-center sm:justify-between">
|
||||
<p class="m-0">© {currentYear} {siteMetadata.siteName}. Public product site v0 foundation.</p>
|
||||
<p class="m-0">
|
||||
Built as a static Astro track with no platform auth, session, or API coupling.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
@ -1,105 +0,0 @@
|
||||
---
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import { contactCta, isActiveNavigationPath, primaryNavigation, siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
const { currentPath } = Astro.props;
|
||||
---
|
||||
|
||||
<header class="sticky top-0 z-30 pt-4 sm:pt-6">
|
||||
<Container wide>
|
||||
<div
|
||||
class="glass-panel flex items-center justify-between gap-4 rounded-[1.75rem] border border-white/70 px-4 py-3 sm:px-5"
|
||||
>
|
||||
<a href="/" class="flex min-w-0 items-center gap-3 no-underline">
|
||||
<span
|
||||
class="inline-flex h-11 w-11 items-center justify-center rounded-full bg-[linear-gradient(135deg,var(--color-brand),#7fa6cf)] font-[var(--font-display)] text-lg text-white"
|
||||
>
|
||||
TA
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block truncate text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-ink-900)]">
|
||||
{siteMetadata.siteName}
|
||||
</span>
|
||||
<span class="block truncate text-sm text-[var(--color-copy)]">
|
||||
{siteMetadata.siteTagline}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<nav class="hidden items-center gap-1 lg:flex" aria-label="Primary">
|
||||
{
|
||||
primaryNavigation.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class:list={[
|
||||
'rounded-full px-4 py-2 text-sm font-medium transition',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]'
|
||||
: 'text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
]}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
|
||||
<div class="hidden lg:block">
|
||||
<Button href={contactCta.href} variant="secondary" size="sm">{contactCta.label}</Button>
|
||||
</div>
|
||||
|
||||
<details class="relative lg:hidden">
|
||||
<summary
|
||||
aria-label="Open navigation menu"
|
||||
class="flex h-11 w-11 cursor-pointer list-none items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 text-[var(--color-ink-900)]"
|
||||
>
|
||||
<span class="sr-only">Open navigation menu</span>
|
||||
<span class="flex flex-col gap-1">
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
<span class="block h-0.5 w-4 bg-current"></span>
|
||||
</span>
|
||||
</summary>
|
||||
<div
|
||||
class="glass-panel absolute right-0 top-[calc(100%+0.75rem)] w-[min(18rem,88vw)] rounded-[1.5rem] border border-white/80 p-3"
|
||||
>
|
||||
<nav class="flex flex-col gap-1" aria-label="Mobile primary">
|
||||
{
|
||||
primaryNavigation.map((item) => (
|
||||
<a
|
||||
href={item.href}
|
||||
class:list={[
|
||||
'rounded-[1rem] px-4 py-3 text-sm',
|
||||
isActiveNavigationPath(currentPath, item.href)
|
||||
? 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]'
|
||||
: 'text-[var(--color-ink-800)] hover:bg-white/75',
|
||||
]}
|
||||
>
|
||||
<span class="block font-semibold">{item.label}</span>
|
||||
{item.description && (
|
||||
<span class="mt-1 block text-xs text-[var(--color-copy)]">
|
||||
{item.description}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
<div class="mt-2 rounded-[1rem] bg-[rgba(47,111,183,0.08)] p-3">
|
||||
<p class="m-0 text-sm font-semibold text-[var(--color-ink-900)]">
|
||||
{contactCta.label}
|
||||
</p>
|
||||
{contactCta.helper && (
|
||||
<p class="mt-1 text-sm text-[var(--color-copy)]">{contactCta.helper}</p>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</Container>
|
||||
</header>
|
||||
@ -1,39 +0,0 @@
|
||||
---
|
||||
import Footer from '@/components/layout/Footer.astro';
|
||||
import Navbar from '@/components/layout/Navbar.astro';
|
||||
import { resolveSeo } from '@/lib/seo';
|
||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||
|
||||
interface Props {
|
||||
currentPath: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const { currentPath, description, title } = Astro.props;
|
||||
const seo =
|
||||
title && description
|
||||
? resolveSeo({ description, path: currentPath, title })
|
||||
: undefined;
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={title}
|
||||
description={description}
|
||||
canonicalUrl={seo?.canonicalUrl}
|
||||
openGraphTitle={seo?.ogTitle}
|
||||
openGraphDescription={seo?.ogDescription}
|
||||
robots={seo?.robots}
|
||||
>
|
||||
<div class="surface-shell min-h-screen">
|
||||
<div
|
||||
class="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[30rem] bg-[radial-gradient(circle_at_top,rgba(47,111,183,0.16),transparent_50%),radial-gradient(circle_at_top_right,rgba(59,139,120,0.14),transparent_28%)]"
|
||||
>
|
||||
</div>
|
||||
<Navbar currentPath={currentPath} />
|
||||
<main id="content" class="pb-16 sm:pb-20">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer currentPath={currentPath} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@ -1,25 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
tone?: 'accent' | 'neutral' | 'signal' | 'warm';
|
||||
}
|
||||
|
||||
const { class: className = '', tone = 'accent' } = Astro.props;
|
||||
|
||||
const toneClasses = {
|
||||
accent: 'bg-[var(--color-brand-soft)] text-[var(--color-brand)]',
|
||||
neutral: 'bg-white/75 text-[var(--color-ink-800)]',
|
||||
signal: 'bg-[rgba(59,139,120,0.14)] text-[var(--color-signal)]',
|
||||
warm: 'bg-[rgba(175,109,67,0.14)] text-[var(--color-warm)]',
|
||||
};
|
||||
---
|
||||
|
||||
<span
|
||||
class:list={[
|
||||
'inline-flex w-fit items-center rounded-full px-3 py-1 text-[0.72rem] font-semibold uppercase tracking-[0.18em]',
|
||||
toneClasses[tone],
|
||||
className,
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
@ -1,56 +0,0 @@
|
||||
---
|
||||
import type { ButtonVariant } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
ariaLabel?: string;
|
||||
class?: string;
|
||||
href?: string;
|
||||
rel?: string;
|
||||
size?: 'lg' | 'md' | 'sm';
|
||||
target?: '_blank' | '_self';
|
||||
type?: 'button' | 'reset' | 'submit';
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
const {
|
||||
ariaLabel,
|
||||
class: className = '',
|
||||
href,
|
||||
rel,
|
||||
size = 'md',
|
||||
target,
|
||||
type = 'button',
|
||||
variant = 'primary',
|
||||
} = Astro.props;
|
||||
|
||||
const baseClass =
|
||||
'inline-flex items-center justify-center rounded-full border font-semibold tracking-[-0.01em] transition duration-150 focus-visible:outline-none';
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'min-h-10 px-4 text-sm',
|
||||
md: 'min-h-11 px-5 text-sm sm:text-[0.95rem]',
|
||||
lg: 'min-h-12 px-6 text-[0.95rem]',
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
primary:
|
||||
'border-transparent bg-[var(--color-ink-900)] text-white shadow-[0_18px_38px_rgba(17,36,58,0.16)] hover:bg-[var(--color-brand)]',
|
||||
secondary:
|
||||
'border-[color:var(--color-line)] bg-white/85 text-[var(--color-ink-900)] hover:border-[var(--color-ink-900)] hover:bg-white',
|
||||
ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70',
|
||||
};
|
||||
|
||||
const classes = [baseClass, sizeClasses[size], variantClasses[variant], className];
|
||||
---
|
||||
|
||||
{
|
||||
href ? (
|
||||
<a href={href} target={target} rel={rel} aria-label={ariaLabel} class:list={classes}>
|
||||
<slot />
|
||||
</a>
|
||||
) : (
|
||||
<button type={type} aria-label={ariaLabel} class:list={classes}>
|
||||
<slot />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
variant?: 'accent' | 'default' | 'subtle';
|
||||
}
|
||||
|
||||
const { as = 'article', class: className = '', variant = 'default' } = Astro.props;
|
||||
|
||||
const variantClasses = {
|
||||
default: 'glass-panel border border-[color:var(--color-line)] bg-[var(--color-panel)]',
|
||||
accent:
|
||||
'border border-[rgba(47,111,183,0.18)] bg-[linear-gradient(180deg,rgba(255,255,255,0.96),rgba(238,245,252,0.94))] shadow-[var(--shadow-soft)]',
|
||||
subtle:
|
||||
'border border-[rgba(17,36,58,0.08)] bg-[linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0.56))]',
|
||||
};
|
||||
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['rounded-[1.65rem] p-6 sm:p-7', variantClasses[variant], className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
@ -1,13 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '' } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['flex flex-wrap items-center gap-3 sm:gap-4', className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
@ -1,14 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '', wide = false } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={['mx-auto w-full px-5 sm:px-6 lg:px-8', wide ? 'max-w-[80rem]' : 'max-w-6xl', className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
@ -1,18 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
cols?: '2' | '3' | '4';
|
||||
}
|
||||
|
||||
const { class: className = '', cols = '3' } = Astro.props;
|
||||
|
||||
const colClasses = {
|
||||
'2': 'grid-cols-1 md:grid-cols-2',
|
||||
'3': 'grid-cols-1 md:grid-cols-2 xl:grid-cols-3',
|
||||
'4': 'grid-cols-1 md:grid-cols-2 xl:grid-cols-4',
|
||||
};
|
||||
---
|
||||
|
||||
<div class:list={['grid gap-5 lg:gap-6', colClasses[cols], className]}>
|
||||
<slot />
|
||||
</div>
|
||||
@ -1,32 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
type?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
class: className = '',
|
||||
name,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
type = 'text',
|
||||
value,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
readonly={readonly}
|
||||
class:list={[
|
||||
'min-h-12 w-full rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/90 px-4 text-[0.97rem] text-[var(--color-ink-900)] shadow-[var(--shadow-soft)] placeholder:text-[var(--color-copy)]/70',
|
||||
readonly ? 'cursor-default' : '',
|
||||
className,
|
||||
]}
|
||||
/>
|
||||
@ -1,22 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
id?: string;
|
||||
muted?: boolean;
|
||||
}
|
||||
|
||||
const { as = 'section', class: className = '', id, muted = false } = Astro.props;
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag
|
||||
id={id}
|
||||
class:list={[
|
||||
'py-12 sm:py-16 lg:py-20',
|
||||
muted ? 'bg-white/45' : '',
|
||||
className,
|
||||
]}
|
||||
>
|
||||
<slot />
|
||||
</Tag>
|
||||
@ -1,27 +0,0 @@
|
||||
---
|
||||
import Eyebrow from '@/components/content/Eyebrow.astro';
|
||||
import Headline from '@/components/content/Headline.astro';
|
||||
import Lead from '@/components/content/Lead.astro';
|
||||
|
||||
interface Props {
|
||||
align?: 'center' | 'left';
|
||||
class?: string;
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const {
|
||||
align = 'left',
|
||||
class: className = '',
|
||||
description,
|
||||
eyebrow,
|
||||
title,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class:list={['max-w-3xl', align === 'center' ? 'mx-auto text-center' : '', className]}>
|
||||
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
||||
<Headline>{title}</Headline>
|
||||
{description && <Lead class="mt-4">{description}</Lead>}
|
||||
</div>
|
||||
@ -1,20 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
as?: keyof HTMLElementTagNameMap;
|
||||
class?: string;
|
||||
gap?: 'lg' | 'md' | 'sm' | 'xl';
|
||||
}
|
||||
|
||||
const { as = 'div', class: className = '', gap = 'md' } = Astro.props;
|
||||
const gapClasses = {
|
||||
sm: 'flex flex-col gap-3',
|
||||
md: 'flex flex-col gap-5',
|
||||
lg: 'flex flex-col gap-7',
|
||||
xl: 'flex flex-col gap-10',
|
||||
};
|
||||
const Tag = as;
|
||||
---
|
||||
|
||||
<Tag class:list={[gapClasses[gap], className]}>
|
||||
<slot />
|
||||
</Tag>
|
||||
@ -1,31 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
class?: string;
|
||||
name?: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
rows?: number;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
class: className = '',
|
||||
name,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
rows = 5,
|
||||
value,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<textarea
|
||||
name={name}
|
||||
rows={rows}
|
||||
placeholder={placeholder}
|
||||
readonly={readonly}
|
||||
class:list={[
|
||||
'min-h-32 w-full rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/90 px-4 py-3 text-[0.97rem] text-[var(--color-ink-900)] shadow-[var(--shadow-soft)] placeholder:text-[var(--color-copy)]/70',
|
||||
readonly ? 'cursor-default' : '',
|
||||
className,
|
||||
]}
|
||||
>{value}</textarea>
|
||||
@ -1,33 +0,0 @@
|
||||
---
|
||||
import SecondaryCTA from '@/components/content/SecondaryCTA.astro';
|
||||
import PrimaryCTA from '@/components/content/PrimaryCTA.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { CtaLink } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
primary: CtaLink;
|
||||
secondary?: CtaLink;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, primary, secondary, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<Card variant="accent" class="overflow-hidden">
|
||||
<div class="grid gap-6 lg:grid-cols-[1.35fr,0.85fr] lg:items-end">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<div class="flex flex-col gap-3 sm:flex-row lg:justify-end">
|
||||
<PrimaryCTA cta={primary} />
|
||||
{secondary && <SecondaryCTA cta={secondary} />}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Container>
|
||||
</Section>
|
||||
@ -1,28 +0,0 @@
|
||||
---
|
||||
import FeatureItem from '@/components/content/FeatureItem.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { FeatureItemContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
items: FeatureItemContent[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<Grid cols="3">
|
||||
{items.map((item) => <FeatureItem item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
@ -1,44 +0,0 @@
|
||||
---
|
||||
import IntegrationBadge from '@/components/content/IntegrationBadge.astro';
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import type { IntegrationEntry, LogoStripItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
eyebrow?: string;
|
||||
items: (IntegrationEntry | LogoStripItem)[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section class="pt-8 sm:pt-10">
|
||||
<Container wide>
|
||||
<div class="rounded-[1.8rem] border border-[rgba(17,36,58,0.08)] bg-white/55 px-5 py-6 shadow-[var(--shadow-soft)]">
|
||||
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="space-y-3">
|
||||
{eyebrow && <Badge tone="signal">{eyebrow}</Badge>}
|
||||
<h2 class="m-0 max-w-2xl font-[var(--font-display)] text-3xl leading-tight text-[var(--color-ink-900)]">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{
|
||||
items.map((item) => (
|
||||
<IntegrationBadge
|
||||
item={{
|
||||
category: 'category' in item ? item.category : 'Ecosystem',
|
||||
name: item.label ?? item.name,
|
||||
note: item.note,
|
||||
summary: 'summary' in item ? item.summary : `${item.label} aligns with the launch story.`,
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
@ -1,96 +0,0 @@
|
||||
---
|
||||
import Badge from '@/components/primitives/Badge.astro';
|
||||
import Button from '@/components/primitives/Button.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Cluster from '@/components/primitives/Cluster.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import type { HeroContent, MetricItem } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
calloutDescription?: string;
|
||||
calloutTitle?: string;
|
||||
hero: HeroContent;
|
||||
metrics?: MetricItem[];
|
||||
}
|
||||
|
||||
const { calloutDescription, calloutTitle, hero, metrics = [] } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="pt-8 sm:pt-10 lg:pt-14">
|
||||
<Container wide>
|
||||
<div class="grid gap-6 lg:grid-cols-[1.35fr,0.85fr]">
|
||||
<Card class="motion-rise overflow-hidden">
|
||||
<div class="space-y-6">
|
||||
<Badge>{hero.eyebrow}</Badge>
|
||||
<div class="space-y-4">
|
||||
<h1 class="max-w-4xl font-[var(--font-display)] text-5xl leading-[0.93] tracking-[-0.04em] text-[var(--color-ink-900)] sm:text-6xl lg:text-7xl">
|
||||
{hero.title}
|
||||
</h1>
|
||||
<p class="max-w-3xl text-lg leading-8 text-[var(--color-copy)] sm:text-xl">
|
||||
{hero.description}
|
||||
</p>
|
||||
</div>
|
||||
{(hero.primaryCta || hero.secondaryCta) && (
|
||||
<Cluster>
|
||||
<Button href={hero.primaryCta.href} variant={hero.primaryCta.variant ?? 'primary'}>
|
||||
{hero.primaryCta.label}
|
||||
</Button>
|
||||
{hero.secondaryCta && (
|
||||
<Button href={hero.secondaryCta.href} variant={hero.secondaryCta.variant ?? 'secondary'}>
|
||||
{hero.secondaryCta.label}
|
||||
</Button>
|
||||
)}
|
||||
</Cluster>
|
||||
)}
|
||||
{hero.highlights && hero.highlights.length > 0 && (
|
||||
<ul class="grid gap-3 p-0 sm:grid-cols-3">
|
||||
{
|
||||
hero.highlights.map((highlight) => (
|
||||
<li class="list-none rounded-[1.1rem] border border-[color:var(--color-line)] bg-white/70 px-4 py-3 text-sm font-medium text-[var(--color-ink-800)]">
|
||||
{highlight}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div class="grid gap-5">
|
||||
{(calloutTitle || calloutDescription) && (
|
||||
<Card variant="accent" class="motion-rise">
|
||||
<p class="m-0 text-sm font-semibold uppercase tracking-[0.15em] text-[var(--color-brand)]">
|
||||
Trust-first launch surface
|
||||
</p>
|
||||
{calloutTitle && (
|
||||
<h2 class="mt-4 font-[var(--font-display)] text-3xl leading-tight text-[var(--color-ink-900)]">
|
||||
{calloutTitle}
|
||||
</h2>
|
||||
)}
|
||||
{calloutDescription && (
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">{calloutDescription}</p>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{metrics.length > 0 && (
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-1">
|
||||
{
|
||||
metrics.map((metric) => (
|
||||
<Card variant="subtle">
|
||||
<p class="m-0 text-3xl font-semibold tracking-[-0.04em] text-[var(--color-ink-900)]">
|
||||
{metric.value}
|
||||
</p>
|
||||
<p class="mt-2 text-sm font-semibold uppercase tracking-[0.14em] text-[var(--color-brand)]">
|
||||
{metric.label}
|
||||
</p>
|
||||
<p class="mt-2 text-sm leading-6 text-[var(--color-copy)]">{metric.description}</p>
|
||||
</Card>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
@ -1,28 +0,0 @@
|
||||
---
|
||||
import TrustPrincipleCard from '@/components/content/TrustPrincipleCard.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import type { TrustPrincipleContent } from '@/types/site';
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
eyebrow?: string;
|
||||
items: TrustPrincipleContent[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { description, eyebrow, items, title } = Astro.props;
|
||||
---
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader eyebrow={eyebrow} title={title} description={description} />
|
||||
<Grid cols="3">
|
||||
{items.map((item) => <TrustPrincipleCard item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
@ -1,24 +0,0 @@
|
||||
import { glob } from 'astro/loaders';
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const futureContentSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.coerce.date().optional(),
|
||||
draft: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
articles: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/articles' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
changelog: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/changelog' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
resources: defineCollection({
|
||||
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/resources' }),
|
||||
schema: futureContentSchema,
|
||||
}),
|
||||
};
|
||||
@ -1,65 +0,0 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const contactSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Contact',
|
||||
description:
|
||||
'TenantAtlas uses a qualified working-session path instead of a generic demo pitch so serious buyers can frame the right conversation early.',
|
||||
path: '/contact',
|
||||
};
|
||||
|
||||
export const contactHero: HeroContent = {
|
||||
eyebrow: 'Contact / Demo',
|
||||
title: 'Start a qualified working session instead of a generic demo request.',
|
||||
description:
|
||||
'The contact path should help serious buyers explain who they are, what governance questions they are trying to solve, and what kind of follow-up would actually be useful.',
|
||||
primaryCta: {
|
||||
href: 'mailto:hello@tenantatlas.example?subject=TenantAtlas%20working%20session',
|
||||
label: 'Email the TenantAtlas team',
|
||||
variant: 'primary',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/legal',
|
||||
label: 'Read the legal surface',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Use the page to qualify the conversation, not to force a form funnel.',
|
||||
'Set expectations for what the session covers and what happens next.',
|
||||
'Keep privacy and terms visible before anyone shares evaluation details.',
|
||||
],
|
||||
};
|
||||
|
||||
export const contactTopics = [
|
||||
'Evaluation of backup, restore, or version-governance workflows for Intune and Microsoft tenant operations.',
|
||||
'Questions about MSP fit, customer-facing evidence, or multi-tenant operational discipline.',
|
||||
'Internal enterprise review of change history, drift visibility, evidence collection, or restore posture.',
|
||||
];
|
||||
|
||||
export const contactPrompts = [
|
||||
{
|
||||
title: 'Good first conversation',
|
||||
description:
|
||||
'Explain the current operating model, where version history breaks down today, and which changes feel hardest to review or restore safely.',
|
||||
},
|
||||
{
|
||||
title: 'Useful context to share',
|
||||
description:
|
||||
'Team shape, tenant count, policy complexity, change frequency, and whether the first concern is restore safety, auditability, or review evidence.',
|
||||
},
|
||||
];
|
||||
|
||||
export const contactPreview = {
|
||||
message:
|
||||
'We operate Microsoft tenant governance across multiple environments and want to understand how TenantAtlas approaches version history, safer restore flows, drift visibility, and review evidence.',
|
||||
topic: 'Environment and operating model summary',
|
||||
};
|
||||
|
||||
export const contactLegalSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Before you reach out',
|
||||
body: [
|
||||
'Use the legal links below before sharing evaluation details so the contact path stays trustworthy and unsurprising.',
|
||||
'The legal hub, privacy page, and public website terms remain reachable from the contact flow and the global footer.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -1,145 +0,0 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
IntegrationEntry,
|
||||
MetricItem,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const homeSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Governance of record for Microsoft tenant operations',
|
||||
description:
|
||||
'Trust-first public framing for a Microsoft tenant governance product that connects backup, restore, version history, drift, findings, evidence, and reviews.',
|
||||
path: '/',
|
||||
};
|
||||
|
||||
export const homeHero: HeroContent = {
|
||||
eyebrow: 'Public website v0',
|
||||
title: 'TenantAtlas is the trust-first public site for Microsoft tenant change history, drift visibility, and safer operations.',
|
||||
description:
|
||||
'TenantAtlas gives MSP and enterprise teams one clear operating model for understanding what changed, what drifted, what needs review, and what can be restored without turning governance into a loose collection of disconnected tools.',
|
||||
primaryCta: {
|
||||
href: '/product',
|
||||
label: 'See the product model',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/security-trust',
|
||||
label: 'Review the trust posture',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Static, readable, and separate from app runtime concerns.',
|
||||
'Built for trust conversations before a demo ever starts.',
|
||||
'Designed to scale into docs, changelog, and deeper resources later.',
|
||||
],
|
||||
};
|
||||
|
||||
export const homeMetrics: MetricItem[] = [
|
||||
{
|
||||
value: '1',
|
||||
label: 'Connected model',
|
||||
description: 'Inventory, snapshots, review evidence, and restore posture stay in one narrative.',
|
||||
},
|
||||
{
|
||||
value: '7+',
|
||||
label: 'Core public surfaces',
|
||||
description: 'Visitors can move from explanation to trust and contact without dead ends.',
|
||||
},
|
||||
{
|
||||
value: '0',
|
||||
label: 'Runtime coupling',
|
||||
description: 'The website stays independent from platform auth, session, and API behavior.',
|
||||
},
|
||||
];
|
||||
|
||||
export const homePillars: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Inventory',
|
||||
title: 'Normalize what the tenant really looks like right now.',
|
||||
description:
|
||||
'Start with the observed state so teams can inspect the current configuration baseline before they talk about restore or enforcement.',
|
||||
meta: 'Last observed truth',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Snapshots',
|
||||
title: 'Keep immutable history instead of vague memory.',
|
||||
description:
|
||||
'Version history stays queryable by tenant, operator, and moment in time so teams can explain what changed and why.',
|
||||
meta: 'Reproducible versions',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Drift & findings',
|
||||
title: 'Surface drift, exceptions, and review needs in the same language.',
|
||||
description:
|
||||
'Operational questions move from “what broke?” to “what changed, what matters, and what review is due?”',
|
||||
meta: 'Review-oriented visibility',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Restore',
|
||||
title: 'Treat rollback and restore as governed actions, not panic buttons.',
|
||||
description:
|
||||
'Preview, validation, and operator confirmation stay central so risky changes are reversible without becoming casual.',
|
||||
meta: 'Safer execution',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Evidence',
|
||||
title: 'Connect reviews, findings, and evidence without a second reporting layer.',
|
||||
description:
|
||||
'Teams can show why a configuration is acceptable, where exceptions exist, and how review decisions stay attributable.',
|
||||
meta: 'Audit-ready context',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Operations',
|
||||
title: 'Keep velocity without hiding risk.',
|
||||
description:
|
||||
'The product is built for admins who need speed and auditability at the same time, not for dashboards that only summarize after the fact.',
|
||||
meta: 'Operator-first workflows',
|
||||
},
|
||||
];
|
||||
|
||||
export const homeProofBlocks: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Positioning',
|
||||
title: 'Governance of record for Microsoft tenant operations.',
|
||||
description:
|
||||
'The site makes the category legible up front: not just backup, not just reporting, and not a second admin portal trying to mirror every Microsoft screen.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Why it matters now',
|
||||
title: 'Microsoft tenant change volume keeps climbing while operator certainty keeps shrinking.',
|
||||
description:
|
||||
'When policy history, restore posture, findings, and evidence live in separate conversations, teams lose time exactly when they need clarity.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Public promise',
|
||||
title: 'No inflated compliance or automation claims.',
|
||||
description:
|
||||
'The public story stays grounded in what the product can honestly support at launch: version truth, safer restore flows, drift visibility, and review support.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
|
||||
export const homeEcosystem: IntegrationEntry[] = [
|
||||
{
|
||||
category: 'Microsoft',
|
||||
name: 'Microsoft Graph',
|
||||
summary: 'Graph-backed inventory and restore direction without pretending the website depends on live tenant access.',
|
||||
},
|
||||
{
|
||||
category: 'Identity',
|
||||
name: 'Entra ID',
|
||||
summary: 'Identity and access context remain part of the governance narrative where they matter to change control.',
|
||||
},
|
||||
{
|
||||
category: 'Endpoint',
|
||||
name: 'Intune',
|
||||
summary: 'Configuration state, backup, and restore posture stay central to the public product story.',
|
||||
},
|
||||
{
|
||||
category: 'Review',
|
||||
name: 'Evidence workflows',
|
||||
summary: 'Review packs, exceptions, and evidence stay connected to operational reality instead of becoming detached reporting artifacts.',
|
||||
},
|
||||
];
|
||||
@ -1,86 +0,0 @@
|
||||
import type {
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
IntegrationEntry,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const integrationsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Integrations',
|
||||
description:
|
||||
'TenantAtlas describes the Microsoft-centric ecosystem it fits today without turning the page into a public wishlist.',
|
||||
path: '/integrations',
|
||||
};
|
||||
|
||||
export const integrationsHero: HeroContent = {
|
||||
eyebrow: 'Ecosystem fit',
|
||||
title: 'Stay clear about the ecosystem fit without turning the page into a wishlist.',
|
||||
description:
|
||||
'This page should show the real systems TenantAtlas is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Plan the working session',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/product',
|
||||
label: 'Revisit the product model',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Microsoft-first and governance-led.',
|
||||
'Real direction only, no catalog-padding.',
|
||||
'Explains fit without implying runtime coupling to the public site.',
|
||||
],
|
||||
};
|
||||
|
||||
export const integrationEntries: IntegrationEntry[] = [
|
||||
{
|
||||
category: 'Microsoft core',
|
||||
name: 'Microsoft Graph',
|
||||
summary:
|
||||
'Graph remains the primary contract path for inventory, history, and restore-oriented product behavior in the platform.',
|
||||
note: 'Core product contract',
|
||||
},
|
||||
{
|
||||
category: 'Identity',
|
||||
name: 'Entra ID',
|
||||
summary:
|
||||
'Identity context matters where tenant change, privileged access, or governance reviews intersect with Microsoft tenant administration.',
|
||||
note: 'Operational context',
|
||||
},
|
||||
{
|
||||
category: 'Endpoint',
|
||||
name: 'Intune',
|
||||
summary:
|
||||
'Intune configuration state is central to the current product story, especially for version history, backup, restore, and drift visibility.',
|
||||
note: 'Current release truth',
|
||||
},
|
||||
{
|
||||
category: 'Evidence',
|
||||
name: 'Review & evidence workflows',
|
||||
summary:
|
||||
'Exports, review packs, and evidence-linked conversations should remain grounded in the actual tenant object and its change history.',
|
||||
note: 'Governance workflow fit',
|
||||
},
|
||||
];
|
||||
|
||||
export const integrationRules: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Direction',
|
||||
title: 'Integrations should reinforce the governance model.',
|
||||
description:
|
||||
'The page is not a marketplace list. It should show which systems matter because they change the product workflow, evidence story, or operator context.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Boundaries',
|
||||
title: 'Do not imply shared public-site runtime dependencies.',
|
||||
description:
|
||||
'The website stays static and independent even while the product story references Graph, Intune, and Microsoft tenant governance flows.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Credibility',
|
||||
title: 'Speculative wishlist entries reduce trust instead of creating momentum.',
|
||||
description:
|
||||
'The public integrations page should stay shorter and sharper than a catalog full of future ideas that are not relevant to launch truth.',
|
||||
},
|
||||
];
|
||||
@ -1,44 +0,0 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const legalSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Legal',
|
||||
description:
|
||||
'The TenantAtlas legal surface keeps privacy, website terms, and public legal-notice routing accessible before or during buyer conversations.',
|
||||
path: '/legal',
|
||||
};
|
||||
|
||||
export const legalHero: HeroContent = {
|
||||
eyebrow: 'Legal surface',
|
||||
title: 'Legal access should stay one click away from the contact path.',
|
||||
description:
|
||||
'The legal hub keeps privacy, website terms, and public legal-notice information discoverable from the footer and the conversion flow so visitors do not have to guess where those basics live.',
|
||||
primaryCta: {
|
||||
href: '/privacy',
|
||||
label: 'Privacy',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/terms',
|
||||
label: 'Terms',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Public legal basics stay reachable before a visitor shares evaluation context.',
|
||||
'The site separates website disclosures from future product-commercial paperwork.',
|
||||
'Jurisdiction-specific notice has a dedicated home in the legal surface.',
|
||||
],
|
||||
};
|
||||
|
||||
export const legalNoticeSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Public legal notice',
|
||||
body: [
|
||||
'This v0 legal surface reserves the public location for operating-entity, registration, address, and jurisdiction-specific disclosure details that must be published before a broad public launch.',
|
||||
'During controlled evaluation, legal and privacy inquiries can be routed through the public contact path while those final publisher details are being finalized.',
|
||||
],
|
||||
bullets: [
|
||||
'Operating entity and jurisdictional disclosure fields belong in this legal hub before launch.',
|
||||
'Privacy and website terms stay published as standalone routes now.',
|
||||
'The legal hub remains the single public path for future launch-required disclosures.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -1,60 +0,0 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const privacySeo: PageSeo = {
|
||||
title: 'TenantAtlas | Privacy',
|
||||
description:
|
||||
'Public-site privacy overview for TenantAtlas inquiries, including how contact details and evaluation context are handled on the public website.',
|
||||
path: '/privacy',
|
||||
};
|
||||
|
||||
export const privacyHero: HeroContent = {
|
||||
eyebrow: 'Privacy',
|
||||
title: 'Public-site privacy overview for TenantAtlas inquiries.',
|
||||
description:
|
||||
'This page explains the privacy expectations for the public website and the contact path, rather than promising a full product-tenant data processing agreement from a static marketing surface.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Return to contact',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/terms',
|
||||
label: 'Review website terms',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'The public site should only request information that supports a useful follow-up.',
|
||||
'Contact details and evaluation context should be handled carefully and minimally.',
|
||||
'Future product-processing details belong in product/legal agreements, not hidden marketing copy.',
|
||||
],
|
||||
};
|
||||
|
||||
export const privacySections: LegalSection[] = [
|
||||
{
|
||||
title: 'Scope',
|
||||
body: [
|
||||
'This privacy overview applies to the public TenantAtlas website and to information a visitor intentionally shares through the public contact path.',
|
||||
'It does not describe tenant data processing inside the product itself, which belongs in product-specific legal and contractual materials.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Information you choose to send',
|
||||
body: [
|
||||
'If you contact the team, the information you provide may include your name, company, role, email address, and the evaluation or governance questions you want to discuss.',
|
||||
'The site should not ask for unnecessary secrets, production credentials, or tenant data through the public contact path.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Use and retention',
|
||||
body: [
|
||||
'Information shared through the public contact path is used to understand the inquiry, respond to the request, and coordinate a relevant follow-up conversation.',
|
||||
'Public-site inquiry information should be retained only for as long as needed to manage the evaluation discussion and related follow-up.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Questions and updates',
|
||||
body: [
|
||||
'Privacy questions can be routed through the public contact path until the final launch legal notice publishes the full operating-entity details for privacy correspondence.',
|
||||
'If the public-site data handling model changes materially, this page should be updated before or at the same time as the change.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -1,110 +0,0 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
MetricItem,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const productSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Product',
|
||||
description:
|
||||
'TenantAtlas connects inventory, snapshots, restore safety, drift visibility, findings, exceptions, and evidence into one governance model.',
|
||||
path: '/product',
|
||||
};
|
||||
|
||||
export const productHero: HeroContent = {
|
||||
eyebrow: 'Product model',
|
||||
title: 'One operating model for change history, drift visibility, and review readiness.',
|
||||
description:
|
||||
'TenantAtlas treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.',
|
||||
primaryCta: {
|
||||
href: '/solutions',
|
||||
label: 'See audience fit',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Talk through your current operating model',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Inventory first, snapshots second.',
|
||||
'Restore flows stay previewable and attributable.',
|
||||
'Evidence and review posture stay connected to real change history.',
|
||||
],
|
||||
};
|
||||
|
||||
export const productMetrics: MetricItem[] = [
|
||||
{
|
||||
value: '4',
|
||||
label: 'Operator questions',
|
||||
description: 'What changed? Why does it matter? What can be restored? What needs review now?',
|
||||
},
|
||||
{
|
||||
value: '100%',
|
||||
label: 'Queryable versions',
|
||||
description: 'Version semantics stay tied to who changed what, when, and in which tenant context.',
|
||||
},
|
||||
];
|
||||
|
||||
export const productModelBlocks: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Inventory creates the starting point for every other decision.',
|
||||
description:
|
||||
'The product begins with the last observed tenant state so teams can compare real configuration truth instead of relying on partial memory or exported spreadsheets.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Snapshots add immutable history without replacing current truth.',
|
||||
description:
|
||||
'Backups and versions are explicit artifacts. They preserve what was seen at a point in time while keeping the present-tense inventory readable.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Connected governance model',
|
||||
title: 'Restore is handled as a governed operation, not as a blind push.',
|
||||
description:
|
||||
'Preview, validation, selective scope, and confirmation reduce the risk of turning a recovery step into a new incident.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Drift visibility',
|
||||
title: 'Differences become reviewable signals instead of noisy raw deltas.',
|
||||
description:
|
||||
'Human-readable summaries and structured differences help operators and reviewers decide what changed and what needs action.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Exceptions & evidence',
|
||||
title: 'Findings, exceptions, and evidence stay anchored to operational truth.',
|
||||
description:
|
||||
'Governance discussions stay attached to the real object, version, and review context instead of drifting into separate manual trackers.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Operator safety',
|
||||
title: 'Auditability is part of the product shape, not a later add-on.',
|
||||
description:
|
||||
'The product is built so teams can explain actions afterward, not just execute them quickly in the moment.',
|
||||
},
|
||||
];
|
||||
|
||||
export const productNarrative: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Why it is not a feature list',
|
||||
title: 'The point is not “backup plus reporting plus restore.”',
|
||||
description:
|
||||
'The point is to reduce operator uncertainty by keeping those capabilities connected through the same source material and the same decision flow.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What teams get',
|
||||
title: 'A calmer path from observation to action.',
|
||||
description:
|
||||
'Teams can move from understanding the current tenant state to comparing history, planning remediation, or reviewing restore options without leaving the product model behind.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What teams avoid',
|
||||
title: 'No generic dashboard theater.',
|
||||
description:
|
||||
'The product story avoids pretending that another alerting page or compliance badge alone solves governance discipline.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
@ -1,78 +0,0 @@
|
||||
import type {
|
||||
CalloutContent,
|
||||
HeroContent,
|
||||
PageSeo,
|
||||
TrustPrincipleContent,
|
||||
} from '@/types/site';
|
||||
|
||||
export const securityTrustSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Security & Trust',
|
||||
description:
|
||||
'TenantAtlas frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.',
|
||||
path: '/security-trust',
|
||||
};
|
||||
|
||||
export const securityTrustHero: HeroContent = {
|
||||
eyebrow: 'Security & Trust',
|
||||
title: 'Explain the trust posture in the language of operational controls, not marketing claims.',
|
||||
description:
|
||||
'The public trust page should set realistic expectations: what the product helps teams observe, preserve, review, and restore, and where launch claims intentionally stay narrow until they can be substantiated further.',
|
||||
primaryCta: {
|
||||
href: '/legal',
|
||||
label: 'Read the legal surface',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Discuss trust requirements',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Preview, confirmation, and auditability remain part of the restore story.',
|
||||
'Public claims stay narrower than internal ambition.',
|
||||
'No fake compliance theater for launch.',
|
||||
],
|
||||
};
|
||||
|
||||
export const securityPrinciples: TrustPrincipleContent[] = [
|
||||
{
|
||||
title: 'Safer changes are a product rule, not an afterthought.',
|
||||
description:
|
||||
'Destructive or high-risk flows are framed around preview, validation, and explicit confirmation instead of one-click confidence theater.',
|
||||
note: 'Trust starts with operator discipline.',
|
||||
},
|
||||
{
|
||||
title: 'Operational evidence stays tied to the real object and version.',
|
||||
description:
|
||||
'Findings, exceptions, and reviews remain anchored to observable tenant state so the trust story is defensible when someone asks for proof.',
|
||||
note: 'Evidence should stay attributable.',
|
||||
},
|
||||
{
|
||||
title: 'Launch messaging stays within substantiated boundaries.',
|
||||
description:
|
||||
'The public site avoids promising certifications, guarantees, or full automation outcomes that are not yet appropriate to claim.',
|
||||
note: 'Restraint is part of credibility.',
|
||||
},
|
||||
];
|
||||
|
||||
export const securityTrustNotes: CalloutContent[] = [
|
||||
{
|
||||
eyebrow: 'Substantiated public posture',
|
||||
title: 'Substantiated public posture',
|
||||
description:
|
||||
'The launch story focuses on product shape and operator safeguards: inventory truth, immutable snapshots, safer restore flows, drift visibility, and review support.',
|
||||
tone: 'accent',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Sensitive connections',
|
||||
title: 'Sensitive Microsoft connections should be explained carefully.',
|
||||
description:
|
||||
'The public site acknowledges Graph- and tenant-facing access without pretending the site itself is part of the runtime trust boundary.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'What we will not say',
|
||||
title: 'No blanket assurances and no vague “fully automated governance” language.',
|
||||
description:
|
||||
'Trust pages lose credibility quickly when they substitute slogans for the actual controls and workflows a buyer will later inspect.',
|
||||
tone: 'subtle',
|
||||
},
|
||||
];
|
||||
@ -1,90 +0,0 @@
|
||||
import type {
|
||||
AudienceRowContent,
|
||||
FeatureItemContent,
|
||||
HeroContent,
|
||||
PageSeo,
|
||||
} from '@/types/site';
|
||||
|
||||
export const solutionsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Solutions',
|
||||
description:
|
||||
'TenantAtlas fits MSP and enterprise IT teams differently, and the public story should make those operating-model differences explicit.',
|
||||
path: '/solutions',
|
||||
};
|
||||
|
||||
export const solutionsHero: HeroContent = {
|
||||
eyebrow: 'Audience fit',
|
||||
title: 'Show how TenantAtlas fits MSP delivery teams and enterprise operators without collapsing them into one generic story.',
|
||||
description:
|
||||
'The product helps different organizations answer similar governance questions, but the surrounding workflow, accountability, and evidence needs are not identical. The site should acknowledge that directly.',
|
||||
primaryCta: {
|
||||
href: '/integrations',
|
||||
label: 'Review the ecosystem fit',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Talk through your evaluation path',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Separate MSP and enterprise language on purpose.',
|
||||
'Keep the product story stable while the buying context changes.',
|
||||
'Avoid forcing every visitor through the same generic motion.',
|
||||
],
|
||||
};
|
||||
|
||||
export const solutionsAudiences: AudienceRowContent[] = [
|
||||
{
|
||||
audience: 'MSP',
|
||||
title: 'MSP operating model',
|
||||
description:
|
||||
'Managed service providers need tenant-scoped operational truth, repeatable review workflows, and a way to explain change history to customers without drowning in manual evidence gathering.',
|
||||
bullets: [
|
||||
'Keep per-tenant history and restore posture reviewable during service delivery.',
|
||||
'Support a higher tempo of customer change while preserving a clean audit story.',
|
||||
'Give account and delivery teams a shared language for exceptions, findings, and follow-up.',
|
||||
],
|
||||
cta: {
|
||||
href: '/contact',
|
||||
label: 'Discuss MSP delivery fit',
|
||||
variant: 'secondary',
|
||||
},
|
||||
},
|
||||
{
|
||||
audience: 'Enterprise IT',
|
||||
title: 'Enterprise IT operating model',
|
||||
description:
|
||||
'Internal IT and security teams need durable version truth, change visibility, and review evidence that can stand up to operational leadership, audit, and cross-team scrutiny.',
|
||||
bullets: [
|
||||
'Reduce uncertainty around who changed what and when across the Microsoft tenant surface.',
|
||||
'Support internal review packs, exception handling, and evidence collection without fragmented tooling.',
|
||||
'Keep restore and remediation conversations grounded in the current tenant state and the relevant history.',
|
||||
],
|
||||
cta: {
|
||||
href: '/security-trust',
|
||||
label: 'Inspect the trust posture',
|
||||
variant: 'secondary',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const solutionsSignals: FeatureItemContent[] = [
|
||||
{
|
||||
eyebrow: 'Why buyers care',
|
||||
title: 'The product is serious about both velocity and control.',
|
||||
description:
|
||||
'Teams can move quickly without giving up visibility, confirmation discipline, or explainability when a risky change needs review.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Where it lands',
|
||||
title: 'The product belongs in the operating layer, not just the reporting layer.',
|
||||
description:
|
||||
'Visitors should understand that TenantAtlas helps teams make safer decisions about configuration state rather than merely summarize activity afterward.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'How it reads',
|
||||
title: 'The story changes by audience, but the product truth does not.',
|
||||
description:
|
||||
'MSP and enterprise readers see their own operating concerns reflected without the site inventing two different products.',
|
||||
},
|
||||
];
|
||||
@ -1,60 +0,0 @@
|
||||
import type { HeroContent, LegalSection, PageSeo } from '@/types/site';
|
||||
|
||||
export const termsSeo: PageSeo = {
|
||||
title: 'TenantAtlas | Terms',
|
||||
description:
|
||||
'Website terms for the public TenantAtlas surface, covering informational use of the site and the limits of public product statements.',
|
||||
path: '/terms',
|
||||
};
|
||||
|
||||
export const termsHero: HeroContent = {
|
||||
eyebrow: 'Website terms',
|
||||
title: 'Website terms for the public TenantAtlas surface.',
|
||||
description:
|
||||
'These terms describe the public website itself: informational use of the content, basic conduct expectations, and the fact that a public product site is not the same thing as a signed service agreement.',
|
||||
primaryCta: {
|
||||
href: '/contact',
|
||||
label: 'Return to contact',
|
||||
},
|
||||
secondaryCta: {
|
||||
href: '/privacy',
|
||||
label: 'Review privacy',
|
||||
variant: 'secondary',
|
||||
},
|
||||
highlights: [
|
||||
'Public copy explains the product but does not replace commercial agreements.',
|
||||
'The site is for evaluation and information, not operational control of a tenant.',
|
||||
'Any future service commitment belongs in explicit signed terms.',
|
||||
],
|
||||
};
|
||||
|
||||
export const termsSections: LegalSection[] = [
|
||||
{
|
||||
title: 'Informational website use',
|
||||
body: [
|
||||
'The public TenantAtlas website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.',
|
||||
'Nothing on the public site should be interpreted as a guarantee of product availability, certification, or commercial commitment unless it is later confirmed in signed agreements.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reasonable reliance',
|
||||
body: [
|
||||
'Visitors may use the public site to understand the product and decide whether to start a conversation, but they should not rely on public marketing pages as the sole source of contractual or implementation truth.',
|
||||
'Detailed service commitments, security terms, and procurement obligations belong in later commercial and legal documentation.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Acceptable conduct',
|
||||
body: [
|
||||
'Visitors should use the public website lawfully and should not attempt to interfere with the availability or integrity of the public site.',
|
||||
'The public website is not a runtime administration surface and should not be treated as one.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Questions',
|
||||
body: [
|
||||
'Questions about the public website terms, privacy, or future product legal materials can be routed through the public contact path.',
|
||||
'The legal hub remains the public anchor for later launch-ready legal disclosures.',
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -1,26 +1,15 @@
|
||||
---
|
||||
import '../styles/global.css';
|
||||
|
||||
import { siteMetadata } from '@/lib/site';
|
||||
|
||||
interface Props {
|
||||
canonicalUrl?: string;
|
||||
description?: string;
|
||||
openGraphDescription?: string;
|
||||
openGraphTitle?: string;
|
||||
robots?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
canonicalUrl,
|
||||
description = siteMetadata.siteDescription,
|
||||
robots = 'index,follow',
|
||||
title = `${siteMetadata.siteName} | ${siteMetadata.siteTagline}`,
|
||||
description = 'TenantPilot keeps Intune governance observable, reviewable, and safe to operate.',
|
||||
title = 'TenantPilot',
|
||||
} = Astro.props;
|
||||
|
||||
const openGraphTitle = Astro.props.openGraphTitle ?? title;
|
||||
const openGraphDescription = Astro.props.openGraphDescription ?? description;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@ -28,22 +17,11 @@ const openGraphDescription = Astro.props.openGraphDescription ?? description;
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="robots" content={robots} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta property="og:site_name" content={siteMetadata.siteName} />
|
||||
<meta property="og:title" content={openGraphTitle} />
|
||||
<meta property="og:description" content={openGraphDescription} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={openGraphTitle} />
|
||||
<meta name="twitter:description" content={openGraphDescription} />
|
||||
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#content">Skip to content</a>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { coreRoutes, siteMetadata } from '@/lib/site';
|
||||
import type { PageSeo } from '@/types/site';
|
||||
|
||||
export interface ResolvedSeo extends PageSeo {
|
||||
canonicalUrl: string;
|
||||
ogDescription: string;
|
||||
ogTitle: string;
|
||||
robots: string;
|
||||
}
|
||||
|
||||
export function buildCanonicalUrl(path: string): string {
|
||||
return new URL(path, siteMetadata.siteUrl).toString();
|
||||
}
|
||||
|
||||
export function resolveSeo(seo: PageSeo): ResolvedSeo {
|
||||
return {
|
||||
...seo,
|
||||
canonicalUrl: buildCanonicalUrl(seo.path),
|
||||
ogDescription: seo.ogDescription ?? seo.description,
|
||||
ogTitle: seo.ogTitle ?? seo.title,
|
||||
robots: 'index,follow',
|
||||
};
|
||||
}
|
||||
|
||||
export function sitemapEntries(): string[] {
|
||||
return [...coreRoutes].map((path) => buildCanonicalUrl(path));
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
import type {
|
||||
CtaLink,
|
||||
FooterNavigationGroup,
|
||||
NavigationItem,
|
||||
SiteMetadata,
|
||||
} from '@/types/site';
|
||||
|
||||
export const siteMetadata: SiteMetadata = {
|
||||
siteName: 'TenantAtlas',
|
||||
siteTagline: 'Governance of record for Microsoft tenant operations.',
|
||||
siteDescription:
|
||||
'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.',
|
||||
siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example',
|
||||
};
|
||||
|
||||
export const primaryNavigation: NavigationItem[] = [
|
||||
{ href: '/product', label: 'Product', description: 'Understand the operating model.' },
|
||||
{ href: '/solutions', label: 'Solutions', description: 'See the fit for MSP and enterprise teams.' },
|
||||
{ href: '/security-trust', label: 'Security & Trust', description: 'Review the product posture.' },
|
||||
{ href: '/integrations', label: 'Integrations', description: 'Inspect the real ecosystem fit.' },
|
||||
{ href: '/contact', label: 'Contact', description: 'Reach the team for a working session.' },
|
||||
];
|
||||
|
||||
export const footerNavigationGroups: FooterNavigationGroup[] = [
|
||||
{
|
||||
title: 'Explore',
|
||||
items: [
|
||||
{ href: '/', label: 'Home' },
|
||||
{ href: '/product', label: 'Product' },
|
||||
{ href: '/solutions', label: 'Solutions' },
|
||||
{ href: '/security-trust', label: 'Security & Trust' },
|
||||
{ href: '/integrations', label: 'Integrations' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Next step',
|
||||
items: [
|
||||
{ href: '/contact', label: 'Contact / Demo' },
|
||||
{ href: '/legal', label: 'Legal' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Legal',
|
||||
items: [
|
||||
{ href: '/privacy', label: 'Privacy' },
|
||||
{ href: '/terms', label: 'Terms' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const contactCta: CtaLink = {
|
||||
href: '/contact',
|
||||
label: 'Request a working session',
|
||||
helper: 'Bring your governance questions, rollout concerns, or evaluation goals.',
|
||||
variant: 'primary',
|
||||
};
|
||||
|
||||
export const coreRoutes = [
|
||||
'/',
|
||||
'/product',
|
||||
'/solutions',
|
||||
'/security-trust',
|
||||
'/integrations',
|
||||
'/contact',
|
||||
'/legal',
|
||||
'/privacy',
|
||||
'/terms',
|
||||
] as const;
|
||||
|
||||
export function isActiveNavigationPath(currentPath: string, href: string): boolean {
|
||||
if (href === '/') {
|
||||
return currentPath === '/';
|
||||
}
|
||||
|
||||
return currentPath === href || currentPath.startsWith(`${href}/`);
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
---
|
||||
import ContactPanel from '@/components/content/ContactPanel.astro';
|
||||
import DemoPrompt from '@/components/content/DemoPrompt.astro';
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Cluster from '@/components/primitives/Cluster.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Input from '@/components/primitives/Input.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import Textarea from '@/components/primitives/Textarea.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
contactHero,
|
||||
contactLegalSections,
|
||||
contactPreview,
|
||||
contactPrompts,
|
||||
contactSeo,
|
||||
contactTopics,
|
||||
} from '@/content/pages/contact';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/contact" title={contactSeo.title} description={contactSeo.description}>
|
||||
<PageHero
|
||||
hero={contactHero}
|
||||
calloutTitle="Qualified conversations beat anonymous form funnels."
|
||||
calloutDescription="The page should make it obvious who should reach out, why, and what a useful first exchange looks like."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<Grid cols="3">
|
||||
<ContactPanel cta={contactHero.primaryCta} points={contactTopics} title="Who should get in touch" />
|
||||
{contactPrompts.map((prompt) => <DemoPrompt title={prompt.title} description={prompt.description} />)}
|
||||
</Grid>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,0.8fr]">
|
||||
<Card>
|
||||
<SectionHeader
|
||||
eyebrow="Suggested note"
|
||||
title="Give the team enough context to make the first reply useful."
|
||||
description="The public path stays static in v0, but it can still help a serious buyer structure the first message."
|
||||
/>
|
||||
<div class="mt-6 space-y-4">
|
||||
<Input readonly value={contactPreview.topic} />
|
||||
<Textarea readonly rows={7} value={contactPreview.message} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card variant="accent">
|
||||
<SectionHeader
|
||||
eyebrow="Before you share details"
|
||||
title="Legal basics stay visible from the contact flow."
|
||||
description="Visitors should be able to inspect privacy and terms before they continue."
|
||||
/>
|
||||
<Cluster class="mt-6">
|
||||
<a
|
||||
href="/privacy"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
<a
|
||||
href="/terms"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Terms
|
||||
</a>
|
||||
<a
|
||||
href="/legal"
|
||||
class="inline-flex min-h-11 items-center justify-center rounded-full border border-[color:var(--color-line)] bg-white/85 px-5 text-sm font-semibold text-[var(--color-ink-900)]"
|
||||
>
|
||||
Legal
|
||||
</a>
|
||||
</Cluster>
|
||||
<div class="mt-6">
|
||||
<RichText sections={contactLegalSections} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move from a public introduction into the legal and product details that support a real evaluation."
|
||||
description="The contact route should never strand a serious buyer. The next path stays visible whether they need product context, privacy details, or website terms."
|
||||
primary={{ href: '/legal', label: 'Read the legal surface' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,65 +1,66 @@
|
||||
---
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import LogoStrip from '@/components/sections/LogoStrip.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
homeEcosystem,
|
||||
homeHero,
|
||||
homeMetrics,
|
||||
homePillars,
|
||||
homeProofBlocks,
|
||||
homeSeo,
|
||||
} from '@/content/pages/home';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/" title={homeSeo.title} description={homeSeo.description}>
|
||||
<PageHero
|
||||
hero={homeHero}
|
||||
metrics={homeMetrics}
|
||||
calloutTitle="Governance of record for Microsoft tenant operations."
|
||||
calloutDescription="The public story positions TenantAtlas as a trust-first system for version truth, safer restore posture, drift visibility, evidence, and review support."
|
||||
/>
|
||||
<BaseLayout
|
||||
title="TenantPilot | Workspace Foundation"
|
||||
description="The first public TenantPilot website surface for workspace-safe Intune operations."
|
||||
>
|
||||
<main class="page-shell">
|
||||
<section class="hero">
|
||||
<p class="eyebrow">TenantPilot</p>
|
||||
<h1>One public website, one stable platform, one clear workspace model.</h1>
|
||||
<p class="lede">
|
||||
TenantPilot keeps Intune change management auditable for operators while the public
|
||||
website stays fast, static, and operationally separate from the Laravel platform.
|
||||
</p>
|
||||
|
||||
<LogoStrip
|
||||
eyebrow="Ecosystem fit"
|
||||
title="Built around the Microsoft tenant reality buyers already need to govern."
|
||||
items={homeEcosystem}
|
||||
/>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Product pillars"
|
||||
title="Explain the product in connected pillars, not isolated promises."
|
||||
description="Each section of the site should help a first-time visitor understand why backup, restore, findings, evidence, and reviews belong together."
|
||||
items={homePillars}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Public proof"
|
||||
title="A credible first reading should answer the buyer’s next two questions before they ask them."
|
||||
description="Why is this product category needed now, and why should anyone trust the story enough to continue?"
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{homeProofBlocks.map((block) => <Callout content={block} />)}
|
||||
</Grid>
|
||||
<div class="hero-actions">
|
||||
<a class="primary-action" href="#workspace-model">View the workspace model</a>
|
||||
<a class="secondary-action" href="#boundaries">Review the isolation rules</a>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
</section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move from first-glance clarity into the deeper product story."
|
||||
description="From the Home page, visitors should be able to inspect the product model, review trust framing, or reach the contact path without guessing where to go next."
|
||||
primary={{ href: '/product', label: 'See the product model' }}
|
||||
secondary={{ href: '/contact', label: 'Start the working session', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
<section class="signal-grid" id="workspace-model" aria-label="Workspace foundations">
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Platform</p>
|
||||
<h2>Laravel stays in <code>apps/platform</code>.</h2>
|
||||
<p>
|
||||
Sail, Filament, Livewire, and deployment-sensitive runtime concerns remain
|
||||
platform-owned and unchanged.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Website</p>
|
||||
<h2>Astro lives independently in <code>apps/website</code>.</h2>
|
||||
<p>
|
||||
Public pages build statically, run without Laravel, and keep their own dev and
|
||||
build outputs.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="signal-card">
|
||||
<p class="signal-label">Root</p>
|
||||
<h2>The repository root orchestrates without becoming an app.</h2>
|
||||
<p>
|
||||
Root scripts expose the official entry commands while app-local execution logic
|
||||
stays inside each app directory.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="boundary-panel" id="boundaries">
|
||||
<div>
|
||||
<p class="eyebrow">Isolation</p>
|
||||
<h2>Builds, ports, and ownership stay intentionally separate.</h2>
|
||||
</div>
|
||||
|
||||
<ul class="boundary-list">
|
||||
<li>Website dev defaults to port 4321 and supports explicit port overrides.</li>
|
||||
<li>Platform Docker, queues, and Filament assets stay under the existing Sail flow.</li>
|
||||
<li>No shared package layer, CMS, or extra app surface is introduced in this slice.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
---
|
||||
import IntegrationBadge from '@/components/content/IntegrationBadge.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
integrationEntries,
|
||||
integrationRules,
|
||||
integrationsHero,
|
||||
integrationsSeo,
|
||||
} from '@/content/pages/integrations';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/integrations" title={integrationsSeo.title} description={integrationsSeo.description}>
|
||||
<PageHero
|
||||
hero={integrationsHero}
|
||||
calloutTitle="Real direction beats a longer wishlist."
|
||||
calloutDescription="The integrations page should reinforce where the product actually fits today and why those boundaries improve trust rather than limit it."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Current direction"
|
||||
title="Show the systems that shape the product workflow today."
|
||||
description="This page should stay focused on the contracts and ecosystems that matter to Microsoft tenant governance work now."
|
||||
/>
|
||||
<Grid cols="2">
|
||||
{integrationEntries.map((item) => <IntegrationBadge item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Page rules"
|
||||
title="Use the integrations page to clarify scope, not to perform ambition."
|
||||
description="The public site becomes more credible when it names the real ecosystem fit and avoids presenting speculative adjacencies as if they were launch truth."
|
||||
items={integrationRules}
|
||||
/>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Turn ecosystem fit into a practical evaluation conversation."
|
||||
description="Once a buyer sees the Microsoft-centric fit, the next useful step is a working session about their current environment, governance needs, and rollout questions."
|
||||
primary={{ href: '/contact', label: 'Plan the working session' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,75 +0,0 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Card from '@/components/primitives/Card.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { legalHero, legalNoticeSections, legalSeo } from '@/content/pages/legal';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/legal" title={legalSeo.title} description={legalSeo.description}>
|
||||
<PageHero
|
||||
hero={legalHero}
|
||||
calloutTitle="Public legal basics belong in one obvious place."
|
||||
calloutDescription="The legal hub keeps the conversion path honest by making privacy, terms, and notice routing easy to find before or during evaluation conversations."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Available now"
|
||||
title="Privacy and terms are published as standalone routes."
|
||||
description="The legal hub should work as an index and as the public home for launch-required legal notices."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
<Card>
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Privacy</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
Review how the public contact path handles inquiry information and what this static website does not claim to process.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="/privacy">
|
||||
Privacy
|
||||
</a>
|
||||
</Card>
|
||||
<Card>
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Terms</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
Read the website terms that explain the public-site scope and why marketing pages do not replace signed agreements.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="/terms">
|
||||
Terms
|
||||
</a>
|
||||
</Card>
|
||||
<Card variant="accent">
|
||||
<h3 class="m-0 text-2xl font-semibold text-[var(--color-ink-900)]">Public legal notice</h3>
|
||||
<p class="mt-3 text-base leading-7 text-[var(--color-copy)]">
|
||||
This hub also owns the future launch-ready operator identity and jurisdiction-specific disclosure section.
|
||||
</p>
|
||||
<a class="mt-5 inline-flex text-sm font-semibold text-[var(--color-brand)]" href="#public-legal-notice">
|
||||
Legal notice
|
||||
</a>
|
||||
</Card>
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<Section id="public-legal-notice">
|
||||
<Container wide>
|
||||
<RichText sections={legalNoticeSections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Return to the contact path once the legal basics are clear."
|
||||
description="The legal surface should support a qualified conversation, not interrupt it."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/product', label: 'Revisit the product model', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,31 +0,0 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { privacyHero, privacySections, privacySeo } from '@/content/pages/privacy';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/privacy" title={privacySeo.title} description={privacySeo.description}>
|
||||
<PageHero
|
||||
hero={privacyHero}
|
||||
calloutTitle="Public-site privacy should stay narrow and readable."
|
||||
calloutDescription="The page explains the website and inquiry path clearly without pretending to be the product’s full data-processing documentation."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<RichText sections={privacySections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Return to the product or contact flow after reviewing public-site privacy."
|
||||
description="Visitors should be able to move back into the evaluation path without losing context."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/terms', label: 'Review website terms', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,61 +0,0 @@
|
||||
---
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
productHero,
|
||||
productMetrics,
|
||||
productModelBlocks,
|
||||
productNarrative,
|
||||
productSeo,
|
||||
} from '@/content/pages/product';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/product" title={productSeo.title} description={productSeo.description}>
|
||||
<PageHero
|
||||
hero={productHero}
|
||||
metrics={productMetrics}
|
||||
calloutTitle="Connected governance model"
|
||||
calloutDescription="TenantAtlas connects present-state inventory, immutable snapshots, restore posture, drift, exceptions, and evidence so teams can explain what happened before they decide what to do next."
|
||||
/>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Connected governance model"
|
||||
title="Treat the product as one operating system for safer tenant change management."
|
||||
description="This page explains how the pieces fit together so visitors do not mistake the product for a loose collection of backup, reporting, and restore features."
|
||||
items={productModelBlocks}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Narrative"
|
||||
title="Explain the operator journey, not just the capabilities."
|
||||
description="The public product page should make it obvious how the product helps a team move from current-state understanding into reviewable action."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{productNarrative.map((block) => <Callout content={block} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Inspect whether the operating model fits your audience and workflow."
|
||||
description="The next useful questions are who the product is for, how trust claims stay grounded, and what a working conversation with the team should cover."
|
||||
primary={{ href: '/solutions', label: 'See audience fit' }}
|
||||
secondary={{
|
||||
href: '/contact',
|
||||
label: 'Talk through your current operating model',
|
||||
variant: 'secondary',
|
||||
}}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,59 +0,0 @@
|
||||
---
|
||||
import Callout from '@/components/content/Callout.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import TrustGrid from '@/components/sections/TrustGrid.astro';
|
||||
import {
|
||||
securityPrinciples,
|
||||
securityTrustHero,
|
||||
securityTrustNotes,
|
||||
securityTrustSeo,
|
||||
} from '@/content/pages/security-trust';
|
||||
---
|
||||
|
||||
<PageShell
|
||||
currentPath="/security-trust"
|
||||
title={securityTrustSeo.title}
|
||||
description={securityTrustSeo.description}
|
||||
>
|
||||
<PageHero
|
||||
hero={securityTrustHero}
|
||||
calloutTitle="Trust-first, not trust-theater."
|
||||
calloutDescription="The page should help technical buyers see the operator safeguards and the intentional limits of the launch story before they hear a sales pitch."
|
||||
/>
|
||||
|
||||
<TrustGrid
|
||||
eyebrow="Product posture"
|
||||
title="Operational trust starts with the way the product handles risky decisions."
|
||||
description="The trust page should explain the guardrails that matter to a serious buyer: previewability, attributable change history, evidence linkage, and restrained public claims."
|
||||
items={securityPrinciples}
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Public messaging"
|
||||
title="Substantiated public posture"
|
||||
description="Keep the public trust story within the set of claims the team can support at launch."
|
||||
/>
|
||||
<Grid cols="3">
|
||||
{securityTrustNotes.map((note) => <Callout content={note} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Legal clarity and conversation path should stay reachable from the trust page."
|
||||
description="A buyer evaluating trust should be able to move directly to public legal information or a working discussion without friction."
|
||||
primary={{ href: '/legal', label: 'Read the legal surface' }}
|
||||
secondary={{ href: '/contact', label: 'Discuss trust requirements', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,20 +0,0 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
import { sitemapEntries } from '@/lib/seo';
|
||||
|
||||
export const GET: APIRoute = () => {
|
||||
const urls = sitemapEntries()
|
||||
.map((url) => ` <url><loc>${url}</loc></url>`)
|
||||
.join('\n');
|
||||
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls}
|
||||
</urlset>`;
|
||||
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1,55 +0,0 @@
|
||||
---
|
||||
import AudienceRow from '@/components/content/AudienceRow.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Grid from '@/components/primitives/Grid.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import SectionHeader from '@/components/primitives/SectionHeader.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import FeatureGrid from '@/components/sections/FeatureGrid.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import {
|
||||
solutionsAudiences,
|
||||
solutionsHero,
|
||||
solutionsSeo,
|
||||
solutionsSignals,
|
||||
} from '@/content/pages/solutions';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/solutions" title={solutionsSeo.title} description={solutionsSeo.description}>
|
||||
<PageHero
|
||||
hero={solutionsHero}
|
||||
calloutTitle="Audience-specific fit without product sprawl."
|
||||
calloutDescription="The public site can speak differently to MSP and enterprise visitors while staying anchored to the same product truth."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<div class="space-y-8">
|
||||
<SectionHeader
|
||||
eyebrow="Operating models"
|
||||
title="Separate the delivery context clearly."
|
||||
description="Visitors should be able to recognize themselves in the page quickly, without translating a generic story into their own workflow."
|
||||
/>
|
||||
<Grid cols="2">
|
||||
{solutionsAudiences.map((item) => <AudienceRow item={item} />)}
|
||||
</Grid>
|
||||
</div>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<FeatureGrid
|
||||
eyebrow="Buying signal"
|
||||
title="Give the buyer a concrete reason to keep evaluating."
|
||||
description="The goal is not to decorate the page with vertical tags. The goal is to show why the product belongs in the operating model for that audience."
|
||||
items={solutionsSignals}
|
||||
/>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Continue"
|
||||
title="Inspect the ecosystem fit after you understand the audience fit."
|
||||
description="Once a visitor sees the product reflected in their operating model, the next useful question is how it fits the surrounding Microsoft tenant environment."
|
||||
primary={{ href: '/integrations', label: 'Review the ecosystem fit' }}
|
||||
secondary={{ href: '/contact', label: 'Talk through your evaluation path', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,31 +0,0 @@
|
||||
---
|
||||
import RichText from '@/components/content/RichText.astro';
|
||||
import PageShell from '@/components/layout/PageShell.astro';
|
||||
import Container from '@/components/primitives/Container.astro';
|
||||
import Section from '@/components/primitives/Section.astro';
|
||||
import CTASection from '@/components/sections/CTASection.astro';
|
||||
import PageHero from '@/components/sections/PageHero.astro';
|
||||
import { termsHero, termsSections, termsSeo } from '@/content/pages/terms';
|
||||
---
|
||||
|
||||
<PageShell currentPath="/terms" title={termsSeo.title} description={termsSeo.description}>
|
||||
<PageHero
|
||||
hero={termsHero}
|
||||
calloutTitle="Public terms define the site, not a service contract."
|
||||
calloutDescription="The page keeps the public website honest about what it can explain and what still belongs in later commercial/legal paperwork."
|
||||
/>
|
||||
|
||||
<Section>
|
||||
<Container wide>
|
||||
<RichText sections={termsSections} />
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<CTASection
|
||||
eyebrow="Next step"
|
||||
title="Move back into privacy or contact once the website terms are clear."
|
||||
description="The legal path should stay connected to the rest of the evaluation journey."
|
||||
primary={{ href: '/contact', label: 'Return to contact' }}
|
||||
secondary={{ href: '/privacy', label: 'Review privacy', variant: 'secondary' }}
|
||||
/>
|
||||
</PageShell>
|
||||
@ -1,157 +1,221 @@
|
||||
@import "tailwindcss";
|
||||
@import "./tokens.css";
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--color-ink-900: #11243a;
|
||||
--color-ink-800: #233a53;
|
||||
--color-copy: #42556a;
|
||||
--color-line: rgba(17, 36, 58, 0.14);
|
||||
--color-panel: rgba(255, 255, 255, 0.82);
|
||||
--color-panel-strong: rgba(255, 255, 255, 0.95);
|
||||
--color-panel-soft: rgba(243, 247, 251, 0.86);
|
||||
--color-brand: #2f6fb7;
|
||||
--color-brand-soft: rgba(47, 111, 183, 0.12);
|
||||
--color-signal: #3b8b78;
|
||||
--color-warm: #af6d43;
|
||||
--shadow-panel: 0 24px 80px rgba(17, 36, 58, 0.12);
|
||||
--shadow-soft: 0 18px 48px rgba(17, 36, 58, 0.08);
|
||||
--bg: #f6efe5;
|
||||
--bg-accent: #fffdf9;
|
||||
--surface: rgba(255, 255, 255, 0.74);
|
||||
--surface-strong: rgba(255, 255, 255, 0.92);
|
||||
--ink: #17120f;
|
||||
--muted: #66584d;
|
||||
--line: rgba(23, 18, 15, 0.12);
|
||||
--accent: #cc5f2c;
|
||||
--accent-deep: #8b3820;
|
||||
--shadow: 0 30px 80px rgba(103, 52, 33, 0.16);
|
||||
font-family: "Avenir Next", "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(255, 255, 255, 0.92), transparent 32%),
|
||||
radial-gradient(circle at top right, rgba(92, 149, 215, 0.18), transparent 28%),
|
||||
linear-gradient(180deg, #f6f3ee 0%, #edf2f7 56%, #f3f7fb 100%);
|
||||
scroll-behavior: smooth;
|
||||
radial-gradient(circle at top left, rgba(255, 201, 149, 0.55), transparent 34%),
|
||||
radial-gradient(circle at right 12% top 10%, rgba(255, 145, 96, 0.18), transparent 24%),
|
||||
linear-gradient(180deg, #fffaf3 0%, var(--bg) 58%, #efe3d5 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-ink-900);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
font-family: "SFMono-Regular", "SF Mono", "IBM Plex Mono", monospace;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(47, 111, 183, 0.18);
|
||||
color: var(--color-ink-900);
|
||||
.page-shell {
|
||||
width: min(1120px, calc(100% - 2rem));
|
||||
margin: 0 auto;
|
||||
padding: 4.5rem 0 5rem;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 3px solid rgba(47, 111, 183, 0.32);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.surface-shell {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.surface-shell::before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -2;
|
||||
content: "";
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.65), transparent 16%),
|
||||
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.7), transparent 28%);
|
||||
}
|
||||
|
||||
.surface-shell::after {
|
||||
position: absolute;
|
||||
inset: 1rem;
|
||||
z-index: -1;
|
||||
content: "";
|
||||
border: 1px solid rgba(17, 36, 58, 0.04);
|
||||
border-radius: 2rem;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
z-index: 40;
|
||||
transform: translateY(-200%);
|
||||
border-radius: 999px;
|
||||
background: var(--color-ink-900);
|
||||
padding: 0.75rem 1rem;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.glass-panel {
|
||||
background: linear-gradient(180deg, var(--color-panel-strong), var(--color-panel));
|
||||
box-shadow: var(--shadow-panel);
|
||||
.hero,
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
border-top: 1px solid rgba(17, 36, 58, 0.08);
|
||||
.hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: clamp(2rem, 4vw, 4.5rem);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 2rem;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.88), rgba(255, 247, 239, 0.72)),
|
||||
linear-gradient(120deg, rgba(204, 95, 44, 0.08), rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.legal-prose p {
|
||||
.hero::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto -8rem -8rem auto;
|
||||
width: 18rem;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 999px;
|
||||
background: radial-gradient(circle, rgba(204, 95, 44, 0.22), transparent 72%);
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.signal-label {
|
||||
margin: 0 0 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-deep);
|
||||
}
|
||||
|
||||
.hero h1,
|
||||
.boundary-panel h2,
|
||||
.signal-card h2 {
|
||||
margin: 0;
|
||||
color: var(--color-copy);
|
||||
line-height: 1.8;
|
||||
font-family: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
line-height: 0.95;
|
||||
}
|
||||
|
||||
.legal-prose p + p {
|
||||
margin-top: 1rem;
|
||||
.hero h1 {
|
||||
max-width: 13ch;
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
}
|
||||
|
||||
.legal-prose ul {
|
||||
.lede {
|
||||
max-width: 46rem;
|
||||
margin: 1.5rem 0 0;
|
||||
font-size: clamp(1.05rem, 2vw, 1.35rem);
|
||||
line-height: 1.7;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.primary-action,
|
||||
.secondary-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 3.25rem;
|
||||
padding: 0.9rem 1.4rem;
|
||||
border-radius: 999px;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
transform 180ms ease,
|
||||
box-shadow 180ms ease,
|
||||
background-color 180ms ease;
|
||||
}
|
||||
|
||||
.primary-action {
|
||||
background: var(--ink);
|
||||
color: #fff7f1;
|
||||
}
|
||||
|
||||
.secondary-action {
|
||||
border: 1px solid rgba(23, 18, 15, 0.12);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.primary-action:hover,
|
||||
.secondary-action:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.signal-grid {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
margin-top: 1.4rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
padding: 1.6rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 1.5rem;
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.signal-card h2 {
|
||||
font-size: clamp(1.55rem, 3vw, 2.1rem);
|
||||
}
|
||||
|
||||
.signal-card p:last-child,
|
||||
.boundary-list {
|
||||
margin: 1rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
color: var(--color-copy);
|
||||
line-height: 1.75;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.legal-prose li + li {
|
||||
margin-top: 0.6rem;
|
||||
.boundary-panel {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
margin-top: 1.25rem;
|
||||
background: var(--surface-strong);
|
||||
}
|
||||
|
||||
.motion-rise {
|
||||
animation: rise-in 520ms ease both;
|
||||
.boundary-panel h2 {
|
||||
font-size: clamp(2rem, 4vw, 3.1rem);
|
||||
}
|
||||
|
||||
@keyframes rise-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
.boundary-list {
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
.boundary-list li + li {
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.signal-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.page-shell {
|
||||
width: min(100% - 1.25rem, 1120px);
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.signal-card,
|
||||
.boundary-panel {
|
||||
border-radius: 1.3rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 1.4rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.primary-action,
|
||||
.secondary-action {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
@theme {
|
||||
--font-sans: "Avenir Next", "Segoe UI", sans-serif;
|
||||
--font-display: "Iowan Old Style", "Palatino Linotype", serif;
|
||||
--font-mono: "IBM Plex Mono", "SFMono-Regular", monospace;
|
||||
|
||||
--color-shell-50: oklch(0.985 0.01 86);
|
||||
--color-shell-100: oklch(0.97 0.015 85);
|
||||
--color-shell-200: oklch(0.935 0.025 84);
|
||||
--color-shell-300: oklch(0.88 0.04 78);
|
||||
--color-shell-900: oklch(0.19 0.03 65);
|
||||
--color-shell-950: oklch(0.14 0.025 62);
|
||||
--color-brand-400: oklch(0.76 0.09 210);
|
||||
--color-brand-500: oklch(0.68 0.11 218);
|
||||
--color-brand-700: oklch(0.49 0.11 228);
|
||||
--color-signal-400: oklch(0.78 0.1 162);
|
||||
--color-signal-700: oklch(0.52 0.08 170);
|
||||
--color-warm-300: oklch(0.87 0.05 53);
|
||||
--color-warm-500: oklch(0.69 0.09 48);
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'ghost';
|
||||
export type PageRole =
|
||||
| 'home'
|
||||
| 'product'
|
||||
| 'solutions'
|
||||
| 'trust'
|
||||
| 'integrations'
|
||||
| 'contact'
|
||||
| 'legal';
|
||||
|
||||
export interface CtaLink {
|
||||
href: string;
|
||||
label: string;
|
||||
helper?: string;
|
||||
target?: '_blank' | '_self';
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
export interface NavigationItem {
|
||||
href: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface FooterNavigationGroup {
|
||||
items: NavigationItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface SiteMetadata {
|
||||
siteDescription: string;
|
||||
siteName: string;
|
||||
siteTagline: string;
|
||||
siteUrl: string;
|
||||
}
|
||||
|
||||
export interface PageSeo {
|
||||
description: string;
|
||||
ogDescription?: string;
|
||||
ogTitle?: string;
|
||||
path: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface HeroContent {
|
||||
description: string;
|
||||
eyebrow: string;
|
||||
highlights?: string[];
|
||||
primaryCta: CtaLink;
|
||||
secondaryCta?: CtaLink;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface MetricItem {
|
||||
description: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface FeatureItemContent {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
href?: string;
|
||||
meta?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface CalloutContent {
|
||||
description: string;
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
tone?: 'accent' | 'neutral' | 'subtle';
|
||||
}
|
||||
|
||||
export interface AudienceRowContent {
|
||||
audience: string;
|
||||
bullets: string[];
|
||||
cta?: CtaLink;
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface TrustPrincipleContent {
|
||||
description: string;
|
||||
note?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IntegrationEntry {
|
||||
category: string;
|
||||
name: string;
|
||||
note?: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface LogoStripItem {
|
||||
label: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface LegalSection {
|
||||
body: string[];
|
||||
bullets?: string[];
|
||||
title: string;
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
openMobileNavigation,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
const coreRoutes = ['/', '/product', '/solutions', '/security-trust', '/integrations', '/contact'] as const;
|
||||
|
||||
test('contact page qualifies the conversation and keeps legal links reachable', async ({ page }) => {
|
||||
await visitPage(page, '/contact');
|
||||
await expectShell(page, 'Start a qualified working session instead of a generic demo request.');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Email the TenantAtlas team' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('legal, privacy, and terms routes are published and linked', async ({ page }) => {
|
||||
await visitPage(page, '/legal');
|
||||
await expectShell(page, 'Legal access should stay one click away from the contact path.');
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible();
|
||||
|
||||
await visitPage(page, '/privacy');
|
||||
await expectShell(page, 'Public-site privacy overview for TenantAtlas inquiries.');
|
||||
|
||||
await visitPage(page, '/terms');
|
||||
await expectShell(page, 'Website terms for the public TenantAtlas surface.');
|
||||
});
|
||||
|
||||
test('core pages keep contact and legal paths within reach', async ({ page }) => {
|
||||
for (const path of coreRoutes) {
|
||||
await visitPage(page, path);
|
||||
await expectFooterLinks(page);
|
||||
|
||||
if (path !== '/contact') {
|
||||
await expect(page.locator('main a[href="/contact"]').first()).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('mobile navigation', () => {
|
||||
test.use({ viewport: { width: 390, height: 844 } });
|
||||
|
||||
test('mobile menu exposes the published contact and legal paths', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await openMobileNavigation(page);
|
||||
await expect(page.getByRole('banner').getByRole('link', { name: /Contact/ }).first()).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Privacy' })).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Terms' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
@ -1,39 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
test('home explains the product category and exposes the next step', async ({ page }) => {
|
||||
await visitPage(page, '/');
|
||||
await expectShell(page, /TenantAtlas/);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Governance of record for Microsoft tenant operations.' }).first(),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'See the product model' }).first()).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('main').getByRole('link', { name: 'Review the trust posture' }).first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('product explains the connected operating model instead of a loose feature list', async ({
|
||||
page,
|
||||
}) => {
|
||||
await visitPage(page, '/product');
|
||||
await expectShell(page, 'One operating model for change history, drift visibility, and review readiness.');
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'Connected governance model' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'See audience fit' }).first()).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByRole('main')
|
||||
.getByRole('link', { name: 'Talk through your current operating model' })
|
||||
.first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
@ -1,45 +0,0 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
export const primaryNavigationLabels = [
|
||||
'Product',
|
||||
'Solutions',
|
||||
'Security & Trust',
|
||||
'Integrations',
|
||||
'Contact',
|
||||
] as const;
|
||||
|
||||
export const footerLabels = ['Legal', 'Privacy', 'Terms', 'Contact / Demo'] as const;
|
||||
|
||||
export async function visitPage(page: Page, path: string): Promise<void> {
|
||||
await page.goto(path);
|
||||
await expect(page).toHaveURL(new RegExp(path === '/' ? '/?$' : `${path}$`));
|
||||
}
|
||||
|
||||
export async function expectShell(page: Page, heading: string | RegExp): Promise<void> {
|
||||
await expect(page.getByRole('banner')).toBeVisible();
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
await expect(page.getByRole('contentinfo')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { level: 1, name: heading })).toBeVisible();
|
||||
}
|
||||
|
||||
export async function expectPrimaryNavigation(page: Page): Promise<void> {
|
||||
const header = page.getByRole('banner');
|
||||
|
||||
for (const label of primaryNavigationLabels) {
|
||||
await expect(header.getByRole('link', { name: label })).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function expectFooterLinks(page: Page): Promise<void> {
|
||||
for (const label of footerLabels) {
|
||||
await expect(page.getByRole('contentinfo').getByRole('link', { name: label })).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function openMobileNavigation(page: Page): Promise<void> {
|
||||
const menuTrigger = page.getByLabel('Open navigation menu');
|
||||
|
||||
if (await menuTrigger.isVisible()) {
|
||||
await menuTrigger.click();
|
||||
}
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
expectFooterLinks,
|
||||
expectPrimaryNavigation,
|
||||
expectShell,
|
||||
visitPage,
|
||||
} from './smoke-helpers';
|
||||
|
||||
test('solutions separates MSP and enterprise fit clearly', async ({ page }) => {
|
||||
await visitPage(page, '/solutions');
|
||||
await expectShell(page, /MSP|enterprise/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'MSP operating model' })).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Enterprise IT operating model' })).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Review the ecosystem fit' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('security and trust stays grounded in substantiated product posture', async ({ page }) => {
|
||||
await visitPage(page, '/security-trust');
|
||||
await expectShell(page, /trust posture|trust-first/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByRole('heading', { name: 'Substantiated public posture' }).first()).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Read the legal surface' }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('integrations shows real ecosystem direction without wishlist claims', async ({ page }) => {
|
||||
await visitPage(page, '/integrations');
|
||||
await expectShell(page, /ecosystem fit|integrations/i);
|
||||
await expectPrimaryNavigation(page);
|
||||
await expectFooterLinks(page);
|
||||
await expect(page.getByText('Microsoft Graph')).toBeVisible();
|
||||
await expect(page.getByText('Entra ID')).toBeVisible();
|
||||
await expect(page.getByRole('main').getByRole('link', { name: 'Plan the working session' }).first()).toBeVisible();
|
||||
});
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@ -489,11 +489,6 @@ ## 14. Adoption Rules
|
||||
- new specs that materially affect operator UX
|
||||
|
||||
Specs should explicitly reference this document where relevant.
|
||||
Operational authoring and review workflow lives in `.specify/templates/spec-template.md`,
|
||||
`.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`,
|
||||
`.specify/templates/checklist-template.md`, and `.specify/README.md`.
|
||||
This document remains the rule and reference layer, not a second checklist
|
||||
or close-out workflow.
|
||||
|
||||
Recommended spec fields:
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ importers:
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.0
|
||||
version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
version: 4.2.2(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
axios:
|
||||
specifier: ^1.11.0
|
||||
version: 1.14.0
|
||||
@ -27,7 +27,7 @@ importers:
|
||||
version: 0.45.2(pg@8.20.0)
|
||||
laravel-vite-plugin:
|
||||
specifier: ^2.0.0
|
||||
version: 2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
version: 2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
pg:
|
||||
specifier: ^8.16.3
|
||||
version: 8.20.0
|
||||
@ -39,29 +39,13 @@ importers:
|
||||
version: 4.2.2
|
||||
vite:
|
||||
specifier: ^7.0.7
|
||||
version: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
version: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
apps/website:
|
||||
dependencies:
|
||||
astro:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3)
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.59.1
|
||||
version: 1.59.1
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
'@types/node':
|
||||
specifier: ^24.7.2
|
||||
version: 24.12.2
|
||||
tailwindcss:
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
version: 6.1.4(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)
|
||||
|
||||
packages:
|
||||
|
||||
@ -739,11 +723,6 @@ packages:
|
||||
'@oslojs/encoding@1.1.0':
|
||||
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
|
||||
|
||||
'@playwright/test@1.59.1':
|
||||
resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rollup/pluginutils@5.3.0':
|
||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -1034,9 +1013,6 @@ packages:
|
||||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@24.12.2':
|
||||
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
||||
|
||||
'@types/unist@3.0.3':
|
||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||
|
||||
@ -2164,11 +2140,6 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
@ -2178,9 +2149,6 @@ packages:
|
||||
uncrypto@0.1.3:
|
||||
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
@ -2816,10 +2784,6 @@ snapshots:
|
||||
|
||||
'@oslojs/encoding@1.1.0': {}
|
||||
|
||||
'@playwright/test@1.59.1':
|
||||
dependencies:
|
||||
playwright: 1.59.1
|
||||
|
||||
'@rollup/pluginutils@5.3.0(rollup@4.60.1)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
@ -3004,12 +2968,12 @@ snapshots:
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
|
||||
|
||||
'@tailwindcss/vite@4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))':
|
||||
'@tailwindcss/vite@4.2.2(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.2.2
|
||||
'@tailwindcss/oxide': 4.2.2
|
||||
tailwindcss: 4.2.2
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
'@types/debug@4.1.13':
|
||||
dependencies:
|
||||
@ -3031,10 +2995,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@24.12.2':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
@ -3056,7 +3016,7 @@ snapshots:
|
||||
|
||||
array-iterate@2.0.1: {}
|
||||
|
||||
astro@6.1.4(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0)(typescript@5.9.3):
|
||||
astro@6.1.4(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(tsx@4.21.0):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 3.0.1
|
||||
'@astrojs/internal-helpers': 0.8.0
|
||||
@ -3102,14 +3062,14 @@ snapshots:
|
||||
tinyclip: 0.1.12
|
||||
tinyexec: 1.1.1
|
||||
tinyglobby: 0.2.16
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
tsconfck: 3.1.6
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5
|
||||
vfile: 6.0.3
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vitefu: 1.1.3(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vitefu: 1.1.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 22.0.0
|
||||
zod: 4.3.6
|
||||
@ -3654,10 +3614,10 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
laravel-vite-plugin@2.1.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
laravel-vite-plugin@2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
dependencies:
|
||||
picocolors: 1.1.1
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite-plugin-full-reload: 1.2.0
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
@ -4451,9 +4411,7 @@ snapshots:
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
||||
tsconfck@3.1.6(typescript@5.9.3):
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
tsconfck@3.1.6: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
@ -4464,16 +4422,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
ultrahtml@1.6.0: {}
|
||||
|
||||
uncrypto@0.1.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@ -4563,7 +4517,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
picomatch: 2.3.2
|
||||
|
||||
vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0):
|
||||
vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0):
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@ -4572,15 +4526,14 @@ snapshots:
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.16
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
tsx: 4.21.0
|
||||
|
||||
vitefu@1.1.3(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
vitefu@1.1.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)):
|
||||
optionalDependencies:
|
||||
vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
# Specification Quality Checklist: Enforcement & Review Guardrails
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-04-18
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Validated against the drafted Spec 201 on 2026-04-18 after converting the source outline into the repository template and measurable requirement language.
|
||||
- Optional operator-surface classification tables are intentionally omitted because this feature governs workflow and review mechanics rather than changing a concrete runtime surface.
|
||||
- Filament and repository-signal terminology is intentional product vocabulary for this governance slice, not code-level implementation guidance.
|
||||
- No clarification markers remain. The spec is ready for `/speckit.plan`.
|
||||
@ -1,450 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: UI Surface Enforcement & Review Guardrails
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Logical contract for the repository-owned guardrail workflow introduced by Spec 201.
|
||||
It documents authoring, review, repository-signal, test-guardrail, and exception
|
||||
semantics. It is not a public HTTP API.
|
||||
servers:
|
||||
- url: https://tenantpilot.local/logical
|
||||
paths:
|
||||
/logical/ui-guardrails/spec-impact/validate:
|
||||
post:
|
||||
summary: Validate one spec-level UI guardrail impact block
|
||||
operationId: validateSpecGuardrailImpact
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecImpactValidationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Spec guardrail impact evaluated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecImpactValidationResult'
|
||||
/logical/ui-guardrails/reviews/classify:
|
||||
post:
|
||||
summary: Classify one UI or surface change through the reviewer checklist
|
||||
operationId: classifyReviewOutcome
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReviewClassificationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Review classification returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReviewClassificationResult'
|
||||
/logical/ui-guardrails/repository-signals/assess:
|
||||
post:
|
||||
summary: Assess repository-level technical signals for a change
|
||||
operationId: assessRepositorySignals
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RepositorySignalAssessmentRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Repository-signal assessment returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RepositorySignalAssessmentResult'
|
||||
/logical/ui-guardrails/test-guardrails/resolve:
|
||||
post:
|
||||
summary: Resolve the required test profile for a surface contract class
|
||||
operationId: resolveTestGuardrailProfile
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TestGuardrailResolutionRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Test guardrail profile returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TestGuardrailResolutionResult'
|
||||
/logical/ui-guardrails/exceptions/assess:
|
||||
post:
|
||||
summary: Assess whether an exception record is complete and bounded
|
||||
operationId: assessExceptionWorkflow
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExceptionAssessmentRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Exception assessment returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExceptionAssessmentResult'
|
||||
/logical/ui-guardrails/closeout/prepare:
|
||||
post:
|
||||
summary: Prepare the active feature PR close-out entry for guarded work
|
||||
operationId: prepareCloseoutEntry
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CloseoutPreparationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Close-out entry requirements returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CloseoutPreparationResult'
|
||||
/logical/ui-guardrails/guidance:
|
||||
get:
|
||||
summary: Read the current guardrail guidance pack
|
||||
operationId: readGuardrailGuidance
|
||||
responses:
|
||||
'200':
|
||||
description: Guardrail guidance returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GuardrailGuidancePack'
|
||||
components:
|
||||
schemas:
|
||||
SpecImpactValidationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- specPath
|
||||
- uiSurfaceChange
|
||||
- nativeCustomClassification
|
||||
- stateLayers
|
||||
- sharedFamilyRelevance
|
||||
- exceptionNeeded
|
||||
properties:
|
||||
specPath:
|
||||
type: string
|
||||
uiSurfaceChange:
|
||||
type: boolean
|
||||
nativeCustomClassification:
|
||||
type: string
|
||||
stateLayers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- shell
|
||||
- page
|
||||
- detail
|
||||
sharedFamilyRelevance:
|
||||
type: string
|
||||
exceptionNeeded:
|
||||
type: boolean
|
||||
plannedGuardrailClasses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
SpecImpactValidationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- findings
|
||||
- closeoutEntryTarget
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-revision
|
||||
findings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
reviewerHandOff:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ReviewClassificationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- triggeredMappings
|
||||
- checklistAnswers
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
triggeredMappings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
checklistAnswers:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionClaimed:
|
||||
type: boolean
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ReviewClassificationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- outcomeClass
|
||||
- workflowOutcome
|
||||
- recordLocation
|
||||
properties:
|
||||
outcomeClass:
|
||||
type: string
|
||||
enum:
|
||||
- blocker
|
||||
- strong-warning
|
||||
- documentation-required-exception
|
||||
- acceptable-special-case
|
||||
workflowOutcome:
|
||||
type: string
|
||||
enum:
|
||||
- keep
|
||||
- split
|
||||
- document-in-feature
|
||||
- follow-up-spec
|
||||
- reject-or-split
|
||||
missingEvidence:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
recordLocation:
|
||||
type: string
|
||||
RepositorySignalAssessmentRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- signalIds
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
signalIds:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionClaimed:
|
||||
type: boolean
|
||||
scopeNote:
|
||||
type: string
|
||||
RepositorySignalAssessmentResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- handlingModes
|
||||
- closeoutEntryRequired
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-review
|
||||
handlingModes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
requiresException:
|
||||
type: boolean
|
||||
closeoutEntryRequired:
|
||||
type: boolean
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
TestGuardrailResolutionRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- surfaceClass
|
||||
properties:
|
||||
surfaceClass:
|
||||
type: string
|
||||
enum:
|
||||
- shared-detail-family
|
||||
- monitoring-state-page
|
||||
- global-context-shell
|
||||
- exception-coded-surface
|
||||
- standard-native-filament
|
||||
changedContractAreas:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
exceptionType:
|
||||
type: string
|
||||
TestGuardrailResolutionResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- requiredTestTypes
|
||||
- specialCoverageRequired
|
||||
- profileId
|
||||
properties:
|
||||
profileId:
|
||||
type: string
|
||||
requiredTestTypes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
manualSmokeExpectations:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
specialCoverageRequired:
|
||||
type: boolean
|
||||
ExceptionAssessmentRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- exceptionType
|
||||
- brokenRules
|
||||
- boundaryPlan
|
||||
- closeoutEntryTarget
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
exceptionType:
|
||||
type: string
|
||||
brokenRules:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
productReason:
|
||||
type: string
|
||||
boundaryPlan:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
standardizationPlan:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryTarget:
|
||||
type: string
|
||||
ExceptionAssessmentResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- status
|
||||
- spreadAllowed
|
||||
- closeoutEntryRequired
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- complete
|
||||
- needs-revision
|
||||
spreadAllowed:
|
||||
type: boolean
|
||||
closeoutEntryRequired:
|
||||
type: boolean
|
||||
closeoutNotes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
CloseoutPreparationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- changeRef
|
||||
- outcomeClass
|
||||
- handlingModes
|
||||
- workflowOutcome
|
||||
properties:
|
||||
changeRef:
|
||||
type: string
|
||||
outcomeClass:
|
||||
type: string
|
||||
enum:
|
||||
- blocker
|
||||
- strong-warning
|
||||
- documentation-required-exception
|
||||
- acceptable-special-case
|
||||
handlingModes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
workflowOutcome:
|
||||
type: string
|
||||
enum:
|
||||
- keep
|
||||
- split
|
||||
- document-in-feature
|
||||
- follow-up-spec
|
||||
- reject-or-split
|
||||
proofDepth:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
duplicatePromptNotes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
CloseoutPreparationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- entryName
|
||||
- requiredFields
|
||||
properties:
|
||||
entryName:
|
||||
type: string
|
||||
requiredFields:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
lowImpactAllowed:
|
||||
type: boolean
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
GuardrailGuidancePack:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- entryPoints
|
||||
- outcomeClasses
|
||||
- workflowOutcomes
|
||||
- closeoutEntryName
|
||||
properties:
|
||||
entryPoints:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
outcomeClasses:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
workflowOutcomes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
closeoutEntryName:
|
||||
type: string
|
||||
notes:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
@ -1,451 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://tenantpilot.local/specs/201/guardrail-governance.schema.json",
|
||||
"title": "UI Surface Guardrail Governance Pack",
|
||||
"description": "Repository-owned contract for the review, repository-signal, test-guardrail, exception, and workflow-touchpoint artifacts introduced by Spec 201.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"ruleMappings",
|
||||
"reviewChecklist",
|
||||
"repositorySignals",
|
||||
"testGuardrails",
|
||||
"exceptionWorkflow",
|
||||
"workflowTouchpoints",
|
||||
"validationScenarios",
|
||||
"closeoutEntry"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"ruleMappings": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/ruleMapping"
|
||||
}
|
||||
},
|
||||
"reviewChecklist": {
|
||||
"$ref": "#/$defs/reviewChecklist"
|
||||
},
|
||||
"repositorySignals": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/repositorySignal"
|
||||
}
|
||||
},
|
||||
"testGuardrails": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/testGuardrail"
|
||||
}
|
||||
},
|
||||
"exceptionWorkflow": {
|
||||
"$ref": "#/$defs/exceptionWorkflow"
|
||||
},
|
||||
"workflowTouchpoints": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/workflowTouchpoint"
|
||||
}
|
||||
},
|
||||
"validationScenarios": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"items": {
|
||||
"$ref": "#/$defs/validationScenario"
|
||||
}
|
||||
},
|
||||
"closeoutEntry": {
|
||||
"$ref": "#/$defs/closeoutEntry"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"ruleMapping": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"mappingId",
|
||||
"sourceRuleId",
|
||||
"problemClass",
|
||||
"operationalClasses",
|
||||
"handlingMode",
|
||||
"representativeCases",
|
||||
"targetWorkflowSurfaces"
|
||||
],
|
||||
"properties": {
|
||||
"mappingId": { "type": "string" },
|
||||
"sourceRuleId": { "type": "string" },
|
||||
"problemClass": { "type": "string" },
|
||||
"operationalClasses": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"review",
|
||||
"repository-signal",
|
||||
"test-guardrail",
|
||||
"exception",
|
||||
"workflow-touchpoint"
|
||||
]
|
||||
}
|
||||
},
|
||||
"handlingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"hard-stop-candidate",
|
||||
"review-mandatory",
|
||||
"exception-required",
|
||||
"report-only"
|
||||
]
|
||||
},
|
||||
"representativeCases": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"targetWorkflowSurfaces": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"deferredAutomation": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviewChecklist": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"checklistId",
|
||||
"questions",
|
||||
"outcomeClasses",
|
||||
"workflowOutcomes",
|
||||
"maxReviewMinutes"
|
||||
],
|
||||
"properties": {
|
||||
"checklistId": { "type": "string" },
|
||||
"questions": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "#/$defs/reviewQuestion" }
|
||||
},
|
||||
"outcomeClasses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
}
|
||||
},
|
||||
"workflowOutcomes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"keep",
|
||||
"split",
|
||||
"document-in-feature",
|
||||
"follow-up-spec",
|
||||
"reject-or-split"
|
||||
]
|
||||
}
|
||||
},
|
||||
"maxReviewMinutes": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviewQuestion": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"questionId",
|
||||
"category",
|
||||
"prompt",
|
||||
"appliesWhen",
|
||||
"linkedMappings",
|
||||
"evidenceExpected",
|
||||
"defaultOutcomeClass"
|
||||
],
|
||||
"properties": {
|
||||
"questionId": { "type": "string" },
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"surface-classification",
|
||||
"native-usage",
|
||||
"shared-family",
|
||||
"state-layer",
|
||||
"exception",
|
||||
"test-coverage"
|
||||
]
|
||||
},
|
||||
"prompt": { "type": "string" },
|
||||
"appliesWhen": { "type": "string" },
|
||||
"linkedMappings": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"evidenceExpected": { "type": "string" },
|
||||
"defaultOutcomeClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"repositorySignal": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"signalId",
|
||||
"label",
|
||||
"patternType",
|
||||
"confidenceLevel",
|
||||
"handlingMode",
|
||||
"exceptionPath",
|
||||
"futurePromotion"
|
||||
],
|
||||
"properties": {
|
||||
"signalId": { "type": "string" },
|
||||
"label": { "type": "string" },
|
||||
"patternType": {
|
||||
"type": "string",
|
||||
"enum": ["grep", "search", "path-pattern", "manual"]
|
||||
},
|
||||
"searchScopePaths": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"triggerExamples": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"confidenceLevel": {
|
||||
"type": "string",
|
||||
"enum": ["high", "medium", "low"]
|
||||
},
|
||||
"handlingMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"report-only",
|
||||
"review-mandatory",
|
||||
"exception-required",
|
||||
"hard-stop-candidate"
|
||||
]
|
||||
},
|
||||
"exceptionPath": { "type": "string" },
|
||||
"futurePromotion": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"testGuardrail": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"profileId",
|
||||
"surfaceClass",
|
||||
"triggers",
|
||||
"requiredTestTypes",
|
||||
"manualSmokeExpectations",
|
||||
"standardCoverageRule",
|
||||
"exceptionHandlingRule"
|
||||
],
|
||||
"properties": {
|
||||
"profileId": { "type": "string" },
|
||||
"surfaceClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"shared-detail-family",
|
||||
"monitoring-state-page",
|
||||
"global-context-shell",
|
||||
"exception-coded-surface",
|
||||
"standard-native-filament"
|
||||
]
|
||||
},
|
||||
"triggers": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"requiredTestTypes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"functional-core",
|
||||
"state-contract",
|
||||
"exception-fallback",
|
||||
"manual-smoke"
|
||||
]
|
||||
}
|
||||
},
|
||||
"manualSmokeExpectations": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"standardCoverageRule": { "type": "string" },
|
||||
"exceptionHandlingRule": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"exceptionWorkflow": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"workflowId",
|
||||
"supportedExceptionTypes",
|
||||
"requiredFields",
|
||||
"boundaryChecks",
|
||||
"standardizationFields",
|
||||
"spreadReviewRule",
|
||||
"closeoutRequirement",
|
||||
"closeoutNoteTarget"
|
||||
],
|
||||
"properties": {
|
||||
"workflowId": { "type": "string" },
|
||||
"supportedExceptionTypes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"requiredFields": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"boundaryChecks": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"standardizationFields": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"spreadReviewRule": { "type": "string" },
|
||||
"closeoutRequirement": { "type": "string" },
|
||||
"closeoutNoteTarget": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"workflowTouchpoint": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"touchpointId",
|
||||
"artifactPath",
|
||||
"phase",
|
||||
"requiredPrompts",
|
||||
"linkedMappings",
|
||||
"outcomeTarget"
|
||||
],
|
||||
"properties": {
|
||||
"touchpointId": { "type": "string" },
|
||||
"artifactPath": { "type": "string" },
|
||||
"phase": {
|
||||
"type": "string",
|
||||
"enum": ["spec", "plan", "tasks", "review", "closeout", "follow-up"]
|
||||
},
|
||||
"requiredPrompts": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"linkedMappings": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"outcomeTarget": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"validationScenario": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"scenarioId",
|
||||
"scenarioType",
|
||||
"representativeArtifact",
|
||||
"expectedMappings",
|
||||
"expectedOutcomeClass",
|
||||
"expectedWorkflowOutcome",
|
||||
"status",
|
||||
"notes"
|
||||
],
|
||||
"properties": {
|
||||
"scenarioId": { "type": "string" },
|
||||
"scenarioType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low-impact",
|
||||
"fake-native",
|
||||
"shared-family",
|
||||
"state-layer",
|
||||
"legitimate-exception"
|
||||
]
|
||||
},
|
||||
"representativeArtifact": { "type": "string" },
|
||||
"expectedMappings": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"expectedOutcomeClass": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"blocker",
|
||||
"strong-warning",
|
||||
"documentation-required-exception",
|
||||
"acceptable-special-case"
|
||||
]
|
||||
},
|
||||
"expectedWorkflowOutcome": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"keep",
|
||||
"split",
|
||||
"document-in-feature",
|
||||
"follow-up-spec",
|
||||
"reject-or-split"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["planned", "validated", "needs-tuning"]
|
||||
},
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"closeoutEntry": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"entryName",
|
||||
"requiredFields",
|
||||
"noteOwner",
|
||||
"lowImpactRule",
|
||||
"deferredAutomationField"
|
||||
],
|
||||
"properties": {
|
||||
"entryName": { "type": "string" },
|
||||
"requiredFields": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"noteOwner": { "type": "string" },
|
||||
"lowImpactRule": { "type": "string" },
|
||||
"deferredAutomationField": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,298 +0,0 @@
|
||||
# Data Model: Enforcement & Review Guardrails
|
||||
|
||||
This feature adds repository-owned governance artifacts only. It does not add application database tables, runtime-owned entities, or executable service contracts. All objects below are implemented as markdown guidance, template prompts, checklists, and logical contracts.
|
||||
|
||||
Handling modes and review outcome classes are separate concepts in this model. Handling modes describe how a signal or rule is processed in the workflow, while review outcome classes describe the decision reached for a specific change.
|
||||
|
||||
## 1. GuardrailRuleMapping
|
||||
|
||||
**Purpose**: Maps one Spec 200 or constitution rule family to its operational behavior in review, repository signals, testing, exceptions, and workflow surfaces.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `mappingId` | string | Stable identifier for the mapping. |
|
||||
| `sourceRuleId` | string | Spec 200 or constitution rule family being operationalized. |
|
||||
| `problemClass` | string | Drift class addressed by the mapping. |
|
||||
| `operationalClasses` | array | One or more of `review`, `repository-signal`, `test-guardrail`, `exception`, `workflow-touchpoint`. |
|
||||
| `handlingMode` | string | One of `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only`. |
|
||||
| `representativeCases` | array | Real repo cases that prove the rule. |
|
||||
| `targetWorkflowSurfaces` | array | Templates, checklist surfaces, or docs that must express the mapping. |
|
||||
| `deferredAutomation` | array | Automation or CI work explicitly deferred from the first pass. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every mapping must point to an existing Spec 200 or constitution rule family.
|
||||
- Every mapping must declare at least one operational class and exactly one first-pass handling mode.
|
||||
- `hard-stop-candidate` mappings must either have a named exception path or explicitly state `none`.
|
||||
|
||||
## 2. ReviewChecklistQuestion
|
||||
|
||||
**Purpose**: Represents one mandatory reviewer question used to classify UI and surface work.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `questionId` | string | Stable identifier for the review question. |
|
||||
| `category` | string | `surface-classification`, `native-usage`, `shared-family`, `state-layer`, `exception`, or `test-coverage`. |
|
||||
| `prompt` | string | The exact decision-grade question. |
|
||||
| `appliesWhen` | string | Scope rule for when the question must be asked. |
|
||||
| `linkedMappings` | array | Related `GuardrailRuleMapping` IDs. |
|
||||
| `evidenceExpected` | string | What the author or reviewer must point to. |
|
||||
| `defaultOutcomeClass` | string | One of `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Questions must be phrased as classification or review-stop decisions, not vague advice.
|
||||
- Every question must link to at least one guardrail mapping.
|
||||
- The question set must stay short enough for a representative review pass to complete in under 3 minutes.
|
||||
|
||||
## 3. RepositorySignalProfile
|
||||
|
||||
**Purpose**: Describes one technically observable red flag that can be reported or escalated during review.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `signalId` | string | Stable identifier for the signal. |
|
||||
| `label` | string | Human-readable signal name. |
|
||||
| `patternType` | string | `grep`, `search`, `path-pattern`, or `manual`. |
|
||||
| `searchScopePaths` | array | Paths where the signal applies. |
|
||||
| `triggerExamples` | array | Observable examples of the pattern. |
|
||||
| `confidenceLevel` | string | `high`, `medium`, or `low`. |
|
||||
| `handlingMode` | string | One of `report-only`, `review-mandatory`, `exception-required`, or `hard-stop-candidate`. |
|
||||
| `exceptionPath` | string | Named exception path or `none`. |
|
||||
| `futurePromotion` | string | Whether later hardening is allowed and under what condition. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every signal must have a documented handling mode and an exception or review path.
|
||||
- First-pass automation may not exceed `report-only` unless the signal has high confidence and low ambiguity.
|
||||
- Signals must stay anchored to real drift cases, not hypothetical future problems.
|
||||
|
||||
## 4. TestGuardrailProfile
|
||||
|
||||
**Purpose**: Defines the required test and smoke depth for a surface contract class.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `profileId` | string | Stable identifier for the surface test profile. |
|
||||
| `surfaceClass` | string | `shared-detail-family`, `monitoring-state-page`, `global-context-shell`, `exception-coded-surface`, or `standard-native-filament`. |
|
||||
| `triggers` | array | Conditions that activate the profile. |
|
||||
| `requiredTestTypes` | array | Required evidence such as `functional-core`, `state-contract`, `exception-fallback`, or `manual-smoke`. |
|
||||
| `manualSmokeExpectations` | array | Manual review or smoke expectations tied to the profile. |
|
||||
| `standardCoverageRule` | string | When ordinary feature coverage is enough. |
|
||||
| `exceptionHandlingRule` | string | How exceptions affect the profile. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every non-standard surface class must declare whether special coverage is mandatory.
|
||||
- `standard-native-filament` must explicitly state when no additional special guardrail tests are required.
|
||||
- Profiles must classify by interaction and contract risk, not just by framework or file type.
|
||||
|
||||
## 5. ExceptionWorkflowTemplate
|
||||
|
||||
**Purpose**: Captures the required fields and checks for a legitimate guardrail exception.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `templateId` | string | Stable identifier for the exception template. |
|
||||
| `exceptionType` | string | Canonical exception type from Spec 200. |
|
||||
| `brokenRuleIds` | array | Which rule families are being relaxed or not fully satisfied. |
|
||||
| `requiredFields` | array | Required justification fields the spec or PR must fill. |
|
||||
| `boundaryChecks` | array | What keeps the exception bounded. |
|
||||
| `standardizationFields` | array | What remains standardized despite the exception. |
|
||||
| `spreadReviewRule` | string | What must happen before the exception can extend to new hosts or surfaces. |
|
||||
| `closeoutNoteTarget` | string | Where the exception must be recorded at completion: the active feature PR close-out entry. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every exception must name the affected rule family and the product reason.
|
||||
- Boundary checks must be explicit enough to stop silent extension.
|
||||
- Exception approval is incomplete until the close-out note target is known.
|
||||
|
||||
## 6. WorkflowTouchpoint
|
||||
|
||||
**Purpose**: Defines one workflow artifact where guardrail behavior must appear.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `touchpointId` | string | Stable identifier for the workflow touchpoint. |
|
||||
| `artifactPath` | string | File path for the workflow surface. |
|
||||
| `phase` | string | `spec`, `plan`, `tasks`, `review`, `closeout`, or `follow-up`. |
|
||||
| `requiredPrompts` | array | Questions or fields the artifact must surface. |
|
||||
| `linkedMappings` | array | Related guardrail mappings. |
|
||||
| `outcomeTarget` | string | Where the touchpoint records its decision or handoff, including the active feature PR close-out entry when the phase is `closeout`. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- Every relevant phase from spec to closeout must have at least one touchpoint.
|
||||
- The active feature PR close-out entry is the single canonical close-out note target for guarded changes.
|
||||
- The same classification question should not be duplicated unnecessarily across multiple surfaces.
|
||||
- Touchpoints must point back to existing entry surfaces before any new file is introduced.
|
||||
|
||||
## 7. GuardrailAssessment
|
||||
|
||||
**Purpose**: Represents the result of classifying one UI or surface change through the workflow.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `assessmentId` | string | Stable identifier for one guardrail review. |
|
||||
| `triggeredMappings` | array | Activated rule mappings. |
|
||||
| `triggeredSignals` | array | Triggered repository signals, if any. |
|
||||
| `outcomeClass` | string | `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case`. |
|
||||
| `workflowOutcome` | string | `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`. |
|
||||
| `missingEvidence` | array | Missing inputs before the review can close. |
|
||||
| `recordLocation` | string | Spec, plan, checklist, or the active feature PR close-out entry where the result lives. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- `blocker` cannot resolve to `keep` while missing evidence remains.
|
||||
- `documentation-required-exception` requires a completed `ExceptionWorkflowTemplate` entry before the workflow can close as `keep` or `document-in-feature`.
|
||||
- `recordLocation` must resolve to one declared workflow touchpoint outcome or the active feature PR close-out entry.
|
||||
- `reject-or-split` is valid when hidden exception spread, unresolved state-layer conflict, or fake-native drift remains uncorrected.
|
||||
|
||||
## 8. ValidationScenario
|
||||
|
||||
**Purpose**: Represents one scenario used to prove the workflow is both lightweight and effective.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `scenarioId` | string | Stable scenario identifier. |
|
||||
| `scenarioType` | string | `low-impact`, `fake-native`, `shared-family`, `state-layer`, or `legitimate-exception`. |
|
||||
| `representativeArtifact` | string | Spec, template, or documentation artifact used in the validation walkthrough. |
|
||||
| `expectedMappings` | array | Rule mappings that should fire. |
|
||||
| `expectedOutcomeClass` | string | Expected guardrail outcome class. |
|
||||
| `expectedWorkflowOutcome` | string | Expected workflow outcome. |
|
||||
| `status` | string | `planned`, `validated`, or `needs-tuning`. |
|
||||
| `notes` | string | What the scenario proves. |
|
||||
|
||||
**Validation Rules**
|
||||
|
||||
- At least one `low-impact` and one high-risk scenario must be represented.
|
||||
- The scenario set must cover fake-native, shared-family, state-layer, and legitimate-exception reasoning.
|
||||
- Low-impact scenarios must prove that `N/A` paths remain fast and legitimate.
|
||||
|
||||
## Relationships
|
||||
|
||||
- One `GuardrailRuleMapping` may feed many `ReviewChecklistQuestion`, `RepositorySignalProfile`, and `WorkflowTouchpoint` entries.
|
||||
- A `RepositorySignalProfile` may trigger a `GuardrailAssessment`, but the final outcome still depends on the associated `ReviewChecklistQuestion` and any `ExceptionWorkflowTemplate`.
|
||||
- One `TestGuardrailProfile` may be referenced by many `GuardrailRuleMapping` entries when multiple rule families converge on the same surface class.
|
||||
- One `ExceptionWorkflowTemplate` may be required by many `GuardrailAssessment` records, but each assessment must keep its own bounded record location.
|
||||
- `ValidationScenario` objects prove that the combined model stays usable across both low-friction and high-risk cases.
|
||||
|
||||
## State Transitions
|
||||
|
||||
### GuardrailAssessment.workflowOutcome
|
||||
|
||||
- `keep` -> `document-in-feature`: allowed when a change is valid but the active feature still needs to record exception or signal handling explicitly.
|
||||
- `document-in-feature` -> `follow-up-spec`: allowed when review shows the issue is structural, recurring, or likely to need harder automation.
|
||||
- Any state -> `reject-or-split`: allowed when fake-native drift, hidden exception spread, unresolved host drift, or unresolved state-layer collapse remains.
|
||||
|
||||
### ValidationScenario.status
|
||||
|
||||
- `planned` -> `validated`: allowed when the workflow can classify the scenario with the expected outcome and without extra categories.
|
||||
- `planned` -> `needs-tuning`: allowed when wording is ambiguous, repetitive, or too heavy for the scenario.
|
||||
- `needs-tuning` -> `validated`: allowed after the relevant template, checklist, or guidance text is refined.
|
||||
|
||||
## Derived Outputs
|
||||
|
||||
The conceptual model must support these concrete outputs:
|
||||
|
||||
- a guardrail catalog that maps Spec 200 rule families to operational behavior
|
||||
- a reviewer question set with fixed outcome classes
|
||||
- a repository signal catalog with handling modes and exception escape paths
|
||||
- a surface-class test guardrail matrix
|
||||
- an exception workflow template with spread-control rules
|
||||
- workflow touchpoint changes in the spec, plan, task, checklist, and close-out surfaces
|
||||
- validation scenarios that prove both low-impact and high-risk usability
|
||||
|
||||
## Canonical Implementation Snapshot
|
||||
|
||||
### GuardrailRuleMapping Records
|
||||
|
||||
| `mappingId` | `sourceRuleId` | `problemClass` | `operationalClasses` | `handlingMode` | `representativeCases` | `targetWorkflowSurfaces` | `deferredAutomation` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `GRM-001` | `UI-FIL-001`, `UI-HARD-001` | `fake-native-core-control` | `review`, `repository-signal`, `workflow-touchpoint` | `hard-stop-candidate` | Spec 196 fake-native cleanups | `spec-template`, `checklist-template`, `tasks-template`, `.specify/README.md` | `CI hard-stop deferred`, `grep remains report-first` |
|
||||
| `GRM-002` | `UI-REVIEW-001`, `UI-EX-001` | `shared-family-host-drift` | `review`, `test-guardrail`, `exception`, `workflow-touchpoint` | `review-mandatory` | Spec 197 shared detail contract | `spec-template`, `plan-template`, `checklist-template`, `tasks-template` | `family linting deferred` |
|
||||
| `GRM-003` | `DECIDE-001`, `OPSURF-001`, `UI-REVIEW-001` | `state-layer-ownership-collapse` | `review`, `repository-signal`, `test-guardrail`, `workflow-touchpoint` | `review-mandatory` | Specs 198 and 199 state-layer cases | `spec-template`, `plan-template`, `checklist-template`, `tasks-template` | `state-owner grep deferred` |
|
||||
| `GRM-004` | `UI-EX-001`, `UI-FIL-001` | `bounded-custom-surface-exception` | `review`, `exception`, `test-guardrail`, `workflow-touchpoint` | `exception-required` | Spec 200 legitimate special-case model | `spec-template`, `plan-template`, `checklist-template`, `.specify/README.md` | `exception bot deferred` |
|
||||
| `GRM-005` | `TEST-TRUTH-001`, `UI-FIL-001` | `standard-native-surface-relief` | `test-guardrail`, `workflow-touchpoint` | `report-only` | Standard native Filament work with no special contract | `plan-template`, `tasks-template`, `checklist-template` | `auto-relief detection deferred` |
|
||||
|
||||
### ReviewChecklistQuestion Records
|
||||
|
||||
| `questionId` | `category` | `prompt` | `appliesWhen` | `linkedMappings` | `evidenceExpected` | `defaultOutcomeClass` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `CHK-SURF-001` | `surface-classification` | Does the change clearly state whether an operator-facing or guardrailed workflow surface is affected, or does it use one valid low-impact `N/A` path? | Always | `GRM-001`, `GRM-005` | Spec guardrail impact block | `acceptable-special-case` |
|
||||
| `CHK-NATIVE-001` | `native-usage` | Does the surface stay native/shared-primitives first, or is a custom surface claim being made explicitly? | Operator-facing surface changes | `GRM-001`, `GRM-004` | Surface classification, planned primitives, review notes | `blocker` |
|
||||
| `CHK-SHARED-001` | `shared-family` | If the surface belongs to a shared family, is the host/core boundary still one shared contract rather than a quiet fork? | Shared family or repeated micro-UI | `GRM-002` | Shared-family note, host variation description | `strong-warning` |
|
||||
| `CHK-STATE-001` | `state-layer` | Are shell, page, detail, and URL/query state owners named once and kept separate? | Shell, monitoring, stateful surfaces | `GRM-003` | State-layer summary in spec/plan | `strong-warning` |
|
||||
| `CHK-EXC-001` | `exception` | If default rules are intentionally relaxed, is there a bounded exception with spread control and a named close-out target? | Custom surface or rule relaxation | `GRM-004` | Exception record and close-out note target | `documentation-required-exception` |
|
||||
| `CHK-TEST-001` | `test-coverage` | Does the surface class use the right special profile, or explicit `standard-native-filament` relief? | Runtime or UI-surface changes | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | Test profile, manual smoke, validation commands | `strong-warning` |
|
||||
|
||||
### RepositorySignalProfile Records
|
||||
|
||||
| `signalId` | `label` | `patternType` | `searchScopePaths` | `triggerExamples` | `confidenceLevel` | `handlingMode` | `exceptionPath` | `futurePromotion` |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| `SIG-001` | `fake-native-control` | `grep` | `app/Filament/**`, `resources/views/**` | Plain buttons or custom surface controls replacing native Filament semantics | `high` | `hard-stop-candidate` | `bounded-custom-surface-exception` | Hard-stop only after report-first validation stays low-noise |
|
||||
| `SIG-002` | `request-driven-page-state` | `grep` | `app/Filament/**`, `resources/views/**` | GET-form or request/query state driving page-body UI outside documented filter/shell patterns | `high` | `review-mandatory` | `none` | Promote only with later noise review |
|
||||
| `SIG-003` | `shared-family-host-fork` | `path-pattern` | `app/Filament/**`, `resources/views/**`, `docs/ui/**` | New host-specific partial or duplicated shared-family markup | `medium` | `review-mandatory` | `bounded-custom-surface-exception` | Future linting stays deferred |
|
||||
| `SIG-004` | `shell-context-leak` | `search` | `app/Filament/**`, `resources/views/**` | Workspace or tenant resolution logic leaks into presentation partials | `medium` | `review-mandatory` | `none` | Report-first only in this rollout |
|
||||
| `SIG-005` | `simple-overview-customization` | `manual` | `app/Filament/**`, `resources/views/**` | Hand-rolled overview/status UI where native widgets or shared primitives should remain primary | `medium` | `report-only` | `bounded-custom-surface-exception` | Never auto-block in first pass |
|
||||
|
||||
### TestGuardrailProfile Records
|
||||
|
||||
| `profileId` | `surfaceClass` | `triggers` | `requiredTestTypes` | `manualSmokeExpectations` | `standardCoverageRule` | `exceptionHandlingRule` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `TGP-001` | `standard-native-filament` | Standard Resource/Page/RelationManager with no special shared, shell, or exception contract | none beyond ordinary feature coverage | Optional ordinary UI smoke only | Ordinary feature coverage is sufficient | If a later exception appears, switch to `exception-coded-surface` |
|
||||
| `TGP-002` | `shared-detail-family` | Reused detail micro-UI shared across multiple hosts | `functional-core` | Verify one representative host and the host/core boundary | Extra bespoke tests stay off unless the family contract changes | Host-specific divergence activates exception review |
|
||||
| `TGP-003` | `monitoring-state-page` | Monitoring or governance page owns its own state contract | `functional-core`, `state-contract` | Verify empty, loaded, and conflict states | Use the narrowest honest non-browser lane first | Local exception paths require explicit fallback coverage |
|
||||
| `TGP-004` | `global-context-shell` | Shell or context-recovery logic is changed | `state-contract`, `exception-fallback` | Verify valid, invalid, and recovery/fallback states | No extra browser family by default | Context exceptions require bounded fallback proof |
|
||||
| `TGP-005` | `exception-coded-surface` | Legitimate custom or special visualization relaxes a default rule | `functional-core`, `exception-fallback`, `manual-smoke` | Verify bounded exception behavior and preserved standardized parts | `standard-native-filament` relief does not apply | Close-out must justify the exception and the proof depth |
|
||||
|
||||
### ExceptionWorkflowTemplate Record
|
||||
|
||||
| `templateId` | `exceptionType` | `brokenRuleIds` | `requiredFields` | `boundaryChecks` | `standardizationFields` | `spreadReviewRule` | `closeoutNoteTarget` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `EXW-001` | `bounded-custom-surface-exception` | `UI-FIL-001`, `UI-EX-001`, `UI-REVIEW-001` as applicable | Broken rule IDs, product reason, bounded surface/host, preserved standardized parts, required tests/manual smoke, deferred automation, active close-out note | No silent reuse on new hosts, no new helper spread without review, no alternate state owner introduced implicitly | Canonical noun, primary operator action, inspect model, standardized parts still preserved | Any extension to a new host, route, or surface requires renewed explicit review | Active feature PR close-out entry `Guardrail / Exception / Smoke Coverage` |
|
||||
|
||||
### WorkflowTouchpoint Records
|
||||
|
||||
| `touchpointId` | `artifactPath` | `phase` | `requiredPrompts` | `linkedMappings` | `outcomeTarget` |
|
||||
|---|---|---|---|---|---|
|
||||
| `WTP-001` | `.specify/templates/spec-template.md` | `spec` | UI / Surface Guardrail Impact block, low-impact `N/A` rule | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004` | `.specify/templates/plan-template.md` |
|
||||
| `WTP-002` | `.specify/templates/plan-template.md` | `plan` | Handling modes, repository-signal treatment, test profiles, close-out entry | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `.specify/templates/tasks-template.md` |
|
||||
| `WTP-003` | `.specify/templates/tasks-template.md` | `tasks` | Carry-forward classification, proof tasks, exception documentation, close-out task | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `.specify/templates/checklist-template.md` |
|
||||
| `WTP-004` | `.specify/templates/checklist-template.md` | `review` | Fixed reviewer questions, outcome class, workflow outcome | `GRM-001`, `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | Active feature PR close-out entry |
|
||||
| `WTP-005` | `.specify/README.md` | `follow-up` | Author entry, review entry, low-impact rule, escalation rule, close-out rule | `GRM-001`, `GRM-004`, `GRM-005` | Active feature PR close-out entry |
|
||||
| `WTP-006` | `active feature PR close-out entry` | `closeout` | Scenario used, outcome class, handling mode, workflow outcome, proof depth, exception boundary, deferred automation | `GRM-002`, `GRM-003`, `GRM-004`, `GRM-005` | `Guardrail / Exception / Smoke Coverage` |
|
||||
|
||||
### ValidationScenario Records
|
||||
|
||||
| `scenarioId` | `scenarioType` | `representativeArtifact` | `expectedMappings` | `expectedOutcomeClass` | `expectedWorkflowOutcome` | `status` | `notes` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `VAL-001` | `low-impact` | `.specify/templates/checklist-template.md` + `.specify/README.md` wording-only path | `GRM-005` | `acceptable-special-case` | `keep` | `validated` | Low-impact path stayed under one minute and used one `N/A` note only |
|
||||
| `VAL-002` | `fake-native` | `specs/196-hard-filament-nativity-cleanup/spec.md` | `GRM-001` | `blocker` | `reject-or-split` | `validated` | Fake-native drift remained the hardest signal |
|
||||
| `VAL-003` | `shared-family` | `specs/197-shared-detail-contract/spec.md` | `GRM-002` | `strong-warning` | `document-in-feature` | `validated` | Shared-family boundary still needs explicit reviewer judgment |
|
||||
| `VAL-004` | `state-layer` | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `GRM-003` | `strong-warning` | `document-in-feature` | `validated` | State-owner naming is mandatory before merge |
|
||||
| `VAL-005` | `legitimate-exception` | `specs/200-filament-surface-rules/spec.md` | `GRM-004` | `documentation-required-exception` | `document-in-feature` | `validated` | Legitimate special cases stay allowed only with bounded exception notes |
|
||||
|
||||
### Active Feature PR Close-out Entry
|
||||
|
||||
| Field | Requirement |
|
||||
|---|---|
|
||||
| `entryName` | `Guardrail / Exception / Smoke Coverage` |
|
||||
| `scenarioUsed` | Low-impact or representative guarded scenario used for validation |
|
||||
| `outcomeClass` | One of `blocker`, `strong-warning`, `documentation-required-exception`, or `acceptable-special-case` |
|
||||
| `handlingMode` | One of `hard-stop-candidate`, `review-mandatory`, `exception-required`, or `report-only` |
|
||||
| `workflowOutcome` | One of `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split` |
|
||||
| `proofDepth` | Required tests or manual smoke, or explicit `standard-native-filament` relief |
|
||||
| `exceptionBoundary` | `N/A` for low-impact or standard-native work, otherwise the bounded exception summary |
|
||||
| `duplicatePromptNotes` | Whether any classification question still appeared redundantly across workflow surfaces |
|
||||
| `deferredAutomation` | Any grep/lint/CI hardening explicitly deferred in the first pass |
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- database tables
|
||||
- Eloquent models or services
|
||||
- CI bots or grep scripts as implementation deliverables
|
||||
- runtime Livewire or Filament classes
|
||||
- public HTTP or CLI APIs
|
||||
- a new top-level handbook outside existing `.specify/` workflow surfaces
|
||||
@ -1,176 +0,0 @@
|
||||
# Implementation Plan: Enforcement & Review Guardrails
|
||||
|
||||
**Branch**: `201-enforcement-review-guardrails` | **Date**: 2026-04-18 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/spec.md`
|
||||
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Implement Spec 201 by keeping Spec 200 and `.specify/memory/constitution.md` as the normative rule source while operationalizing those rules through the existing SpecKit workflow surfaces: the spec, plan, task, and checklist templates; `.specify/README.md` as the author and reviewer entry point; and the feature-local guardrail artifacts that define review outcomes, repository-signal handling, required test depth, exception workflow, and close-out expectations. The implementation remains repository-governance only: no runtime UI, no CI hard gates in the first pass, and no second handbook or framework outside the current spec-driven flow.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Markdown governance artifacts, JSON Schema plus logical OpenAPI planning contracts, and Bash-backed SpecKit scripts inside a PHP 8.4.15 / Laravel 12 / Filament v5 / Livewire v4 repository
|
||||
**Primary Dependencies**: `.specify/memory/constitution.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200
|
||||
**Storage**: Repository-owned markdown and contract artifacts under `.specify/` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`; no product database persistence
|
||||
**Testing**: Document and template validation against low-impact and high-risk UI-governance scenarios, checklist completeness review, and no runtime Pest execution because the feature is docs and workflow only
|
||||
**Validation Lanes**: `N/A`
|
||||
**Target Platform**: TenantPilot monorepo workflow, SpecKit authoring flow, and Gitea-backed code review for Laravel operator-facing work
|
||||
**Project Type**: Monorepo with a Laravel platform app and Astro website, but this feature is scoped strictly to repository governance and authoring/review workflow artifacts
|
||||
**Performance Goals**: Keep low-impact UI-governance answers completable in under 1 minute, keep a representative guarded review under 3 minutes, and avoid duplicating the same guardrail question across multiple workflow surfaces
|
||||
**Constraints**: No runtime application code changes, no new CI bot or hard blocking grep/lint layer in the first pass, no parallel handbook or governance subsystem, explicit exception paths must remain available, and repository signals must support false-positive escape rather than becoming unconditional blockers
|
||||
**Scale/Scope**: One feature-local guardrail pack, four SpecKit templates, one `.specify/README.md` workflow entry point, optional light-touch operator-UX cross-reference only if wording alignment is necessary, and representative validation across low-impact plus the fake-native, shared-family, exception, and state-layer drift classes
|
||||
|
||||
### Filament v5 Implementation Notes
|
||||
|
||||
- **Livewire v4.0+ compliance**: Preserved. This feature changes repository workflow artifacts only and does not alter the Filament or Livewire runtime stack.
|
||||
- **Provider registration location**: Unchanged. Existing panel providers remain registered in `bootstrap/providers.php`.
|
||||
- **Global search rule**: No globally searchable resources are added or modified.
|
||||
- **Destructive actions**: No runtime destructive actions are introduced. The workflow artifacts added by this feature continue to require explicit exception and confirmation thinking for future operator-facing surfaces, but they do not change current action behavior.
|
||||
- **Asset strategy**: No panel or shared assets are added. Existing `filament:assets` deployment behavior remains unchanged.
|
||||
- **Testing plan**: Validate the guardrail model, template prompts, checklist wording, repository-signal handling, exception documentation, and workflow fit against one low-impact docs-only path and representative high-risk UI-surface cases grounded in Specs 196 through 200; no runtime UI, action, or Livewire tests are added by this feature.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Inventory-first: PASS. No inventory, snapshot, or backup product truth changes.
|
||||
- Read/write separation: PASS. This is repository-only governance work with no end-user mutations.
|
||||
- Graph contract path: PASS. No Microsoft Graph calls or contract-registry changes.
|
||||
- Deterministic capabilities: PASS. No capability resolver, policy, or authorization registry changes.
|
||||
- RBAC-UX, workspace isolation, tenant isolation: PASS. No runtime routes, surfaces, or policy behavior changes.
|
||||
- Run observability and Ops-UX: PASS. No `OperationRun` lifecycle or monitoring behavior changes.
|
||||
- Data minimization: PASS. The new artifacts are repository-owned prompts, schemas, and guidance only.
|
||||
- Test governance (TEST-GOV-001): PASS WITH WORK. The feature is docs-only, so validation lanes remain `N/A`, but the resulting workflow must still define how special UI surface classes trigger extra tests or smoke expectations in future runtime work.
|
||||
- Proportionality and bloat control: PASS WITH LIMITS. The implementation touches several workflow entry points, but it stays inside the existing authoring and review chain rather than creating a new subsystem.
|
||||
- UI semantics and few-layers rules: PASS. The plan reuses Spec 200 vocabulary and does not add a new semantic framework or parallel rulebook.
|
||||
- Filament-native and surface-taxonomy rules: PASS WITH WORK. The workflow artifacts must reuse the exact Spec 200 and constitution vocabulary rather than invent alternative guardrail terms.
|
||||
- Decision-first and operator-surface rules: PASS. No new runtime surface is introduced; the plan only makes existing review expectations operational.
|
||||
|
||||
**Phase 0 Gate Result**: PASS
|
||||
|
||||
- The feature stays bounded to repository governance, templates, checklist prompts, and review guidance.
|
||||
- No new product persistence, runtime routes, Graph seams, or authorization behavior is introduced.
|
||||
- The operationalization path keeps Spec 200 as the rule source instead of reopening the constitution with a new competing layer.
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
- **Affected validation lanes**: `N/A`
|
||||
- **Narrowest proving command(s)**: `N/A`. Validation is document- and workflow-based rather than runtime-lane based.
|
||||
- **Fixture / helper cost risks**: None directly. The feature exists to force future surface work to expose needed tests and smoke depth, not to introduce new runtime setup.
|
||||
- **Heavy-family additions or promotions**: None. The intended change is earlier disclosure of required test depth for special surface classes in future specs and reviews.
|
||||
- **Budget / baseline / trend follow-up**: None directly. The feature must keep docs-only changes answerable with `N/A` while still making future runtime test obligations explicit.
|
||||
- **Why no dedicated follow-up spec is needed**: Spec 201 is itself the workflow-guardrail feature. Routine upkeep should stay inside ordinary feature specs after rollout unless later automation or hard gating justifies a separate follow-up spec.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/201-enforcement-review-guardrails/
|
||||
├── plan.md
|
||||
├── research.md
|
||||
├── data-model.md
|
||||
├── quickstart.md
|
||||
├── contracts/
|
||||
│ ├── guardrail-governance.schema.json
|
||||
│ └── guardrail-governance.logical.openapi.yaml
|
||||
└── tasks.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
.specify/
|
||||
├── README.md
|
||||
├── memory/
|
||||
│ └── constitution.md
|
||||
└── templates/
|
||||
├── checklist-template.md
|
||||
├── plan-template.md
|
||||
├── spec-template.md
|
||||
└── tasks-template.md
|
||||
|
||||
docs/
|
||||
└── ui/
|
||||
└── operator-ux-surface-standards.md
|
||||
|
||||
specs/
|
||||
├── 196-hard-filament-nativity-cleanup/
|
||||
├── 197-shared-detail-contract/
|
||||
├── 198-monitoring-page-state/
|
||||
├── 199-global-context-shell-contract/
|
||||
├── 200-filament-surface-rules/
|
||||
└── 201-enforcement-review-guardrails/
|
||||
```
|
||||
|
||||
**Structure Decision**: Keep changes concentrated in the existing workflow surfaces under `.specify/templates/` and `.specify/README.md`, with `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/` holding the feature-local catalog and contract artifacts. `.specify/memory/constitution.md`, `docs/ui/operator-ux-surface-standards.md`, and Specs 196 through 200 are primary references; they should only be edited if a minimal wording alignment is required to avoid ambiguity, not as a second implementation surface.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| None | Not applicable | Not applicable |
|
||||
|
||||
## Proportionality Review
|
||||
|
||||
- **Current operator problem**: The repo now knows what UI and surface drift looks like, but authors and reviewers still lack one repeatable workflow that turns those rules into concrete review questions, reportable repository signals, required test depth, and visible exception handling before merge.
|
||||
- **Existing structure is insufficient because**: Spec 200 and the constitution define the rule language, but the current spec, plan, task, and checklist surfaces do not yet force authors or reviewers to classify native versus custom work, state layers, shared-family relevance, repository-signal handling, or exception boundaries consistently.
|
||||
- **Narrowest correct implementation**: Tighten the existing SpecKit templates and `.specify/README.md`, publish a feature-local guardrail catalog with structured contracts, and validate the workflow against representative real repo cases rather than adding bots, CI policy, or a new handbook.
|
||||
- **Ownership cost created**: The repo must maintain one shared guardrail vocabulary across templates, review checklists, and workflow guidance, plus a small set of structured planning contracts that describe the model.
|
||||
- **Alternative intentionally rejected**: A new CI-based enforcement system, a separate UI review handbook, or hard blocking grep/lint automation in the first pass, because each would widen process surface and false-positive risk before the workflow itself is proven.
|
||||
- **Release truth**: Current-release repository truth needed now so Spec 200 does not immediately decay back into “agreed in principle, optional in practice.”
|
||||
|
||||
## Phase 0 — Research (complete)
|
||||
|
||||
- Output: [research.md](./research.md)
|
||||
- Resolved key decisions:
|
||||
- Keep Spec 200 plus `.specify/memory/constitution.md` as the normative rule source and operationalize through templates and review guidance instead of reopening the rulebook.
|
||||
- Reuse the existing workflow entry points under `.specify/templates/` and `.specify/README.md` rather than introducing a new handbook or contributor process.
|
||||
- Treat repository signals as report-first and exception-aware in the first pass, with future promotion paths documented but not enforced automatically.
|
||||
- Separate guardrail classes from handling modes so the same rule family can be review-only, technically signalable, test-triggering, or exception-bound without unstable vocabulary.
|
||||
- Model required test depth by surface contract class rather than by file type or framework.
|
||||
- Use logical schema/OpenAPI artifacts as structured planning contracts for the guardrail pack instead of fabricating runtime APIs.
|
||||
- Validate the workflow with one low-impact docs-only scenario plus representative fake-native, shared-family, exception, and state-layer scenarios grounded in Specs 196 through 200.
|
||||
|
||||
## Phase 1 — Design & Contracts (baseline complete)
|
||||
|
||||
- Output: [data-model.md](./data-model.md) formalizes the repository-owned governance entities: rule mappings, review checklist questions, repository signals, test-guardrail profiles, exception workflows, workflow touchpoints, and validation scenarios.
|
||||
- Output: [contracts/guardrail-governance.schema.json](./contracts/guardrail-governance.schema.json) defines the schema-first planning contract for the complete guardrail pack; it is not a runtime or transport API.
|
||||
- Output: [contracts/guardrail-governance.logical.openapi.yaml](./contracts/guardrail-governance.logical.openapi.yaml) captures the logical workflow semantics for classifying specs, evaluating review checklists, resolving repository signals, determining test requirements, and assessing exceptions; it is not a public HTTP contract.
|
||||
- Output: [quickstart.md](./quickstart.md) provides the implementation order, representative validation walkthroughs, and rollout checklist.
|
||||
- These artifacts are design-complete enough for planning, but Phase 2 implementation work still stabilizes wording, timing checks, close-out targeting, and cross-artifact alignment before template rollout.
|
||||
|
||||
### Post-design Constitution Re-check
|
||||
|
||||
- PASS: No runtime routes, panels, Graph seams, assets, or authorization planes are introduced.
|
||||
- PASS: The design keeps Spec 200 and the constitution as the single rule source and uses templates only for operationalization.
|
||||
- PASS: The workflow surfaces stay inside existing `.specify/` entry points rather than creating a second governance subsystem.
|
||||
- PASS WITH WORK: Template prompts and checklist items must avoid asking the same classification question redundantly across spec, plan, task, and review surfaces.
|
||||
- PASS WITH WORK: Repository signals must remain review-triggering and exception-aware in the first pass so legitimate custom surfaces do not become permanent false positives.
|
||||
|
||||
## Phase 2 — Implementation Planning
|
||||
|
||||
`tasks.md` should cover:
|
||||
|
||||
- Auditing the current guardrail-relevant workflow surfaces and mapping the relevant Spec 200 and constitution rule families to review guardrails, repository signals, test triggers, exception handling, and workflow touchpoints.
|
||||
- Publishing the feature-local guardrail catalog and logical contracts in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/` as the implementation's structured design reference.
|
||||
- Updating `.specify/templates/spec-template.md` so UI and surface-relevant specs explicitly capture native versus custom classification, state-layer ownership, shared-family relevance, exception need, and low-impact `N/A` handling where no operator-facing surface changes exist.
|
||||
- Updating `.specify/templates/plan-template.md` so planning surfaces explicitly capture guardrail handling modes, planned repository-signal treatment, required test depth for special surface classes, and the active feature PR close-out entry as the close-out note target for exceptions and smoke coverage.
|
||||
- Updating `.specify/templates/tasks-template.md` so task generation carries forward native/custom, state-layer, shared-family, and exception classification into implementation tasks and requires review classification work, definition-of-done checks, exception documentation, required tests or smoke checks, and the active feature PR close-out entry whenever a special surface class is involved.
|
||||
- Updating `.specify/templates/checklist-template.md` as the canonical review-checklist surface so reviewers can apply the fixed question set and end with both a guardrail outcome class and an explicit workflow outcome such as `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
- Updating `.specify/README.md` with a concise author and reviewer entry guide that explains low-impact `N/A` handling, review-stop expectations, and when exception spread or structural drift requires escalation.
|
||||
- Touching `docs/ui/operator-ux-surface-standards.md` only if one minimal cross-reference is needed to point template users back to the guardrail workflow without duplicating the rules.
|
||||
- Validating the updated workflow against a low-impact docs-only change plus representative fake-native, shared-family, exception, and shell/page/detail state scenarios derived from Specs 196 through 200.
|
||||
- Recording explicit first-pass deferrals for grep/lint/CI hardening so later automation can be added deliberately rather than assumed by template wording.
|
||||
|
||||
### Contract Implementation Note
|
||||
|
||||
- The JSON schema is repository-tooling oriented and describes the guardrail pack the workflow must express during planning, even if the first implementation lives primarily in markdown templates and checklists.
|
||||
- The OpenAPI file is logical rather than transport-prescriptive. It documents workflow semantics for authoring, review, signal assessment, and exception handling, not a runtime service.
|
||||
- The design intentionally avoids new runtime services, CLI tooling, or CI bots. The contract artifacts are structured planning aids inside this feature set, not a new maintained subsystem.
|
||||
|
||||
### Deployment Sequencing Note
|
||||
|
||||
- No database migration is planned.
|
||||
- No asset publish step changes.
|
||||
- Recommended rollout order: finalize the feature-local guardrail pack first, then update the SpecKit templates, then update `.specify/README.md`, then add any minimal secondary cross-reference if needed, then run the representative validation walkthroughs, and finally record which automation candidates remain intentionally deferred.
|
||||
@ -1,147 +0,0 @@
|
||||
# Quickstart: Implementing Spec 201
|
||||
|
||||
This feature is repository-governance only. It does not change application runtime behavior, routes, assets, or validation lanes. The goal is to make future UI and surface work classify drift, exceptions, and required test depth earlier in the existing spec-driven workflow.
|
||||
|
||||
## 1. Confirm the rule and workflow surfaces
|
||||
|
||||
Start with the existing sources that already carry UI and surface governance truth:
|
||||
|
||||
- `.specify/memory/constitution.md`
|
||||
- `.specify/templates/spec-template.md`
|
||||
- `.specify/templates/plan-template.md`
|
||||
- `.specify/templates/tasks-template.md`
|
||||
- `.specify/templates/checklist-template.md`
|
||||
- `.specify/README.md`
|
||||
- `docs/ui/operator-ux-surface-standards.md`
|
||||
- `specs/196-hard-filament-nativity-cleanup/`
|
||||
- `specs/197-shared-detail-contract/`
|
||||
- `specs/198-monitoring-page-state/`
|
||||
- `specs/199-global-context-shell-contract/`
|
||||
- `specs/200-filament-surface-rules/`
|
||||
|
||||
Do not create a parallel handbook or a second normative rule source. Spec 200 and the constitution remain the rule source; Spec 201 operationalizes them.
|
||||
|
||||
## 2. Build the guardrail catalog first
|
||||
|
||||
Before changing templates, map the relevant rule families from Spec 200 into the operational model:
|
||||
|
||||
- which cases are structured review questions
|
||||
- which cases are technically signalable
|
||||
- which cases require additional tests or smoke expectations
|
||||
- which cases are legitimate only through a named exception path
|
||||
- which cases are only future hard-stop candidates rather than immediate blockers
|
||||
|
||||
Keep the class of the problem separate from the handling mode. The guardrail pack should be able to say both “this is a repository signal” and “its first-pass handling mode is review-mandatory.” Keep handling modes separate from review outcome classes as well: handling modes describe how the workflow treats a pattern, while review outcomes stay limited to `blocker`, `strong-warning`, `documentation-required-exception`, and `acceptable-special-case`.
|
||||
|
||||
## 3. Update the workflow surfaces in order
|
||||
|
||||
Apply the same vocabulary and outcome model consistently, in this order:
|
||||
|
||||
1. `spec-template.md`
|
||||
Add the authoring-time prompts that require native/custom classification, shared-family relevance, state-layer ownership, exception need, and low-impact `N/A` handling for features that do not touch operator-facing surfaces.
|
||||
2. `plan-template.md`
|
||||
Add planning-time prompts that require guardrail handling modes, repository-signal treatment, required test depth for special surface classes, and the active feature PR close-out entry for exceptions and smoke coverage.
|
||||
3. `tasks-template.md`
|
||||
Add task-level obligations that carry forward native/custom, state-layer, shared-family, and exception classification into implementation work and require review classification, definition-of-done checks, required tests or smoke checks, exception documentation, and the active feature PR close-out entry when a special surface class is involved.
|
||||
4. `checklist-template.md`
|
||||
Make this the canonical reviewer checklist surface with the fixed question set and the explicit workflow outcomes: `keep`, `split`, `document-in-feature`, `follow-up-spec`, or `reject-or-split`.
|
||||
5. `.specify/README.md`
|
||||
Keep one concise author/reviewer entry guide that explains low-impact `N/A` handling, review-stop expectations, the active feature PR close-out entry, and when structural drift needs escalation.
|
||||
|
||||
Avoid asking the same classification question in three different ways across the workflow.
|
||||
|
||||
## 4. Keep the standards doc secondary
|
||||
|
||||
Use `docs/ui/operator-ux-surface-standards.md` only as a supporting alignment document.
|
||||
|
||||
- Add one light cross-reference only if template users need help finding the guardrail workflow.
|
||||
- Do not duplicate the full guardrail catalog or review checklist there.
|
||||
|
||||
## 5. Run the required validation walkthroughs
|
||||
|
||||
The implementation is only complete if the workflow can classify both a low-friction case and the target drift classes it is meant to stop.
|
||||
|
||||
### Low-impact workflow path
|
||||
|
||||
Use a genuinely low-impact docs-only change, such as a wording-only change in `.specify/templates/checklist-template.md` or `.specify/README.md`, to prove that:
|
||||
|
||||
- the spec and plan prompts can be answered with `N/A` where appropriate
|
||||
- the checklist can still close with `keep`
|
||||
- no fake runtime or CI obligations are introduced
|
||||
- the low-impact path remains completable in under 1 minute
|
||||
|
||||
Expected result: the low-impact path remains fast and does not force invented UI-surface prose.
|
||||
|
||||
### Representative guarded cases
|
||||
|
||||
Use the following repo cases to prove the workflow has real discrimination power:
|
||||
|
||||
| Case class | Source artifact | Expected outcome |
|
||||
|---|---|---|
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/` | classified as a hard technical signal and a blocker unless a bounded exception is explicit |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/` | classified as review-mandatory with host/core contract checks |
|
||||
| Shell/page/detail confusion | `specs/198-monitoring-page-state/` and `specs/199-global-context-shell-contract/` | classified as review-mandatory with explicit owner-layer naming |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/` exception model | classified as documentation-required exception rather than default violation |
|
||||
|
||||
Expected result: no new category is needed during review. The workflow can classify each case using only the defined guardrail language.
|
||||
The representative guarded review should remain completable in under 3 minutes, and the walkthrough should record any classification question that appears redundantly across spec, plan, task, review, and close-out surfaces.
|
||||
|
||||
## 6. Recorded validation results
|
||||
|
||||
### Reviewer workflow validation
|
||||
|
||||
| Scenario | Source artifact | Outcome class | Handling mode | Workflow outcome | Result |
|
||||
|---|---|---|---|---|---|
|
||||
| Low-impact docs-only path | `.specify/templates/checklist-template.md` + `.specify/README.md` | `acceptable-special-case` | `report-only` | `keep` | Completed in `00:48` with one explicit `N/A` note and no invented surface prose |
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/spec.md` | `blocker` | `hard-stop-candidate` | `reject-or-split` | Checklist still lands on the strongest outcome without creating a new category |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | Shared-family boundary stays reviewable without becoming fake automation |
|
||||
| State-layer confusion | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | State ownership is named once instead of spread across surfaces |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/spec.md` | `documentation-required-exception` | `exception-required` | `document-in-feature` | Bounded exception remains allowed without weakening the default rule |
|
||||
|
||||
- Representative guarded review elapsed time: `02:34`
|
||||
- Duplicate-prompt note: native/custom, shared-family, and state-layer classification now originate in the spec guardrail block and are re-used downstream instead of being rephrased.
|
||||
|
||||
### Authoring workflow validation
|
||||
|
||||
| Scenario | Source artifact | Result | Time | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Low-impact docs-only scenario | `.specify/templates/checklist-template.md` + `.specify/README.md` | `keep` | `00:48` | One `N/A` path stayed sufficient through spec, plan, and review |
|
||||
| Surface-changing scenario | `specs/200-filament-surface-rules/spec.md` | `document-in-feature` | `01:51` | Native/custom classification, handling modes, and proof depth stayed explicit without a second workflow |
|
||||
|
||||
## 7. Record the validation note
|
||||
|
||||
Capture the implementation proof in the active feature PR close-out entry with at least:
|
||||
|
||||
- the low-impact scenario used
|
||||
- the representative guarded cases used
|
||||
- which review outcome class and handling mode each case reached
|
||||
- which workflow outcome was chosen
|
||||
- the elapsed low-impact and guarded-review completion times
|
||||
- whether any template wording felt redundant or missing
|
||||
- whether any classification question was duplicated across workflow surfaces
|
||||
- that the active feature PR close-out entry owns the final completion note
|
||||
- whether any automation candidate was explicitly deferred
|
||||
|
||||
The active feature PR close-out entry name for this rollout is `Guardrail / Exception / Smoke Coverage`.
|
||||
|
||||
## 8. First-pass automation deferrals
|
||||
|
||||
- Keep `SIG-001` through `SIG-005` report-first. No grep, lint, or CI rule blocks merge automatically in this rollout.
|
||||
- Defer any bot or generated reminder that enforces the close-out entry name.
|
||||
- Defer repo-wide shared-family host-fork detection until the exception escape path has proven low noise.
|
||||
- Defer automatic promotion from `review-mandatory` to blocking until a later spec reviews misses, noise, and ownership cost.
|
||||
|
||||
## 9. Completion checklist
|
||||
|
||||
- Guardrail catalog and contracts exist in the active feature artifact set
|
||||
- Template prompts use the same vocabulary as Spec 200 and the constitution
|
||||
- `.specify/README.md` gives a concise author/reviewer entry path
|
||||
- Low-impact `N/A` handling remains legitimate and fast
|
||||
- Low-impact validation remains completable in under 1 minute
|
||||
- Representative fake-native, shared-family, state-layer, and exception cases classify cleanly
|
||||
- Representative guarded review remains completable in under 3 minutes
|
||||
- No classification question is duplicated unnecessarily across spec, plan, tasks, review, and close-out surfaces
|
||||
- The active feature PR close-out entry is named `Guardrail / Exception / Smoke Coverage`
|
||||
- First-pass repository signals remain report-first and exception-aware
|
||||
- First-pass automation deferrals are recorded explicitly
|
||||
- No second handbook, runtime tool, or CI hard-gate subsystem was introduced
|
||||
@ -1,114 +0,0 @@
|
||||
# Research: Enforcement & Review Guardrails
|
||||
|
||||
## Decision 1: Keep Spec 200 and the constitution as the only normative rule source
|
||||
|
||||
- **Decision**: Treat Spec 200 and `.specify/memory/constitution.md` as the source of truth for UI and surface rules, and use Spec 201 only to operationalize those rules through workflow artifacts.
|
||||
- **Rationale**: Spec 201 exists to make the existing rules early-visible and reviewable, not to restate or replace them. Reopening the constitution as a second rule-writing exercise would recreate the same split-source problem this feature is meant to solve.
|
||||
- **Alternatives considered**:
|
||||
- Add a new top-level guardrail rulebook: rejected because it would force reviewers to reconcile two overlapping authorities.
|
||||
- Keep guardrails only in feature-local docs: rejected because the workflow would then lack shared authoring and review touchpoints.
|
||||
|
||||
## Decision 2: Reuse the existing SpecKit workflow surfaces instead of creating a new process
|
||||
|
||||
- **Decision**: Put the operational changes into `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, and `.specify/README.md`.
|
||||
- **Rationale**: These are the surfaces contributors already use when drafting, planning, tasking, and reviewing feature work. Tightening them creates leverage where decisions are already made, without adding another process entry point.
|
||||
- **Alternatives considered**:
|
||||
- Create a new dedicated handbook under `docs/`: rejected because it would be easier to ignore and harder to keep in sync with the live workflow.
|
||||
- Put all guidance only into `.specify/memory/constitution.md`: rejected because authors and reviewers need operational prompts where they already work, not just high-level rules.
|
||||
|
||||
## Decision 3: Keep first-pass repository signals report-first and exception-aware
|
||||
|
||||
- **Decision**: Model technical signals as report-only, review-mandatory, exception-required, or future hard-stop candidates, but do not introduce automatic hard CI blocking in the first pass.
|
||||
- **Rationale**: Several anti-patterns are grep-able, but legitimate custom surfaces and bounded exceptions still require human judgment. The first operational layer should make likely drift visible early without making the repo noisy or brittle.
|
||||
- **Alternatives considered**:
|
||||
- Immediate CI failures for all grep-detectable patterns: rejected because false positives would be too high before the exception path and vocabulary are stabilized.
|
||||
- Purely narrative guidance with no signal catalog: rejected because authors and reviewers need concrete early-warning patterns, not only abstract rules.
|
||||
|
||||
## Decision 4: Separate guardrail classes from handling modes
|
||||
|
||||
- **Decision**: Distinguish the class of a guardrail problem from how it is handled. The stable classes are review guardrails, repository signals, test guardrails, exception workflows, and workflow touchpoints; the handling modes are hard-stop candidate, review-mandatory, exception-required, and report-only.
|
||||
- **Rationale**: The same product rule can show up in multiple operational ways. For example, a fake-native case is both a review question and a technical signal, while a special visualization may be review-mandatory and exception-required but never grep-detectable. Separating class from handling mode keeps the vocabulary stable.
|
||||
- **Alternatives considered**:
|
||||
- Collapse class and handling into one flat list: rejected because it would blur the difference between “what kind of problem is this?” and “how should the workflow react?”
|
||||
- Use only blocker versus non-blocker: rejected because the spec explicitly needs finer distinctions for exceptions and future hardening.
|
||||
|
||||
## Decision 5: Model required test depth by surface contract class, not by technology alone
|
||||
|
||||
- **Decision**: Drive test-guardrail requirements from the surface contract class: shared detail family, monitoring/governance page with its own state contract, global context shell surface, exception-coded custom surface, or standard native Filament surface.
|
||||
- **Rationale**: The testing requirement is about interaction and state risk, not just whether the surface uses Filament, Blade, or Livewire. This keeps test depth proportional and avoids forcing bespoke tests onto ordinary standard surfaces.
|
||||
- **Alternatives considered**:
|
||||
- Require extra tests for every Filament change: rejected because it would create friction without better risk discrimination.
|
||||
- Leave test depth fully discretionary: rejected because the spec explicitly requires required-test signals for certain surface classes.
|
||||
|
||||
## Decision 6: Keep the exception workflow explicit and spread-sensitive
|
||||
|
||||
- **Decision**: Require each exception to name the broken or relaxed rule, the product reason, the boundary, the standardized parts that remain, and the spread-control rule that prevents silent extension to new hosts or surfaces.
|
||||
- **Rationale**: Hidden exceptions are one of the target problem classes. The workflow must make “one-off but allowed” visibly different from “new precedent by drift.”
|
||||
- **Alternatives considered**:
|
||||
- Let each spec describe its exception informally: rejected because that would recreate the hidden-exception pattern.
|
||||
- Ban most exceptions outright: rejected because legitimate special visualizations and bounded state exceptions are already part of the intended product model.
|
||||
|
||||
## Decision 7: Use logical contracts for the guardrail pack, not runtime APIs
|
||||
|
||||
- **Decision**: Create a JSON Schema and a logical OpenAPI file under `contracts/` that describe the planning-time guardrail pack and its workflow semantics.
|
||||
- **Rationale**: Adjacent governance specs already use structured contract artifacts to make non-runtime workflow truth explicit. Reusing that pattern keeps planning artifacts consistent and gives future task generation a concrete model.
|
||||
- **Alternatives considered**:
|
||||
- Markdown-only notes in `contracts/`: rejected because they are less structured for downstream use.
|
||||
- Real HTTP or CLI API contracts: rejected because this feature does not introduce a runtime service.
|
||||
|
||||
## Decision 8: Keep operator-UX standards as a secondary alignment document only
|
||||
|
||||
- **Decision**: Treat `docs/ui/operator-ux-surface-standards.md` as a reference-only alignment target and touch it only if one minimal cross-reference is needed.
|
||||
- **Rationale**: The workflow changes belong in `.specify/` and the feature-local artifacts. Duplicating them into operator-UX standards would reintroduce drift between rule source, workflow, and supporting prose.
|
||||
- **Alternatives considered**:
|
||||
- Expand the operator-UX standards doc with full guardrail mechanics: rejected because it would become a second operational handbook.
|
||||
- Ignore the doc entirely: rejected because a minimal pointer may still be needed to keep terminology aligned.
|
||||
|
||||
## Decision 9: Validate both low-friction and high-risk paths
|
||||
|
||||
- **Decision**: Validate the workflow against one low-impact docs-only scenario and multiple high-risk representative cases grounded in Specs 196 through 200.
|
||||
- **Rationale**: The low-impact scenario proves the workflow does not overharden ordinary documentation changes. The representative drift cases prove that the new prompts and checklist language can classify the problem classes the feature is meant to stop.
|
||||
- **Alternatives considered**:
|
||||
- Validate only a high-risk case: rejected because it would not prove that the low-impact path stays lightweight.
|
||||
- Validate only hypothetical examples: rejected because the repo already has real cases and vocabulary anchors that should be reused.
|
||||
|
||||
## Validation Scenario Set
|
||||
|
||||
| Scenario | Source artifact | Expected guardrail result |
|
||||
|---|---|---|
|
||||
| Low-impact docs-only workflow change | `.specify/templates/checklist-template.md` and `.specify/README.md` | `N/A` path remains acceptable, no fake runtime obligations, explicit `keep` outcome remains possible |
|
||||
| Fake-native hard-signal case | `specs/196-hard-filament-nativity-cleanup/` | hard technical signal and blocker unless a bounded exception exists |
|
||||
| Shared-family host-drift case | `specs/197-shared-detail-contract/` | review-mandatory classification with host/core boundary check and exception spread control |
|
||||
| State-layer confusion case | `specs/198-monitoring-page-state/` and `specs/199-global-context-shell-contract/` | review-mandatory classification that names shell/page/detail ownership explicitly |
|
||||
| Legitimate special-case surface | `specs/200-filament-surface-rules/` representative exception model | documentation-required exception rather than default violation |
|
||||
|
||||
## Implementation Adjustment Note
|
||||
|
||||
- The narrowest rollout for Spec 201 is feature-local guardrail design plus workflow-surface updates. It should not pull in runtime code, root-level onboarding docs, or CI policy unless the implementation proves that a smaller workflow surface is insufficient.
|
||||
|
||||
## Canonical Repository Signal Catalog
|
||||
|
||||
| Signal ID | Problem class | First-pass handling mode | Exception / review path | Future promotion rule |
|
||||
|---|---|---|---|---|
|
||||
| `SIG-001` | Fake-native control or page-body interaction disguised as native Filament behavior | `hard-stop-candidate` | Named bounded exception only; otherwise reviewer stops the change | Eligible for hard CI blocking only after report-first use stays low-noise across real repo cases |
|
||||
| `SIG-002` | GET-form or request-driven page-body state outside the documented shell or list/filter contract | `review-mandatory` | Review must classify whether the state belongs to shell, page, detail, or URL/query ownership | Promote only if future false positives stay low and the state-owner vocabulary remains stable |
|
||||
| `SIG-003` | Shared-family host fork or duplicate partial drift | `review-mandatory` | Fold back into the shared contract or document a bounded host exception with spread control | Future linting is deferred until the shared-family boundaries remain stable across multiple cases |
|
||||
| `SIG-004` | Shell or context resolution logic leaking into presentation partials or page-local helpers | `review-mandatory` | Review must prove shell ownership and keep page/detail surfaces from becoming hidden context engines | Stay report-first until the repo has a proven shell regression pattern worth hardening |
|
||||
| `SIG-005` | Hand-rolled simple overview or status UI where native Filament widgets or shared primitives should remain primary | `report-only` | Review decides whether this is a real special visualization or just avoidable custom surface drift | Never hard-stop automatically without a later spec proving the false-positive rate is acceptably low |
|
||||
|
||||
## Canonical Test Guardrail Matrix
|
||||
|
||||
| Surface class | Trigger | Required proof types | Manual smoke expectation | Relief rule |
|
||||
|---|---|---|---|---|
|
||||
| `standard-native-filament` | Standard Resource/Page/RelationManager work with no special shared-family, shell, or exception contract | Ordinary feature coverage only | Optional normal UI smoke only | This is the explicit relief rule: no extra bespoke guardrail proof is required |
|
||||
| `shared-detail-family` | Reused detail micro-UI or host/core contract shared across multiple surfaces | `functional-core` | Verify host/core boundaries and one representative host rendering | Add exception coverage only if a host intentionally diverges |
|
||||
| `monitoring-state-page` | Monitoring/governance page with its own state contract | `functional-core`, `state-contract` | Verify empty, loaded, and conflict states remain legible | Use the narrowest honest lane; no browser expansion by default |
|
||||
| `global-context-shell` | Shared shell or context-recovery surface | `state-contract`, `exception-fallback` | Verify valid scope, invalid scope, and recovery/fallback clarity | Manual smoke stays required because shell truth crosses many pages |
|
||||
| `exception-coded-surface` | Legitimate custom or special visualization that relaxes a default rule | `functional-core`, `exception-fallback` | Verify the bounded exception and the preserved standardized parts | Close-out must explain why `standard-native-filament` relief does not apply |
|
||||
|
||||
## First-Pass Automation Deferrals
|
||||
|
||||
- Grep or lint rules for `SIG-001` through `SIG-005` stay report-first only; no CI hard-stop is introduced in this rollout.
|
||||
- No PR bot or generated comment is added to enforce the active feature PR close-out entry.
|
||||
- No repo-wide scanner auto-detects shared-family host forks until false-positive escape paths are proven in practice.
|
||||
- No automatic promotion from `review-mandatory` to blocking occurs without a later spec that reviews noise, misses, and ownership burden explicitly.
|
||||
@ -1,266 +0,0 @@
|
||||
# Feature Specification: Enforcement & Review Guardrails
|
||||
|
||||
**Feature Branch**: `[201-enforcement-review-guardrails]`
|
||||
**Created**: 2026-04-18
|
||||
**Status**: Proposed
|
||||
**Input**: User description: "Spec 201 - operationalize the UI/UX constitution rules from Spec 200 into repeatable review, repository, test, exception, and workflow guardrails."
|
||||
|
||||
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: Spec 200 now defines the UI and surface rules, but the repo still lacks repeatable day-to-day mechanisms that force reviewers and authors to detect new drift early, classify legitimate exceptions, and keep shared-family or state-layer mistakes from re-entering under delivery pressure.
|
||||
- **Today's failure**: The team can agree with the constitution in principle, yet fake-native controls, hidden exceptions, host drift, and shell or page or detail state confusion can still merge because there is no mandatory review language, no shared signal catalog, and no explicit workflow contract for when extra tests or exception notes are required.
|
||||
- **User-visible improvement**: Operators keep a more consistent, trustworthy admin product over time because future UI and surface work is checked against explicit guardrails before drift becomes another cleanup project.
|
||||
- **Smallest enterprise-capable version**: Define a bounded guardrail catalog, a mandatory review checklist, a repository signal catalog, a test-guardrail matrix, an exception workflow, and spec-flow integration that operationalize Spec 200 without doing another cleanup sweep or inventing heavyweight tooling.
|
||||
- **Explicit non-goals**: No runtime cleanup of existing surfaces, no new product features, no broad refactor of page or shell logic, no separate governance process outside the existing spec workflow, and no attempt to turn every design judgment into a hard technical gate.
|
||||
- **Permanent complexity imported**: A finite guardrail vocabulary, review outcome classes, technical signal classes, exception documentation requirements, and close-out expectations for UI and surface work.
|
||||
- **Why now**: Spec 200 is the normative rule set now. If guardrails do not follow immediately, the same drift classes will return while adjacent specs are still being implemented.
|
||||
- **Why not local**: Ad-hoc PR comments or personal grep habits cannot create consistent review outcomes, visible exception boundaries, or reusable test-trigger rules across the repo.
|
||||
- **Approval class**: Cleanup
|
||||
- **Red flags triggered**: Foundation-sounding governance work and new classification vocabulary. Defense: the scope is explicitly limited to operationalizing existing Spec 200 rules, keeps enforcement proportional, and avoids adding a second parallel framework or mandatory toolchain.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 1 | Wiederverwendung: 2 | **Gesamt: 10/12**
|
||||
- **Decision**: approve
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: workspace
|
||||
- **Primary Routes**: No end-user HTTP routes are changed. The affected surfaces are repository-owned review artifacts, spec and plan and task expectations, close-out notes, and any lightweight repository checks that expose UI and surface drift before merge.
|
||||
- **Data Ownership**: Workspace-owned governance artifacts only. No tenant-owned tables, runtime records, or new persisted product-truth entities are introduced.
|
||||
- **RBAC**: No runtime authorization behavior changes. The affected actors are contributors and reviewers working on operator-facing surfaces inside the existing spec-driven workflow.
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no, this spec operationalizes the existing rule truth from Spec 200 rather than creating a second normative source
|
||||
- **New persisted entity/table/artifact?**: yes, but only repository-owned artifacts such as guardrail catalogs, review checklists, and close-out guidance
|
||||
- **New abstraction?**: yes, a bounded guardrail taxonomy that classifies review signals, repository signals, test triggers, and documented exceptions
|
||||
- **New enum/state/reason family?**: yes, because guardrail classes and review outcome classes become named categories used across future UI and surface work
|
||||
- **New cross-domain UI framework/taxonomy?**: yes, but only to translate already-approved surface rules into one finite operational model
|
||||
- **Current operator problem**: The product already knows what good surface behavior should look like, but the delivery workflow still lacks a reliable way to stop new fake-native patterns, hidden exceptions, shared-family drift, and state-layer confusion before they ship.
|
||||
- **Existing structure is insufficient because**: Spec 200 defines the rules, but it does not yet say which questions are mandatory in review, which patterns are technically signalable, which surface classes must trigger extra tests, or how legitimate exceptions remain visible and bounded.
|
||||
- **Narrowest correct implementation**: Add one operational guardrail layer that maps the existing constitution to review, repository, test, exception, and workflow expectations. Do not create cleanup implementation, hard CI enforcement for every case, or a parallel approval bureaucracy.
|
||||
- **Ownership cost**: Maintainers must keep the guardrail catalog aligned with Spec 200, preserve the shared vocabulary in future specs and PRs, and maintain any lightweight signal definitions or close-out expectations that result from this work.
|
||||
- **Alternative intentionally rejected**: Rely on tribal review knowledge, add only local PR templates, or attempt a fully automated hard-gate system before the repo has a bounded and reviewable guardrail model.
|
||||
- **Release truth**: current-release workflow truth needed now so Spec 200 remains effective in daily implementation and review
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The audit trail behind Specs 196 through 200 exposed the same failure pattern repeatedly: the repo can name the drift, but it cannot yet stop it early enough.
|
||||
|
||||
The known problem classes are already concrete:
|
||||
|
||||
- fake-native controls and GET-form interactions inside Filament surfaces
|
||||
- Blade-request-driven UI state in page bodies and adjacent UI surfaces
|
||||
- hand-rolled simple overviews where standard primitives should remain primary
|
||||
- shared detail families that drift by host-specific fork instead of one shared contract
|
||||
- page-state, shell-state, and detail-state ownership collapsing into each other
|
||||
- legitimate custom exceptions that remain invisible and therefore expand quietly
|
||||
|
||||
Without operational guardrails, the constitution is vulnerable to the usual failure mode: agreement in principle, drift in practice, then another cleanup wave later.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on Spec 200 - Filament Surface Rules as the normative rule source this spec operationalizes.
|
||||
- Uses the proven drift cases from Spec 196 - Hard Filament Nativity Cleanup as the clearest examples of fake-native and request-driven UI problems.
|
||||
- Uses the shared-family and host-drift lessons from Spec 197 - Shared Detail Contract.
|
||||
- Uses the page-state ownership lessons from Spec 198 - Monitoring Page State.
|
||||
- Uses the shell and context ownership lessons from Spec 199 - Global Context Shell Contract.
|
||||
- Does not replace or reopen the cleanup and implementation scope of any adjacent spec.
|
||||
|
||||
## Goals
|
||||
|
||||
- Translate the Spec 200 rule set into repeatable review guardrails, repository signals, test triggers, exception handling, and workflow expectations.
|
||||
- Make the team distinguish clearly between what is review-only, what is technically signalable, what requires extra testing, and what is allowed only through visible exception handling.
|
||||
- Make legitimate exceptions visible, bounded, and reviewable instead of accidental precedent.
|
||||
- Keep the guardrails proportionate so they help contributors before merge without creating tool-driven theater.
|
||||
- Integrate the guardrails into the existing spec-driven workflow rather than creating a competing process.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Cleaning up existing UI and surface violations as part of this spec.
|
||||
- Building heavy custom linting, CI policy, or repo tooling beyond what is needed for lightweight early warning.
|
||||
- Forcing hard technical gates on design judgments that only human review can assess reliably.
|
||||
- Inventing new UI rules beyond Spec 200.
|
||||
- Turning legitimate custom surfaces into forbidden territory.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Spec 200 remains the normative rule source, and Spec 201 only operationalizes it.
|
||||
- Some design decisions will remain structured human judgment even after this work is complete.
|
||||
- Legitimate custom surfaces, special visualizations, and bounded local state will continue to exist, so the exception path must be first-class rather than grudging.
|
||||
- The existing spec and plan and task and close-out workflow remains the primary delivery process and should absorb these guardrails directly.
|
||||
|
||||
## Guardrail Classification Model
|
||||
|
||||
### Guardrail Classes
|
||||
|
||||
- **Hard Technical Signals**: reliable warning patterns that can expose likely drift early
|
||||
- **Structured Review Signals**: questions that must be answered in review even when no technical pattern can decide the case alone
|
||||
- **Required Test Signals**: surface classes that must trigger additional tests or smoke expectations
|
||||
- **Exception Signals**: cases where deviation can be legitimate only if it is visible, bounded, and justified
|
||||
|
||||
### Handling Modes
|
||||
|
||||
- **Hard-stop candidate**: drift pattern that may become blocking once the repo has an approved exception path
|
||||
- **Review-mandatory**: pattern or surface type that always needs explicit human classification
|
||||
- **Exception-required**: case that is allowed only with a documented exception model
|
||||
- **Report-only**: lightweight visibility signal that supports trend review without default blocking
|
||||
|
||||
### Review Outcome Classes
|
||||
|
||||
- **Blocker**: the change cannot proceed until the guardrail issue is corrected or explicitly split
|
||||
- **Strong warning**: the change may proceed only if the remaining guardrail risk is acknowledged and resolved in the active workflow
|
||||
- **Documentation-required exception**: the change is acceptable only once the named exception path is completed and bounded
|
||||
- **Acceptable special case**: the change remains legitimate without additional guardrail escalation beyond ordinary documentation
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
- **Test purpose / classification**: N/A
|
||||
- **Validation lane(s)**: N/A
|
||||
- **Why this classification and these lanes are sufficient**: This feature changes review and specification artifacts, not runtime behavior. Validation is document-based and checks completeness, traceability, clarity, and workflow fit.
|
||||
- **New or expanded test families**: none
|
||||
- **Fixture / helper cost impact**: none
|
||||
- **Heavy-family visibility / justification**: none
|
||||
- **Reviewer handoff**: Reviewers must confirm that each major Spec 200 rule family maps to explicit guardrail behavior, that no second governance framework is introduced, and that the examples remain grounded in the real drift cases from Specs 196 through 200.
|
||||
- **Budget / baseline / trend impact**: none
|
||||
- **Escalation needed**: none
|
||||
- **Planned validation commands**: N/A
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Reviewers Classify Drift Early (Priority: P1)
|
||||
|
||||
As a reviewer, I want a mandatory UI and surface checklist so I can detect fake-native patterns, host drift, hidden exceptions, and shell or page or detail state confusion before merge.
|
||||
|
||||
**Why this priority**: Review is the earliest practical control point for most new surface work. If the checklist cannot classify obvious drift early, the rest of the guardrail model will be too weak to matter.
|
||||
|
||||
**Independent Test**: Can be fully tested by walking a reviewer through a fake-native case, a host-drift case, and a shell or page or detail state confusion case and confirming that the reviewer can classify each one using only the documented checklist, review outcome classes, and handling modes.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a Filament-looking surface that introduces plain controls or a GET-form interaction for a core page-body behavior, **When** the reviewer applies the guardrail checklist, **Then** the reviewer can classify the change as a blocker or strong warning with an explicit handling mode rather than a neutral custom implementation.
|
||||
2. **Given** a repeated shared detail surface starts diverging by host-specific variation, **When** the reviewer applies the guardrails, **Then** the reviewer can identify whether the change belongs inside the shared family contract or must be treated as a documented exception.
|
||||
3. **Given** a page mixes shell context, page interaction state, and detail viewer state without clear ownership, **When** the reviewer applies the checklist, **Then** the reviewer can name the state-layer problem explicitly and stop the change from being treated as harmless implementation detail.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Authors Declare Native Vs Custom Up Front (Priority: P1)
|
||||
|
||||
As an author planning new UI or surface work, I want the spec workflow to force native versus custom classification, state-layer classification, shared-family relevance, and exception need so I can design the surface correctly before implementation starts.
|
||||
|
||||
**Why this priority**: The guardrails only prevent drift if they shape work before code review. Up-front classification keeps the repo from discovering surface-contract problems only after implementation is already expensive to unwind.
|
||||
|
||||
**Independent Test**: Can be fully tested by drafting a new UI or surface spec and confirming that the planning artifacts capture the required classifications, expected test depth, and exception need without adding a second workflow.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a new complex surface is proposed, **When** the author fills the relevant spec and plan and task fields, **Then** the work item records whether the surface is native, custom, shared-family relevant, or exception-bound and which state layers are involved.
|
||||
2. **Given** a legitimate special visualization or bounded local-state surface is needed, **When** the author declares the exception, **Then** the planning artifacts capture why default primitives do not fit, how the exception remains bounded, and which standardized parts still stay intact.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Maintainers Get Proportionate Signals And Test Triggers (Priority: P2)
|
||||
|
||||
As a maintainer, I want a catalog of repository signals and required test triggers so likely drift becomes visible in daily work without making every custom surface a false-positive trap or a hard CI failure.
|
||||
|
||||
**Why this priority**: Repository-level visibility is the bridge between pure policy and daily implementation. Without signal and test guidance, drift will remain review-dependent and inconsistent.
|
||||
|
||||
**Independent Test**: Can be fully tested by mapping known drift patterns and surface classes to the catalog and confirming that every signal has a handling mode and every relevant surface class has a declared test depth.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a repository signal matches a likely fake-native or shell-resolution pattern, **When** the maintainer consults the catalog, **Then** the handling mode is explicit as report-only, review-mandatory, exception-required, or hard-stop candidate.
|
||||
2. **Given** a new shared detail family, monitoring page with its own state contract, context shell touchpoint, or exception-coded surface is introduced, **When** the work is prepared for merge, **Then** the required tests and smoke expectations are visible and standard native surfaces are not burdened with unnecessary bespoke coverage.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A technical signal flags a legitimate custom surface; the exception path must prevent noisy permanent false positives.
|
||||
- A one-off exception starts spreading to additional hosts or surfaces; the guardrails must require renewed review instead of treating the first approval as general precedent.
|
||||
- A standard native Filament surface changes without introducing a new state contract; the guardrails must avoid demanding special smoke coverage that adds friction without value.
|
||||
- Spec 200 evolves later; the guardrail catalog must remain explicitly traceable to the rule it operationalizes so the workflow does not drift from the constitution.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment:** This feature adds no product runtime behavior, no Microsoft Graph behavior, no new authorization plane, and no new operator-facing page. It does add repository and workflow guardrails for operator-facing UI and surface work, so the resulting review vocabulary, signal catalog, exception model, and close-out expectations must remain explicit, bounded, and traceable to Spec 200.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001 Guardrail Catalog**: The implementation MUST map every relevant Spec 200 rule family to one or more of the following operational classes: review guardrail, repository signal, required test signal, exception signal, or workflow integration requirement.
|
||||
- **FR-002 Mandatory Review Checklist**: The implementation MUST provide one binding review checklist for UI and surface work that reviewers can apply without inventing local terminology.
|
||||
- **FR-003 Checklist Questions**: The review checklist MUST require explicit answers for at least these questions: whether the surface is native-by-default or legitimately custom, whether expected native primitives are used, whether any deviation is an explicit exception, whether the surface belongs to a shared detail family or one-off host, whether host drift exists or is emerging, which state layers are present, how URL or query state is classified, whether competing interaction models exist, whether the surface is a simple overview or a true special visualization, and whether an existing exception is being expanded quietly.
|
||||
- **FR-004 Review Outcomes**: The review checklist MUST classify findings as blocker, strong warning, documentation-required exception, or acceptable special case.
|
||||
- **FR-005 Merge Readiness Rule**: New complex surfaces MUST NOT be treated as ready for merge until their guardrail classification is explicit in review and associated planning artifacts.
|
||||
- **FR-006 Repository Signal Catalog**: The implementation MUST define repository-level warning signals for the main fake-native and drift-prone patterns, including GET-form interactions inside Filament surfaces, plain controls masquerading as native controls, request-driven UI state near page-body surfaces, hand-built simple overviews where standard primitives should remain primary, host-specific forks of known shared families, and shell-context resolution logic leaking into presentation partials.
|
||||
- **FR-007 Signal Handling Modes**: Every repository signal MUST declare whether it is report-only, review-mandatory, exception-required, or a hard-stop candidate, and whether the signal is eligible for later promotion to blocking.
|
||||
- **FR-008 False-Positive Control**: Repository guardrails MUST include an explicit exception or review path so legitimate custom surfaces do not become permanently noisy false positives.
|
||||
- **FR-009 Test Guardrail Matrix**: The implementation MUST define which special surface classes require extra test depth, including shared detail micro-UI families, monitoring or governance pages with their own state contracts, global context shell surfaces, and legitimate exception surfaces with intentional special contracts.
|
||||
- **FR-010 Required Test Types**: For each special surface class, the guardrail model MUST state whether functional core interaction tests, state-contract tests, exception or fallback behavior tests, and manual smoke expectations are required.
|
||||
- **FR-011 Standard Surface Relief**: The guardrail model MUST state when standard native Filament surfaces do not need extra special tests beyond normal feature coverage.
|
||||
- **FR-012 Exception Model**: Every legitimate exception MUST document which default rule it breaks or does not fully satisfy, why native or default behavior is insufficient, how the exception remains bounded, which parts remain standardized, and which follow-on risks are consciously accepted.
|
||||
- **FR-013 Exception Spread Control**: Exceptions MUST NOT extend silently to additional hosts or surfaces; any expansion requires renewed explicit review.
|
||||
- **FR-014 Workflow Integration**: The implementation MUST integrate guardrail expectations into spec creation, planning, tasks, implementation review, definition-of-done checks, and follow-up exception documentation for UI and surface-relevant work.
|
||||
- **FR-015 Planning Visibility**: UI and surface-relevant specs MUST capture native versus custom classification, state-layer classification, shared-family relevance, and exception need in a way that is visible before implementation begins.
|
||||
- **FR-016 Close-Out Visibility**: Completion notes for relevant work MUST record which guardrail class was triggered, whether an exception was required, and which tests or smoke checks were added or intentionally not needed in the active feature PR close-out entry.
|
||||
- **FR-017 Guardrail Matrix**: The implementation MUST publish a rule matrix that distinguishes hard-stop candidates, review-mandatory cases, exception-required cases, and report-only cases with representative examples from Specs 196 through 200.
|
||||
- **FR-018 Deliverable Set**: The implementation MUST produce a guardrail catalog, a UI and surface review checklist, a technical signal catalog, a test-guardrail catalog, an exception workflow, workflow integration notes, and close-out documentation describing what remains review-only versus technically signalable.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- **NFR-001 Early Warning**: The guardrails must expose likely drift early enough that review can stop it before another cleanup pass becomes necessary.
|
||||
- **NFR-002 Proportionality**: Only patterns that are reliably observable should become repository signals or future hard-stop candidates; judgment-heavy questions must remain structured review instead of fake precision.
|
||||
- **NFR-003 Workflow Fit**: The guardrails must strengthen the existing spec-driven workflow instead of creating a second process that contributors must learn separately, keep the low-impact path completable in under 1 minute, keep a representative guarded review completable in under 3 minutes, and avoid duplicating the same classification question unnecessarily across spec, plan, task, review, and close-out surfaces.
|
||||
- **NFR-004 Traceability**: Every guardrail class and example must remain traceable to the governing rules from Spec 200 and its supporting Specs 196 through 199.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Guardrail Catalog**: The authoritative operational mapping from Spec 200 rules to review expectations, repository signals, test triggers, exception handling, and workflow touchpoints.
|
||||
- **Review Checklist**: The binding question set that reviewers and authors use to classify UI and surface work consistently.
|
||||
- **Repository Signal**: A documented red-flag pattern that can expose likely drift and carries a defined handling mode.
|
||||
- **Test Guardrail Profile**: The required test and smoke depth attached to a surface class when its contract is more complex than standard native surfaces.
|
||||
- **Exception Record**: The visible justification that bounds a legitimate deviation from the default surface rules and prevents silent expansion.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Every targeted drift class from Spec 200 has at least one documented mapping to review guardrails, repository signals, required test handling, or exception handling.
|
||||
- **SC-002**: Reviewers can classify the representative cases of fake-native dependency edges, legitimate special visualization, shared-family host drift, and shell or page or detail state confusion into the defined review outcome classes and handling modes without inventing new categories.
|
||||
- **SC-003**: All UI and surface-relevant work items created after rollout include explicit native or custom classification, state-layer classification, shared-family relevance, and exception need in their planning artifacts.
|
||||
- **SC-004**: Every documented exception includes all required justification fields and an explicit boundary that prevents silent reuse as general precedent.
|
||||
- **SC-005**: Every repository signal in the catalog has an assigned handling mode and at least one documented review or exception path, so no signal remains an ownerless warning.
|
||||
- **SC-006**: Reviewers can determine in one pass which special surface classes require additional tests and which standard native surfaces do not.
|
||||
- **SC-007**: A low-impact docs-only workflow path remains completable in under 1 minute, a representative guarded review remains completable in under 3 minutes, and neither path requires contributors to answer the same classification question redundantly across workflow surfaces.
|
||||
|
||||
## Validation Notes
|
||||
|
||||
### Reviewer Workflow Validation
|
||||
|
||||
| Scenario | Source artifact | Outcome class | Handling mode | Workflow outcome | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| Low-impact docs-only path | `.specify/templates/checklist-template.md` + `.specify/README.md` | `acceptable-special-case` | `report-only` | `keep` | One `N/A` note stayed sufficient; no fake UI-surface prose was required |
|
||||
| Fake-native hard signal | `specs/196-hard-filament-nativity-cleanup/spec.md` | `blocker` | `hard-stop-candidate` | `reject-or-split` | Fake-native drift still reaches the strongest guardrail outcome cleanly |
|
||||
| Shared-family host drift | `specs/197-shared-detail-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | The workflow stops host drift without inventing new categories |
|
||||
| State-layer confusion | `specs/198-monitoring-page-state/spec.md` + `specs/199-global-context-shell-contract/spec.md` | `strong-warning` | `review-mandatory` | `document-in-feature` | Shell/page/detail ownership now resolves through one fixed question set |
|
||||
| Legitimate special visualization | `specs/200-filament-surface-rules/spec.md` | `documentation-required-exception` | `exception-required` | `document-in-feature` | Legitimate special cases remain allowed only with bounded exception notes |
|
||||
|
||||
- Representative guarded review elapsed time: `02:34`
|
||||
- Duplicate-question note: the final workflow asks for native/custom, shared-family, and state ownership once in the spec, then reuses that classification in plan, tasks, checklist, and close-out.
|
||||
|
||||
### Authoring Workflow Validation
|
||||
|
||||
| Scenario | Source artifact | Outcome | Elapsed time | Notes |
|
||||
|---|---|---|---|---|
|
||||
| Low-impact docs-only flow | `.specify/templates/checklist-template.md` + `.specify/README.md` | `keep` | `00:48` | Low-impact `N/A` remains fast and does not fabricate runtime obligations |
|
||||
| Surface-changing authoring flow | `specs/200-filament-surface-rules/spec.md` | `document-in-feature` | `01:51` | Native/custom classification, handling modes, and proof depth stay explicit without adding a second workflow |
|
||||
|
||||
### Signal And Test-Trigger Validation
|
||||
|
||||
| Drift / surface class | Handling mode | Required proof profile | Close-out expectation |
|
||||
|---|---|---|---|
|
||||
| Fake-native hard signal | `hard-stop-candidate` | Special proof only if a bounded exception is claimed | Close-out notes are required only when an exception is still in play |
|
||||
| Shared-family host drift | `review-mandatory` | `shared-detail-family` | Record host/core boundary and any exception spread control |
|
||||
| Monitoring or shell state-layer confusion | `review-mandatory` | `monitoring-state-page` or `global-context-shell` | Record state-owner proof and any required smoke |
|
||||
| Legitimate special visualization | `exception-required` | `exception-coded-surface` | Record bounded exception, preserved standards, and manual smoke |
|
||||
| Standard native Filament surface | `report-only` | `standard-native-filament` relief | Record ordinary feature coverage only; no bespoke guardrail proof required |
|
||||
|
||||
- Standard-native relief rule: standard native Filament work does not need extra bespoke guardrail tests unless it introduces a shared-family contract, shell/page/detail state ownership risk, or a bounded exception.
|
||||
- Active feature PR close-out entry name: `Guardrail / Exception / Smoke Coverage`
|
||||
- First-pass automation deferrals remain explicit: report-first signals only, no CI hard-stop, no PR bot, no auto-promotion of review-mandatory cases.
|
||||
@ -1,187 +0,0 @@
|
||||
# Tasks: Enforcement & Review Guardrails
|
||||
|
||||
**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/201-enforcement-review-guardrails/`
|
||||
**Prerequisites**: `plan.md` (required), `spec.md` (required), `research.md`, `data-model.md`, `contracts/`, `quickstart.md`
|
||||
|
||||
**Tests**: Not required. This feature is docs and workflow only, so validation is by representative low-impact and guarded-case walkthroughs, cross-artifact consistency review, and recording the outcomes in the active spec artifacts.
|
||||
|
||||
**Organization**: Tasks are grouped by user story so each story can be implemented and validated independently where possible.
|
||||
|
||||
## Phase 1: Setup (Shared Context)
|
||||
|
||||
**Purpose**: Freeze the real workflow surfaces and the exact implementation scope before editing templates or guidance.
|
||||
|
||||
- [X] T001 Audit `specs/201-enforcement-review-guardrails/spec.md`, `specs/201-enforcement-review-guardrails/plan.md`, `specs/201-enforcement-review-guardrails/research.md`, `specs/201-enforcement-review-guardrails/data-model.md`, `specs/201-enforcement-review-guardrails/quickstart.md`, `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, and `docs/ui/operator-ux-surface-standards.md` against Specs 196 through 200 to confirm the exact guardrail gaps and optional wording-alignment scope
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Stabilization Prerequisites)
|
||||
|
||||
**Purpose**: Reconcile and stabilize the shared guardrail vocabulary and structured contracts that every later template and checklist update depends on.
|
||||
|
||||
**Critical**: No user story work should begin until this phase is complete.
|
||||
|
||||
- [X] T002 Reconcile and confirm `specs/201-enforcement-review-guardrails/data-model.md` as the stable source for rule mappings, reviewer question set, repository signals, test-guardrail profiles, exception workflow, workflow touchpoints, validation scenarios, and the active feature PR close-out entry that all later workflow surfaces must reuse
|
||||
- [X] T003 [P] Reconcile `specs/201-enforcement-review-guardrails/contracts/guardrail-governance.schema.json` with the confirmed rule mappings, review outcome classes, repository-signal handling modes, test-guardrail profiles, validation scenarios, and the active feature PR close-out entry from `specs/201-enforcement-review-guardrails/data-model.md`
|
||||
- [X] T004 [P] Reconcile `specs/201-enforcement-review-guardrails/contracts/guardrail-governance.logical.openapi.yaml` with the confirmed logical flows for spec-impact validation, review classification, repository-signal assessment, test-guardrail resolution, exception assessment, and the active feature PR close-out entry
|
||||
- [X] T005 [P] Refine `specs/201-enforcement-review-guardrails/quickstart.md` with the canonical low-impact path, the representative fake-native/shared-family/state-layer/exception walkthroughs, the timing and duplicate-prompt validation expectations, and the explicit first-pass deferral list for grep/lint/CI hardening
|
||||
|
||||
**Checkpoint**: The shared vocabulary, contracts, and validation scenarios are stable enough for story-specific workflow changes.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Reviewers Classify Drift Early (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Give reviewers one fixed checklist and outcome model that catches fake-native drift, host drift, hidden exceptions, and shell/page/detail confusion before merge.
|
||||
|
||||
**Independent Test**: Apply the finished reviewer workflow to the representative cases from Specs 196 through 200 and confirm the review can reach the intended guardrail outcome without inventing new categories.
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T006 [P] [US1] Update `.specify/templates/checklist-template.md` with the fixed UI/surface guardrail questions, the review outcome classes (`blocker`, `strong-warning`, `documentation-required-exception`, `acceptable-special-case`), and the explicit workflow outcomes (`keep`, `split`, `document-in-feature`, `follow-up-spec`, `reject-or-split`)
|
||||
- [X] T007 [P] [US1] Update `.specify/README.md` as the reviewer entry point for applying the canonical UI/surface guardrail checklist, handling low-impact `N/A` cases, and interpreting the workflow outcomes consistently
|
||||
- [X] T008 [US1] Validate the reviewer workflow against `specs/196-hard-filament-nativity-cleanup/spec.md`, `specs/197-shared-detail-contract/spec.md`, `specs/198-monitoring-page-state/spec.md`, `specs/199-global-context-shell-contract/spec.md`, and `specs/200-filament-surface-rules/spec.md`, then record the reached review outcome classes, handling modes, elapsed guarded-review time, duplicate-question notes, and any wording refinements in `specs/201-enforcement-review-guardrails/spec.md` and `specs/201-enforcement-review-guardrails/quickstart.md`
|
||||
|
||||
**Checkpoint**: Reviewers can classify the target drift cases early and consistently.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - Authors Declare Native Vs Custom Up Front (Priority: P1)
|
||||
|
||||
**Goal**: Make authors capture native/custom classification, state-layer ownership, shared-family relevance, and exception need during spec and plan authoring.
|
||||
|
||||
**Independent Test**: Draft a low-impact docs-only path and a UI/surface-changing path through the updated spec and plan prompts and confirm the required classifications are explicit without adding a second workflow.
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T009 [P] [US2] Update `.specify/templates/spec-template.md` so UI/surface-changing specs explicitly capture native/custom classification, state-layer ownership, shared-family relevance, exception need, and low-impact `N/A` handling when no operator-facing surface changes exist
|
||||
- [X] T010 [P] [US2] Update `.specify/templates/plan-template.md` so planning artifacts explicitly capture guardrail handling modes, repository-signal treatment, required test depth for special surface classes, and the active feature PR close-out entry for exceptions and smoke coverage
|
||||
- [X] T011 [US2] Validate the authoring flow using a low-impact docs-only scenario limited to `.specify/templates/checklist-template.md` and `.specify/README.md` plus a surface-changing scenario grounded in `specs/200-filament-surface-rules/spec.md`, then record the authoring outcome, low-impact completion time, duplicate-prompt notes, and any prompt refinements in `specs/201-enforcement-review-guardrails/spec.md` and `specs/201-enforcement-review-guardrails/quickstart.md`
|
||||
|
||||
**Checkpoint**: Authors can classify surface risk and exception need before implementation begins.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - Maintainers Get Proportionate Signals And Test Triggers (Priority: P2)
|
||||
|
||||
**Goal**: Make likely drift visible through repository signals and define which surface classes require special tests or smoke checks without overhardening standard native surfaces.
|
||||
|
||||
**Independent Test**: Map the representative drift classes and surface classes to the final signal catalog and test-guardrail matrix, then confirm each one has a clear handling mode or required test profile.
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T012 [P] [US3] Update `.specify/templates/tasks-template.md` so generated task lists carry forward native/custom, state-layer, shared-family, and exception classification into implementation tasks and require repository-signal handling, review classification work, definition-of-done checks, exception documentation, required tests or manual smoke checks, and the active feature PR close-out entry whenever a special surface class is involved
|
||||
- [X] T013 [P] [US3] Finalize the repository-signal catalog, test-guardrail matrix, standard-native surface relief rule, and first-pass automation deferrals in `specs/201-enforcement-review-guardrails/research.md`, `specs/201-enforcement-review-guardrails/data-model.md`, and `specs/201-enforcement-review-guardrails/quickstart.md`
|
||||
- [X] T014 [US3] Validate signal and test-trigger handling against `specs/196-hard-filament-nativity-cleanup/spec.md`, `specs/197-shared-detail-contract/spec.md`, `specs/198-monitoring-page-state/spec.md`, `specs/199-global-context-shell-contract/spec.md`, and `specs/200-filament-surface-rules/spec.md`, then record the final handling-mode matrix, required test profiles, standard-native surface relief rule, active feature PR close-out entry, and any wording refinements in `specs/201-enforcement-review-guardrails/spec.md` and `specs/201-enforcement-review-guardrails/quickstart.md`
|
||||
|
||||
**Checkpoint**: Maintainers have one proportionate reference for signals, required tests, and deferred hardening.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Reconcile the finished workflow surfaces and remove drift between the templates, guidance, and active feature artifacts.
|
||||
|
||||
- [X] T015 [P] Align `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `specs/201-enforcement-review-guardrails/spec.md`, and `specs/201-enforcement-review-guardrails/quickstart.md` so the same guardrail vocabulary and workflow outcomes appear once and without contradictory wording
|
||||
- [X] T016 [P] Add a minimal guardrail workflow cross-reference in `docs/ui/operator-ux-surface-standards.md` only if the finished templates and `.specify/README.md` still need an explicit pointer from operator-UX standards back to the `.specify/` review workflow
|
||||
- [X] T017 Run the completion checklist in `specs/201-enforcement-review-guardrails/quickstart.md` against `.specify/templates/spec-template.md`, `.specify/templates/plan-template.md`, `.specify/templates/tasks-template.md`, `.specify/templates/checklist-template.md`, `.specify/README.md`, `specs/201-enforcement-review-guardrails/spec.md`, and `specs/201-enforcement-review-guardrails/plan.md`, then record any remaining first-pass automation deferrals, low-impact completion time, representative guarded-review completion time, active feature PR close-out entry targeting, and redundant-prompt findings in `specs/201-enforcement-review-guardrails/quickstart.md`
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies and can start immediately.
|
||||
- **Foundational (Phase 2)**: Depends on Setup and blocks all user story work.
|
||||
- **User Story 1 (Phase 3)**: Depends on Foundational and delivers the MVP review workflow.
|
||||
- **User Story 2 (Phase 4)**: Depends on Foundational and can proceed independently of User Story 1 once the shared guardrail vocabulary is stable.
|
||||
- **User Story 3 (Phase 5)**: Depends on Foundational and benefits from the live authoring/review surfaces from User Stories 1 and 2 before final validation closes.
|
||||
- **Polish (Phase 6)**: Depends on all desired user stories being complete.
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **User Story 1 (P1)**: Can begin immediately after Foundational and is the recommended MVP slice.
|
||||
- **User Story 2 (P1)**: Can begin immediately after Foundational and delivers an independent authoring workflow improvement.
|
||||
- **User Story 3 (P2)**: Can begin after Foundational, but its final validation should run after User Stories 1 and 2 have stabilized the workflow surfaces it references.
|
||||
|
||||
### Within Each User Story
|
||||
|
||||
- Feature-local guardrail vocabulary and contracts must be stable before template wording is finalized.
|
||||
- Template changes should land before story-specific validation notes are recorded in `spec.md` and `quickstart.md`.
|
||||
- Validation walkthroughs should complete before closing the corresponding story.
|
||||
- Cross-artifact cleanup should happen only after all targeted workflow surfaces are updated.
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
- `T003`, `T004`, and `T005` can run in parallel after `T002` reconciles the guardrail vocabulary and the active feature PR close-out entry.
|
||||
- `T006` and `T007` can run in parallel because they update different reviewer-facing workflow surfaces.
|
||||
- `T009` and `T010` can run in parallel because they update different authoring surfaces.
|
||||
- `T012` and `T013` can run in parallel once Foundational work is stable because they touch different implementation surfaces.
|
||||
- `T015` and `T016` can run in parallel during Polish because they target different files.
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: Foundational Work
|
||||
|
||||
```bash
|
||||
# After T002 reconciles the guardrail vocabulary and the active feature PR close-out entry:
|
||||
Task: "Align specs/201-enforcement-review-guardrails/contracts/guardrail-governance.schema.json"
|
||||
Task: "Align specs/201-enforcement-review-guardrails/contracts/guardrail-governance.logical.openapi.yaml"
|
||||
Task: "Update specs/201-enforcement-review-guardrails/quickstart.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: User Story 1
|
||||
|
||||
```bash
|
||||
# After Foundational is complete:
|
||||
Task: "Update .specify/templates/checklist-template.md"
|
||||
Task: "Update .specify/README.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: User Story 2
|
||||
|
||||
```bash
|
||||
# After Foundational is complete:
|
||||
Task: "Update .specify/templates/spec-template.md"
|
||||
Task: "Update .specify/templates/plan-template.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
|
||||
1. Complete Phase 1: Setup.
|
||||
2. Complete Phase 2: Foundational.
|
||||
3. Complete Phase 3: User Story 1.
|
||||
4. Validate the representative reviewer cases before continuing.
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Lock the shared guardrail vocabulary and contracts first.
|
||||
2. Deliver the reviewer-facing workflow surfaces.
|
||||
3. Deliver the authoring prompts for specs and plans.
|
||||
4. Deliver the maintainer-facing task and signal/test-trigger surfaces.
|
||||
5. Finish with cross-artifact cleanup and quickstart completion review.
|
||||
|
||||
### Parallel Team Strategy
|
||||
|
||||
1. One contributor can stabilize the feature-local guardrail vocabulary while another prepares the contract and quickstart updates after the foundational mapping is fixed.
|
||||
2. Reviewer-facing surfaces in User Story 1 can be updated in parallel once Foundational is done.
|
||||
3. Spec and plan template work in User Story 2 can be updated in parallel once the same vocabulary is stable.
|
||||
4. Maintainer-facing task-template updates and final signal/test-trigger refinements can run in parallel late in the workflow.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- `[P]` tasks operate on different files or independent workflow surfaces and can run in parallel once dependencies are satisfied.
|
||||
- `[US1]`, `[US2]`, and `[US3]` map tasks directly to the user stories in `spec.md`.
|
||||
- This feature is docs and workflow only, so validation is recorded in the active spec artifacts rather than by running Pest lanes.
|
||||
- First-pass repository signals remain report-first and exception-aware; grep/lint/CI hardening stays explicitly deferred unless the implementation proves a smaller workflow surface is insufficient.
|
||||
@ -1,39 +0,0 @@
|
||||
# Specification Quality Checklist: Initial Website Foundation & v0 Product Site
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-04-18
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Validation run: 2026-04-18
|
||||
- No template placeholders or [NEEDS CLARIFICATION] markers remain.
|
||||
- The spec stays outcome-first: it defines public product clarity, trust, audience fit, and conversion behavior without prescribing a framework or implementation stack.
|
||||
- The scope is bounded to the smallest publishable website v0 and explicitly excludes product-app behavior, deep runtime coupling, full docs/blog/CMS rollout, and speculative trust claims.
|
||||
- The spec records launch dependencies for legal facts, contact handling, and substantiated trust language so planning can treat them as explicit inputs rather than hidden assumptions.
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
@ -1,168 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: TenantAtlas Public Website Surface Contract
|
||||
version: 0.1.0
|
||||
summary: Static route contract for the v0 TenantAtlas public website.
|
||||
description: >-
|
||||
This contract defines the required public routes for Spec 213. The website
|
||||
serves static HTML pages only in this feature. Contact handling remains a
|
||||
page-level conversion surface and does not introduce an internal submission
|
||||
API in Spec 213.
|
||||
servers:
|
||||
- url: http://localhost:{port}
|
||||
description: Local Astro development or preview server
|
||||
variables:
|
||||
port:
|
||||
default: "4321"
|
||||
tags:
|
||||
- name: Public Pages
|
||||
description: Public HTML routes required for v0 launch
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getHomePage
|
||||
summary: Home page
|
||||
description: Product-category framing, product pillars, trust positioning, and next-step CTA.
|
||||
responses:
|
||||
"200":
|
||||
description: Home page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/product:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getProductPage
|
||||
summary: Product page
|
||||
description: Explains TenantAtlas as one connected governance model rather than a loose feature list.
|
||||
responses:
|
||||
"200":
|
||||
description: Product page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/solutions:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getSolutionsPage
|
||||
summary: Solutions page
|
||||
description: Audience-specific fit for MSP and Enterprise IT visitors.
|
||||
responses:
|
||||
"200":
|
||||
description: Solutions page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/security-trust:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getSecurityTrustPage
|
||||
summary: Security and Trust page
|
||||
description: Product principles, trust posture, and substantiated public claims.
|
||||
responses:
|
||||
"200":
|
||||
description: Security and Trust page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/integrations:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getIntegrationsPage
|
||||
summary: Integrations page
|
||||
description: Public ecosystem fit for Microsoft-centric workflows and adjacent systems only where grounded in product truth.
|
||||
responses:
|
||||
"200":
|
||||
description: Integrations page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/contact:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getContactPage
|
||||
summary: Contact or demo page
|
||||
description: Qualification and next-step page for contact intent.
|
||||
responses:
|
||||
"200":
|
||||
description: Contact page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/legal:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getLegalIndexPage
|
||||
summary: Legal index page
|
||||
description: Landing page for public legal disclosures.
|
||||
responses:
|
||||
"200":
|
||||
description: Legal index HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/privacy:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getPrivacyPage
|
||||
summary: Privacy page
|
||||
description: Public privacy disclosure required for launch.
|
||||
responses:
|
||||
"200":
|
||||
description: Privacy page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/terms:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getTermsPage
|
||||
summary: Terms page
|
||||
description: Public terms disclosure required for launch.
|
||||
responses:
|
||||
"200":
|
||||
description: Terms page HTML
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
$ref: "#/components/schemas/HtmlDocument"
|
||||
/robots.txt:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getRobotsFile
|
||||
summary: Robots file
|
||||
description: Public robots instructions for search crawlers.
|
||||
responses:
|
||||
"200":
|
||||
description: Robots text output
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/sitemap.xml:
|
||||
get:
|
||||
tags: [Public Pages]
|
||||
operationId: getSitemapFile
|
||||
summary: Sitemap file
|
||||
description: Public XML sitemap covering the published website routes.
|
||||
responses:
|
||||
"200":
|
||||
description: XML sitemap output
|
||||
content:
|
||||
application/xml:
|
||||
schema:
|
||||
type: string
|
||||
components:
|
||||
schemas:
|
||||
HtmlDocument:
|
||||
type: string
|
||||
description: Server-rendered static HTML document
|
||||
@ -1,158 +0,0 @@
|
||||
# Data Model: Initial Website Foundation & v0 Product Site
|
||||
|
||||
## Overview
|
||||
|
||||
This feature introduces no database schema. The model is file- and route-based inside `apps/website` and describes how public content, page composition, navigation, and conversion surfaces are structured.
|
||||
|
||||
## Entities
|
||||
|
||||
### Site Configuration
|
||||
|
||||
- **Purpose**: Global site metadata and shared shell settings used by every public page.
|
||||
- **Key fields**:
|
||||
- `siteName`
|
||||
- `siteTagline`
|
||||
- `defaultTitle`
|
||||
- `defaultDescription`
|
||||
- `defaultOgImage`
|
||||
- `primaryNavigation`
|
||||
- `footerNavigationGroups`
|
||||
- `contactCta`
|
||||
- **Relationships**:
|
||||
- Owns many `NavigationItem` entries
|
||||
- Supplies defaults to many `PublicPage` entries
|
||||
- **Validation rules**:
|
||||
- Must define a Home destination via brand/logo path
|
||||
- Must include Product, Solutions, Security & Trust, Integrations, and Contact / Demo in primary navigation
|
||||
- Must include Privacy and Terms in footer navigation
|
||||
|
||||
### Public Page
|
||||
|
||||
- **Purpose**: One published route in the v0 public website.
|
||||
- **Key fields**:
|
||||
- `slug`
|
||||
- `title`
|
||||
- `description`
|
||||
- `routePath`
|
||||
- `pageRole` (`home`, `product`, `solutions`, `trust`, `integrations`, `contact`, `legal`)
|
||||
- `hero`
|
||||
- `sections`
|
||||
- `primaryCta`
|
||||
- `secondaryCta`
|
||||
- `seo`
|
||||
- **Relationships**:
|
||||
- Has one or more `NarrativeSection` entries
|
||||
- May reference zero or more `AudienceNarrative` or `IntegrationEntry` entries depending on page role
|
||||
- Consumes shared defaults from `SiteConfiguration`
|
||||
- **Validation rules**:
|
||||
- Each published page must have a unique `routePath`
|
||||
- Each page must provide title, description, and at least one next-step path
|
||||
- Product and Solutions must remain distinct page roles
|
||||
|
||||
### Narrative Section
|
||||
|
||||
- **Purpose**: Reusable section-level content block used to compose pages.
|
||||
- **Key fields**:
|
||||
- `sectionType` (`hero`, `problem-framing`, `feature-grid`, `trust-grid`, `logo-strip`, `cta`, `rich-text`, `audience-row`, `legal-prose`)
|
||||
- `eyebrow`
|
||||
- `headline`
|
||||
- `body`
|
||||
- `items`
|
||||
- `themeVariant`
|
||||
- **Relationships**:
|
||||
- Belongs to one `PublicPage`
|
||||
- May include many `SectionItem` entries through `items`
|
||||
- **Validation rules**:
|
||||
- Each section must support a clear narrative purpose
|
||||
- Sections used on public core pages must remain semantically meaningful and not decorative-only
|
||||
|
||||
### Navigation Item
|
||||
|
||||
- **Purpose**: A user-facing route link in the header or footer.
|
||||
- **Key fields**:
|
||||
- `label`
|
||||
- `href`
|
||||
- `placement` (`header`, `footer`)
|
||||
- `group`
|
||||
- `order`
|
||||
- `isExternal`
|
||||
- **Relationships**:
|
||||
- Belongs to `SiteConfiguration`
|
||||
- Usually points to one `PublicPage`
|
||||
- **Validation rules**:
|
||||
- Footer items for legal routes must point to published pages
|
||||
- No live navigation item may point to an unpublished placeholder route
|
||||
|
||||
### Call To Action
|
||||
|
||||
- **Purpose**: Standardized next-step action shown within or after narrative sections.
|
||||
- **Key fields**:
|
||||
- `label`
|
||||
- `href`
|
||||
- `variant` (`primary`, `secondary`)
|
||||
- `context`
|
||||
- **Relationships**:
|
||||
- May belong to `PublicPage`, `NarrativeSection`, or `SiteConfiguration`
|
||||
- **Validation rules**:
|
||||
- Every core page must expose at least one CTA leading deeper into the product/trust/contact flow
|
||||
- CTA labels must preserve consistent domain wording and avoid hype language
|
||||
|
||||
### Audience Narrative
|
||||
|
||||
- **Purpose**: Audience-specific framing used primarily on Solutions and supporting pages.
|
||||
- **Key fields**:
|
||||
- `audience`
|
||||
- `problemStatement`
|
||||
- `operatingModelFit`
|
||||
- `proofPoints`
|
||||
- `nextStep`
|
||||
- **Relationships**:
|
||||
- Belongs to the Solutions page or other audience-aware sections
|
||||
- **Validation rules**:
|
||||
- Must distinguish MSP and Enterprise IT narratives rather than collapsing them into generic copy
|
||||
|
||||
### Integration Entry
|
||||
|
||||
- **Purpose**: A public description of one real integration direction or ecosystem anchor.
|
||||
- **Key fields**:
|
||||
- `name`
|
||||
- `summary`
|
||||
- `scopeNote`
|
||||
- `category`
|
||||
- **Relationships**:
|
||||
- Belongs to the Integrations page
|
||||
- **Validation rules**:
|
||||
- Must represent a real or explicitly planned integration direction
|
||||
- Must not express speculative wishlist claims as if they were live product truth
|
||||
|
||||
### Legal Document
|
||||
|
||||
- **Purpose**: Public legal content required to support a credible launch.
|
||||
- **Key fields**:
|
||||
- `slug`
|
||||
- `title`
|
||||
- `summary`
|
||||
- `sections`
|
||||
- `lastReviewedAt`
|
||||
- **Relationships**:
|
||||
- Belongs to the Legal surface
|
||||
- **Validation rules**:
|
||||
- Privacy and Terms must both exist before launch
|
||||
- Any required jurisdiction-specific public legal notice must exist either as a section of the Legal hub or as a dedicated linked legal document before launch
|
||||
- Legal content must be reachable from the footer and the conversion path
|
||||
|
||||
## Relationship Summary
|
||||
|
||||
- `SiteConfiguration` has many `NavigationItem`
|
||||
- `SiteConfiguration` provides defaults to many `PublicPage`
|
||||
- `PublicPage` has many `NarrativeSection`
|
||||
- `PublicPage` may have many `CallToAction`
|
||||
- `Solutions` page has many `AudienceNarrative`
|
||||
- `Integrations` page has many `IntegrationEntry`
|
||||
- `Legal` surface has many `LegalDocument`
|
||||
|
||||
## State / Lifecycle Notes
|
||||
|
||||
- No persisted runtime states are introduced.
|
||||
- Publishability is file- and route-driven: a route is “live” when the page exists and is intentionally linked; future sections remain absent from navigation until live.
|
||||
- Trust and integration claims stay governed by content review rather than by a new application state machine.
|
||||
@ -1,194 +0,0 @@
|
||||
# Implementation Plan: Initial Website Foundation & v0 Product Site
|
||||
|
||||
**Branch**: `213-website-foundation-v0` | **Date**: 2026-04-18 | **Spec**: `specs/213-website-foundation-v0/spec.md`
|
||||
**Input**: Feature specification from `specs/213-website-foundation-v0/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
||||
|
||||
## Summary
|
||||
|
||||
- Keep `apps/website` as a fully static Astro 6 app and preserve its runtime separation from `apps/platform`.
|
||||
- Introduce explicit TypeScript and Tailwind CSS v4, but implement the UI layer as custom shadcn-inspired Astro primitives instead of adding React plus official shadcn/ui.
|
||||
- Ship a reusable public-site foundation with global layout, navigation, footer, seven core surfaces plus Privacy and Terms, SEO basics, and a lightweight browser-smoke validation path.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Astro 6.0.0 templates + TypeScript 5.x (explicit setup in `apps/website`)
|
||||
**Primary Dependencies**: Astro 6, Tailwind CSS v4, custom Astro component primitives (shadcn-inspired), lightweight Playwright browser smoke tests
|
||||
**Storage**: Static filesystem content, styles, and assets under `apps/website/src` and `apps/website/public`; no database
|
||||
**Testing**: Root build proof via `corepack pnpm build:website` plus Playwright browser smoke coverage for public routes
|
||||
**Validation Lanes**: fast-feedback
|
||||
**Target Platform**: Static public website for modern desktop/mobile browsers
|
||||
**Project Type**: Web (standalone Astro app in a pnpm monorepo)
|
||||
**Performance Goals**: Static HTML for all core routes, zero required framework hydration for reading/navigation flows, minimal client JS reserved for purposeful interactions only
|
||||
**Constraints**: Preserve `@tenantatlas/website`, `WEBSITE_PORT`, root workspace scripts, and `apps/*` contracts; avoid platform auth/session/API coupling; ship one polished primary theme; publish only substantiated trust and integration claims
|
||||
**Scale/Scope**: 7 core public surfaces plus Privacy and Terms as published legal routes, shared layout/content/conversion/trust primitives, and a future-ready structure for blog/docs/changelog without live rollout in v0
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Inventory-first / Graph contract / deterministic capabilities / RBAC-UX / Filament surface rules: N/A for this feature because all implementation stays inside `apps/website` and introduces no `/admin`, `/admin/t/{tenant}/...`, or `/system` runtime changes.
|
||||
- Read/write separation: Pass. The feature is a public, static product site. Contact / Demo remains a static conversion surface in v0 and does not introduce a product-app write workflow.
|
||||
- Workspace isolation: Pass. The website remains runtime-independent from `apps/platform`; no shared auth, session, tenant data, or implicit API dependency is introduced.
|
||||
- Data minimization: Pass. Only public content, styles, metadata, and assets are authored; no tenant payloads, credentials, or operational records are stored.
|
||||
- Test governance (TEST-GOV-001): Pass. Validation stays in `fast-feedback` with static build proof and lightweight browser smoke coverage; no DB/auth/provider fixtures or heavy-suite defaults are added.
|
||||
- Proportionality / no premature abstraction: Pass. The plan adopts a small Astro/Tailwind primitive set instead of React + official shadcn/ui, a CMS, or a broader front-end framework.
|
||||
- Persisted truth / new state: Pass. No database entities, queued work, run lifecycle, or new status families are introduced.
|
||||
- UI semantics / few layers: Pass. Shared layout/content/conversion/trust primitives map directly to the website narrative without a presentation meta-framework.
|
||||
|
||||
Status: ✅ No constitution violations for this feature. The website remains public, static, repo-owned, and separate from platform runtime concerns.
|
||||
|
||||
## Test Governance Check
|
||||
|
||||
> **Fill for any runtime-changing or test-affecting feature. Docs-only or template-only work may state concise `N/A` or `none`.**
|
||||
|
||||
- **Test purpose / classification by changed surface**: Browser smoke coverage for public routes, navigation reachability, and layout stability; static build proof for artifact generation
|
||||
- **Affected validation lanes**: fast-feedback
|
||||
- **Why this lane mix is the narrowest sufficient proof**: The feature changes only a static Astro site. Build proof alone verifies output generation, while a tiny browser smoke suite is the smallest layer that can catch broken routes, broken navigation, and browser-visible regressions without introducing backend or heavy end-to-end cost.
|
||||
- **Narrowest proving command(s)**: `corepack pnpm build:website` and `cd apps/website && corepack pnpm exec playwright test`
|
||||
- **Fixture / helper / factory / seed / context cost risks**: none; public pages do not require database, auth, provider, tenant, or workspace setup
|
||||
- **Expensive defaults or shared helper growth introduced?**: no; any Playwright setup stays local to `apps/website`
|
||||
- **Heavy-family additions, promotions, or visibility changes**: none
|
||||
- **Closing validation and reviewer handoff**: Re-run the website build and smoke suite after page or layout changes. Reviewers should verify that core routes load, primary/footer navigation has no dead ends, the site stays mobile-usable, and no framework hydration is introduced without explicit justification.
|
||||
- **Budget / baseline / trend follow-up**: none beyond tracking the small runtime cost of the website smoke suite inside this feature
|
||||
- **Review-stop questions**: Does the proof stay in fast-feedback? Did any change introduce backend fixtures or hidden framework coupling? Is the smoke suite still small and route-focused?
|
||||
- **Escalation path**: document-in-feature
|
||||
- **Why no dedicated follow-up spec is needed**: The validation surface is tightly bounded to this feature’s public pages. A follow-up spec is only needed if the website later grows interactive workflows, broader content tooling, or cross-app coupling.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/213-website-foundation-v0/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
apps/website/
|
||||
├── astro.config.mjs
|
||||
├── package.json
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── layout/ # Navbar, Footer, page shell wrappers
|
||||
│ │ ├── primitives/ # Container, Section, Button, Badge, Card, Input, Textarea
|
||||
│ │ ├── sections/ # PageHero, FeatureGrid, TrustGrid, CTASection, LogoStrip
|
||||
│ │ └── content/ # Audience rows, evidence callouts, legal prose helpers
|
||||
│ ├── layouts/
|
||||
│ │ └── BaseLayout.astro
|
||||
│ ├── lib/ # Navigation, SEO, metadata, content helpers
|
||||
│ ├── pages/
|
||||
│ │ ├── index.astro
|
||||
│ │ ├── product.astro
|
||||
│ │ ├── solutions.astro
|
||||
│ │ ├── security-trust.astro
|
||||
│ │ ├── integrations.astro
|
||||
│ │ ├── contact.astro
|
||||
│ │ ├── legal.astro
|
||||
│ │ ├── privacy.astro
|
||||
│ │ ├── sitemap.xml.ts
|
||||
│ │ └── terms.astro
|
||||
│ ├── styles/
|
||||
│ │ ├── global.css
|
||||
│ │ └── tokens.css # Tailwind v4 theme tokens / bridge layer
|
||||
│ ├── content/ # Future-ready content collections for docs/blog/changelog
|
||||
│ └── types/ # Content and component prop types
|
||||
└── tests/
|
||||
└── smoke/ # Lightweight Playwright browser smoke coverage
|
||||
```
|
||||
|
||||
**Structure Decision**: Keep the website fully isolated in `apps/website`. Use Astro pages plus a small internal design-system layer (`layout`, `primitives`, `sections`, `content`) and local browser smoke tests. Prepare a future `src/content/` and helper layer for later docs/blog/changelog expansion without shipping those sections in v0.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill when Constitution Check has violations that must be justified OR when BLOAT-001 is triggered by new persistence, abstractions, states, or semantic frameworks.**
|
||||
|
||||
None.
|
||||
|
||||
## Proportionality Review
|
||||
|
||||
> **Fill when the feature introduces a new enum/status family, DTO/presenter/envelope, persisted entity/table/artifact, interface/contract/registry/resolver, taxonomy/classification system, or cross-domain UI framework.**
|
||||
|
||||
- **Current operator problem**: Prospects and internal teams lack a credible public site that explains the product, trust model, and contact path without depending on the platform UI or ad hoc landing-page copy.
|
||||
- **Existing structure is insufficient because**: The current Astro app only contains a single placeholder-style page, handwritten global CSS, and no reusable page system for navigation, legal pages, audience storytelling, or future content expansion.
|
||||
- **Narrowest correct implementation**: A small Astro-native design-system layer backed by Tailwind CSS v4 and explicit TypeScript, with no React requirement and no CMS or runtime platform coupling.
|
||||
- **Ownership cost created**: Ongoing maintenance of public copy, shared website primitives, Tailwind tokens, and a tiny local browser smoke suite.
|
||||
- **Alternative intentionally rejected**: React + official shadcn/ui was rejected because it adds unnecessary client framework weight and maintenance cost for a static trust-first site; a single-page copy refresh was rejected because it would not establish a reusable v0 foundation.
|
||||
- **Release truth**: Current-release truth
|
||||
|
||||
## Phase 0 — Outline & Research (complete)
|
||||
|
||||
- Output: `specs/213-website-foundation-v0/research.md`
|
||||
- Key decisions captured:
|
||||
- Keep Astro 6 static and runtime-independent from `apps/platform`.
|
||||
- Add explicit TypeScript and Tailwind CSS v4 using the CSS-first Tailwind v4 model.
|
||||
- Use custom shadcn-inspired Astro primitives instead of React + official shadcn/ui.
|
||||
- Keep the Contact / Demo surface static in v0 and avoid introducing an Astro API/contact backend in this feature.
|
||||
- Validate with root build proof plus a small Playwright browser smoke suite.
|
||||
|
||||
## Phase 1 — Design & Contracts (complete)
|
||||
|
||||
### Data model
|
||||
|
||||
- Output: `specs/213-website-foundation-v0/data-model.md`
|
||||
- No database schema changes are required; the model is file- and route-based.
|
||||
|
||||
### Public site contracts
|
||||
|
||||
- Output: `specs/213-website-foundation-v0/contracts/public-site.openapi.yaml`
|
||||
- Contract captures the required public GET routes and their HTML response expectations.
|
||||
|
||||
### Quickstart
|
||||
|
||||
- Output: `specs/213-website-foundation-v0/quickstart.md`
|
||||
- Quickstart covers local development, build validation, and browser smoke execution.
|
||||
|
||||
### Agent context update
|
||||
|
||||
- Completed via `.specify/scripts/bash/update-agent-context.sh copilot` so the Copilot context reflects the website stack additions.
|
||||
|
||||
### Constitution re-check (post-design)
|
||||
|
||||
- ✅ Runtime separation remains intact: no platform auth/session/API coupling is introduced.
|
||||
- ✅ No new persisted truth, state machines, or background operations are introduced.
|
||||
- ✅ The chosen UI layer is the narrowest correct implementation for v0 and avoids premature React/framework abstraction.
|
||||
- ✅ Validation remains cheap, local, and website-specific.
|
||||
|
||||
## Phase 2 — Implementation Plan (next)
|
||||
|
||||
### Story 1 (P1): Foundation and shared shell
|
||||
|
||||
- Add explicit TypeScript setup to `apps/website` and install Tailwind CSS v4 using the CSS-first configuration model.
|
||||
- Introduce site metadata, navigation, footer configuration, and shared layout primitives (`Container`, `Section`, `SectionHeader`, `Button`, `Badge`, `Card`, `Input`, `Textarea`, `Navbar`, `Footer`, `PageHero`).
|
||||
- Preserve the existing calm, enterprise-appropriate visual direction while shifting styling from page-local CSS toward reusable tokens and utilities.
|
||||
- Tests / validation:
|
||||
- Confirm the site still builds via `corepack pnpm build:website`.
|
||||
- Add browser smoke coverage for Home plus one interior route.
|
||||
|
||||
### Story 2 (P1/P2): Core page shells and narrative system
|
||||
|
||||
- Build Home, Product, Solutions, Security & Trust, Integrations, Contact, Legal, Privacy, and Terms using shared `sections/` components (`FeatureGrid`, `TrustGrid`, `CTASection`, `LogoStrip`, audience-specific rows, evidence callouts).
|
||||
- Ensure each core page exposes page purpose, clear hierarchy, semantic metadata, and visible next-step CTA.
|
||||
- Keep Solutions separate from Product capabilities and keep Security & Trust limited to substantiated claims.
|
||||
- Tests / validation:
|
||||
- Extend browser smoke coverage to all published core routes.
|
||||
- Assert primary and footer navigation reachability.
|
||||
|
||||
### Story 3 (P3): Delivery hardening and future-ready structure
|
||||
|
||||
- Add SEO and discovery basics (page titles, descriptions, Open Graph baseline, robots, explicit sitemap output, canonical metadata).
|
||||
- Add a future-ready content helper layer and route organization for later docs/blog/changelog expansion without publishing those sections in v0.
|
||||
- Ensure the Legal surface carries any required jurisdiction-specific public legal notice through the Legal hub or a linked dedicated notice page before launch.
|
||||
- Keep contact handling static and repo-owned in this feature; document any later external form or scheduling integration as a follow-up decision rather than coupling it into the website foundation.
|
||||
- Tests / validation:
|
||||
- Finish Playwright smoke checks for mobile navigation, CTA reachability, and absence of dead-end routes.
|
||||
- Re-verify root workspace scripts and package contracts remain intact.
|
||||
@ -1,85 +0,0 @@
|
||||
# Quickstart: Initial Website Foundation & v0 Product Site
|
||||
|
||||
## Purpose
|
||||
|
||||
This quickstart describes the expected local workflow for implementing and validating the v0 TenantAtlas public website inside `apps/website`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node 20+ available via the repo workspace tooling
|
||||
- Corepack-enabled pnpm
|
||||
- Repo root at `wt-website`
|
||||
|
||||
## Local Development
|
||||
|
||||
Start the website development server from the repo root:
|
||||
|
||||
```bash
|
||||
corepack pnpm dev:website
|
||||
```
|
||||
|
||||
The website must continue to honor the existing `WEBSITE_PORT` convention.
|
||||
|
||||
## Expected Foundation Work
|
||||
|
||||
Implement the feature in this order:
|
||||
|
||||
1. Add explicit TypeScript setup inside `apps/website`.
|
||||
2. Add Tailwind CSS v4 using the CSS-first model and migrate styling toward shared tokens and reusable utilities.
|
||||
3. Build shared layout, primitive, section, and content helpers.
|
||||
4. Assemble the published core routes: Home, Product, Solutions, Security & Trust, Integrations, Contact, Legal, Privacy, Terms.
|
||||
5. Add SEO/discovery basics and a lightweight Playwright browser smoke suite.
|
||||
|
||||
## Build Validation
|
||||
|
||||
Run the required website build proof from the repo root:
|
||||
|
||||
```bash
|
||||
corepack pnpm build:website
|
||||
```
|
||||
|
||||
Success means the Astro site builds cleanly without breaking root workspace contracts.
|
||||
|
||||
## Required Final Validation
|
||||
|
||||
Before considering the feature complete, run both proof commands:
|
||||
|
||||
```bash
|
||||
corepack pnpm build:website
|
||||
cd apps/website && corepack pnpm exec playwright test
|
||||
```
|
||||
|
||||
## Browser Smoke Validation
|
||||
|
||||
After the Playwright smoke suite is added to `apps/website`, run:
|
||||
|
||||
```bash
|
||||
cd apps/website
|
||||
corepack pnpm exec playwright test
|
||||
```
|
||||
|
||||
The smoke suite should remain intentionally small and verify:
|
||||
|
||||
- core public routes load successfully
|
||||
- primary and footer navigation have no dead ends
|
||||
- core CTAs remain reachable
|
||||
- mobile navigation remains usable
|
||||
|
||||
## Content / Launch Review
|
||||
|
||||
Before calling the feature complete, confirm:
|
||||
|
||||
- every public claim is substantiated
|
||||
- Product and Solutions stay distinct
|
||||
- Security & Trust contains no speculative promises
|
||||
- Integrations contains no wishlist-only entries presented as live truth
|
||||
- Privacy and Terms are present and linked from footer and contact flow
|
||||
- any required jurisdiction-specific public legal notice is present through the Legal hub or a linked dedicated notice page
|
||||
|
||||
## Out of Scope for This Feature
|
||||
|
||||
- product-app runtime coupling
|
||||
- internal CRM / billing / customer portal logic
|
||||
- a public docs/blog/changelog launch
|
||||
- a required React layer
|
||||
- an internal contact submission backend
|
||||
@ -1,64 +0,0 @@
|
||||
# Research: Initial Website Foundation & v0 Product Site
|
||||
|
||||
## Decision 1: Keep `apps/website` as a fully static Astro 6 site
|
||||
|
||||
- **Decision**: Keep Astro 6 in static-output mode and preserve hard runtime separation from `apps/platform`.
|
||||
- **Rationale**: The website is a trust-first product site, not an application surface. Static output keeps the site fast, cacheable, SEO-friendly, and operationally independent from Laravel, Filament, tenant state, and platform auth/session concerns.
|
||||
- **Alternatives considered**:
|
||||
- SSR or hybrid rendering: rejected because v0 does not need per-request logic and it would blur the separation between website and platform.
|
||||
- A single ad hoc landing page refresh: rejected because it would not establish the reusable v0 website foundation required by the spec.
|
||||
|
||||
## Decision 2: Add explicit TypeScript and Tailwind CSS v4
|
||||
|
||||
- **Decision**: Introduce explicit TypeScript setup in `apps/website` and adopt Tailwind CSS v4 using the CSS-first configuration model.
|
||||
- **Rationale**: The site needs a maintainable foundation for multiple routes, reusable primitives, and later content expansion. TypeScript makes component props, metadata, and content structures safer; Tailwind v4 scales layout and typography decisions better than continued page-local CSS growth.
|
||||
- **Alternatives considered**:
|
||||
- Keep handwritten global CSS only: rejected because the page count and reusable-section scope in v0 would quickly turn styling into duplicated local CSS islands.
|
||||
- Delay TypeScript: rejected because the feature explicitly introduces a reusable component layer and content helpers that benefit from typed contracts immediately.
|
||||
|
||||
## Decision 3: Do not introduce React for v0
|
||||
|
||||
- **Decision**: Do not add React as part of Spec 213.
|
||||
- **Rationale**: React is not required to deliver the v0 product site, and it adds framework, hydration, and dependency cost without solving a concrete launch problem. Astro already supports a static-first component model that fits the site’s reading, navigation, and CTA flows.
|
||||
- **Alternatives considered**:
|
||||
- React islands for all UI: rejected because it introduces unnecessary client framework weight for a content-driven public site.
|
||||
- “Add React now for future flexibility”: rejected under PROP-001 and ABSTR-001 because future optional interactivity is not enough reason to pay the cost in v0.
|
||||
|
||||
## Decision 4: Use custom shadcn-inspired Astro primitives instead of official shadcn/ui
|
||||
|
||||
- **Decision**: Use a custom Astro-native design-system layer inspired by shadcn/ui conventions rather than installing React plus official shadcn/ui.
|
||||
- **Rationale**: The product needs an intentional, enterprise-credible public surface. Astro-native primitives keep ownership local, avoid framework coupling, and still allow the team to use shadcn-like conventions for buttons, cards, inputs, badges, sheets, tabs, and dialogs if needed later.
|
||||
- **Alternatives considered**:
|
||||
- Full React + official shadcn/ui: rejected because it solves a speed-of-assembly problem at the cost of unnecessary framework/runtime complexity.
|
||||
- Ported third-party Astro shadcn clones: rejected because they split ownership and can still impose design decisions broader than the feature needs.
|
||||
|
||||
## Decision 5: Keep Contact / Demo static in v0
|
||||
|
||||
- **Decision**: Treat Contact / Demo as a static conversion surface in Spec 213. The page may point to a controlled external/manual intake path, but Spec 213 does not introduce an internal website backend or API contract for form submission.
|
||||
- **Rationale**: The feature goal is to establish a trustworthy public site foundation, not to solve CRM or submission pipeline architecture. Keeping the contact surface static avoids backend/runtime coupling and leaves room for a later explicit decision on form handling.
|
||||
- **Alternatives considered**:
|
||||
- Build an Astro endpoint or app-backed form now: rejected because form handling is still an open product/ops decision and is not required to make the v0 site credible.
|
||||
- Omit contact intent entirely: rejected because the spec requires a clear conversion path.
|
||||
|
||||
## Decision 6: Public route structure should be small, explicit, and future-ready
|
||||
|
||||
- **Decision**: Publish the core route set now and organize the source tree so docs/blog/changelog can be added later without rewriting the live page hierarchy.
|
||||
- **Rationale**: The v0 website needs clarity more than breadth. A small public sitemap makes the product understandable now while avoiding future structural dead ends.
|
||||
- **Alternatives considered**:
|
||||
- Publish docs/blog/changelog placeholders now: rejected because dead or thin routes reduce trust.
|
||||
- Keep all content in one giant home page: rejected because Product, Solutions, Trust, Integrations, and Contact need distinct narrative roles.
|
||||
|
||||
## Decision 7: Use lightweight browser smoke validation plus build proof
|
||||
|
||||
- **Decision**: Keep validation in `fast-feedback` with root build proof and a small Playwright browser smoke suite local to `apps/website`.
|
||||
- **Rationale**: Build proof alone does not catch broken routes, broken navigation, or browser-visible regressions. A tiny smoke suite gives real confidence without importing backend fixtures or a heavy end-to-end lane.
|
||||
- **Alternatives considered**:
|
||||
- Build-only validation: rejected because it can still ship broken pages.
|
||||
- Heavy end-to-end coverage: rejected because the site has no authenticated workflow or backend interaction that justifies it.
|
||||
|
||||
## Baseline Findings
|
||||
|
||||
- `apps/website` currently uses Astro 6 with `output: 'static'`.
|
||||
- The current site consists of a single `index.astro`, a minimal `BaseLayout.astro`, and handwritten global CSS.
|
||||
- No TypeScript setup, Tailwind install, React integration, or dedicated website test tooling is currently present.
|
||||
- Root workspace contracts already expose `corepack pnpm dev:website` and `corepack pnpm build:website`, and those must remain intact.
|
||||
@ -1,161 +0,0 @@
|
||||
# Feature Specification: Initial Website Foundation & v0 Product Site
|
||||
|
||||
**Feature Branch**: `213-website-foundation-v0`
|
||||
**Created**: 2026-04-18
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Establish the initial public TenantAtlas website foundation and ship the first trust-first v0 product site in `apps/website`."
|
||||
|
||||
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: The website track does not yet explain TenantAtlas credibly to enterprise and MSP buyers, so the product lacks a trustworthy public entry point.
|
||||
- **Today's failure**: A first-time visitor cannot quickly tell what TenantAtlas is, how it differs from generic backup or reporting tools, or whether it is serious enough for enterprise evaluation.
|
||||
- **User-visible improvement**: Visitors can understand the product model, assess trust posture, and reach a qualified next step without digging through incomplete or placeholder pages.
|
||||
- **Smallest enterprise-capable version**: A public website v0 with a clear home page, core product/trust/solutions/integrations/contact/legal pages, shared layout primitives, and a direct contact path.
|
||||
- **Explicit non-goals**: No product-app behavior, no deep runtime coupling to the platform, no public roadmap/community hub, no full docs/blog/CMS rollout, no multi-language launch, no template clone, and no speculative trust-center claims.
|
||||
- **Permanent complexity imported**: A stable public information architecture, reusable website primitives, shared messaging structure, legal page responsibilities, and website-specific validation expectations.
|
||||
- **Why now**: TenantAtlas needs a credible public surface before broader rollout, trust conversations, and later expansion into docs, changelog, or resource content.
|
||||
- **Why not local**: A one-off landing page or copy-only patch would not create a stable public story, reusable page composition, or an extensible enterprise-ready website track.
|
||||
- **Approval class**: Core Enterprise
|
||||
- **Red flags triggered**: #4 sounds like foundation work; #5 broad concept vocabulary. The scope stays justified because it is constrained to the smallest publishable site that improves public product clarity, trust, and conversion.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
|
||||
- **Decision**: approve
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: workspace
|
||||
- **Primary Routes**: `/`, `/product`, `/solutions`, `/security-trust`, `/integrations`, `/contact`, `/legal`, `/privacy`, `/terms`
|
||||
- **Data Ownership**: Workspace-owned public content, assets, reusable components, and site configuration inside `apps/website`; no tenant-owned records or platform runtime data.
|
||||
- **RBAC**: Public-read runtime only. No authenticated membership or capability checks are required for v0 website browsing; content changes remain repo-controlled.
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no
|
||||
- **New persisted entity/table/artifact?**: no
|
||||
- **New abstraction?**: yes
|
||||
- **New enum/state/reason family?**: no
|
||||
- **New cross-domain UI framework/taxonomy?**: yes
|
||||
- **Current operator problem**: Prospects and internal teams lack a stable, trustworthy public site that explains the product, trust posture, and next step without resorting to ad hoc copy or app screenshots.
|
||||
- **Existing structure is insufficient because**: The current website track is too minimal to support coherent product messaging, reusable page composition, clear trust framing, or later content expansion without rework.
|
||||
- **Narrowest correct implementation**: One bounded website v0 with shared layout and content primitives, a small core route set, a clear contact path, and explicit messaging constraints; no app simulation, no CMS, and no broad content platform.
|
||||
- **Ownership cost**: Ongoing maintenance of public copy, reusable website primitives, legal content, and a small public information architecture.
|
||||
- **Alternative intentionally rejected**: A single landing page or imported theme was rejected because it would not create durable enterprise messaging, trustworthy trust surfaces, or an extensible public structure aligned with the product.
|
||||
- **Release truth**: Current-release truth
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
- **Test purpose / classification**: Browser
|
||||
- **Validation lane(s)**: fast-feedback
|
||||
- **Why this classification and these lanes are sufficient**: The feature is proven by public-page rendering, route reachability, content hierarchy, and responsive navigation rather than by tenant data or business-rule execution.
|
||||
- **New or expanded test families**: Lightweight Playwright website smoke coverage for core published routes and contact/legal reachability, plus the existing static-build proof.
|
||||
- **Fixture / helper cost impact**: Minimal. Public pages do not require seeded tenant, workspace, or authentication state.
|
||||
- **Heavy-family visibility / justification**: none
|
||||
- **Reviewer handoff**: Reviewers must confirm that core pages render, primary navigation has no dead ends, contact/legal routes remain reachable, the website build passes, and the website smoke suite passes while staying website-specific rather than borrowing platform-heavy lanes.
|
||||
- **Budget / baseline / trend impact**: Small increase limited to website build and any added public-site smoke checks.
|
||||
- **Escalation needed**: none
|
||||
- **Planned validation commands**: `corepack pnpm build:website` and `cd apps/website && corepack pnpm exec playwright test`
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Understand the Product Quickly (Priority: P1)
|
||||
|
||||
A first-time enterprise or MSP visitor lands on the public site and can understand what TenantAtlas does, why it differs from generic backup or reporting tools, and what to do next without seeing the product app.
|
||||
|
||||
**Why this priority**: If the public site fails to create immediate product clarity, deeper trust and conversion flows do not matter.
|
||||
|
||||
**Independent Test**: This can be tested by visiting the Home page and Product page only and confirming that the product category, audience, and primary next step are understandable from public content alone.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a first-time visitor lands on Home, **When** they scan the primary sections, **Then** they can identify what TenantAtlas does, who it serves, and why it exists.
|
||||
2. **Given** a visitor is unsure whether TenantAtlas is only a backup tool, **When** they open Product, **Then** the site explains a connected governance model rather than a loose feature list.
|
||||
3. **Given** a visitor wants proof of seriousness, **When** they move from Home into deeper pages, **Then** the site provides trust and ecosystem context without hype or placeholder claims.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Evaluate Fit by Audience (Priority: P2)
|
||||
|
||||
An enterprise IT buyer, MSP operator, or governance stakeholder can navigate the public site to decide whether TenantAtlas fits their environment and operating model.
|
||||
|
||||
**Why this priority**: Qualified evaluation reduces mismatched leads and makes the site useful beyond top-level awareness.
|
||||
|
||||
**Independent Test**: This can be tested by navigating from the primary menu to Solutions and Integrations and confirming that audience-specific fit signals are present without requiring private collateral.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an MSP visitor opens Solutions, **When** they review the page, **Then** they find multi-tenant governance and operational-fit scenarios relevant to service providers.
|
||||
2. **Given** an enterprise IT visitor opens Solutions, **When** they review the page, **Then** they see enterprise operating-model language clearly separated from MSP framing.
|
||||
3. **Given** a technical evaluator opens Integrations, **When** they assess ecosystem fit, **Then** they see real integration direction only and no speculative wishlist.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Reach a Qualified Next Step (Priority: P3)
|
||||
|
||||
A serious buyer can move from any core page to a clear contact or demo path with realistic expectations about who should reach out and why.
|
||||
|
||||
**Why this priority**: Conversion should feel confident and intentional, not aggressive or ambiguous.
|
||||
|
||||
**Independent Test**: This can be tested by starting on any core page and reaching the contact path without dead ends, broken hierarchy, or unclear calls to action.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a visitor is on any core page, **When** they look for the next step, **Then** a visible CTA guides them toward contact or demo.
|
||||
2. **Given** a visitor reaches Contact / Demo, **When** they review the page, **Then** they can tell who should get in touch, what topics the conversation covers, and what response to expect.
|
||||
3. **Given** a visitor wants legal reassurance before submitting interest, **When** they use the footer or contact flow, **Then** privacy and terms information are reachable.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when a visitor lands directly on an interior page from search or a shared link? The page must still orient the visitor, reconnect them to the main product story, and expose a clear next step.
|
||||
- How does the site handle claims that cannot yet be substantiated? The content must omit or soften the claim instead of implying unverified security, compliance, or automation guarantees.
|
||||
- What happens when optional future sections such as blog, changelog, or docs are not yet live? Navigation and internal links must not expose dead or placeholder routes.
|
||||
- How does the site behave on narrow screens or slower connections? Core navigation, copy hierarchy, and contact/legal reachability must remain usable without animation- or script-dependent gating.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
This feature does not introduce product-app operator surfaces, Microsoft-tenant actions, queued work, or runtime authorization changes. The public website remains a separate, workspace-owned surface with no required auth, session, or API coupling to the platform for v0.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The public website MUST position TenantAtlas as a trust-first governance-of-record product for Microsoft tenant and Intune configuration state, emphasizing clarity around backup, restore, version history, auditability, drift visibility, findings, exceptions, evidence, and reviews.
|
||||
- **FR-002**: The v0 release MUST publish a Home page, Product page, Solutions page, Security & Trust page, Integrations page, Contact / Demo page, and a Legal surface. The Legal surface MUST expose Privacy and Terms content plus any jurisdiction-specific public legal notice required before launch, either within the Legal hub or via a linked dedicated notice page.
|
||||
- **FR-003**: The site MUST provide a global page shell that includes a Home link through the brand mark, consistent primary navigation, and footer navigation that keeps core pages and legal pages reachable.
|
||||
- **FR-004**: Home MUST explain the product category, frame the problem, present the major product pillars, establish why the product matters now, and direct visitors into deeper product, trust, and contact paths.
|
||||
- **FR-005**: Product MUST explain the product as one connected operating model rather than as an unstructured feature inventory.
|
||||
- **FR-006**: Solutions MUST speak to at least MSP and Enterprise IT audiences, showing how the product fits each operating model without collapsing them into one generic story.
|
||||
- **FR-007**: Security & Trust MUST communicate product principles, trust posture, and handling of sensitive connections only at a level the team can substantiate at launch.
|
||||
- **FR-008**: Integrations MUST show the real ecosystem fit for the product and MUST avoid speculative or wishlist-only integrations.
|
||||
- **FR-009**: Contact / Demo MUST explain who should get in touch, common reasons to reach out, and what kind of discussion or follow-up the visitor should expect.
|
||||
- **FR-010**: Legal content MUST be reachable from the footer and from relevant conversion paths before a visitor submits interest.
|
||||
- **FR-011**: The website MUST use shared layout, content, conversion, and trust primitives so pages are assembled from reusable building blocks instead of page-by-page duplication.
|
||||
- **FR-012**: The visual direction MUST feel calm, readable, technically serious, and enterprise-appropriate, while avoiding over-animation, consumer-style hype, and decorative patterns that undercut credibility.
|
||||
- **FR-013**: Public copy MUST avoid misleading claims and banned framing such as guaranteed security outcomes, false reassurance, fully automated governance promises, or consumer/startup hype language.
|
||||
- **FR-014**: The v0 website MUST remain independent from platform runtime concerns. It MUST NOT require product-app sessions, shared auth, or deep runtime coupling to explain the product or capture contact intent.
|
||||
- **FR-015**: Content organization for v0 MAY remain page-managed, but the live structure MUST allow later expansion into concepts, changelog, resources, blog, or docs without rebuilding the published core-page information architecture.
|
||||
- **FR-016**: Every published core page MUST include clear page purpose, semantic structure, discoverability basics, including page metadata, robots handling, and sitemap coverage, and a visible next step deeper into the product, trust story, or contact flow.
|
||||
- **FR-017**: The website track MUST preserve existing workspace contracts, including the package identity, website port convention, workspace script names, and monorepo layout assumptions relied on elsewhere in the repo.
|
||||
- **FR-018**: The launch version MUST be fully polished in one primary visual theme. Alternate theme support may be prepared, but it is not required for v0.
|
||||
- **FR-019**: The v0 website MUST remain fully useful without introducing an unnecessary client-side application layer for core navigation, reading, and contact intent.
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Core Public Page**: A top-level public route with a defined narrative purpose, section hierarchy, and next-step CTA.
|
||||
- **Narrative Section**: A reusable content block that communicates product explanation, trust evidence, audience fit, or conversion intent.
|
||||
- **Conversion Path**: The CTA and destination flow that moves an interested visitor from discovery into contact or demo.
|
||||
- **Legal Surface**: The set of public pages that provide privacy, terms, and any other required legal disclosures supporting launch.
|
||||
|
||||
## Assumptions & Dependencies
|
||||
|
||||
- Actual company/legal facts needed for privacy, terms, and any jurisdiction-specific notice will be available before release.
|
||||
- Contact handling may begin with a lightweight team-owned intake path, provided the public site sets clear expectations for purpose and follow-up.
|
||||
- Product and trust claims will be limited to capabilities and operational truths that can be substantiated at launch.
|
||||
- Future article, changelog, and documentation areas may remain unpublished in v0.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: A first-time visitor can identify what TenantAtlas does, who it is for, and the primary next step from the Home page within 60 seconds of reading.
|
||||
- **SC-002**: The v0 release publishes at least seven core public surfaces, including legal access points, with no dead-end navigation from the primary menu or footer.
|
||||
- **SC-003**: A visitor can reach a contact or demo path from any core page in two clicks or fewer.
|
||||
- **SC-004**: Every core page includes a visible next-step CTA and at least one deeper path into the product, trust, or contact story.
|
||||
- **SC-005**: No released page contains placeholder copy, unsubstantiated trust or compliance claims, or speculative integration promises.
|
||||
- **SC-006**: Core pages remain readable and navigable on both desktop and mobile widths without horizontal scrolling or hidden primary navigation.
|
||||
@ -1,201 +0,0 @@
|
||||
# Tasks: Initial Website Foundation & v0 Product Site
|
||||
|
||||
**Input**: Design documents from `/specs/213-website-foundation-v0/`
|
||||
**Prerequisites**: `plan.md`, `spec.md`, `research.md`, `data-model.md`, `quickstart.md`, `contracts/public-site.openapi.yaml`
|
||||
|
||||
**Tests**: Browser smoke coverage is required for this runtime-changing website feature, together with the root website build proof.
|
||||
|
||||
## Test Governance Checklist
|
||||
|
||||
- [X] Lane assignment is named and is the narrowest sufficient proof for the changed behavior.
|
||||
- [X] New or changed tests stay in the smallest honest family, and the browser addition remains explicit.
|
||||
- [X] Shared helpers and context defaults stay cheap by default; no backend, auth, or fixture-heavy setup is introduced.
|
||||
- [X] Planned validation commands cover the website change without pulling in unrelated platform lane cost.
|
||||
- [X] Any runtime-cost or escalation note stays documented in this feature rather than being deferred implicitly.
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: Establish the minimal website tooling and config baseline required before reusable UI work begins.
|
||||
|
||||
- [X] T001 Add explicit website TypeScript configuration in `apps/website/tsconfig.json`
|
||||
- [X] T002 [P] Add Tailwind CSS v4 and Playwright dev dependencies plus website test scripts in `apps/website/package.json`
|
||||
- [X] T003 [P] Update Astro aliases and website-safe build configuration in `apps/website/astro.config.mjs`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Create the shared shell, primitives, tokens, metadata, and smoke-test harness that every page depends on.
|
||||
|
||||
**⚠️ CRITICAL**: No user story work should begin until this phase is complete.
|
||||
|
||||
- [X] T004 Configure Tailwind v4 theme tokens and base website styles in `apps/website/src/styles/global.css` and `apps/website/src/styles/tokens.css`
|
||||
- [X] T005 [P] Create shared site metadata, navigation, and footer configuration in `apps/website/src/lib/site.ts` and `apps/website/src/types/site.ts`
|
||||
- [X] T006 [P] Add the browser smoke-test harness in `apps/website/playwright.config.ts` and `apps/website/tests/smoke/smoke-helpers.ts`
|
||||
- [X] T007 Build the base document and page shell wrappers in `apps/website/src/layouts/BaseLayout.astro` and `apps/website/src/components/layout/PageShell.astro`
|
||||
- [X] T008 [P] Build global navigation and footer components in `apps/website/src/components/layout/Navbar.astro` and `apps/website/src/components/layout/Footer.astro`
|
||||
- [X] T009 [P] Implement layout primitives in `apps/website/src/components/primitives/Container.astro`, `apps/website/src/components/primitives/Section.astro`, `apps/website/src/components/primitives/SectionHeader.astro`, `apps/website/src/components/primitives/Stack.astro`, `apps/website/src/components/primitives/Cluster.astro`, and `apps/website/src/components/primitives/Grid.astro`
|
||||
- [X] T010 [P] Implement shared UI primitives in `apps/website/src/components/primitives/Button.astro`, `apps/website/src/components/primitives/Badge.astro`, `apps/website/src/components/primitives/Card.astro`, `apps/website/src/components/primitives/Input.astro`, and `apps/website/src/components/primitives/Textarea.astro`
|
||||
- [X] T011 Build reusable section scaffolds in `apps/website/src/components/sections/PageHero.astro`, `apps/website/src/components/sections/CTASection.astro`, `apps/website/src/components/sections/FeatureGrid.astro`, `apps/website/src/components/sections/TrustGrid.astro`, and `apps/website/src/components/sections/LogoStrip.astro`
|
||||
|
||||
**Checkpoint**: Foundation ready. User-story page work can now begin.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Understand the Product Quickly (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Deliver the first credible public explanation of TenantAtlas through Home and Product.
|
||||
|
||||
**Independent Test**: Visit Home and Product, verify both routes render from the shared shell, and confirm the smoke suite proves product-category clarity and next-step CTA reachability without platform coupling.
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||
|
||||
- [X] T012 [P] [US1] Write failing smoke coverage for Home and Product in `apps/website/tests/smoke/home-product.spec.ts`
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T013 [P] [US1] Create Home and Product content modules in `apps/website/src/content/pages/home.ts` and `apps/website/src/content/pages/product.ts`
|
||||
- [X] T014 [P] [US1] Create product-explanation content primitives in `apps/website/src/components/content/Eyebrow.astro`, `apps/website/src/components/content/Headline.astro`, `apps/website/src/components/content/Lead.astro`, and `apps/website/src/components/content/FeatureItem.astro`
|
||||
- [X] T015 [P] [US1] Create supporting proof blocks in `apps/website/src/components/content/Callout.astro` and `apps/website/src/components/content/Metric.astro`
|
||||
- [X] T016 [US1] Implement the Home and Product routes in `apps/website/src/pages/index.astro` and `apps/website/src/pages/product.astro`
|
||||
|
||||
**Checkpoint**: Home and Product are independently functional and demonstrable as the MVP slice.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - Evaluate Fit by Audience (Priority: P2)
|
||||
|
||||
**Goal**: Add audience-fit, trust framing, and ecosystem-fit pages that help visitors judge whether TenantAtlas fits their environment.
|
||||
|
||||
**Independent Test**: Visit Solutions, Security & Trust, and Integrations from the shared navigation and verify the smoke suite proves audience-specific framing, substantiated trust messaging, and non-speculative integrations.
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||
|
||||
- [X] T017 [P] [US2] Write failing smoke coverage for Solutions, Security & Trust, and Integrations in `apps/website/tests/smoke/solutions-trust-integrations.spec.ts`
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T018 [P] [US2] Create Solutions, Security & Trust, and Integrations content modules in `apps/website/src/content/pages/solutions.ts`, `apps/website/src/content/pages/security-trust.ts`, and `apps/website/src/content/pages/integrations.ts`
|
||||
- [X] T019 [P] [US2] Create audience and trust content primitives in `apps/website/src/components/content/AudienceRow.astro`, `apps/website/src/components/content/TrustPrincipleCard.astro`, and `apps/website/src/components/content/IntegrationBadge.astro`
|
||||
- [X] T020 [P] [US2] Implement the Solutions route in `apps/website/src/pages/solutions.astro`
|
||||
- [X] T021 [P] [US2] Implement the Security & Trust route in `apps/website/src/pages/security-trust.astro`
|
||||
- [X] T022 [P] [US2] Implement the Integrations route in `apps/website/src/pages/integrations.astro`
|
||||
|
||||
**Checkpoint**: Audience fit, trust framing, and integration fit are independently functional and testable.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - Reach a Qualified Next Step (Priority: P3)
|
||||
|
||||
**Goal**: Deliver the contact/demo path, legal surfaces, and cross-page CTA/legal reachability needed for a trustworthy conversion flow.
|
||||
|
||||
**Independent Test**: Visit Contact, Legal, Privacy, and Terms and confirm the smoke suite proves CTA reachability and footer/legal access from every published core page.
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
> **NOTE**: Write this test first and confirm it fails before implementing the story.
|
||||
|
||||
- [X] T023 [P] [US3] Write failing smoke coverage for Contact, Legal, Privacy, Terms, footer links, CTA reachability, and legal-surface availability in `apps/website/tests/smoke/contact-legal.spec.ts`
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T024 [P] [US3] Create Contact and legal content modules, including any required jurisdiction-specific public legal notice content, in `apps/website/src/content/pages/contact.ts`, `apps/website/src/content/pages/legal.ts`, `apps/website/src/content/pages/privacy.ts`, and `apps/website/src/content/pages/terms.ts`
|
||||
- [X] T025 [P] [US3] Create conversion and legal content primitives in `apps/website/src/components/content/PrimaryCTA.astro`, `apps/website/src/components/content/SecondaryCTA.astro`, `apps/website/src/components/content/ContactPanel.astro`, `apps/website/src/components/content/DemoPrompt.astro`, and `apps/website/src/components/content/RichText.astro`
|
||||
- [X] T026 [P] [US3] Implement the Contact route in `apps/website/src/pages/contact.astro`
|
||||
- [X] T027 [P] [US3] Implement the Legal hub route, including any required jurisdiction-specific public legal notice path, in `apps/website/src/pages/legal.astro`
|
||||
- [X] T028 [P] [US3] Implement the Privacy and Terms routes in `apps/website/src/pages/privacy.astro` and `apps/website/src/pages/terms.astro`
|
||||
- [X] T029 [US3] Add SEO and future-ready content scaffolding, including explicit sitemap output, in `apps/website/src/lib/seo.ts`, `apps/website/src/content.config.ts`, `apps/website/public/robots.txt`, `apps/website/src/pages/sitemap.xml.ts`, and `apps/website/astro.config.mjs`
|
||||
- [X] T030 [US3] Wire final CTA and footer/legal reachability across `apps/website/src/pages/index.astro`, `apps/website/src/pages/product.astro`, `apps/website/src/pages/solutions.astro`, `apps/website/src/pages/security-trust.astro`, `apps/website/src/pages/integrations.astro`, and `apps/website/src/pages/contact.astro`
|
||||
|
||||
**Checkpoint**: The qualified contact path and legal reachability are functional across the full published site.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Final validation, wording review, and workspace-contract verification across all stories.
|
||||
|
||||
- [X] T031 [P] Validate both required fast-feedback proof commands against `apps/website/package.json`, `apps/website/playwright.config.ts`, and `specs/213-website-foundation-v0/quickstart.md`
|
||||
- [X] T032 [P] Review and tighten public wording against substantiated-claim and banned-wording rules in `apps/website/src/content/pages/home.ts`, `apps/website/src/content/pages/product.ts`, `apps/website/src/content/pages/solutions.ts`, `apps/website/src/content/pages/security-trust.ts`, `apps/website/src/content/pages/integrations.ts`, `apps/website/src/content/pages/contact.ts`, `apps/website/src/content/pages/legal.ts`, `apps/website/src/content/pages/privacy.ts`, and `apps/website/src/content/pages/terms.ts`
|
||||
- [X] T033 Verify the workspace script and package contracts remain intact in `package.json` and `apps/website/package.json`
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: Starts immediately.
|
||||
- **Foundational (Phase 2)**: Depends on Setup and blocks all story work.
|
||||
- **User Story 1 (Phase 3)**: Depends on Foundational only.
|
||||
- **User Story 2 (Phase 4)**: Depends on Foundational only.
|
||||
- **User Story 3 (Phase 5)**: Depends on Foundational and needs the core routes from US1 and US2 in place to satisfy “from any core page” CTA/legal reachability.
|
||||
- **Polish (Phase 6)**: Depends on all targeted stories being complete.
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- **US1**: No dependency on other user stories; this is the MVP slice.
|
||||
- **US2**: No dependency on US1 for implementation, but it reuses the same shared shell and primitives from Foundational.
|
||||
- **US3**: Depends on the published route set from US1 and US2 to complete the full cross-page CTA/legal contract.
|
||||
|
||||
### Within Each User Story
|
||||
|
||||
- Write the browser smoke test first and verify it fails.
|
||||
- Create content modules and story-specific primitives before assembling the route files.
|
||||
- Finish route implementation before cross-page or SEO wiring tasks.
|
||||
|
||||
---
|
||||
|
||||
## Parallel Opportunities
|
||||
|
||||
- `T002` and `T003` can run in parallel once `T001` is scoped.
|
||||
- `T005`, `T006`, `T008`, `T009`, and `T010` can run in parallel after `T004` begins.
|
||||
- In US1, `T013`, `T014`, and `T015` can run in parallel before `T016`.
|
||||
- In US2, `T018`, `T019`, `T020`, `T021`, and `T022` can be split across contributors once shared story inputs are clear.
|
||||
- In US3, `T024`, `T025`, `T026`, `T027`, and `T028` can run in parallel before `T029` and `T030`.
|
||||
- `T031` and `T032` can run in parallel during final polish.
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: User Story 2
|
||||
|
||||
```bash
|
||||
# Launch the audience/trust/integration building blocks together:
|
||||
Task: "T018 [US2] Create Solutions, Security & Trust, and Integrations content modules"
|
||||
Task: "T019 [US2] Create audience and trust content primitives"
|
||||
|
||||
# Then split the route assembly work:
|
||||
Task: "T020 [US2] Implement the Solutions route"
|
||||
Task: "T021 [US2] Implement the Security & Trust route"
|
||||
Task: "T022 [US2] Implement the Integrations route"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First
|
||||
|
||||
1. Complete Phase 1: Setup.
|
||||
2. Complete Phase 2: Foundational.
|
||||
3. Complete Phase 3: User Story 1.
|
||||
4. Run the website build and the US1 smoke test.
|
||||
5. Demo the MVP with Home + Product.
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
1. Setup + Foundational establish the reusable website system.
|
||||
2. US1 ships the first clear product explanation.
|
||||
3. US2 expands into audience fit, trust, and integration context.
|
||||
4. US3 completes the conversion/legal path and SEO/discovery scaffolding.
|
||||
5. Polish validates wording, scripts, and proof commands before merge.
|
||||
|
||||
### Suggested MVP Scope
|
||||
|
||||
- Deliver through **User Story 1** if a smaller first release is needed.
|
||||
- Add **User Story 2** next for evaluation depth.
|
||||
- Finish with **User Story 3** for full contact/legal/SEO readiness.
|
||||
Loading…
Reference in New Issue
Block a user