diff --git a/.opencode/skills/impeccable-design-polish/SKILL.md b/.opencode/skills/impeccable-design-polish/SKILL.md new file mode 100644 index 0000000..07234a8 --- /dev/null +++ b/.opencode/skills/impeccable-design-polish/SKILL.md @@ -0,0 +1,68 @@ +--- +name: impeccable-design-polish +description: | + Follow-up design polish skill inspired by Impeccable. Use after a web or HTML artifact exists to audit, critique, polish, animate, harden, and prepare the page for a live/share pass. +triggers: + - "impeccable" + - "design polish" + - "polish page" + - "anti ai polish" + - "critique design" + - "animate page" + - "harden ui" + - "live review" + - "反 AI 味" +od: + mode: prototype + surface: web + platform: desktop + category: creative-direction + upstream: "https://github.com/pbakaus/impeccable" + preview: + type: html + design_system: + requires: true + craft: + requires: + - typography + - color + - anti-ai-slop + - animation-discipline + - accessibility-baseline + example_prompt: | + Use impeccable-design-polish on the current HTML artifact: audit visual hierarchy, remove AI tells, tighten copy, add restrained motion, and harden responsive/accessibility issues. +--- + +# Impeccable Design Polish + +Use this skill as the post-generation pass for an existing design. It should not restart the project from scratch; it should make the current artifact sharper, more usable, and closer to something a designer would ship. + +## Follow-Up Modes + +- **Audit**: identify the highest-impact issues in hierarchy, spacing, color, type, interaction states, responsiveness, and accessibility. +- **Critique**: explain what feels generic, overdesigned, underdesigned, or inconsistent. +- **Polish**: directly edit the artifact to improve the top issues while preserving the user's intent. +- **Animate**: add restrained, useful motion only where it improves feedback or storytelling. +- **Harden**: repair mobile overflow, text clipping, contrast problems, missing states, broken links, and fragile layout assumptions. +- **Live**: prepare the artifact for presentation or sharing, including final visual QA and clear next actions. + +## Operating Rules + +1. Inspect the current HTML/page before editing. Do not guess from the prompt alone. +2. Keep the existing content, brand, and scenario unless the user explicitly asks to change them. +3. Prefer a few decisive fixes over broad cosmetic churn. +4. Remove common AI tells: + - purple-blue glow gradients with no product reason + - generic 3-card feature rows + - oversized rounded cards everywhere + - empty marketing adjectives + - inconsistent spacing and type scale + - decorative effects that do not support comprehension +5. Preserve accessibility: focus states, contrast, semantic controls, readable text, and reduced-motion fallbacks. +6. Finish with the artifact in a better runnable state, not just a critique list. + +## Best Pairings + +- Pair with `design-taste-frontend` or `gpt-taste` for stronger anti-slop redesign work. +- Pair with `emilkowalski-motion` or GSAP skills for motion-specific polish. +- Pair with image/video skills when the artifact needs real visual assets rather than CSS-only decoration. \ No newline at end of file diff --git a/.opencode/skills/impeccable-design-polish/THEMIS_NOTES.md b/.opencode/skills/impeccable-design-polish/THEMIS_NOTES.md new file mode 100644 index 0000000..097d4c8 --- /dev/null +++ b/.opencode/skills/impeccable-design-polish/THEMIS_NOTES.md @@ -0,0 +1,21 @@ +# Themis Notes — impeccable-design-polish + +## Provenance + +- Upstream: https://github.com/nexu-io/open-design/tree/main/skills/impeccable-design-polish +- Originally inspired by https://github.com/pbakaus/impeccable +- Vendored into Themis on 2026-06-26 as part of the [`2026-06-26-ui-designer-app`](../../../docs/specs/2026-06-26-ui-designer-app/) spec. + +## How Themis uses this skill + +Use this skill after authoring a prototype via the [`themis-ui-prototype`](../themis-ui-prototype/) skill. The audit/critique/polish modes apply directly to prototype HTML files in `apps/web/ui-designer/src/prototypes/`. + +The "remove common AI tells" rules align with the [`docs/design-system/tokens.md`](../../../docs/design-system/tokens.md) and [`docs/design-system/recipes.md`](../../../docs/design-system/recipes.md) contracts. The Themis token set already excludes purple-blue glow gradients and decorative effects, so the polish pass is mostly about tightening copy and verifying motion + accessibility. + +## Upstream version + +This is the upstream `SKILL.md` content as fetched on 2026-06-26 from the `main` branch of `nexu-io/open-design`. To upgrade, diff against the upstream and copy the new `SKILL.md` into this folder. + +## Local modifications + +None. The upstream content ships verbatim. If a Themis-specific divergence is needed, capture it here instead of editing the `SKILL.md` directly. \ No newline at end of file diff --git a/.opencode/skills/login-flow/SKILL.md b/.opencode/skills/login-flow/SKILL.md new file mode 100644 index 0000000..45c23be --- /dev/null +++ b/.opencode/skills/login-flow/SKILL.md @@ -0,0 +1,48 @@ +--- +name: login-flow +description: Mobile login and authentication flow screens +od: + mode: prototype + platform: mobile +triggers: + - login + - sign in + - 注册登录 + - 登录注册 + - 手机号登录 + - 验证码登录 + - 密码登录 +--- + +# Login Flow Skill + +A skill for generating mobile-first login and authentication screens. Use this when the user wants a sign-in experience for a mobile app, including phone + SMS verification, password-based login, and social SSO options. + +## Workflow + +1. **Read reference files first** (see below) +2. **Clarify auth method**: phone/SMS, password, or social SSO +3. **Checklist gate** — verify P0 items before emitting `` +4. **Build the HTML prototype** with proper states (default, loading, error) +5. **Wrap in `` tag** referencing the output file + +## Side Files + +- `references/checklist.md` — P0/P1 acceptance criteria + +## Output + +A single standalone HTML file implementing the login screen with: +- Labels above inputs (never placeholder-only) +- Password field with show/hide toggle +- Social SSO buttons with SVG icons +- Error states below fields +- Loading spinner in primary CTA +- Touch targets minimum 44px + +## Mobile-First Constraints + +- Viewport: 375px wide (iPhone standard) +- No horizontal scroll +- Safe area insets for notched devices +- Input keyboards: `tel` for phone, `password` for password fields \ No newline at end of file diff --git a/.opencode/skills/login-flow/THEMIS_NOTES.md b/.opencode/skills/login-flow/THEMIS_NOTES.md new file mode 100644 index 0000000..074887f --- /dev/null +++ b/.opencode/skills/login-flow/THEMIS_NOTES.md @@ -0,0 +1,24 @@ +# Themis Notes — login-flow + +## Provenance + +- Upstream: https://github.com/nexu-io/open-design/tree/main/skills/login-flow +- Vendored into Themis on 2026-06-26 as part of the [`2026-06-26-ui-designer-app`](../../../docs/specs/2026-06-26-ui-designer-app/) spec. + +## How Themis uses this skill + +Pair this skill with [`themis-ui-prototype`](../themis-ui-prototype/) to author auth-related prototype screens (sign-in, sign-up, recover-password, reset-password, OTP). The mobile-first constraints here align with Themis's first-mobile mandate. + +When composing the prototype: + +- Use the Themis Catalyst Tailwind v4 token set from [`/docs/design-system/tokens.md`](../../../docs/design-system/tokens.md). +- Follow the auth chrome recipe in [`/docs/design-system/recipes.md`](../../../docs/design-system/recipes.md) for layout rhythm. +- The seed prototype `apps/web/ui-designer/src/prototypes/app-auth-shell.html` is the canonical example. + +## Upstream version + +This is the upstream `SKILL.md` content as fetched on 2026-06-26 from the `main` branch of `nexu-io/open-design`. To upgrade, diff against the upstream and copy the new `SKILL.md` and `references/checklist.md` into this folder. + +## Local modifications + +None. The upstream content ships verbatim. \ No newline at end of file diff --git a/.opencode/skills/login-flow/references/checklist.md b/.opencode/skills/login-flow/references/checklist.md new file mode 100644 index 0000000..e27a602 --- /dev/null +++ b/.opencode/skills/login-flow/references/checklist.md @@ -0,0 +1,19 @@ +# Login Flow Checklist + +P0 (must pass before emitting artifact): + +- [ ] Labels above inputs, never placeholder-only +- [ ] Password field has show/hide toggle +- [ ] Social buttons use SVG icons, not emoji +- [ ] Touch targets are minimum 44px +- [ ] Error states show red text below the field +- [ ] Primary CTA button has hover/active states +- [ ] No placeholder text like "example@email.com" without indication + +P1 (should pass): + +- [ ] Loading spinner in button during submission +- [ ] "Forgot password" link present +- [ ] "Don't have an account" link present +- [ ] Country picker for phone input (if phone auth) +- [ ] Input focus state uses brand color \ No newline at end of file diff --git a/.opencode/skills/themis-design-system/DESIGN.md b/.opencode/skills/themis-design-system/DESIGN.md deleted file mode 100644 index 4a7ff07..0000000 --- a/.opencode/skills/themis-design-system/DESIGN.md +++ /dev/null @@ -1,231 +0,0 @@ -# Themis Design System - -Themis is a structured operational surface for defining, documenting, updating, and executing technical work. Source evidence from `/home/visomi/Projects/GitHub/visomi-dev/themis` shows an Nx monorepo with an Astro marketing site, Angular application, Express API, BullMQ worker, Socket.IO realtime runtime, and a gateway server. The product model is a developer-first task ledger: projects, tasks, documents, decisions, seed jobs, execution history, and agent-readable context live together so humans and code agents can share durable state. - -The system should feel like a technical manuscript rather than a generic SaaS dashboard: calm, exact, low-glare, and structured. Use tonal surface hierarchy, quiet controls, compact status metadata, and typography-led layouts. The UI must make agent activity reviewable without turning the product into chat-first noise. - -## Source Evidence - -- `context/source-context.md` defines the design-system project boundary and required package outputs. -- `context/local-code/themis.md` records the local evidence intake and copied source snapshots. -- `context/local-code/themis/files/styles.base.css` is the shared token source for light and dark themes. -- `context/local-code/themis/files/DESIGN.md` and `context/local-code/themis/files/DESIGN_DARK.md` provide source design rules. -- `source_examples/` preserves high-signal Angular templates and source files for sidebar, logo, projects, project detail, and sign-in surfaces. -- `assets/` and `build/` preserve real Themis marks and screenshot evidence. - -## Product Context - -Themis supports software engineers, technical leads, engineering managers, solo builders, product-minded collaborators, and AI/code agents. Primary surfaces include: - -- Public landing site explaining AI-integrated technical execution. -- Authentication and activation flows with language/theme controls. -- Project dashboard and project registry. -- Project detail surface with project seed actions, status metadata, documents, and progress. -- Navigation shell with collapsible desktop sidebar, mobile overlay, user menu, and theme toggle. - -Core capabilities evidenced by source files: - -- Create and inspect projects. -- Preserve summaries, documents, sources, status, created dates, and seed job progress. -- Structure work so humans and agents can understand state without reconstructing context from chat. -- Support light and dark modes from the same token vocabulary. - -## Visual Foundations - -Themis is calm technical minimalism. The mood is precise, durable, and operational. Avoid decorative dashboards, playful illustration systems, and overloaded card grids. Depth is created by tonal stacking rather than heavy elevation. - -Principles: - -- One clear focal surface per screen. -- Use surface shifts before borders; use ghost borders only when density demands separation. -- Keep action color deliberate: primary blue/cyan for primary actions, focus, and active navigation. -- Use tertiary/success/error only for meaningful status. -- Prefer structured rows, panels, side rails, and document wells over floating cards. -- Use machine-readable labels and timestamps sparingly, with mono typography. - -## Color - -Use `colors_and_type.css` as the reusable token file. Do not invent tokens outside this palette unless a future source update adds them. - -### Light: Slate & Syntax - -- Canvas: `--color-background: #faf8ff`. -- Base surface: `--color-surface: #faf8ff`. -- Raised/contained surfaces: `--color-surface-container-lowest: #ffffff`, `--color-surface-container-low: #f4f3fa`, `--color-surface-container: #efedf4`, `--color-surface-container-high: #e9e7ee`, `--color-surface-container-highest: #e3e1e8`. -- Primary action: `--color-primary: #1b4490`. -- Product anchor/focus container: `--color-primary-container: #385ca9`. -- Text: `--color-on-surface: #1a1b20`, `--color-on-surface-variant: #434651`. -- Borders/ghost lines: `--color-outline: #747782`, `--color-outline-variant: #c4c6d3`. -- Error: `--color-error: #ba1a1a`. -- Success: `--color-success: #1b6e3a`. - -### Dark: Slate & Syntax Night Edition - -- Canvas: `--color-background: #0c1325`. -- Deep base: `--color-surface-container-lowest: #070d1f`. -- Workspace surfaces: `--color-surface-container-low: #151b2d`, `--color-surface-container: #191f32`, `--color-surface-container-high: #23293d`, `--color-surface-container-highest: #2e3448`. -- Primary action: `--color-primary: #c3e7ff`. -- Cyan focus/product container: `--color-primary-container: #7bd0ff`. -- Text: `--color-on-surface: #dce1fb`, `--color-on-surface-variant: #bfc8cf`. -- Borders/ghost lines: `--color-outline: #899299`, `--color-outline-variant: #3f484e`. -- Error: `--color-error: #ffb4ab`. -- Success: `--color-success: #81c784`. - -### Usage Rules - -- Use `background`/`surface` for full page canvases. -- Use the surface container ladder to communicate hierarchy: base canvas, nav rail, content well, action panel, transient popover. -- Primary appears on buttons, active navigation, focus rings, progress, and important agent signals. -- Tertiary and success indicate domain state, not decoration. -- Error must only signal failures, destructive actions, or blockers. - -## Typography - -The source uses a tri-font strategy: - -- Display/headings: `Manrope`, fallback `Inter`, system sans. -- Body/UI: `Inter`, system sans. -- Mono/technical labels: `JetBrains Mono`, `SFMono-Regular`, monospace. - -Type scale: - -- Display large: 56px / 62px, Manrope 700, `-0.02em`. -- Headline XL: 40px / 48px, Manrope 700, `-0.02em`. -- Headline LG: 32px / 40px, Manrope 600-700, `-0.01em`. -- Headline MD: 24px / 32px, Manrope 600. -- Body LG: 18px / 28-30px, Inter 400. -- Body MD: 16px / 24-26px, Inter 400. -- Body SM: 14px / 20-22px, Inter 400. -- Label MD: 12-13px / 12-16px, JetBrains Mono or Inter 700, uppercase/tracked for metadata. - -Rules: - -- Use Manrope for page titles, section headings, product marks, and large editorial statements. -- Use Inter for dense rows, form text, navigation, and descriptions. -- Use JetBrains Mono for task IDs, agent IDs, technical labels, timestamps, code, and machine-readable metadata only. -- Keep body measure under 72 characters for long descriptions. - -## Spacing, Radius, And Elevation - -Spacing is 8px-based and mobile-first. - -- Base unit: 8px. -- Mobile margins: 16px. -- Tablet gutters: 24px. -- Desktop page margin: 48px. -- Container max: 1440px for broad app/landing layouts, 1152px for focused manuscript content. -- Section padding: mobile `32px`, desktop `64px`. -- Card/panel padding: mobile `16px`, desktop `24-32px`. - -Radius: - -- Small tags: `0.25rem` or `0.125rem` in the darker manuscript style. -- Buttons/inputs: `0.5rem`. -- Panels/modals: `0.75rem` to `1rem`. -- Avoid large pill shapes except avatars, counters, and compact badges. - -Elevation: - -- Default to no shadow. -- Use tonal surface shifts for hierarchy. -- Use `--shadow-panel` only for overlay panels, active nav selections, auth panels, and mobile drawers where separation is necessary. -- Ghost borders should be `outline-variant` at low opacity. - -## Layout And Composition - -The product is project-first and mobile-first. - -- App shell: collapsible left navigation on desktop, mobile overlay drawer, topbar, scrollable main content. -- Project views: max-width content column with header, status metadata, action panel, and document rows. -- Lists: use rows with column headers on desktop; collapse into stacked metadata on mobile. -- Auth: split layout on large screens with brand/positioning copy on the left and a contained form panel on the right. -- Landing: large Manrope hero, product screenshot/panel, value sections, technical task cards, and restrained CTA rhythm. -- Avoid designer controls, viewport toggles, style knobs, or design-process cards inside product UIs. - -Responsive contracts: - -- Mobile starts as a single column with `16px` margins and full-width CTAs. -- Tablet can introduce two-column content only when hierarchy remains clear. -- Desktop can use side rails, 12-column grid, and fixed/fluid max widths. -- Never squeeze dense desktop tables onto mobile; reflow row metadata vertically. - -## Components - -### Buttons - -- Primary: `background: var(--color-primary)`, `color: var(--color-on-primary)`, `radius: 0.5rem`, font weight 700. -- Secondary: transparent or tonal surface with `outline`/`outline-variant` ghost border. -- Text buttons: primary color, semibold, underline only on hover where links are expected. -- Disabled states reduce opacity and should not rely only on color. - -### Inputs And Forms - -- Inputs are recessed wells: transparent or tonal background, quiet bottom/ghost border, focus ring in primary. -- Labels are Inter semibold, 14px. -- Help and error copy lives below fields using `on-surface-variant` or error tokens. -- Auth forms use explicit labels, stable button names, and accessible autocomplete attributes. - -### Navigation - -- Sidebar uses `surface-container` or `surface-container-lowest` depending on mode and breakpoint. -- Active nav may use tonal surface, subtle shadow, and primary left border. -- Section labels are uppercase, tracked, low contrast. -- Mobile nav is a full-height overlay with a dim backdrop. - -### Panels, Cards, And Records - -- Treat cards as recessed records, not floating marketing cards. -- Use tonal backgrounds and compact padding. -- Use borders only for dense data rows, selected states, or destructive/status differentiation. -- Status tags are small, rounded, and high-signal. - -### Project And Agent Modules - -- Project list row: project name, summary, status, date, actions. -- Project detail: status/source badges, title, summary, created date, seed job action, seed progress, document rows. -- Agent insight modules: compact signal list, decision log, task artifact row, security/audit status. -- Code/log areas: mono text, copy affordance, low-glare surface. - -### Brand Marks - -- Use preserved `assets/wordmark.svg` and `assets/isotype.svg` when possible. -- The Angular source also supports color-inheriting inline marks for dynamic themes. -- Do not redraw the mark unless implementing a tiny color-inheriting UI variant. - -## Motion And Interaction - -- Standard transitions: 100-200ms for hover/focus, 200ms for sidebar/layout changes, 300ms for upward enter/leave. -- Motion should clarify state changes, not decorate. -- Respect reduced motion by disabling non-essential transforms and animations. -- Focus states must be visible and primary-colored. -- Interactive controls need at least 44px mobile hit targets. - -## Voice And Brand - -Voice is concise, technical, and operational. - -- Prefer nouns like `Project`, `Document`, `Decision`, `Execution log`, `Agent insight`, `Seed job`, `Workspace`. -- Use short verbs: `Run`, `Create`, `View`, `Delete`, `Copy`, `Approve`. -- Avoid hype, vague productivity claims, and invented metrics. -- When data is unknown, show an honest placeholder or omit the metric. -- The product can speak bilingually where source supports English/Spanish, but component names and token docs should stay English for reuse. - -## Anti-Patterns - -- No aggressive purple gradients. -- No generic emoji icon rows. -- No warm beige/peach/pink AI canvas unless future brand evidence changes the palette. -- No rounded cards with decorative left-color stripes unless the stripe is an active/status signal from source patterns. -- No anonymous metric grids or fake performance numbers. -- No heavy shadows for structural hierarchy. -- No decorative icons where a label or row state is clearer. -- No designer/demo controls inside end-user product prototypes. -- No monochrome-only greyscale outputs; always use the source palette and primary/domain/status colors intentionally. - -## Reuse Checklist - -- Import `colors_and_type.css` first. -- Use preserved assets from `assets/` or `build/` for marks. -- Review `preview/` cards before generating a new artifact. -- Inspect `source_examples/` for source-backed Angular patterns. -- Start new product examples from `ui_kits/app/` when building an app/workspace surface. diff --git a/.opencode/skills/themis-design-system/PROVENANCE.md b/.opencode/skills/themis-design-system/PROVENANCE.md deleted file mode 100644 index 7c57b23..0000000 --- a/.opencode/skills/themis-design-system/PROVENANCE.md +++ /dev/null @@ -1,31 +0,0 @@ -# Provenance Notes - -## Source Intake - -- Project source folder: `/home/visomi/Projects/GitHub/visomi-dev/themis`. -- Required runbook: `context/source-context.md`. -- Evidence note: `context/local-code/themis.md`. -- Manual snapshots: `context/local-code/themis/files/`. - -The bounded local intake wrapper could not run in this chat because `OD_NODE_BIN` and `OD_BIN` were missing from the execution environment. The linked local folder was readable, so source evidence was manually captured into the same project-local context area before final package files were generated. - -## Preserved Evidence - -- Source tokens: `styles.base.css`, `DESIGN.md`, `DESIGN_DARK.md`, `apps/web/app/src/styles.css`. -- Product context: `README.md`, `docs/product/prd.md`. -- Source-backed components: sidebar menu, logo, sign-in form, form field, projects list, project detail, landing page. -- Assets: wordmark, isotype, favicon SVG/ICO, sign-in screenshot, projects overview screenshot. - -## Generated Package Files - -- Canonical rules: `DESIGN.md`. -- Package guide: `README.md`. -- Agent entry: `SKILL.md`. -- Tokens: `colors_and_type.css`. -- Preview cards: `preview/*.html`. -- Applied kit: `ui_kits/app/`. -- Reusable source references: `source_examples/`. - -## Audit Limitation - -The configured design-system package audit command was attempted but unavailable because the wrapper paths were absent. A local consistency check verified required files, preview manifest entries, preserved assets, component globals, and UI-kit token imports. diff --git a/.opencode/skills/themis-design-system/README.md b/.opencode/skills/themis-design-system/README.md deleted file mode 100644 index 3587549..0000000 --- a/.opencode/skills/themis-design-system/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Themis Design System Package - -Reusable Open Design package for Themis, a structured developer task ledger for defining, documenting, updating, and executing technical work with durable human and AI-agent context. - -## Product Overview - -Themis is evidenced from the local source folder `/home/visomi/Projects/GitHub/visomi-dev/themis` and copied snapshots under `context/local-code/themis/files/`. The repository is an Nx monorepo with an Astro marketing site, Angular web application, Express API, BullMQ worker, Socket.IO realtime runtime, and gateway server. - -The product is not a generic chat tool. It is a project-first operational ledger where humans and code agents share durable state: projects, tasks, documents, source summaries, decisions, seed jobs, execution history, and reviewable context. The visual system should therefore feel like a technical manuscript: precise, low-glare, structured, and reviewable. - -Primary UI surfaces evidenced by package/source files: - -- Public landing site explaining AI-integrated technical execution. -- Sign-in/auth flow with language and theme controls. -- App shell with responsive sidebar, mobile drawer behavior, topbar, logo, user menu, and theme toggle. -- Projects registry with project name, summary, status, date, and actions. -- Project detail view with status/source badges, seed job action, seed progress, documents, and metadata. -- Preserved brand marks and screenshots: wordmark, isotype, favicon, projects overview, and sign-in evidence. - -Core capabilities evidenced by source and package files: - -- Create and inspect structured projects. -- Preserve summaries, documents, source context, status, created dates, and execution seed progress. -- Support reviewable agent activity without making the product chat-first noise. -- Render consistent light/dark themes from the same token vocabulary. - -## Source Context References - -- Linked source folder: `/home/visomi/Projects/GitHub/visomi-dev/themis`. -- Intake/evidence note: `context/local-code/themis.md`. -- Copied snapshots: `context/local-code/themis/files/`. -- Canonical token evidence: `context/local-code/themis/files/styles.base.css`. -- Source design rules: `context/local-code/themis/files/DESIGN.md` and `context/local-code/themis/files/DESIGN_DARK.md`. -- Source-backed component examples: `source_examples/`. -- Preserved source assets: `assets/` and runtime-intent copies in `build/`. - -## Package Contents - -- `DESIGN.md` - canonical rules document for future design work. -- `colors_and_type.css` - reusable CSS variables, light/dark theme tokens, type helpers, panel classes, and button classes. -- `SKILL.md` - Claude Design-style skill entry with frontmatter, package contents, source context, usage workflow, and highlights. -- `PROVENANCE.md` - evidence collection, preservation, and audit notes. -- `assets/` - preserved `wordmark.svg`, `isotype.svg`, `favicon.svg`, `projects-overview.png`, and `themis-sign-in.png`. -- `build/` - runtime-intent preserved originals: `build/wordmark.svg`, `build/isotype.svg`, and `build/favicon.ico`. -- `source_examples/` - substantial source-backed examples copied outside `context/`: `logo.html`, `sidebar-menu.html`, `sidebar-menu.ts`, `sign-in.html`, `projects.html`, and `project-detail.html`. -- `preview/` - focused review cards for colors, typography, spacing, components, inputs, and brand assets. -- `ui_kits/app/` - runnable React/Babel interface kit using modular components and `../../colors_and_type.css`. -- `context/` - source snapshots and intake notes used to ground the package. - -## Preview Manifest - -- `preview/colors-theme-light.html` - Inspect Slate & Syntax light tokens, surface ladder, primary/action color, status colors, and a source-backed project row derived from `projects.html` patterns. -- `preview/colors-theme-dark.html` - Inspect Night Edition dark tokens, low-glare slate surfaces, cyan focus/action color, and dark operational project-detail panels. -- `preview/typography-specimens.html` - Inspect Manrope heading hierarchy, Inter body/UI copy, JetBrains Mono technical labels, and source-backed terms like Project, Document, Seed job, and Execution log. -- `preview/spacing-radius.html` - Inspect the 8px rhythm and radius scale through source-backed modules: sidebar-menu item, projects row, and project-detail/auth panel surfaces. -- `preview/components-buttons.html` - Inspect source-backed action controls modeled on project-detail seed actions, document actions, sidebar active state, and status tags. -- `preview/components-inputs.html` - Inspect auth/form patterns modeled on `sign-in.html` and `form-field.html`: labels, autocomplete, help/error copy, checkbox row, and full-width submit action. -- `preview/brand-assets.html` - Inspect preserved source assets loaded visibly from `assets/` and `build/`, including wordmark/isotype SVGs, favicon assets, and screenshot evidence. - -Keep this manifest synchronized whenever preview files are added, renamed, or removed. - -## Preserved Assets, Build Files, And Source Examples - -Use preserved files directly in future artifacts. Do not redraw the Themis mark or replace runtime evidence with prose-only notes. - -- Brand assets: `assets/wordmark.svg`, `assets/isotype.svg`, `assets/favicon.svg`. -- Screenshot evidence: `assets/projects-overview.png`, `assets/themis-sign-in.png`. -- Runtime/build representatives: `build/wordmark.svg`, `build/isotype.svg`, `build/favicon.ico`. -- Component evidence: `source_examples/sidebar-menu.html`, `source_examples/sidebar-menu.ts`, `source_examples/logo.html`, `source_examples/sign-in.html`, `source_examples/projects.html`, `source_examples/project-detail.html`. - -## UI Kit - -`ui_kits/app/` is the reusable applied interface kit for app/workspace surfaces. It is intentionally product-like, not a static marketing page. - -- `ui_kits/app/index.html` loads React, ReactDOM, Babel, `../../colors_and_type.css`, each modular JSX file, and mounts `window.App` into `#root`. -- `ui_kits/app/components/Sidebar.jsx` models the source sidebar/logo/navigation posture. -- `ui_kits/app/components/AssistantsList.jsx` models the project/agent list rail. -- `ui_kits/app/components/ChatArea.jsx` models the workspace/project detail area with seed progress and document context. -- `ui_kits/app/components/MessageBubble.jsx` models compact reviewable comments/messages. -- `ui_kits/app/components/InputBar.jsx` models an operational composer/action input. -- `ui_kits/app/components/App.jsx` composes the modules into one runnable workspace. -- `ui_kits/app/README.md` documents structure, source basis, usage workflow, and design notes. - -## Reuse Workflow - -1. Read `DESIGN.md` before generating artifacts; it is the canonical design-system contract. -2. Import `colors_and_type.css` first and bind tokens instead of inventing new palette names. -3. Pick the appropriate theme: light by default, `data-theme="dark"` or `.dark` for Night Edition. -4. Use `assets/` or `build/` for real marks and runtime icons. -5. Inspect `source_examples/` when matching sidebar, logo, sign-in, projects, or project-detail behavior. -6. Review the exact `preview/` card relevant to your surface before final output. -7. Start app/workspace prototypes from `ui_kits/app/` when possible and keep component composition modular. -8. Preserve Themis voice: concise technical nouns, honest placeholders, no invented metrics, no decorative chat-first noise. - -## Review Workflow - -1. Open `preview/brand-assets.html` to confirm preserved files load from `assets/` and `build/`. -2. Open `preview/colors-theme-light.html` and `preview/colors-theme-dark.html` to compare theme hierarchy. -3. Open `preview/components-buttons.html`, `preview/components-inputs.html`, and `preview/spacing-radius.html` to verify source-backed component posture. -4. Open `ui_kits/app/index.html` to confirm the runnable interface kit composes all modular components. -5. Cross-check `README.md`, `SKILL.md`, and `DESIGN.md` when changing file structure or preview names. - -## Design Notes - -- Use tonal stacking instead of heavy shadows. -- Use Manrope for structural headings, Inter for UI/body, and JetBrains Mono for technical labels. -- Prefer structured rows, document wells, project metadata, status badges, and seed job panels over anonymous card grids. -- Reserve primary blue/cyan for primary actions, active navigation, focus, and important agent signals. -- Avoid fake metrics, generic emoji feature rows, warm beige AI canvases, decorative icon clutter, and designer/demo controls inside product UI. - -## Provenance - -This package was regenerated inside the project workspace from local source evidence. The bounded intake wrapper was unavailable in the earlier runtime, so evidence was manually copied from the linked local folder into `context/local-code/themis/files/` and summarized in `context/local-code/themis.md`. Source-backed examples and preserved assets were then copied outside `context/` for future agents and package consumers. diff --git a/.opencode/skills/themis-design-system/SKILL.md b/.opencode/skills/themis-design-system/SKILL.md deleted file mode 100644 index 951befa..0000000 --- a/.opencode/skills/themis-design-system/SKILL.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: themis-design-system -description: Generate Themis-branded product surfaces with preserved design tokens, Angular source examples, runnable UI kit, and technical manuscript posture. -license: MIT -compatibility: opencode -metadata: - audience: designers - workflow: design-system ---- - -## What I do - -- Guide generation of Themis product screens, dashboards, app shells, auth flows, and project/workspace prototypes -- Enforce canonical design tokens (Slate & Syntax light / Night Edition dark) from `colors_and_type.css` -- Reference preserved source-backed Angular patterns from `source_examples/` for sidebar, logo, sign-in, projects, and project-detail -- Provide a runnable React/Babel UI kit in `ui_kits/app/` for interactive workspace prototypes -- Validate output against preview cards in `preview/` for color, typography, spacing, buttons, inputs, and brand assets -- Preserve Themis voice: concise technical nouns, honest placeholders, no fake metrics, no decorative chat-first noise -- Use preserved brand assets (`assets/`, `build/`) instead of redrawing logos - -## When to use me - -Use this when a brief asks for Themis product surfaces, operational dashboards, developer task ledgers, app shells, auth/project/workspace prototypes, or previews that must match the preserved Themis source posture. - -Do not use for playful consumer products, image-led editorial pages, decorative chat-first assistants, or generic SaaS dashboards unless the brief explicitly asks for Themis' operational manuscript language. - -## How to use - -1. Read `DESIGN.md` before generating — it is the canonical design-system contract -2. Import `colors_and_type.css` first; bind existing tokens, don't invent new palette names -3. Light mode by default; dark mode via `data-theme="dark"` or `.dark` -4. Reference `source_examples/` for sidebar, logo, sign-in, projects, and project-detail patterns -5. Check relevant `preview/` card before final output -6. Start app/workspace prototypes from `ui_kits/app/` diff --git a/.opencode/skills/themis-design-system/assets_DESIGN.md b/.opencode/skills/themis-design-system/assets_DESIGN.md deleted file mode 100644 index 908a819..0000000 --- a/.opencode/skills/themis-design-system/assets_DESIGN.md +++ /dev/null @@ -1,191 +0,0 @@ ---- -name: Themis — Slate & Syntax -dark-mode-name: Themis — Slate & Syntax: Night Edition -color-scheme: light dark -design-tokens: colors_and_type.css -colors: - surface: - light: '#faf8ff' - dark: '#0c1325' - surface-dim: - light: '#dbd9e0' - dark: '#0c1325' - surface-bright: - light: '#faf8ff' - dark: '#33384c' - surface-container-lowest: - light: '#ffffff' - dark: '#070d1f' - surface-container-low: - light: '#f4f3fa' - dark: '#151b2d' - surface-container: - light: '#efedf4' - dark: '#191f32' - surface-container-high: - light: '#e9e7ee' - dark: '#23293d' - surface-container-highest: - light: '#e3e1e8' - dark: '#2e3448' - on-surface: - light: '#1a1b20' - dark: '#dce1fb' - on-surface-variant: - light: '#434651' - dark: '#bfc8cf' - inverse-surface: - light: '#2f3035' - dark: '#dce1fb' - inverse-on-surface: - light: '#f2f0f7' - dark: '#2a3043' - outline: - light: '#747782' - dark: '#899299' - outline-variant: - light: '#c4c6d3' - dark: '#3f484e' - surface-tint: - light: '#385ca9' - dark: '#7cd0ff' - primary: - light: '#1b4490' - dark: '#c3e7ff' - on-primary: - light: '#ffffff' - dark: '#00344a' - primary-container: - light: '#385ca9' - dark: '#7bd0ff' - on-primary-container: - light: '#ccd9ff' - dark: '#005979' - inverse-primary: - light: '#b0c6ff' - dark: '#00668a' - secondary: - light: '#535f70' - dark: '#9ad1c6' - on-secondary: - light: '#ffffff' - dark: '#003731' - secondary-container: - light: '#d7e3f8' - dark: '#185249' - on-secondary-container: - light: '#596576' - dark: '#8cc3b8' - tertiary: - light: '#633b49' - dark: '#ffdad7' - on-tertiary: - light: '#ffffff' - dark: '#611213' - tertiary-container: - light: '#7d5260' - dark: '#ffb3ae' - on-tertiary-container: - light: '#ffcbda' - dark: '#8f3432' - error: - light: '#ba1a1a' - dark: '#ffb4ab' - on-error: - light: '#ffffff' - dark: '#690005' - error-container: - light: '#ffdad6' - dark: '#93000a' - on-error-container: - light: '#93000a' - dark: '#ffdad6' - success: - light: '#1b6e3a' - dark: '#81c784' - on-success: - light: '#ffffff' - dark: '#00340a' - success-container: - light: '#a5d6a7' - dark: '#1b5e20' - on-success-container: - light: '#0e3b1f' - dark: '#a5d6a7' - background: - light: '#faf8ff' - dark: '#0c1325' - on-background: - light: '#1a1b20' - dark: '#dce1fb' - surface-variant: - light: '#e3e1e8' - dark: '#2e3448' -typography: - headline-xl: - fontFamily: Manrope - fontSize: 40px - fontWeight: '700' - lineHeight: 48px - letterSpacing: -0.02em - headline-lg: - fontFamily: Manrope - fontSize: 32px - fontWeight: '600' - lineHeight: 40px - letterSpacing: -0.01em - headline-lg-mobile: - fontFamily: Manrope - fontSize: 28px - fontWeight: '600' - lineHeight: 36px - headline-md: - fontFamily: Manrope - fontSize: 24px - fontWeight: '600' - lineHeight: 32px - body-lg: - fontFamily: Inter - fontSize: 18px - fontWeight: '400' - lineHeight: 28px - body-md: - fontFamily: Inter - fontSize: 16px - fontWeight: '400' - lineHeight: 24px - body-sm: - fontFamily: Inter - fontSize: 14px - fontWeight: '400' - lineHeight: 20px - label-md: - fontFamily: JetBrains Mono - fontSize: 13px - fontWeight: '500' - lineHeight: 16px - letterSpacing: 0.02em - label-sm: - fontFamily: JetBrains Mono - fontSize: 11px - fontWeight: '500' - lineHeight: 14px - letterSpacing: 0.04em -rounded: - sm: 0.25rem - DEFAULT: 0.5rem - md: 0.75rem - lg: 1rem - xl: 1.5rem - full: 9999px -spacing: - unit: 8px - gutter: 24px - margin-mobile: 16px - margin-desktop: 48px - container-max: 1440px -motion: - fast: 100ms ease - base: 200ms ease - enter: 300ms ease-in-out ---- diff --git a/.opencode/skills/themis-design-system/colors_and_type.css b/.opencode/skills/themis-design-system/colors_and_type.css deleted file mode 100644 index 6abca03..0000000 --- a/.opencode/skills/themis-design-system/colors_and_type.css +++ /dev/null @@ -1,214 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Manrope:wght@500;600;700;800&display=swap'); - -:root { - color-scheme: light dark; - - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-heading: 'Manrope', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-mono: 'JetBrains Mono', 'SFMono-Regular', ui-monospace, Menlo, monospace; - --font-family: var(--font-sans); - --font-family-display: var(--font-heading); - --font-family-mono: var(--font-mono); - - --color-surface: #faf8ff; - --color-surface-dim: #dbd9e0; - --color-surface-bright: #faf8ff; - --color-surface-container-lowest: #ffffff; - --color-surface-container-low: #f4f3fa; - --color-surface-container: #efedf4; - --color-surface-container-high: #e9e7ee; - --color-surface-container-highest: #e3e1e8; - --color-on-surface: #1a1b20; - --color-on-surface-variant: #434651; - --color-inverse-surface: #2f3035; - --color-inverse-on-surface: #f2f0f7; - --color-outline: #747782; - --color-outline-variant: #c4c6d3; - --color-surface-tint: #385ca9; - --color-primary: #1b4490; - --color-on-primary: #ffffff; - --color-primary-container: #385ca9; - --color-on-primary-container: #ccd9ff; - --color-inverse-primary: #b0c6ff; - --color-primary-fixed: #d9e2ff; - --color-primary-fixed-dim: #b0c6ff; - --color-on-primary-fixed: #001945; - --color-on-primary-fixed-variant: #1a438f; - --color-primary-dim: #15387a; - --color-secondary: #535f70; - --color-on-secondary: #ffffff; - --color-secondary-container: #d7e3f8; - --color-on-secondary-container: #596576; - --color-secondary-fixed: #d7e3f8; - --color-secondary-fixed-dim: #bbc7db; - --color-on-secondary-fixed: #101c2b; - --color-on-secondary-fixed-variant: #3c4858; - --color-secondary-dim: #475260; - --color-tertiary: #633b49; - --color-on-tertiary: #ffffff; - --color-tertiary-container: #7d5260; - --color-on-tertiary-container: #ffcbda; - --color-tertiary-fixed: #ffd9e3; - --color-tertiary-fixed-dim: #eeb8c8; - --color-on-tertiary-fixed: #31111d; - --color-on-tertiary-fixed-variant: #633b48; - --color-tertiary-dim: #56323e; - --color-error: #ba1a1a; - --color-on-error: #ffffff; - --color-error-container: #ffdad6; - --color-on-error-container: #93000a; - --color-error-dim: #a61717; - --color-success: #1b6e3a; - --color-on-success: #ffffff; - --color-success-container: #a5d6a7; - --color-on-success-container: #0e3b1f; - --color-background: #faf8ff; - --color-on-background: #1a1b20; - --color-surface-variant: #e3e1e8; - --color-switch-muted: #434651; - - --radius-sm: 0.25rem; - --radius: 0.5rem; - --radius-md: 0.75rem; - --radius-lg: 1rem; - --radius-xl: 1.5rem; - --radius-full: 9999px; - - --space-1: 0.25rem; - --space-2: 0.5rem; - --space-3: 0.75rem; - --space-4: 1rem; - --space-5: 1.25rem; - --space-6: 1.5rem; - --space-8: 2rem; - --space-12: 3rem; - --space-16: 4rem; - --container-max: 1440px; - --content-max: 1152px; - - --shadow-panel: 0 32px 64px -12px rgb(26 27 32 / 0.08); - --motion-fast: 100ms ease; - --motion-base: 200ms ease; - --motion-enter: 300ms ease-in-out; -} - -:root[data-theme='dark'], -.dark { - --color-surface: #0c1325; - --color-surface-dim: #0c1325; - --color-surface-bright: #33384c; - --color-surface-container-lowest: #070d1f; - --color-surface-container-low: #151b2d; - --color-surface-container: #191f32; - --color-surface-container-high: #23293d; - --color-surface-container-highest: #2e3448; - --color-on-surface: #dce1fb; - --color-on-surface-variant: #bfc8cf; - --color-inverse-surface: #dce1fb; - --color-inverse-on-surface: #2a3043; - --color-outline: #899299; - --color-outline-variant: #3f484e; - --color-surface-tint: #7cd0ff; - --color-primary: #c3e7ff; - --color-on-primary: #00344a; - --color-primary-container: #7bd0ff; - --color-on-primary-container: #005979; - --color-inverse-primary: #00668a; - --color-primary-fixed: #c4e7ff; - --color-primary-fixed-dim: #7cd0ff; - --color-on-primary-fixed: #001e2c; - --color-on-primary-fixed-variant: #004c69; - --color-primary-dim: #a8d4f0; - --color-secondary: #9ad1c6; - --color-on-secondary: #003731; - --color-secondary-container: #185249; - --color-on-secondary-container: #8cc3b8; - --color-secondary-fixed: #b5eee2; - --color-secondary-fixed-dim: #9ad1c6; - --color-on-secondary-fixed: #00201c; - --color-on-secondary-fixed-variant: #154f47; - --color-secondary-dim: #87bdb3; - --color-tertiary: #ffdad7; - --color-on-tertiary: #611213; - --color-tertiary-container: #ffb3ae; - --color-on-tertiary-container: #8f3432; - --color-tertiary-fixed: #ffdad7; - --color-tertiary-fixed-dim: #ffb3ae; - --color-on-tertiary-fixed: #410004; - --color-on-tertiary-fixed-variant: #7f2927; - --color-tertiary-dim: #f0c5c3; - --color-error: #ffb4ab; - --color-on-error: #690005; - --color-error-container: #93000a; - --color-on-error-container: #ffdad6; - --color-error-dim: #e8a19b; - --color-success: #81c784; - --color-on-success: #00340a; - --color-success-container: #1b5e20; - --color-on-success-container: #a5d6a7; - --color-background: #0c1325; - --color-on-background: #dce1fb; - --color-surface-variant: #2e3448; - --color-switch-muted: #bfc8cf; - --shadow-panel: 0 32px 64px -12px rgb(0 0 0 / 0.5); -} - -body { - margin: 0; - background: var(--color-background); - color: var(--color-on-surface); - font-family: var(--font-family); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: geometricPrecision; -} - -.themis-heading { - font-family: var(--font-family-display); - letter-spacing: -0.02em; -} - -.themis-label { - color: var(--color-on-surface-variant); - font-family: var(--font-family-mono); - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.themis-panel { - background: var(--color-surface-container-lowest); - border: 1px solid color-mix(in srgb, var(--color-outline-variant) 28%, transparent); - border-radius: var(--radius); -} - -.themis-button-primary { - min-height: 44px; - border: 0; - border-radius: var(--radius); - background: var(--color-primary); - color: var(--color-on-primary); - font: 700 0.9375rem/1 var(--font-family); - padding: 0.75rem 1rem; -} - -.themis-button-secondary { - min-height: 44px; - border: 1px solid color-mix(in srgb, var(--color-outline) 32%, transparent); - border-radius: var(--radius); - background: transparent; - color: var(--color-on-surface); - font: 700 0.9375rem/1 var(--font-family); - padding: 0.75rem 1rem; -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 1ms !important; - scroll-behavior: auto !important; - transition-duration: 1ms !important; - } -} diff --git a/.opencode/skills/themis-ui-prototype/SKILL.md b/.opencode/skills/themis-ui-prototype/SKILL.md new file mode 100644 index 0000000..0e1fcf7 --- /dev/null +++ b/.opencode/skills/themis-ui-prototype/SKILL.md @@ -0,0 +1,107 @@ +--- +name: themis-ui-prototype +description: Author and preview Themis UI prototypes against the live Catalyst token set before implementation. Use this skill to compose a screen in HTML + Tailwind v4 utilities, render it in the local ui-designer preview server, and validate it across mobile, tablet, and desktop viewports in light and dark mode. +license: MIT +compatibility: opencode +metadata: + audience: designers, frontend engineers + workflow: prototype +--- + +# Themis UI Prototype + +Author Themis prototypes that consume the live Catalyst Tailwind v4 token set and preview them at `http://localhost:4300/preview/` before any Angular code lands. + +## What I do + +- Compose a screen as a single self-contained HTML file under `apps/web/ui-designer/src/prototypes/.html`. +- Reuse the Themis tokens by linking `/public/tailwind.css` (the locally built stylesheet that imports `styles.base.css` + Tailwind utilities). +- Run `pnpm nx serve ui-designer` to expose the preview server at `http://localhost:4300/` with the index, manifest, and preview routes. +- Preview at `?viewport=mobile|tablet|desktop` and toggle `?theme=dark` on the iframe frame URL. +- Capture screenshots at the three viewport presets with the [`ui-screenshots`](../ui-screenshots/) skill. +- Review visual polish with the [`web-design-reviewer`](../web-design-reviewer/) skill. +- Polish typography and remove AI tells with the [`impeccable-design-polish`](../impeccable-design-polish/) skill. + +## When to use me + +Use this skill when a brief asks for a new Themis screen, an alternative layout for an existing route, or a quick visual exploration that should not yet touch the Angular app. Do not use it for production code in `apps/web/app` — that flows through the Angular component API and the `shared/ui` primitives. + +## How to use + +### Step 0 — Read the contracts + +- [`/docs/design-system/tokens.md`](../../../docs/design-system/tokens.md) for the token vocabulary. +- [`/docs/design-system/recipes.md`](../../../docs/design-system/recipes.md) for layout rhythms (auth shell, app shell, project list, dialog, etc.). +- [`/docs/agents/design-system.md`](../../../docs/agents/design-system.md) for the workspace's Tailwind + accessibility rules. + +### Step 1 — Pick a slug + +Use kebab-case. Examples: `app-auth-shell`, `project-list-empty-state`, `sign-up-with-otp`. The slug becomes the filename and the preview URL path segment. + +### Step 2 — Author the prototype + +Create `apps/web/ui-designer/src/prototypes/.html`. The file must: + +- Link the locally built stylesheet: ``. +- Use Tailwind utilities only. No raw hex colors. No `bg-[#…]` arbitrary values for token colors. +- Apply mobile-first spacing and scale up at `md:` and `lg:`. +- Use semantic HTML5 elements (`
`, `
`, `
`, `
`, ` + + +
+
+
+
+

+ Kicker +

+

+ Title +

+

+ One-line description that orients the user. +

+
+ + +
+ + +
+ +
+ + +
+ + + + +

Footer prompt.

+ + Footer link + +
+
+ + \ No newline at end of file diff --git a/.opencode/skills/themis-ui-prototype/references/preview-chrome.md b/.opencode/skills/themis-ui-prototype/references/preview-chrome.md new file mode 100644 index 0000000..cb4c8e9 --- /dev/null +++ b/.opencode/skills/themis-ui-prototype/references/preview-chrome.md @@ -0,0 +1,53 @@ +# Preview Chrome + +The preview server wraps each prototype in a small toolbar that lets the engineer switch viewport and toggle dark mode without leaving the page. The iframe loads `/preview//frame?theme=light|dark`. + +## Toolbar + +| Control | Default | Effect | +| ---------- | ------------------- | --------------------------------------------------------------------- | +| Index link | `← Index` | Returns to `http://localhost:4300/` | +| Viewport | `Mobile / Tablet / Desktop` | Reloads the page with `?viewport=`. Default is `desktop`. | +| Theme | `Light / Dark` | Toggles the iframe `src` between `?theme=light` and `?theme=dark`. | + +## Viewport Presets + +| Preset | Width | When to use | +| -------- | ------ | ---------------------------------------------------- | +| Mobile | 375px | First visual check after authoring. | +| Tablet | 768px | Second check; verifies two-column layouts reflow. | +| Desktop | 1280px | Third check; verifies the full layout rhythm. | + +The iframe width is set via `[data-viewport="…"]` selectors in `apps/web/ui-designer/src/preview/preview.css`. The stylesheet is built into `dist/apps/web/ui-designer/public/tailwind.css` by the `build-css` target. + +## URL Contract + +``` +GET /preview/:slug # preview chrome with iframe +GET /preview/:slug?viewport=mobile # mobile viewport +GET /preview/:slug/frame?theme=light # raw prototype, light +GET /preview/:slug/frame?theme=dark # raw prototype, dark +GET /api/prototypes # JSON manifest +GET /healthz # { "ok": true } +GET / # index page +``` + +## Watch + Live Reload + +The `serve` target watches `src/**/*` via `@nx/js:node`. When a prototype HTML file or `styles.base.css` changes, the next request gets the updated content. There is no SSE push yet (deferred to a follow-up spec); a manual reload suffices for the current loop. + +If the CSS bundle goes stale, run: + +```bash +pnpm nx run ui-designer:build-css --skip-nx-cache +``` + +## Troubleshooting + +| Symptom | Fix | +| -------------------------------------------------- | ------------------------------------------------------------- | +| Prototype shows raw HTML with no styles | `pnpm nx run ui-designer:build-css --skip-nx-cache` | +| New utility class not generated | Add the file under `src/prototypes/`, re-run `build-css` | +| Preview iframe is blank | Check the dev server console for the load error | +| `404 Prototype "x" not found` | The slug must be kebab-case; the file must end in `.html` | +| `400 Invalid viewport` | Use `mobile`, `tablet`, or `desktop` (lowercase) | \ No newline at end of file diff --git a/.opencode/skills/themis-ui-prototype/references/tokens-cheatsheet.md b/.opencode/skills/themis-ui-prototype/references/tokens-cheatsheet.md new file mode 100644 index 0000000..09ff873 --- /dev/null +++ b/.opencode/skills/themis-ui-prototype/references/tokens-cheatsheet.md @@ -0,0 +1,78 @@ +# Tokens Cheatsheet + +A minimal map of the Themis visual language to Tailwind v4 utilities. These are the safe defaults the `themis-ui-prototype` skill expects prototypes to use until the Catalyst semantic tokens (`bg-bg`, `bg-panel`, `bg-accent`) land in `styles.base.css` via the `2026-06-23-catalyst-pure-tokens-alignment` spec. + +## Surfaces + +| Role | Light utility | Dark utility | +| ------------------ | ---------------------------------------------- | ----------------------------------------------- | +| Page canvas | `bg-white` | `dark:bg-zinc-950` | +| Card / panel | `bg-zinc-50` + `border-zinc-950/10` | `dark:bg-zinc-900` + `dark:border-white/10` | +| Raised panel | `bg-zinc-100` + `border-zinc-950/10` | `dark:bg-zinc-800` + `dark:border-white/10` | +| Subtle divider | `border-zinc-950/5` | `dark:border-white/5` | +| Quiet divider | `border-zinc-950/10` | `dark:border-white/10` | + +## Text + +| Role | Light utility | Dark utility | +| ------------------ | ---------------------------------------------- | ----------------------------------------------- | +| Primary text | `text-zinc-950` | `dark:text-zinc-50` | +| Secondary text | `text-zinc-500` | `dark:text-zinc-400` | +| Inverse text | `text-white` | `text-white` | +| Link text | `text-blue-600` | `dark:text-blue-500` | + +## Accent and Status + +| Role | Light | Dark | +| ------------------ | ---------------------------------------------- | ----------------------------------------------- | +| Primary action | `bg-blue-600 text-white hover:bg-blue-700` | `dark:bg-blue-500` | +| Destructive | `bg-red-600 text-white` | `dark:bg-red-500` | +| Success | `bg-emerald-600 text-white` | `dark:bg-emerald-500` | +| Warning | `bg-amber-500 text-zinc-950` | `dark:bg-amber-400` | + +## Spacing + +| Role | Utility | +| ------------------ | ---------------------------------------------- | +| Mobile page margin | `px-4` | +| Tablet gutter | `md:px-6` | +| Desktop page margin | `md:px-8 lg:px-12` | +| Section padding | `py-8 md:py-12 lg:py-16` | +| Card padding | `p-4 md:p-6 lg:p-8` | +| Stack gap | `gap-4 md:gap-6 lg:gap-8` | + +## Radii + +| Role | Utility | +| ------------------ | ---------------------------------------------- | +| Tag / chip | `rounded-sm` | +| Button / input | `rounded-md` | +| Card / panel | `rounded-lg` | +| Modal / drawer | `rounded-xl` | +| Pill | `rounded-full` | + +## Typography + +| Role | Utility | +| ------------------ | ------------------------------------------------------------- | +| Display heading | `font-heading text-4xl font-bold tracking-tight md:text-6xl` | +| Page title | `font-heading text-3xl font-bold md:text-4xl` | +| Section heading | `font-heading text-2xl font-semibold md:text-3xl` | +| Body | `text-base leading-6` | +| Body small | `text-sm leading-5` | +| Mono / ID | `font-mono text-xs uppercase tracking-widest font-semibold` | + +## Touch Targets + +| Role | Utility | +| ------------------ | ---------------------------------------------- | +| All interactive | `min-h-11 min-w-11` | +| Primary CTA (mobile) | `w-full sm:w-auto` | + +## Accessibility + +| Role | Utility | +| ------------------ | ---------------------------------------------- | +| Focus ring | Tailwind's default `focus-visible:ring-2` + `focus-visible:ring-blue-500` | +| Reduced motion | Wrap custom animation in `motion-safe:animate-*` | +| Screen reader only | `sr-only` | \ No newline at end of file diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-1280-dark.png b/.opencode/snapshots/auth-phase10/forgotten-password-1280-dark.png new file mode 100644 index 0000000..be9d411 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-1280-light.png b/.opencode/snapshots/auth-phase10/forgotten-password-1280-light.png new file mode 100644 index 0000000..ebb8aff Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-360-dark.png b/.opencode/snapshots/auth-phase10/forgotten-password-360-dark.png new file mode 100644 index 0000000..df9d3fe Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-360-light.png b/.opencode/snapshots/auth-phase10/forgotten-password-360-light.png new file mode 100644 index 0000000..8a146b8 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-390-dark.png b/.opencode/snapshots/auth-phase10/forgotten-password-390-dark.png new file mode 100644 index 0000000..899c7b4 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-390-light.png b/.opencode/snapshots/auth-phase10/forgotten-password-390-light.png new file mode 100644 index 0000000..38ceb61 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-520-dark.png b/.opencode/snapshots/auth-phase10/forgotten-password-520-dark.png new file mode 100644 index 0000000..4b3498e Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-520-light.png b/.opencode/snapshots/auth-phase10/forgotten-password-520-light.png new file mode 100644 index 0000000..34bc956 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-768-dark.png b/.opencode/snapshots/auth-phase10/forgotten-password-768-dark.png new file mode 100644 index 0000000..b491621 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/forgotten-password-768-light.png b/.opencode/snapshots/auth-phase10/forgotten-password-768-light.png new file mode 100644 index 0000000..4ed7d75 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/forgotten-password-768-light.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-1280-dark.png b/.opencode/snapshots/auth-phase10/reset-password-1280-dark.png new file mode 100644 index 0000000..b87d2cc Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-1280-light.png b/.opencode/snapshots/auth-phase10/reset-password-1280-light.png new file mode 100644 index 0000000..039392d Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-360-dark.png b/.opencode/snapshots/auth-phase10/reset-password-360-dark.png new file mode 100644 index 0000000..de6d676 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-360-light.png b/.opencode/snapshots/auth-phase10/reset-password-360-light.png new file mode 100644 index 0000000..cbb12fa Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-390-dark.png b/.opencode/snapshots/auth-phase10/reset-password-390-dark.png new file mode 100644 index 0000000..c2d6957 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-390-light.png b/.opencode/snapshots/auth-phase10/reset-password-390-light.png new file mode 100644 index 0000000..343033f Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-520-dark.png b/.opencode/snapshots/auth-phase10/reset-password-520-dark.png new file mode 100644 index 0000000..1228535 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-520-light.png b/.opencode/snapshots/auth-phase10/reset-password-520-light.png new file mode 100644 index 0000000..9de771b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-768-dark.png b/.opencode/snapshots/auth-phase10/reset-password-768-dark.png new file mode 100644 index 0000000..39bc1ce Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/reset-password-768-light.png b/.opencode/snapshots/auth-phase10/reset-password-768-light.png new file mode 100644 index 0000000..9e888d9 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/reset-password-768-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-1280-dark.png b/.opencode/snapshots/auth-phase10/sign-in-1280-dark.png new file mode 100644 index 0000000..0af0875 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-1280-light.png b/.opencode/snapshots/auth-phase10/sign-in-1280-light.png new file mode 100644 index 0000000..07bb403 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-360-dark.png b/.opencode/snapshots/auth-phase10/sign-in-360-dark.png new file mode 100644 index 0000000..c08aec6 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-360-light.png b/.opencode/snapshots/auth-phase10/sign-in-360-light.png new file mode 100644 index 0000000..ca01cba Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-390-dark.png b/.opencode/snapshots/auth-phase10/sign-in-390-dark.png new file mode 100644 index 0000000..d21117b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-390-light.png b/.opencode/snapshots/auth-phase10/sign-in-390-light.png new file mode 100644 index 0000000..7acd27b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-520-dark.png b/.opencode/snapshots/auth-phase10/sign-in-520-dark.png new file mode 100644 index 0000000..c8c8236 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-520-light.png b/.opencode/snapshots/auth-phase10/sign-in-520-light.png new file mode 100644 index 0000000..254a91f Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-768-dark.png b/.opencode/snapshots/auth-phase10/sign-in-768-dark.png new file mode 100644 index 0000000..b27924c Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-in-768-light.png b/.opencode/snapshots/auth-phase10/sign-in-768-light.png new file mode 100644 index 0000000..556c2bf Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-in-768-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-1280-dark.png b/.opencode/snapshots/auth-phase10/sign-up-1280-dark.png new file mode 100644 index 0000000..df7ce8d Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-1280-light.png b/.opencode/snapshots/auth-phase10/sign-up-1280-light.png new file mode 100644 index 0000000..7b94e1d Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-360-dark.png b/.opencode/snapshots/auth-phase10/sign-up-360-dark.png new file mode 100644 index 0000000..ec49281 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-360-light.png b/.opencode/snapshots/auth-phase10/sign-up-360-light.png new file mode 100644 index 0000000..0662a3f Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-390-dark.png b/.opencode/snapshots/auth-phase10/sign-up-390-dark.png new file mode 100644 index 0000000..6973f01 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-390-light.png b/.opencode/snapshots/auth-phase10/sign-up-390-light.png new file mode 100644 index 0000000..02de740 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-520-dark.png b/.opencode/snapshots/auth-phase10/sign-up-520-dark.png new file mode 100644 index 0000000..ee1d6b8 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-520-light.png b/.opencode/snapshots/auth-phase10/sign-up-520-light.png new file mode 100644 index 0000000..4a737ab Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-768-dark.png b/.opencode/snapshots/auth-phase10/sign-up-768-dark.png new file mode 100644 index 0000000..2b1f7a6 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/sign-up-768-light.png b/.opencode/snapshots/auth-phase10/sign-up-768-light.png new file mode 100644 index 0000000..4121686 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/sign-up-768-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-1280-dark.png b/.opencode/snapshots/auth-phase10/verify-device-1280-dark.png new file mode 100644 index 0000000..0af0875 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-1280-light.png b/.opencode/snapshots/auth-phase10/verify-device-1280-light.png new file mode 100644 index 0000000..07bb403 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-360-dark.png b/.opencode/snapshots/auth-phase10/verify-device-360-dark.png new file mode 100644 index 0000000..c08aec6 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-360-light.png b/.opencode/snapshots/auth-phase10/verify-device-360-light.png new file mode 100644 index 0000000..ca01cba Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-390-dark.png b/.opencode/snapshots/auth-phase10/verify-device-390-dark.png new file mode 100644 index 0000000..d21117b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-390-light.png b/.opencode/snapshots/auth-phase10/verify-device-390-light.png new file mode 100644 index 0000000..7acd27b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-520-dark.png b/.opencode/snapshots/auth-phase10/verify-device-520-dark.png new file mode 100644 index 0000000..c8c8236 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-520-light.png b/.opencode/snapshots/auth-phase10/verify-device-520-light.png new file mode 100644 index 0000000..254a91f Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-768-dark.png b/.opencode/snapshots/auth-phase10/verify-device-768-dark.png new file mode 100644 index 0000000..b27924c Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-device-768-light.png b/.opencode/snapshots/auth-phase10/verify-device-768-light.png new file mode 100644 index 0000000..556c2bf Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-device-768-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-1280-dark.png b/.opencode/snapshots/auth-phase10/verify-email-1280-dark.png new file mode 100644 index 0000000..0af0875 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-1280-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-1280-light.png b/.opencode/snapshots/auth-phase10/verify-email-1280-light.png new file mode 100644 index 0000000..07bb403 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-1280-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-360-dark.png b/.opencode/snapshots/auth-phase10/verify-email-360-dark.png new file mode 100644 index 0000000..c08aec6 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-360-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-360-light.png b/.opencode/snapshots/auth-phase10/verify-email-360-light.png new file mode 100644 index 0000000..ca01cba Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-360-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-390-dark.png b/.opencode/snapshots/auth-phase10/verify-email-390-dark.png new file mode 100644 index 0000000..d21117b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-390-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-390-light.png b/.opencode/snapshots/auth-phase10/verify-email-390-light.png new file mode 100644 index 0000000..7acd27b Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-390-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-520-dark.png b/.opencode/snapshots/auth-phase10/verify-email-520-dark.png new file mode 100644 index 0000000..c8c8236 Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-520-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-520-light.png b/.opencode/snapshots/auth-phase10/verify-email-520-light.png new file mode 100644 index 0000000..254a91f Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-520-light.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-768-dark.png b/.opencode/snapshots/auth-phase10/verify-email-768-dark.png new file mode 100644 index 0000000..b27924c Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-768-dark.png differ diff --git a/.opencode/snapshots/auth-phase10/verify-email-768-light.png b/.opencode/snapshots/auth-phase10/verify-email-768-light.png new file mode 100644 index 0000000..556c2bf Binary files /dev/null and b/.opencode/snapshots/auth-phase10/verify-email-768-light.png differ diff --git a/AGENTS.md b/AGENTS.md index 7d4a768..fb6929e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,7 @@ OpenCode does not automatically expand file references from this document. Befor - Backend, API, worker, realtime, validation, or tenancy work: read `docs/agents/backend.md`. - Playwright, end-to-end, route-flow, or auth-flow testing work: read `docs/agents/e2e.md`. For e2e work that needs the full gateway (api + app + site + worker + realtime) running, follow the "Full-Server E2E Playbook" section there before changing tests or hooks. - Tailwind, UI polish, visual design, accessibility, or design-token work: read `docs/agents/design-system.md`. +- Authoring a Themis UI prototype (HTML + Tailwind v4, run in the local preview server) without touching Angular: use the `themis-ui-prototype` opencode skill. - Feature planning, implementation slicing, PR boundaries, or multi-agent handoff work: read `docs/agents/workflow.md`. - Code review requests: read `docs/agents/review.md`. diff --git a/apps/web/ui-designer/eslint.config.mjs b/apps/web/ui-designer/eslint.config.mjs new file mode 100644 index 0000000..de7b5ea --- /dev/null +++ b/apps/web/ui-designer/eslint.config.mjs @@ -0,0 +1,3 @@ +import baseConfig from '../../../eslint.config.mjs'; + +export default [...baseConfig]; diff --git a/apps/web/ui-designer/project.json b/apps/web/ui-designer/project.json new file mode 100644 index 0000000..564a2c2 --- /dev/null +++ b/apps/web/ui-designer/project.json @@ -0,0 +1,107 @@ +{ + "name": "ui-designer", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/web/ui-designer/src", + "projectType": "application", + "tags": ["scope:client", "type:tooling"], + "targets": { + "lint": { + "executor": "@nx/eslint:lint" + }, + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "dependsOn": [ + "^build", + { + "projects": ["app", "site"], + "target": "build", + "params": "forward" + } + ], + "options": { + "platform": "node", + "outputPath": "dist/apps/web/ui-designer", + "format": ["esm"], + "bundle": true, + "main": "apps/web/ui-designer/src/main.ts", + "tsConfig": "apps/web/ui-designer/tsconfig.app.json", + "assets": ["apps/web/ui-designer/src/preview/chrome.html", "apps/web/ui-designer/src/prototypes"], + "generatePackageJson": true, + "esbuildOptions": { + "sourcemap": true, + "outExtension": { + ".js": ".js" + } + } + }, + "configurations": { + "development": {}, + "production": { + "esbuildOptions": { + "sourcemap": false, + "outExtension": { + ".js": ".js" + } + } + } + } + }, + "prune-lockfile": { + "dependsOn": ["build"], + "cache": true, + "executor": "@nx/js:prune-lockfile", + "outputs": [ + "{workspaceRoot}/dist/apps/web/ui-designer/package.json", + "{workspaceRoot}/dist/apps/web/ui-designer/pnpm-lock.yaml" + ], + "options": { + "buildTarget": "build" + } + }, + "copy-workspace-modules": { + "dependsOn": ["build"], + "cache": true, + "outputs": ["{workspaceRoot}/dist/apps/web/ui-designer/workspace_modules"], + "executor": "@nx/js:copy-workspace-modules", + "options": { + "buildTarget": "build" + } + }, + "prune": { + "dependsOn": ["prune-lockfile", "copy-workspace-modules"], + "executor": "nx:noop" + }, + "build-css": { + "dependsOn": ["build"], + "executor": "nx:run-commands", + "options": { + "command": "mkdir -p dist/apps/web/ui-designer/public/prototypes dist/apps/web/ui-designer/public/preview && cp apps/web/ui-designer/src/preview/chrome.html dist/apps/web/ui-designer/public/preview/chrome.html && cp apps/web/ui-designer/src/prototypes/*.html dist/apps/web/ui-designer/public/prototypes/ && pnpm exec tailwindcss -i apps/web/ui-designer/src/preview/preview.css -o dist/apps/web/ui-designer/public/tailwind.css" + }, + "outputs": [ + "{workspaceRoot}/dist/apps/web/ui-designer/public/tailwind.css", + "{workspaceRoot}/dist/apps/web/ui-designer/public/prototypes", + "{workspaceRoot}/dist/apps/web/ui-designer/public/preview" + ] + }, + "serve": { + "continuous": true, + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "dependsOn": ["build", "build-css"], + "options": { + "buildTarget": "ui-designer:build", + "runBuildTargetDependencies": false + }, + "configurations": { + "development": { + "buildTarget": "ui-designer:build:development" + }, + "production": { + "buildTarget": "ui-designer:build:production" + } + } + } + } +} diff --git a/apps/web/ui-designer/src/main.ts b/apps/web/ui-designer/src/main.ts new file mode 100644 index 0000000..28b2c5b --- /dev/null +++ b/apps/web/ui-designer/src/main.ts @@ -0,0 +1,11 @@ +import { createServer } from './server'; + +const host = process.env['HOST'] ?? 'localhost'; + +const port = process.env['PORT'] ? Number(process.env['PORT']) : 4300; + +createServer().then((app) => { + app.listen(port, host, () => { + console.log(`[ ui-designer ] http://${host}:${port}`); + }); +}); diff --git a/apps/web/ui-designer/src/preview/chrome.html b/apps/web/ui-designer/src/preview/chrome.html new file mode 100644 index 0000000..e5f66be --- /dev/null +++ b/apps/web/ui-designer/src/preview/chrome.html @@ -0,0 +1,46 @@ + + + + + + Preview Chrome Source + + +
+
+
+ ← Index + slug +
+
+
+ + + +
+ +
+
+
+
+ +
+ + diff --git a/apps/web/ui-designer/src/preview/preview.css b/apps/web/ui-designer/src/preview/preview.css new file mode 100644 index 0000000..c2f1097 --- /dev/null +++ b/apps/web/ui-designer/src/preview/preview.css @@ -0,0 +1,69 @@ +@import 'tailwindcss'; + +@import '../../../../../styles.base.css'; + +@source '../prototypes/*.html'; + +@custom-variant dark (&:where(.dark, .dark *)); + +html, +body { + min-height: 100%; +} + +button, +input, +textarea { + font: inherit; +} + +input, +textarea, +select { + color: inherit; + background-color: transparent; +} + +a { + color: inherit; +} + +.preview-frame { + display: flex; + justify-content: center; + padding: 16px; + min-height: calc(100vh - 50px); +} + +#preview-iframe { + max-width: 100%; + border: 1px solid color-mix(in srgb, currentColor 8%, transparent); + border-radius: 8px; + background: #fff; +} + +.viewport-btn[data-active='true'] { + background-color: var(--color-blue-600, #2563eb); + color: #fff; +} + +html.dark .viewport-btn[data-active='true'] { + background-color: var(--color-blue-500, #3b82f6); +} + +html.dark body { + background: #09090b; + color: #fafafa; +} + +iframe#preview-iframe { + width: 1280px; +} + +iframe#preview-iframe[data-viewport='mobile'] { + width: 375px; +} + +iframe#preview-iframe[data-viewport='tablet'] { + width: 768px; +} diff --git a/apps/web/ui-designer/src/prototypes/app-auth-shell.html b/apps/web/ui-designer/src/prototypes/app-auth-shell.html new file mode 100644 index 0000000..367e607 --- /dev/null +++ b/apps/web/ui-designer/src/prototypes/app-auth-shell.html @@ -0,0 +1,85 @@ + + + + + + App Auth Shell + + + +
+ Themis +
+ + +
+
+
+
+
+

+ Account access +

+

+ Sign in +

+

+ Welcome back. Use your work email to access your Themis workspace. +

+
+ +
+
+ + +
+ +
+ + +
+ + +
+ +

New to Themis?

+ Create an account +
+
+ + diff --git a/apps/web/ui-designer/src/routes/health.ts b/apps/web/ui-designer/src/routes/health.ts new file mode 100644 index 0000000..c540b90 --- /dev/null +++ b/apps/web/ui-designer/src/routes/health.ts @@ -0,0 +1,11 @@ +import { Router } from 'express'; + +export function healthRouter(): Router { + const router = Router(); + + router.get('/healthz', (_req, res) => { + res.json({ ok: true }); + }); + + return router; +} diff --git a/apps/web/ui-designer/src/routes/index.ts b/apps/web/ui-designer/src/routes/index.ts new file mode 100644 index 0000000..122e8e2 --- /dev/null +++ b/apps/web/ui-designer/src/routes/index.ts @@ -0,0 +1,109 @@ +import { copyFile, mkdir, readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import { Router } from 'express'; + +const prototypesSourceDir = join(process.cwd(), 'apps/web/ui-designer/src/prototypes'); + +const publicDir = join(process.cwd(), 'dist/apps/web/ui-designer/public'); + +const prototypesPublicDir = join(publicDir, 'prototypes'); + +type PrototypeSummary = { + slug: string; + title: string; +}; + +export async function ensurePrototypesSynced(): Promise { + await mkdir(prototypesPublicDir, { recursive: true }); + + const entries = await readdir(prototypesSourceDir).catch(() => [] as string[]); + + await Promise.all( + entries + .filter((entry) => entry.endsWith('.html')) + .map(async (entry) => { + await copyFile(join(prototypesSourceDir, entry), join(prototypesPublicDir, entry)); + }), + ); +} + +export async function listPrototypes(): Promise { + await ensurePrototypesSynced(); + + const entries = await readdir(prototypesPublicDir).catch(() => [] as string[]); + + return entries + .filter((entry) => entry.endsWith('.html')) + .map((file) => { + const slug = file.replace(/\.html$/, ''); + + return { + slug, + title: slug + .split('-') + .map((word) => (word ? word[0]?.toUpperCase() + word.slice(1) : '')) + .join(' '), + }; + }); +} + +export async function loadPrototype(slug: string): Promise { + if (!isSafeSlug(slug)) { + return undefined; + } + + await ensurePrototypesSynced(); + + try { + return await readFile(join(prototypesPublicDir, `${slug}.html`), 'utf8'); + } catch { + return undefined; + } +} + +function isSafeSlug(slug: string): boolean { + return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug); +} + +export function indexRouter(): Router { + const router = Router(); + + router.get('/', async (_req, res) => { + const prototypes = await listPrototypes(); + + const cards = prototypes + .map( + (p) => ` +
  • + ${p.title} +

    ${p.slug}

    +
  • `, + ) + .join(''); + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(` + + + + + Themis UI Designer + + + +
    +

    Themis UI Designer

    +

    Pick a prototype to preview.

    +
    +
    +
      + ${cards || '
    • No prototypes yet. Add an HTML file under apps/web/ui-designer/src/prototypes/.
    • '} +
    +
    + +`); + }); + + return router; +} diff --git a/apps/web/ui-designer/src/routes/manifest.ts b/apps/web/ui-designer/src/routes/manifest.ts new file mode 100644 index 0000000..9bb54d7 --- /dev/null +++ b/apps/web/ui-designer/src/routes/manifest.ts @@ -0,0 +1,15 @@ +import { Router } from 'express'; + +import { listPrototypes } from './index'; + +export function manifestRouter(): Router { + const router = Router(); + + router.get('/prototypes', async (_req, res) => { + const prototypes = await listPrototypes(); + + res.json({ prototypes }); + }); + + return router; +} diff --git a/apps/web/ui-designer/src/routes/preview.ts b/apps/web/ui-designer/src/routes/preview.ts new file mode 100644 index 0000000..4296728 --- /dev/null +++ b/apps/web/ui-designer/src/routes/preview.ts @@ -0,0 +1,118 @@ +import { Router } from 'express'; + +import { loadPrototype } from './index'; + +export function previewRouter(): Router { + const router = Router(); + + router.get('/preview/:slug', async (req, res) => { + const slug = String(req.params['slug'] ?? ''); + + const prototypeHtml = await loadPrototype(slug); + + if (!prototypeHtml) { + res.status(404).send(`Prototype "${slug}" not found.`); + + return; + } + + const viewport = String(req.query['viewport'] ?? 'desktop'); + + if (viewport !== 'mobile' && viewport !== 'tablet' && viewport !== 'desktop') { + res.status(400).send(`Invalid viewport "${viewport}". Use mobile, tablet, or desktop.`); + + return; + } + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(renderChrome(slug, viewport)); + }); + + router.get('/preview/:slug/frame', async (req, res) => { + const slug = String(req.params['slug'] ?? ''); + + const prototypeHtml = await loadPrototype(slug); + + if (!prototypeHtml) { + res.status(404).send(`Prototype "${slug}" not found.`); + + return; + } + + const theme = String(req.query['theme'] ?? 'light') === 'dark' ? 'dark' : 'light'; + + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(prototypeHtml.replace(' + + + + + ${slug} — Preview + + + +
    +
    +
    + ← Index + ${slug} +
    +
    +
    + + + +
    + +
    +
    +
    +
    + +
    + + +`; +} + +function chromeScript(slug: string, initialViewport: string): string { + return ` + const iframe = document.getElementById('preview-iframe'); + const themeButton = document.getElementById('theme-toggle'); + const viewportButtons = document.querySelectorAll('[data-viewport]'); + + const setTheme = (theme) => { + iframe.src = '/preview/${slug}/frame?theme=' + theme; + themeButton.querySelector('[data-theme-label="light"]').hidden = theme === 'dark'; + themeButton.querySelector('[data-theme-label="dark"]').hidden = theme !== 'dark'; + }; + + const setViewport = (viewport) => { + const url = new URL(window.location.href); + url.searchParams.set('viewport', viewport); + window.location.search = url.searchParams.toString(); + }; + + themeButton.addEventListener('click', () => { + const current = new URL(iframe.src).searchParams.get('theme') ?? 'light'; + setTheme(current === 'dark' ? 'light' : 'dark'); + }); + + viewportButtons.forEach((button) => { + if (button.dataset.viewport === '${initialViewport}') { + button.dataset.active = 'true'; + } + button.addEventListener('click', () => setViewport(button.dataset.viewport)); + }); + `; +} diff --git a/apps/web/ui-designer/src/server.ts b/apps/web/ui-designer/src/server.ts new file mode 100644 index 0000000..eafe371 --- /dev/null +++ b/apps/web/ui-designer/src/server.ts @@ -0,0 +1,30 @@ +import { join } from 'node:path'; + +import express, { type Express } from 'express'; + +import { healthRouter } from './routes/health'; +import { indexRouter } from './routes/index'; +import { manifestRouter } from './routes/manifest'; +import { previewRouter } from './routes/preview'; + +const publicDir = join(process.cwd(), 'dist/apps/web/ui-designer/public'); + +export async function createServer(): Promise { + const app = express(); + + app.disable('x-powered-by'); + + app.use((_req, res, next) => { + res.setHeader('X-Content-Type-Options', 'nosniff'); + next(); + }); + + app.use('/public', express.static(publicDir, { fallthrough: false })); + + app.use('/', indexRouter()); + app.use('/', previewRouter()); + app.use('/api', manifestRouter()); + app.use('/', healthRouter()); + + return app; +} diff --git a/apps/web/ui-designer/tsconfig.app.json b/apps/web/ui-designer/tsconfig.app.json new file mode 100644 index 0000000..0fe1e10 --- /dev/null +++ b/apps/web/ui-designer/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "esnext", + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.spec.ts", "**/*.test.ts"] +} diff --git a/apps/web/ui-designer/tsconfig.json b/apps/web/ui-designer/tsconfig.json new file mode 100644 index 0000000..92de93e --- /dev/null +++ b/apps/web/ui-designer/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/docs/agents/design-system.md b/docs/agents/design-system.md index 81f6edd..fd7cc02 100644 --- a/docs/agents/design-system.md +++ b/docs/agents/design-system.md @@ -2,6 +2,11 @@ These instructions apply to Tailwind, visual design, UI polish, accessibility, and Themis design-token work. +## Prototyping + +- For new screens that are not yet ready for the Angular app, use the [`themis-ui-prototype`](../../.opencode/skills/themis-ui-prototype/) opencode skill to compose a prototype in `apps/web/ui-designer/src/prototypes/.html` and preview it at `http://localhost:4300/preview/`. +- The preview app shares `styles.base.css` with the Angular app and the Astro site. A change in the token file reflects in all three at the next build. + ## Tailwind And CSS - Tailwind is the primary styling tool. Use utility classes directly in templates. diff --git a/docs/constitution/roadmap.md b/docs/constitution/roadmap.md index cb276c1..1f56485 100644 --- a/docs/constitution/roadmap.md +++ b/docs/constitution/roadmap.md @@ -66,7 +66,7 @@ Refactor the Angular app to author server-compatible components per the [Angular ## Design System Alignment (Catalyst Purity) -The Catalyst Angular foundation shipped with a Material 3 token base and a Catalyst-style alias layer. The user prefers the pure Catalyst visual language; this phase retires the Material 3 layer and exposes only the Catalyst semantic token set (`bg`, `panel`, `panel-raised`, `fg`, `muted-fg`, `accent`, `danger`, `ring`, `border`). The Themis brand color becomes Tailwind `blue-600`. Components adopt Catalyst visual patterns: optical borders via `before/after`, `--btn-bg` / `--btn-border` / `--btn-icon` custom properties, and `data-*` state selectors. The Open Design package at `~/.od/projects/ds-themis-is-a-developer-native-design-system/` is realigned in lockstep. +The Catalyst Angular foundation shipped with a Material 3 token base and a Catalyst-style alias layer. The user prefers the pure Catalyst visual language; this phase retires the Material 3 layer and exposes only the Catalyst semantic token set (`bg`, `panel`, `panel-raised`, `fg`, `muted-fg`, `accent`, `danger`, `ring`, `border`). The Themis brand color becomes Tailwind `blue-600`. Components adopt Catalyst visual patterns: optical borders via `before/after`, `--btn-bg` / `--btn-border` / `--btn-icon` custom properties, and `data-*` state selectors. - See spec: [`docs/specs/2026-06-23-catalyst-pure-tokens-alignment/`](./specs/2026-06-23-catalyst-pure-tokens-alignment/) - Branch: `feat/OC/catalyst-pure-tokens-alignment` @@ -74,14 +74,13 @@ The Catalyst Angular foundation shipped with a Material 3 token base and a Catal Slice plan: -- [ ] Phase 0: external package realignment (`colors_and_type.css`, `DESIGN.md`, `ui_kits/app/`, `preview/`). - [ ] PR1: token foundation in `styles.base.css` + `docs/design-system/tokens.md` + `components.md` + `recipes.md`. - [ ] PR2: `shared/ui` components adopt the new tokens and Catalyst `data-*` patterns. -## Auth Flow Fidelity Pass +## UI Designer App -Bring the five auth route families (sign-in, sign-up, recover-password, verify-email, verify-device) to a 1:1 visual match with the Open Design prototypes in `resources/open-design/themis-app/`, and ship the deferred password reset flow. Migrate auth forms from Reactive Forms to Signal Forms, port the password strength meter from `~/Projects/GitHub/visomi-dev/.legacy/nive-web-app-old`, and lock the visual contract with Playwright snapshots + AXE. +Replace the vendored Open Design prototypes and the inherited design-system skill with a first-party Node + Tailwind v4 preview application. The app reuses `styles.base.css`, serves a local preview server with light/dark + mobile/tablet/desktop viewports, ships one seed prototype that mirrors the auth shell recipe, and is paired with a `themis-ui-prototype` opencode skill that drives the workflow. The cleanup drops `resources/open-design/themis-app/`, `.opencode/skills/themis-design-system/`, and the historical specs that referenced them. Two upstream open-design skills (`impeccable-design-polish`, `login-flow`) fill the gap left by the deleted skill; the Themis brand already lives in `docs/design-system/tokens.md` and `DESIGN.md`, so no brand skill is vendored. Node is the only runtime target — Bun is intentionally out of scope to keep the workspace runtime uniform. -- See spec: [`docs/specs/2026-06-23-themis-auth-fidelity-pass/`](./specs/2026-06-23-themis-auth-fidelity-pass/) -- Branch: `feat/OC/themis-auth-fidelity-pass` -- Version target: `1.2.0` +- See spec: [`docs/specs/2026-06-26-ui-designer-app/`](./specs/2026-06-26-ui-designer-app/) +- Branch: `feat/OC/ui-designer-app` +- Version target: `1.4.0` diff --git a/docs/specs/2026-06-22-catalyst-angular-ui-foundation/requirements.md b/docs/specs/2026-06-22-catalyst-angular-ui-foundation/requirements.md index fbdd2e7..408c76e 100644 --- a/docs/specs/2026-06-22-catalyst-angular-ui-foundation/requirements.md +++ b/docs/specs/2026-06-22-catalyst-angular-ui-foundation/requirements.md @@ -27,10 +27,9 @@ Project status: ## Non-Goals 1. Do not redesign Themis routes in this spec. -2. Do not implement the `resources/open-design` prototypes in this spec. -3. Do not remove every PrimeNG dependency yet; removal happens after migrated routes no longer need it. -4. Do not create a public package or move components into `libs/ui` in the first iteration. -5. Do not port Catalyst literally when an Angular-native API is simpler. +2. Do not remove every PrimeNG dependency yet; removal happens after migrated routes no longer need it. +3. Do not create a public package or move components into `libs/ui` in the first iteration. +4. Do not port Catalyst literally when an Angular-native API is simpler. ## Required Components diff --git a/docs/specs/2026-06-22-themis-web-app-redesign/plan.md b/docs/specs/2026-06-22-themis-web-app-redesign/plan.md deleted file mode 100644 index 31815ff..0000000 --- a/docs/specs/2026-06-22-themis-web-app-redesign/plan.md +++ /dev/null @@ -1,131 +0,0 @@ -# Themis Web App Redesign — Implementation Plan - -## Phase 0 — Design Audit And Mapping - -1. Review every artifact in `resources/open-design/themis-app`. -2. Extract visual decisions from `critique.json`. -3. Map Open Design files to Themis routes and identify missing route references. -4. Inventory current route-level PrimeNG imports and `.p-*` styling dependencies. -5. Confirm the UI foundation primitives needed by each route family. - -## Phase 1 — Auth Routes - -1. Redesign sign-in using `sign-in.html` and shared auth flow references. -2. Redesign sign-up using `sign-up.html`. -3. Redesign forgotten-password using `recover-password.html`. -4. Redesign verify-email and verify-device using `confirm-account.html` intent. -5. Replace PrimeNG form controls/messages with `shared/ui/forms` primitives. -6. Preserve labels, headings, button names, and auth guard behavior. - -## Phase 2 — Password Reset Coverage - -1. Map `reset-password.html` and `themis-reset.html` to current or planned routes. -2. Implement the UI only for routes that already exist or are explicitly added. -3. Keep backend/API additions out of scope unless required by existing product requirements. - -## Phase 3 — App Shell - -1. Redesign authenticated shell using `index.html` as the visual direction. -2. Replace existing sidebar/topbar markup with `shared/ui/layout` primitives. -3. Validate desktop sidebar, mobile menu, backdrop, escape close, and close-on-navigation. -4. Preserve `hideAppShell` route data behavior. - -## Phase 4 — Activation And Dashboard - -1. Migrate activation screens to the new layout and form primitives. -2. Redesign dashboard panels with `shared/ui/data`, `shared/ui/typography`, and `shared/ui/layout` primitives. -3. Add empty/loading/error states using the new feedback primitives. - -## Phase 5 — Projects - -1. Redesign projects overview using new table/card/list primitives. -2. Redesign project creation form. -3. Redesign project detail surfaces. -4. Preserve existing project services, realtime updates, and navigation. - -## Phase 6 — PrimeNG Cleanup - -1. Search for remaining `primeng/*` imports. -2. Search for remaining `primeicons` usage. -3. Search for remaining `.p-*` CSS overrides. -4. Remove PrimeNG-specific global styles. -5. Remove `tailwindcss-primeui`, `primeng`, and `primeicons` after all imports are gone. - -## Phase 7 — Documentation And Regression Notes - -1. Document final route-to-Open-Design mapping. -2. Record deviations from static Open Design references and why they were necessary. -3. Update `docs/design-system/recipes.md` with real Themis route examples. -4. Update e2e support docs if selectors or visible copy changed. - -## Nx Verification Commands - -```bash -pnpm nx lint app -pnpm nx test app -pnpm nx e2e app-e2e -pnpm nx build app -``` - -If a target is missing or changes, inspect it first with: - -```bash -pnpm nx show project app --json -pnpm nx show project app-e2e --json -``` - ---- - -## Implementation Outcome - -All seven phases were executed against the actual repository state. The app source no longer references PrimeNG, PrimeIcons, or `tailwindcss-primeui`. Verified with `rg "primeng|primeicons|tailwindcss-primeui|pi pi-|\.p-" apps/web/app/src` returning no matches. - -### Final Route-To-Open-Design Mapping - -| Open Design File | Themis Surface | Notes | -| --------------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `sign-in.html` | `apps/web/app/src/app/auth/sign-in` | Two-column auth layout with brand column on the left and form card on the right. | -| `sign-up.html` | `apps/web/app/src/app/auth/sign-up` | Same layout. Replaces `p-password` with `app-password-input`. | -| `recover-password.html` | `apps/web/app/src/app/auth/forgotten-password` | Single-column form card. Success and error states use `app-alert`. | -| `confirm-account.html` | `apps/web/app/src/app/auth/verify-email`, `verify-device`, `auth/verification-code-form` | `app-pin-input` replaces `p-inputOtp`. | -| `themis-auth-flow.html` | `app-auth-layout` primitive | Brand column and form column composition. | -| `themis-reset.html` | Not implemented | No password reset route exists yet. Documented as deferred. | -| `index.html` | `shared/layout/layout`, `sidebar-menu`, `topbar`, `dashboard` | Layout shell uses `app-icon` and `app-avatar`. | -| `critique.json` | Source of truth for tone | The single accent is `accent`, the brand mark is the in-house `app-logo`. | -| `.open-design/project.json` | Project metadata only | No app code change. | - -### Deviations From Open Design - -- **Sidebar collapse animation.** Open Design shows a smooth width transition; the legacy sidebar already animates via `transition-all duration-200`. Kept the legacy behavior for now. -- **Password strength meter.** Open Design's `sign-up.html` and `reset-password.html` include a multi-tier strength indicator. The current `app-password-input` exposes the same data through `password-input.html` description slot, but a dedicated strength component is out of scope here. The `` next to the password field mirrors the helper copy. -- **Launcher grid.** Open Design's `index.html` shows a launcher grid for auth. The Themis app routes directly to `/app/sign-in` via the guard flow, so no launcher is rendered. -- **PrimeIcons classes.** The Open Design references `primeicons` for inline glyphs. We replaced them with `app-icon` SVG primitives backed by a small `icon-paths.ts` set in `shared/ui/media/icon`. -- **Theme tokens.** Open Design uses `--tm-*` variables inline. The Themis app already has a token layer in `apps/web/app/src/styles.css` and `apps/web/site/src/styles/global.css`; we keep the existing tokens to avoid changing the theme contract. - -### PrimeNG Cleanup Outcome - -- Removed `providePrimeNG` and the `ThemisPreset` Aura mapping from `app.config.ts` and deleted `app.theme.ts`. -- Removed `@import 'tailwindcss-primeui';` and `@import 'primeicons/primeicons.css' layer(utilities);` from `apps/web/app/src/styles.css`, plus every `.p-*` override. -- Verified `rg "primeng|primeicons|tailwindcss-primeui|pi pi-|\.p-" apps/web/app/src` returns zero matches. -- Removed `primeicons/primeicons.css` from `apps/web/site/src/styles/global.css`, then removed root dependencies `primeng`, `primeicons`, `tailwindcss-primeui`, `@primeuix/themes`, and `@primeng/mcp` from `package.json` and `pnpm-lock.yaml`. - -### Validation Run - -```text -pnpm nx lint app → ✔ All files pass linting -pnpm nx vite:test app → 19 files, 35 tests passed -pnpm nx build app → built dist/apps/web/app (browser + server bundles) -pnpm nx extract-i18n app → 121 messages extracted; source xliff updated -pnpm nx e2e app-e2e → not run in this session (requires live backend, see below) -``` - -The production build reports a soft budget warning (`initial` bundle 602.62 kB against a 500 kB budget). The warning is pre-existing in scale; it is not introduced by this migration. The bundle delta from removing PrimeNG is net-negative because we no longer load `primeng/*` and the Aura theme. - -### Notes For Future Cleanup - -- The pnpm workspace had pre-existing peer resolution issues between `drizzle-orm`, `@electric-sql/pglite`, and `pg`. The final fix aligns `@electric-sql/pglite`, `drizzle-orm`, `pg`, and `ioredis` across the root package and internal libraries, removes the temporary `@ts-nocheck` comments, and removes the temporary `skipLibCheck` setting from `libs/projects/tsconfig.lib.json`. -- The e2e suite was updated only for the OTP helper (`fillOtp` now targets `[data-slot=pin-input] input` instead of the PrimeNG `.p-inputotp-input`). The rest of the e2e suite was not executed in this session because it requires a live backend and mailbox. Re-run `pnpm nx e2e app-e2e` against the standard dev stack before merging. - -### Final Files Touched - -See `plan/feature-web-app-redesign-1.md` for the structured file and task list, and the recipes in `docs/design-system/recipes.md` for runnable UI examples. diff --git a/docs/specs/2026-06-22-themis-web-app-redesign/requirements.md b/docs/specs/2026-06-22-themis-web-app-redesign/requirements.md deleted file mode 100644 index 295f6fa..0000000 --- a/docs/specs/2026-06-22-themis-web-app-redesign/requirements.md +++ /dev/null @@ -1,72 +0,0 @@ -# Themis Web App Redesign — Requirements - -## Context - -After the Catalyst Angular UI Foundation exists in `apps/web/app/src/app/shared/ui`, Themis Web App should be fully refactored to use the new design system and the Open Design prototypes stored in `resources/open-design/themis-app`. - -This spec depends on `docs/specs/2026-06-22-catalyst-angular-ui-foundation`. - -Open Design references: - -- `resources/open-design/themis-app/index.html` -- `resources/open-design/themis-app/sign-in.html` -- `resources/open-design/themis-app/sign-up.html` -- `resources/open-design/themis-app/recover-password.html` -- `resources/open-design/themis-app/reset-password.html` -- `resources/open-design/themis-app/confirm-account.html` -- `resources/open-design/themis-app/themis-auth-flow.html` -- `resources/open-design/themis-app/themis-reset.html` -- `resources/open-design/themis-app/critique.json` -- `resources/open-design/themis-app/.open-design/project.json` - -## Goals - -1. Refactor the full Themis Web App UI to the new design system. -2. Use `resources/open-design/themis-app` as the visual source for the redesigned app surfaces. -3. Remove PrimeNG usage from migrated routes and eliminate PrimeNG dependencies once no imports remain. -4. Preserve existing auth, activation, projects, dashboard, realtime, routing, guards, and SSR behavior. -5. Improve visual cohesion, mobile behavior, accessibility, and perceived performance. -6. Keep all source copy, docs, tests, and comments in English. - -## Non-Goals - -1. Do not redesign the public Astro website in this spec unless explicitly added later. -2. Do not change backend contracts unless a frontend migration exposes a confirmed API gap. -3. Do not rebuild route behavior in components when guards/resolvers/services already own it. -4. Do not introduce new visual dependencies that bypass the UI foundation. - -## Target Routes And Surfaces - -| Area | Routes/Surfaces | -| ---------- | -------------------------------------------------------------- | -| Auth | sign-in, sign-up, verify-email, verify-device, password flows | -| Activation | first-run activation | -| Shell | authenticated app layout, sidebar, topbar, mobile navigation | -| Dashboard | authenticated landing route | -| Projects | projects overview, project creation, project detail | -| Shared | loading states, empty states, form errors, alerts, account nav | - -## Functional Requirements - -- Replace route-level PrimeNG components with `shared/ui` primitives. -- Keep route guards and route data behavior intact. -- Keep all critical route data loading in resolvers or existing shared services where applicable. -- Use signals for async UI state and short-lived feedback. -- Use Signal Forms for redesigned forms when practical; retain typed Reactive Forms only when safer for incremental migration. -- Preserve i18n conventions for Angular templates and `$localize` strings. - -## Visual Requirements - -- Match the intent of the Open Design prototypes, not necessarily every static HTML detail. -- Use the new token layer and UI primitives rather than ad hoc Tailwind blocks. -- Preserve desktop and mobile behavior. -- Use tonal surfaces instead of heavy borders where the design system calls for it. -- Maintain dark mode and first-render theme stability. - -## Accessibility Requirements - -- Migrated routes must pass AXE checks. -- Auth forms must keep explicit labels and stable button names for tests. -- Mobile navigation must support keyboard operation and escape close behavior. -- Error messages must be associated with fields. -- Focus order must be predictable across forms, shell navigation, and dialogs. diff --git a/docs/specs/2026-06-22-themis-web-app-redesign/sdd.md b/docs/specs/2026-06-22-themis-web-app-redesign/sdd.md deleted file mode 100644 index aa7f338..0000000 --- a/docs/specs/2026-06-22-themis-web-app-redesign/sdd.md +++ /dev/null @@ -1,85 +0,0 @@ -# Themis Web App Redesign — Software Design Document - -## Decision - -The app redesign will be a second, dependent effort after the UI foundation is in place. It will consume `apps/web/app/src/app/shared/ui` primitives and use `resources/open-design/themis-app` as the visual reference set for route-level implementation. - -The redesign should avoid route-specific UI hacks. If a screen needs a reusable primitive, the primitive belongs in the UI foundation first or in a clearly named shared route-level component. - -## Dependency - -This spec starts after the UI foundation provides at least: - -- Actions: button/link/icon button. -- Typography: heading/text/divider. -- Forms: field/input/select/checkbox/radio/switch/error primitives. -- Layout: auth layout, app shell, sidebar, topbar, container, page header. -- Data: badge/avatar/table/description list. -- Feedback: alert and loading/empty-state patterns. - -## Open Design Mapping - -| Open Design File | Themis Surface | -| --------------------------- | ----------------------------------------------- | -| `sign-in.html` | `apps/web/app/src/app/auth/sign-in` | -| `sign-up.html` | `apps/web/app/src/app/auth/sign-up` | -| `recover-password.html` | `apps/web/app/src/app/auth/forgotten-password` | -| `reset-password.html` | Password reset route when implemented/available | -| `confirm-account.html` | Email/device verification surfaces | -| `themis-auth-flow.html` | Shared auth flow composition | -| `themis-reset.html` | Password recovery/reset composition | -| `index.html` | Authenticated shell/dashboard direction | -| `critique.json` | Design review guidance and refinements | -| `.open-design/project.json` | Open Design project metadata | - -## Refactor Strategy - -1. Keep behavior stable first, then change visuals. -2. Migrate one route family at a time. -3. Replace PrimeNG imports as each route is migrated. -4. Move duplicated route chrome into `shared/ui` or `shared/layout` only when it is reusable. -5. Run route-specific unit/e2e checks after each route family. - -## Route Architecture - -- Keep lazy route components with `loadComponent`. -- Keep auth and activation checks in guards, not components. -- Keep critical route data in resolvers or existing services. -- Keep SSR/browser-specific logic in existing browser/server abstractions. -- Keep async view state as signals. - -## Forms Strategy - -- Auth and password forms should use the new `shared/ui/forms` primitives. -- Prefer Signal Forms for newly rewritten flows when practical. -- Retain typed Reactive Forms only when it keeps migration risk low. -- Validation messages must remain centralized in component helpers or shared form helpers. -- Tests must keep stable labels and button names. - -## PrimeNG Removal Strategy - -PrimeNG removal happens incrementally: - -1. Remove PrimeNG from auth routes. -2. Remove PrimeNG from activation routes. -3. Remove PrimeNG from app shell/dashboard/projects. -4. Remove `.p-*` overrides from `apps/web/app/src/styles.css`. -5. Remove `tailwindcss-primeui`, `primeng`, and `primeicons` after imports are gone. - -## Risks - -| Risk | Mitigation | -| ----------------------------------------------- | ----------------------------------------------------------------- | -| Open Design static HTML conflicts with app flow | Preserve Themis behavior and map the visual intent, not markup | -| Route migration breaks auth tests | Migrate auth first with stable labels, headings, and button names | -| PrimeNG removal is too broad | Remove per route family and verify after each step | -| UI foundation lacks a needed primitive | Add the primitive to `shared/ui` before route-specific hacks | -| SSR regressions | Keep browser-only code inside existing platform abstractions | - -## Success Criteria - -- All Web App route families use the new design system. -- Open Design references are reflected in the app's auth and shell experience. -- PrimeNG and PrimeIcons can be removed from dependencies. -- App e2e tests pass for auth, activation, dashboard, projects, and theme behavior. -- The refactor keeps existing backend/API behavior intact. diff --git a/docs/specs/2026-06-22-themis-web-app-redesign/validation.md b/docs/specs/2026-06-22-themis-web-app-redesign/validation.md deleted file mode 100644 index 37d0fa2..0000000 --- a/docs/specs/2026-06-22-themis-web-app-redesign/validation.md +++ /dev/null @@ -1,83 +0,0 @@ -# Themis Web App Redesign — Validation Plan - -## Status - -Completed on 2026-06-23. All ten route families have been migrated to the `shared/ui` foundation, PrimeNG has been removed from `apps/web/app/src` and the workspace dependencies, the redesign was deployed successfully, and the e2e suite is green in the integration environment. - -## Validation Run - -```text -pnpm nx run app:lint → ✔ All files pass linting -pnpm nx run app:vite:test → 19 files, 35 tests passed -pnpm nx run app:build --skip-nx-cache → built dist/apps/web/app (browser + server bundles) -pnpm nx extract-i18n app → 121 messages extracted; source xliff updated -pnpm nx e2e app-e2e → passed in the integration environment (requires live backend + mailbox; not re-executed in this validation run) -``` - -The production build reports a soft budget warning (`initial` bundle 603.03 kB against a 500 kB budget). The warning is pre-existing in scale and is not introduced by this migration; the bundle delta from removing PrimeNG is net-negative because `primeng/*` and the Aura theme are no longer loaded. - -## Static Validation - -1. `pnpm nx lint app` — passed with no warnings introduced by the migration. -2. `pnpm nx test app` — 19 spec files / 35 tests pass (`pnpm nx run app:vite:test`). -3. `pnpm nx build app` — production build succeeds; SSR bundle and browser bundle are emitted; `withHttpTransferCacheOptions` filter and platform abstractions remain intact. -4. `pnpm nx e2e app-e2e` — auth, activation, projects, and theme suites pass in the integration environment. The OTP helper was updated from the PrimeNG `.p-inputotp-input` selector to `[data-slot=pin-input] input`; the rest of the suite was not re-executed in this validation run because it requires a live backend and mailbox. - -## Route Validation - -Routes migrated to the new design system (verified against `apps/web/app/src/app/app.routes.ts`): - -- `/app/sign-in` — `apps/web/app/src/app/auth/sign-in` -- `/app/sign-up` — `apps/web/app/src/app/auth/sign-up` -- `/app/verify-email` — `apps/web/app/src/app/auth/verify-email` (consumes `verification-code-form`) -- `/app/verify-device` — `apps/web/app/src/app/auth/verify-device` (consumes `verification-code-form`) -- `/app/forgotten-password` — `apps/web/app/src/app/auth/forgotten-password` -- `/app/activation` — `apps/web/app/src/app/activation` -- `/app/` — `apps/web/app/src/app/dashboard` (via `DASHBOARD_PATH`, redirects from `APP_PATH`) -- `/app/projects` — `apps/web/app/src/app/projects` -- `/app/projects/new` — `apps/web/app/src/app/projects/project-new` -- `/app/projects/:projectId` — `apps/web/app/src/app/projects/project-detail` - -`hideAppShell` route data continues to hide the shell on the five auth routes via the `Layout` signal in `shared/layout`. - -## Accessibility Validation - -- AXE checks pass on migrated route families. — verified in the integration environment; label, `aria-describedby`, and `aria-invalid` wiring is preserved across form primitives. -- Auth forms keep explicit labels. — `sign-in`, `sign-up`, `forgotten-password`, and the verification forms still use `app-label` with stable `for`/`id` pairs. -- Error messages are associated with fields. — `app-error-message` is rendered inside `app-field` and is referenced via `aria-describedby`. -- Mobile navigation is keyboard accessible. — `app-sidebar` mobile menu opens via the topbar trigger, closes on escape, click-outside, and route navigation; focus is restored on close. -- Dialog/dropdown interactions support escape and focus restoration. — `app-dialog` uses Angular CDK focus trap with escape and focus restoration; `app-dropdown` closes on escape and outside click. -- Focus order remains predictable after visual refactors. — order follows DOM order; no `tabindex` overrides were introduced. - -## Visual Validation - -Compare migrated screens against `resources/open-design/themis-app` references (visual pass done in the integration environment): - -- Mobile: 360px and 390px. — covered. -- Tablet: 768px. — covered. -- Desktop: 1280px and 1440px. — covered. -- Light mode. — covered. -- Dark mode. — covered. -- First render before theme hydration. — verified; the persisted theme applies after hydration and the initial HTML stays at the `light` default. - -## PrimeNG Removal Checklist - -- No remaining `primeng/*` imports. — verified by `rg "primeng" apps/web/app/src` returning zero matches. -- No remaining `primeicons` usage. — verified by `rg "primeicons|pi pi-" apps/web/app/src` returning zero matches. -- No remaining `.p-*` global overrides. — verified by `rg "\.p-" apps/web/app/src` returning zero matches. -- `tailwindcss-primeui` removed when safe. — removed from `package.json` and `pnpm-lock.yaml`; `@import 'tailwindcss-primeui';` removed from `apps/web/app/src/styles.css`. -- `primeng` and `primeicons` removed when safe. — removed from `package.json` and `pnpm-lock.yaml` along with `@primeuix/themes` and `@primeng/mcp`. `providePrimeNG` and `ThemisPreset` (Aura) were removed from `app.config.ts`; `app.theme.ts` was deleted. `primeicons/primeicons.css` was removed from `apps/web/site/src/styles/global.css`. - -## Regression Checklist - -- Auth flows still sign in, sign up, verify, and handle errors. — covered by the e2e suite (`auth/sign-in`, `auth/sign-up`, `auth/verify-email`, `auth/forgotten-password`). -- Activation flow still protects app access. — `authenticatedGuard` + `activatedGuard` are preserved; `activation.spec.ts` still passes. -- Projects list, create, and detail flows still work. — `projects.spec.ts` and `project-detail.spec.ts` still pass; realtime subscriptions and `ProjectsApi` integration are unchanged. -- Theme behavior remains stable across auth and app routes. — `theme.spec.ts` passes; `dark` class toggles post-hydration via `AppThemeInit` mounted in `app.html`. -- SSR build succeeds after browser-only logic remains guarded. — `pnpm nx run app:build --skip-nx-cache` succeeds; `app:build` produces both browser and server bundles; the `withHttpTransferCacheOptions` filter and platform abstractions remain intact. - -## Notes For Future Cleanup - -- The pnpm workspace had pre-existing peer resolution issues between `drizzle-orm`, `@electric-sql/pglite`, and `pg`. The final fix aligns `@electric-sql/pglite`, `drizzle-orm`, `pg`, and `ioredis` across the root package and internal libraries, removes the temporary `@ts-nocheck` comments, and removes the temporary `skipLibCheck` setting from `libs/projects/tsconfig.lib.json`. -- The `pnpm nx e2e app-e2e` target was not re-executed in this validation run because it requires a live backend and mailbox; the suite was confirmed green in the integration environment during the deploy pass and re-run before merge. The OTP helper was updated to target `[data-slot=pin-input] input` instead of the PrimeNG `.p-inputotp-input` selector. -- The initial bundle soft budget warning (`initial` 603.03 kB against a 500 kB budget) is pre-existing in scale and is not introduced by this migration. Tracking a follow-up to split large feature modules is out of scope for this spec. diff --git a/docs/specs/2026-06-23-themis-auth-fidelity-pass/plan.md b/docs/specs/2026-06-23-themis-auth-fidelity-pass/plan.md deleted file mode 100644 index 7a81eee..0000000 --- a/docs/specs/2026-06-23-themis-auth-fidelity-pass/plan.md +++ /dev/null @@ -1,331 +0,0 @@ -# Themis Auth Fidelity Pass — Implementation Plan - -The work is sliced into ten phases. Each phase ends with a verification command. Phases 1–3 cover shared primitives and the auth shell; phase 4 fixes the backend OTP reset contract; phases 5–8 cover the per-route rewrites; phase 9 covers e2e visual assertions and snapshots; phase 10 is the mobile-first polish and visual quality pass derived from the `web-design-reviewer` and `premium-frontend-ui` skills. No phase combines infrastructure, product behavior, broad refactors, and e2e coverage in a single PR — the slices below are independently shippable. - -The branch is `feat/OC/themis-auth-fidelity-pass`. Each phase lands as one PR (or stacked PRs) on top of `main`. - -## Phase 0 — Audit And Selector Inventory - -1. Diff each Open Design prototype against the current Angular route, listing every visual element that diverges from the prototype. -2. Re-run `rg "primeng|primeicons|tailwindcss-primeui|pi pi-|\.p-" apps/web/app/src` to confirm there are still no PrimeNG references after the previous spec. -3. Audit route templates and component CSS for semantic/BEM-style prototype classes copied into Angular code. Replace them with Tailwind utilities and `data-slot` / `data-od-id` hooks unless they are exceptional selectors inside reusable primitives. -4. Capture the current selector surface used by `apps/web/app-e2e/src/auth/*.spec.ts` and lock it as the migration contract (see Stable Selectors table in `requirements.md`). -5. Inventory which routes consume `app-auth-layout` (only the five auth routes) to confirm the rewrite scope. - -Suggested checks: - -```bash -rg 'class="[a-z][a-z0-9-]*(__|--)|auth-controls|auth-card__|password-strength__' apps/web/app/src -rg '^\.[a-z][a-z0-9-]*(__|--)|\.auth-|\.password-strength' apps/web/app/src --glob '*.css' -``` - -Exit criteria: audit document committed under `docs/specs/2026-06-23-themis-auth-fidelity-pass/audit.md` (out of band — does not block subsequent phases). - -## Phase 1 — Auth Shell, Brand Mark, Lang Switcher, Theme Toggle - -1. Add the balance-scale icon to `shared/ui/media/icon` (SVG paths from `sign-in.html:680-694`). Export as `icon-paths.logo-mark`. -2. Extend `shared/ui/layout/logo/logo.ts` with `variant: 'mark' | 'wordmark' | 'mark-name'` and add the new SVG path for the mark. -3. Rewrite `shared/ui/layout/auth-layout/auth-layout.html` to the sticky-header + centered-card structure (`auth-shell` + `auth-main` + ``). -4. Add `shared/ui/layout/auth-card/` with selector `app-auth-card`, max-width 440px, padding 40/28, surface + subtle border + shadow-sm. -5. Add `shared/ui/layout/lang-switcher/` (selector `app-lang-switcher`) with `
    ` markup matching the prototype. -6. Extend `shared/ui/layout/theme-switcher` with `variant="toggle"` (sun/moon icon button) + persistence of `localStorage["tm-theme"]` + early `data-theme` application to avoid FOUC. -7. Style the chrome in `apps/web/app/src/styles.css` (tokens already exist; the chrome only consumes them). Confirm dark-mode parity. -8. Add `data-od-id` attributes on every chrome element (`auth-shell`, `brand`, `lang-menu`, `theme-toggle`, `auth-card`) so e2e can assert presence. - -Verification: - -```bash -pnpm nx run shared-ui:lint -pnpm nx run app:lint -pnpm nx run app:vite:test -pnpm nx run app:build -``` - -Expected: lint + unit tests pass, build emits browser + SSR bundle, no FOUC on the auth routes. - -## Phase 2 — Password Strength Primitive - -1. Search for an existing migrated password requirements validator/component in Themis. Reuse it if present. -2. If no migrated implementation exists, port `~/Projects/GitHub/visomi-dev/.legacy/nive-web-app-old/src/app/components/auth/password-requirements/password-requirements.component.ts` to `shared/ui/forms/password-strength/password-strength.ts`. -3. Translate the copy to English and align the visual with the Open Design prototype while preserving the 8+ character threshold used by backend `passwordSchema` and the Nive legacy implementation. -4. Replace the legacy checklist visual with the four-bar meter (`data-level="0..4"`) while keeping an accessible requirements list where the Open Design screen calls for it. -5. Export a `computePasswordStrength(value: string): { level: 0|1|2|3|4; label: string }` helper from `shared/ui/forms/password-strength` so it can be unit-tested without rendering. -6. Add unit specs under `shared/ui/forms/password-strength/password-strength.spec.ts` with a table of sample passwords and expected levels. - -Verification: - -```bash -pnpm nx run shared-ui:lint -pnpm nx run shared-ui:test -pnpm nx run app:vite:test -``` - -## Phase 3 — Field Chrome Adjustments - -1. Update `shared/ui/forms/label/label.ts` so the default tone renders mono uppercase tracking-wider text-xs font-semibold muted-fg. The existing plain tone stays as `tone="plain"`. -2. Update `shared/ui/forms/password-input/password-input.ts` so the suffix button defaults to text "Show" / "Hide" with mono uppercase styling. `variant="icon"` retains the eye/eye-off icon. Stable aria-labels: `"Show password"` / `"Hide password"`. -3. Update `shared/ui/forms/error-message/error-message.ts` to render an inline error icon (lucide `circle-alert`) next to the message text, matching the prototype field chrome. -4. Extend `shared/ui/overlays/alert/alert.ts` with `variant="auth"` (soft container + leading icon + `role="alert"`). - -Verification: - -```bash -pnpm nx run shared-ui:lint -pnpm nx run shared-ui:test -pnpm nx run app:vite:test -pnpm nx run app:build -``` - -## Phase 4 — Backend OTP Reset Contracts - -1. Extend `apps/web/api/src/auth/auth-schemas.ts` so `challengeSchema.purpose` supports `password_reset` in addition to `sign_in` and `sign_up`. -2. Update `apps/web/api/src/auth/auth-service.ts` so `requestPasswordReset(email)` creates `createChallenge(user, 'password_reset')`, not `createChallenge(user, 'sign_in')`. -3. Update `apps/web/api/src/auth/auth-mail.ts` so `password_reset` emails use reset-specific copy. -4. Add `POST /auth/password/reset/verify` to consume `{ challengeId, pin }`, verify a `password_reset` challenge, and establish a short-lived server-owned reset session. -5. Add `POST /auth/password/reset` to require the reset session, update the password hash, invalidate the reset session, and return success. -6. Add `GET /auth/password/reset/session` if the frontend needs to validate the reset session for `resetSessionGuard`. -7. Update OpenAPI schemas, test mailbox schemas, and e2e mailbox helpers to accept `password_reset` purpose. -8. Add API tests for request, verify, reset submit, expired challenge, consumed challenge, wrong purpose, and missing reset session. - -Verification: - -```bash -pnpm nx run api:lint -pnpm nx run api:test -pnpm nx run app-e2e:lint -``` - -## Phase 5 — Verify Reset And Reset Password Routes - -1. Add `RESET_PASSWORD_PATH` and `RESET_PASSWORD_URL` constants to `apps/web/app/src/app/shared/constants/routes.ts`. There is no separate verify-reset route — `/app/reset-password` is a single-screen OTP + password flow. -2. Register `/app/reset-password` in `app.routes.ts` with `canActivate: [anonymousGuard, resetSessionGuard]`, `data: { hideAppShell: true }`, and `loadComponent`. -3. Create `apps/web/app/src/app/auth/reset-password/reset-password.{ts,html,css,spec.ts}`. -4. Update `Auth.requestPasswordReset(email)` so it stores the returned `AuthChallenge` and navigates from `forgotten-password` to `/app/reset-password`. -5. Add `Auth.submitPasswordResetVerification(pin)` to call `POST /auth/password/reset/verify`. On success, the route reveals the password step without navigation. -6. Add `Auth.resetPassword({ password })` to call `POST /auth/password/reset` using the active reset session. -7. Add `resetSessionGuard` to redirect missing/expired reset sessions to `/app/forgotten-password`. -8. Migrate the OTP step and the password step to Signal Forms. OTP step uses `app-pin-input`; password step uses new password + confirm new password. -9. Wire `app-password-strength` under the new password field (revealed only after OTP verification). -10. Implement the success state transition (form → success card) with title "Password updated", copy "You're all set. Sign in with your new password to continue.", and "Sign in to continue" CTA. -11. Add the e2e spec `reset-password.spec.ts` covering OTP validation, password validation, strength meter levels, success state, cancel link, and resend cooldown. - -Verification: - -```bash -pnpm nx run app:lint -pnpm nx run app:vite:test -pnpm nx run app:build -pnpm nx e2e app-e2e -- --grep "reset-password" -``` - -## Phase 6 — Sign-In And Sign-Up Rewrites - -1. Migrate `apps/web/app/src/app/auth/sign-in/sign-in.{ts,html,css,spec.ts}` to Signal Forms. -2. Rewrite the template to use `app-auth-layout` + `app-auth-card` + Open Design copy. -3. Replace `app-password-input` with `variant="text"` so the suffix is text. -4. Apply the prototype copy: kicker `Account access`, title `Sign in`, sub `Welcome back. Use your work email to access your Themis workspace.`, button `Sign in`, footer link `Create an account`, forgot-password link `Forgot password?`, remember-device label `Remember this device`. -5. Repeat for `sign-up`. Add the `Confirm password` field with its own `app-password-input` (text variant). Add `app-password-strength` under the password field. Apply the prototype copy verbatim (kicker `New account`, title `Create your account`, helper `Use 8+ characters. Mix uppercase, lowercase, numbers, and symbols for the strongest result.`). -6. Update the existing e2e specs to assert the Open Design chrome (`data-od-id="auth-shell"`, `auth-card`, `lang-menu`, `theme-toggle`) and the verbatim copy. - -Verification: - -```bash -pnpm nx run app:lint -pnpm nx run app:vite:test -pnpm nx run app:build -pnpm nx e2e app-e2e -- --grep "sign-in|sign-up" -``` - -## Phase 7 — Forgotten Password And Verification Routes - -1. Migrate `apps/web/app/src/app/auth/forgotten-password/{ts,html,css,spec.ts}` to Signal Forms. -2. Apply the Open Design copy: kicker `Account recovery`, title `Recover password`, sub `Enter your work email and we'll send you a recovery link. The link expires in 30 minutes.`, button `Send recovery link`. -3. Implement the success state inside the same card: `We sent a recovery link to . Open it on this device to choose a new password.` plus `Back to sign in`. -4. Migrate `verify-email` and `verify-device` to the Open Design chrome. -5. Apply the Open Design copy: - - verify-email: kicker `Email verification`, title `Verify email`, sub `Enter the 6-digit code we sent to .`, button `Verify and continue`. - - verify-device: kicker `Device verification`, title `Verify device`, sub `Enter the 6-digit code we sent to .`, button `Verify and continue`. -6. Drop the duplicated email mention in `verify-email.html` (per `critique.json` self-critique). -7. Add `verify-device.spec.ts` if missing. -8. Update `forgotten-password.spec.ts` so success navigates/links into the reset OTP flow (`/app/reset-password`) instead of implying a token link. -9. Update `forgotten-password.spec.ts` and `verify-email.spec.ts` to assert the Open Design chrome and copy. - -Verification: - -```bash -pnpm nx run app:lint -pnpm nx run app:vite:test -pnpm nx run app:build -pnpm nx e2e app-e2e -- --grep "forgotten-password|verify-email|verify-device" -``` - -## Phase 8 — i18n Extraction And Source-Text Sweep - -1. Run `pnpm nx extract-i18n app` and confirm the xliff updates include every new label (titles, kickers, sub-text, helper text, alerts, success messages, strength meter labels, link labels, button labels). -2. Grep every auth route for raw copy and confirm `$localize` (or `i18n` attribute) wrapping. -3. Update `docs/design-system/recipes.md` with the auth-shell, auth-card, password-strength, and reset-password recipes. - -Verification: - -```bash -pnpm nx extract-i18n app -pnpm nx run app:lint -pnpm nx run app:vite:test -``` - -## Phase 9 — E2E Visual Assertions And Snapshots - -1. Add a Playwright helper `assertOpenDesignChrome(page)` in `apps/web/app-e2e/src/support/auth-layout.ts` that asserts: - - `[data-od-id="auth-shell"]` is visible. - - `[data-od-id="brand"]` has the `Themis` text and the `logo-mark` SVG. - - `[data-od-id="lang-menu"]` summary is visible and opens on click. - - `[data-od-id="theme-toggle"]` button is visible and labeled `Toggle light/dark theme`. - - `[data-od-id="auth-card"]` is visible with the route-specific kicker and title text. -2. Add `assertOpenDesignCopy(page, routeKey)` to assert every piece of prototype copy is present on the page. -3. Add visual snapshots at 360px, 768px, and 1280px in light and dark mode for each route. Land snapshots under `apps/web/app-e2e/src/auth/__screenshots__/` with descriptive filenames. -4. Wire AXE checks (`@axe-core/playwright`) into every existing auth spec plus the new reset-password spec. -5. Run the full e2e suite end-to-end: - -```bash -pnpm nx e2e app-e2e -``` - -6. Re-run with `--update-snapshots` only if the diff is intentional (audit the diff in the PR description). - -Verification: - -```bash -pnpm nx e2e app-e2e -pnpm nx run-many --target lint --projects app,app-e2e,shared-ui -pnpm nx run-many --target test --projects app,app-e2e,shared-ui -pnpm nx run app:build --skip-nx-cache -``` - -Expected: all auth route suites pass (sign-in, sign-up, forgotten-password, verify-email, verify-device, reset-password), all visual snapshots match, AXE reports zero violations, lint and unit tests pass. - -## Nx Verification Commands (consolidated) - -```bash -pnpm nx lint app -pnpm nx lint shared-ui -pnpm nx lint app-e2e -pnpm nx test app -pnpm nx test shared-ui -pnpm nx build app -pnpm nx extract-i18n app -pnpm nx e2e app-e2e -``` - -If a target is missing or changes, inspect it first with: - -```bash -pnpm nx show project app --json -pnpm nx show project app-e2e --json -pnpm nx show project shared-ui --json -``` - -## Phase 10 — Mobile-First Polish And Visual Quality Pass - -This phase operationalizes the `.opencode/skills/web-design-reviewer` and `.opencode/skills/premium-frontend-ui` skills inside the auth fidelity pass scope, since those skills cannot be executed from open-design. It does not introduce new visual primitives; it tightens the existing ones against a mobile-first viewport matrix and a premium-UI checklist. - -### 10.1 — Viewport Matrix - -For every auth route (`sign-in`, `sign-up`, `forgotten-password`, `verify-email`, `verify-device`, `reset-password`), capture screenshots at: - -| Name | Width | Height | Notes | -| ------- | ------ | ------ | ---------------------------------- | -| Mobile | 360px | 720px | Tightest target (Android baseline) | -| Mobile | 390px | 844px | iPhone 13/14 baseline | -| Mobile | 520px | 720px | Tailwind `sm:` breakpoint boundary | -| Tablet | 768px | 1024px | Tailwind `md:` breakpoint boundary | -| Desktop | 1280px | 800px | Tailwind `lg:` breakpoint boundary | -| Wide | 1920px | 1080px | Optional sanity check | - -Snapshots land under `apps/web/app-e2e/src/auth/__screenshots__/` with the naming pattern `--.png`. Only `360`, `390`, `520`, `768`, and `1280` are committed; `1920` is on-demand. - -### 10.2 — Issue Matrix (web-design-reviewer) - -Each issue is logged in the PR description using the skill's template: - -| Severity | Class | Examples | -| -------- | ------------------ | -------------------------------------------------------------------------- | -| P1 | Functional layout | Element overflow, viewport overflow, content unreachable on mobile | -| P1 | Touch targets | Buttons or rows < 44px on touch devices | -| P1 | Focus state | Missing or invisible `:focus-visible` ring on any interactive element | -| P2 | Responsive | Layout breaks between breakpoints, awkward horizontal scroll, awkward wrap | -| P2 | Visual consistency | Mixed font families, off-token colors, inconsistent spacing scale | -| P2 | Accessibility | Insufficient contrast, missing labels, missing `aria-describedby` wiring | -| P3 | Polish | Inconsistent radius, drift from token scale, micro-spacing issues | - -### 10.3 — Premium UI Checklist (premium-frontend-ui) - -For each route, run through these checks: - -1. **Typography rhythm**: title size uses `clamp()` or token scale; body copy keeps a readable rhythm (≥ 16px on mobile, 0.9375rem is acceptable here). -2. **Spacing scale**: padding/margin adhere to the 8/16/24/40 grid; no off-scale values. -3. **Motion**: entrance transitions for kicker/title/sub use `transform`/`opacity` only; respect `prefers-reduced-motion`. -4. **Focus visible**: every interactive element has a visible `:focus-visible` ring driven by the `--color-ring` token. -5. **Touch targets**: every clickable element is at least 44×44px on mobile (use `ui-touch-target` utility where available). -6. **Surface hierarchy**: tonal surfaces over heavy borders; avoid stacking more than two surfaces on a single screen. -7. **Optical balance**: card content is centered with `mx-auto`, has consistent vertical rhythm, and does not hug the viewport edges (padding stays at `px-4` minimum on mobile). -8. **Dark mode parity**: every color used has an explicit dark-mode value through the Catalyst token layer. -9. **Glass/depth**: avoid ad-hoc `backdrop-filter` blur; if used, it lives in `app-auth-layout` chrome only and is gated behind `@media (prefers-reduced-motion: no-preference)`. - -### 10.4 — Implementation Slices - -To stay within the "small vertical slice" rule from `AGENTS.md`, Phase 10 lands as **one PR per P1 cluster**, not one mega PR: - -1. **PR10.1 — Touch targets + focus rings audit.** Walk every interactive element on the five routes. Fix any < 44px touch target and any missing `:focus-visible` ring. Snapshot diff at 360/390/520 light + dark. -2. **PR10.2 — Mobile-first layout fixes.** Overflow, wrap, viewport-edge padding, breakpoint awkwardness at 360→520→768. Snapshot diff at the same viewports. -3. **PR10.3 — Visual consistency sweep.** Token drift (off-token colors), spacing scale drift, font drift. Snapshot diff at 768/1280. -4. **PR10.4 — Dark mode parity.** Audit every route in dark mode; fix any element without an explicit dark token. Snapshot diff at 360/768 dark. -5. **PR10.5 — Polish pass.** Radius/spacing/optical balance. Snapshot diff at 1280 light. - -Each PR runs the full e2e suite plus the relevant Nx lint/test/build targets before merge. - -### 10.5 — Workflow - -Because the skills cannot be executed directly in open-design, this spec follows an iterative loop until zero P1 issues remain: - -1. Boot the gateway locally: `pnpm nx run server:build:production && node dist/apps/web/server/main.js &`. -2. For each route, capture screenshots at the viewport matrix using Playwright (already wired into `apps/web/app-e2e/src/auth/__screenshots__/`). -3. Inspect each screenshot against the issue matrix and premium checklist. -4. Fix one cluster of P1 issues at a time at the source (`shared/ui`, `app-auth-layout`, `app-auth-card`, or route templates). -5. Re-capture and compare with the previous baseline. -6. When no P1 issues remain, mark the PR ready for review. P2/P3 issues are tracked separately and may ship in the polish PR10.5. - -### 10.6 — Exit Criteria - -- Zero P1 issues at 360/390/520/768/1280px in light and dark mode. -- All five routes have updated snapshots checked into `apps/web/app-e2e/src/auth/__screenshots__/`. -- AXE checks pass at 360px and 1280px for every route. -- `pnpm nx run app:build --skip-nx-cache` passes. -- Bundle delta is within the existing budget (no new primitives introduced). - -Verification commands per PR (rotate depending on the slice): - -```bash -pnpm nx lint app -pnpm nx lint shared-ui -pnpm nx run app:vite:test -pnpm nx run shared-ui:test -pnpm nx run app:build --skip-nx-cache -pnpm nx e2e app-e2e -``` - -## Definition Of Done - -- All ten phases merged. -- The five auth route families render 1:1 with the Open Design prototypes in light and dark mode at 360/768/1280px. -- The new `app-password-strength` primitive has unit + visual coverage. -- The new OTP reset route sequence (`/app/forgotten-password` → `/app/reset-password` single-screen OTP + password) is mounted, validated, and transitions to the success state. -- Every e2e spec in `apps/web/app-e2e/src/auth/` passes (existing + new). -- Visual snapshots and AXE checks pass. -- `pnpm nx run app:lint`, `pnpm nx run app:vite:test`, `pnpm nx run app:build`, and `pnpm nx extract-i18n app` all green. -- `apps/web/app/version.json` bumped. - -## Out Of Scope For This Spec - -- Token-in-URL reset-password links. -- Runtime locale switching via the language menu (the menu persists the preference only). -- Site (Astro) auth routes. diff --git a/docs/specs/2026-06-23-themis-auth-fidelity-pass/requirements.md b/docs/specs/2026-06-23-themis-auth-fidelity-pass/requirements.md deleted file mode 100644 index 359e01f..0000000 --- a/docs/specs/2026-06-23-themis-auth-fidelity-pass/requirements.md +++ /dev/null @@ -1,189 +0,0 @@ -# Themis Auth Fidelity Pass — Requirements - -## Context - -The previous spec, [`docs/specs/2026-06-22-themis-web-app-redesign/`](../2026-06-22-themis-web-app-redesign/), migrated every auth route family to the `shared/ui` foundation and removed PrimeNG. The migration preserved the existing product copy and the two-column auth layout (brand column + form column). - -A visual review against the Open Design prototypes in `resources/open-design/themis-app/` shows that the current Themis auth screens are a **hybrid** of the old two-column Themis posture and the new single-column centered-card posture. They do not match the Open Design prototypes 1:1. Two pieces of work were deferred in the previous spec: - -1. The Open Design password reset flow (`reset-password.html`) was marked as **not implemented** because no reset route existed. -2. The Open Design prototype layout for the auth shell (sticky header, brand mark, language switcher, theme toggle, single-column card) was not adopted — the current `app-auth-layout` keeps the legacy brand-column + form-column grid. - -This spec is a fidelity pass: every auth surface must reproduce the Open Design prototype 1:1 in structure and copy, while staying inside the existing `shared/ui` primitive contract and Angular route plumbing. - -## Dependencies - -- `docs/specs/2026-06-22-catalyst-angular-ui-foundation/` — provides `shared/ui/forms`, `shared/ui/layout/auth-layout`, `shared/ui/actions/button`, `shared/ui/overlays/alert`, `shared/ui/forms/password-input`, `shared/ui/forms/pin-input`, `shared/ui/data/avatar`, `shared/ui/media/icon`, etc. -- `docs/specs/2026-06-22-themis-web-app-redesign/` — established the route-to-prototype mapping that this spec finishes. -- `docs/specs/2026-06-23-catalyst-pure-tokens-alignment/` — retires Material 3 tokens and exposes the Catalyst token set (`--color-bg`, `--color-panel`, `--color-panel-raised`, `--color-fg`, `--color-muted-fg`, `--color-accent`, `--color-accent-fg`, `--color-danger`, `--color-danger-fg`, `--color-ring`, `--color-border`, `--color-border-subtle`) consumed through Tailwind utilities (`bg-bg`, `text-fg`, `border-border`, etc.). This spec consumes those tokens and must not reintroduce the legacy `--tm-*` palette from the Open Design prototypes. -- `~/Projects/GitHub/visomi-dev/.legacy/nive-web-app-old/src/app/components/auth/password-requirements/` — the source for password requirement logic if it has not already been migrated. - -## Open Design Mapping - -| Open Design File | Themis Surface | Status before this spec | Status after this spec | -| ----------------------- | ---------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sign-in.html` | `apps/web/app/src/app/auth/sign-in` | Hybrid layout, old copy | 1:1 Open Design, copy verbatim | -| `sign-up.html` | `apps/web/app/src/app/auth/sign-up` | Hybrid layout, missing fields | 1:1 Open Design, copy verbatim | -| `recover-password.html` | `apps/web/app/src/app/auth/forgotten-password` | Hybrid layout, old copy | 1:1 Open Design, copy verbatim | -| `confirm-account.html` | `apps/web/app/src/app/auth/verify-email`, `verify-device` | Hybrid layout, old copy | 1:1 Open Design, copy verbatim | -| `sign-in.html` (chrome) | `shared/ui/layout/auth-layout` (sticky header + controls) | Two-column grid (rebranded) | Single-column Open Design shell. The auth chrome is identical across every auth HTML in the package, so any auth HTML can be used as the canonical reference for the shell. | -| `reset-password.html` | New `apps/web/app/src/app/auth/reset-password` (canonical) | Not implemented | Canonical reset screen (single-screen OTP + password), copy verbatim | -| `index.html` | Demonstrative launcher only — no Angular route | n/a | Out of scope. `index.html` is an Open Design artifact used to showcase the five flows. It is not shipped as a Themis route and is not consumed by the app shell. | -| `critique.json` | Visual + copy guidance | Referenced but partial | Heeded for the verify screen subtitle | - -Note on reset variants: `reset-password.html` is the only reset reference shipped in the Open Design package and is authoritative for this spec. Earlier working drafts (`themis-reset.html`, `reset-password-2.html`) are no longer in the package — their artifact metadata was removed in this spec to keep the mapping one-to-one. The current `reset-password.html` already encodes the single-screen OTP + password flow with copy "Reset your password" / "Enter the 6-digit code we sent to your email, then choose a new password." / success title "Password updated" / success copy "You're all set. Sign in with your new password to continue." - -## Goals - -1. Replace every auth screen's visual structure so it matches the Open Design prototype 1:1, including the chrome (sticky header, brand mark, language switcher, theme toggle), the centered single-column card, the field chrome, the show/hide button, and the strength meter. -2. Replace the auth copy with the prototype copy verbatim, wrapped in `$localize` (`i18n`) and surfaced through `pnpm nx extract-i18n app`. -3. Add the password reset flow using OTP verification (`/app/forgotten-password` → `/app/reset-password` single-screen) following `recover-password.html`, `confirm-account.html`, and the canonical `reset-password.html`. -4. Reuse the migrated password requirements validator if it exists. If it does not, port the requirements logic from `~/Projects/GitHub/visomi-dev/.legacy/nive-web-app-old/src/app/components/auth/password-requirements/password-requirements.component.ts` into `shared/ui/forms/password-strength` with English copy and the Open Design 4-bar visual. -5. Migrate auth forms from Reactive Forms to **Signal Forms**, preserving typed field access and stable selectors. -6. Update the e2e suite in `apps/web/app-e2e/src/auth/` so every assertion verifies that the screen matches the Open Design prototype structurally and that the copy matches verbatim. - -## Non-Goals - -1. Do not introduce new visual dependencies. Every visual change uses `shared/ui/*` and the existing Themis token layer (`apps/web/app/src/styles.css`). -2. Do not redesign the public Astro website in this spec. -3. Do not implement token-in-URL password reset. The reset flow must use the same OTP challenge pattern as sign-up and sign-in verification. -4. Do not change the authenticated shell, dashboard, projects, or activation routes. -5. Do not introduce internationalization tooling beyond what `extract-i18n` already provides. - -## Functional Requirements - -### Auth Shell (`shared/ui/layout/auth-layout`) - -- The auth shell renders a sticky top header with three slots: brand slot (left), controls slot (right). The form/content slot fills the centered single-column card area below. -- The brand slot shows the Themis balance-scale mark inside a rounded square (`bg-accent` background, `text-accent-fg` icon) plus the wordmark `Themis` in display font. The whole brand links to `/`. -- The controls slot shows: - - A language switcher (`
    ` / `` with menu items EN/ES/PT-BR/JA/DE/ZH). The visible code is the active language. The menu uses `data-od-id="lang-menu"`. - - A theme toggle button (sun/moon SVG, `aria-label="Toggle light/dark theme"`). -- The auth shell applies the persisted theme on first render (no flash). The theme toggle writes `localStorage["tm-theme"]` and updates the `data-theme` attribute. -- The auth shell accepts the form/card via `` (no `data-slot` requirement). Remove the legacy brand-column slot. -- All chrome elements must be keyboard accessible (Tab order: brand link → language summary → theme toggle → first form control). The language menu opens on Enter/Space, closes on Escape, click-outside, and selection. - -### Auth Card - -- Single column, max width 440px on desktop, full width minus 32px on mobile. -- Card padding: 40px on desktop (≥520px viewport), 28px×22px on mobile (<520px). -- Card surface: `bg-panel` (white in light, dark slate in dark). Subtle 1px border (`border-border-subtle`) and shadow-sm. -- Card structure (top to bottom): kicker label → title → optional sub-text → optional alert → form → footer link. -- The kicker is uppercase mono caps (Inter Mono / JetBrains Mono), 12px, muted fg. -- The title is display font (Manrope), 26px, semibold, letter-spacing −0.025em. -- The sub-text is body font, 15px, muted fg. -- Card footer (when present) is a top divider with the secondary action link. - -### Field Chrome - -- Each field renders: mono uppercase label (`font-mono text-xs font-semibold uppercase tracking-widest text-muted-fg`, 12px) → control → error message (only when invalid). -- Inputs render at min-height 44px, padding 0 14px, `rounded-sm` radius, 1px border `border-border`. Focus: 3px accent soft ring via the `ui-focus-ring` utility. -- Error state: input border becomes `border-danger`, error message appears with the inline error icon (lucide `circle-alert`). -- Password input suffix is a **text** button ("Show" / "Hide") in mono uppercase 11px, not an icon. `aria-label` toggles between `"Show password"` and `"Hide password"`. - -### Auth Alert - -- The auth alert (top-of-card, above the form) renders danger or success. It carries a `role="alert"` and an icon (lucide `circle-alert` for danger, `circle-check` for success). Tone colors use the soft fills (`bg-danger/10 text-danger`, `bg-accent/10 text-accent`) with strong fg. -- The alert must NOT appear below the form. It must always appear above. - -### Forgot Password Success State - -- After a successful `forgotten-password` submit, the form is replaced with a success state inside the same card: "We sent a recovery link to **``**. Open it on this device to choose a new password." plus a "Back to sign in" link. - -### Password Reset OTP Flow (single-screen) - -The Open Design `reset-password.html` collapses the verify-code step and the set-new-password step into one screen. The route renders a single component that owns both steps; it does not split them across `/app/verify-reset` and `/app/reset-password`. - -- `/app/forgotten-password` collects the email and calls `POST /auth/password/forgotten`. -- `POST /auth/password/forgotten` creates a verification challenge with purpose `password_reset` and sends a 6-digit OTP email. It must not create a `sign_in` challenge and must not send a magic-link token. -- After a successful request, the frontend stores the returned `AuthChallenge` in `Auth.pendingChallenge` and navigates to `/app/reset-password`. -- `/app/reset-password` renders the OTP step first. On OTP verification success (via `POST /auth/password/reset/verify`) it reveals the password step in the same card without changing routes. The backend establishes a short-lived reset session after OTP verification (HttpOnly cookie or server session state — never a reset token in the URL or frontend storage). -- The same submit button toggles its label between "Verify code" (OTP step) and "Update password" (password step). -- Resend button with cooldown timer, "Change email" link back to `/app/forgotten-password`, and footer "Back to sign in" link are all visible inside the card on every step. -- After a successful `POST /auth/password/reset`, the form is replaced by a success state inside the same card: title "Password updated", copy "You're all set. Sign in with your new password to continue.", and a "Sign in to continue" link to `/app/sign-in`. -- The reset session is server-owned. If the user reopens `/app/reset-password` without a valid reset session, the route redirects to `/app/forgotten-password`. - -### Reset Password — New Route - -- New route: `/app/reset-password` with `canActivate: [anonymousGuard, resetSessionGuard]` and `data: { hideAppShell: true }`. The component lives in `apps/web/app/src/app/auth/reset-password/`. -- Form steps (single screen, two internal states): - - OTP step: 6 cells using `app-pin-input` directly. Do **not** wrap the OTP step in `app-verification-code-form` — that wrapper emits `(verify)` and `(resend)` outputs and assumes a separate route, which the single-screen flow does not have. The component must own the OTP submit, the resend cooldown, and the password-step reveal itself, and consume `app-pin-input` directly with `[digits]="6"`, `[idPrefix]="'reset-password-otp'"`, and `idPrefix` rendered into the OTP cells. - - Password step: new password (with `app-password-strength` + show/hide text button) and confirm new password (with show/hide text button). -- Email hint card above the form: label "Code sent to", email address, "Change email" link. -- Helper text under the new password field: "Use 8+ characters. Mix uppercase, lowercase, numbers, and symbols for the strongest result." -- Submit button label: "Verify code" while the OTP step is active, "Update password" once the OTP step is verified. -- Cancel link: "Back to sign in" footer link → `/app/sign-in`. -- Success state (replaces the form inside the same card): title "Password updated" + copy "You're all set. Sign in with your new password to continue." + "Sign in to continue" button → `/app/sign-in`. -- The reset session is server-owned. If it is missing, expired, or invalid, the route redirects to `/app/forgotten-password`. If submit fails, the form stays mounted and the alert shows the localized failure copy. - -### Backend Requirements - -- Extend `challengeSchema.purpose` from `sign_in | sign_up` to `sign_in | sign_up | password_reset`. -- Update `VerificationPurpose` consumers, mailbox test schemas, OpenAPI schemas, and e2e mailbox helpers to support `password_reset`. -- Update `requestPasswordReset(email)` so it creates `createChallenge(user, 'password_reset')`, not `createChallenge(user, 'sign_in')`. -- Update `createMessageBody` so `password_reset` emails say the OTP is for resetting the password, not signing in. -- Add `POST /auth/password/reset/verify` accepting `{ challengeId, pin }`, consuming a `password_reset` challenge, and establishing a short-lived reset session. -- Add `POST /auth/password/reset` accepting `{ password }`, requiring the active reset session, updating the password hash, invalidating the reset session, and signing out other sessions/devices where supported. -- Add `GET /auth/password/reset/session` or expose reset-session state through a minimal endpoint if the frontend guard needs to validate session presence before rendering `/app/reset-password`. - -### Password Strength (`shared/ui/forms/password-strength`) - -- New shared primitive. Selector: `app-password-strength`. Inputs: `password: Signal`. Output: `value: Signal<0|1|2|3|4>` and `label: Signal`. -- Visual: 4 horizontal bars + a label. `data-level` attribute `0..4` drives which bars are filled. -- Levels: `0 → "—"`, `1 → "Weak"`, `2 → "Fair"`, `3 → "Strong"`, `4 → "Excellent"`. -- Rules (in order, all four required for level 4): length ≥ 8, contains lowercase, contains uppercase, contains number, contains symbol. This matches the backend `passwordSchema` minimum and the Nive legacy requirements logic. -- A11y: `aria-hidden="true"` on the visual meter (the label sits next to the input and acts as the accessible value via `aria-describedby`). The label updates live as the user types. -- English copy (matches the Open Design prototype). - -## Visual Requirements - -- Every screen must reproduce the Open Design prototype structure 1:1: chrome, card, fields, alerts, footer. -- The current two-column brand-column auth layout is removed from `app-auth-layout`. -- Dark mode must reproduce the Open Design dark palette swap (no light bleed-through). -- Mobile (360/390/520px) follows the Open Design responsive rules: card padding shrinks to 28/22px, OTP cells to 52px, language menu collapses correctly. -- Tailwind utility classes are the default styling mechanism in Angular templates. Semantic CSS classes copied from Open Design prototypes (for example `auth-controls`, `auth-card__title`, or `password-strength__bar`) must not appear in route templates. -- Component `.css` files are allowed only for exceptional cases that Tailwind cannot express cleanly: keyframes, `@starting-style`, reduced-motion overrides, or very small element-level selectors inside a reusable primitive. Any custom selector must be documented in the component comment or spec implementation notes. -- Shared primitives may expose `data-slot` and `data-od-id` attributes for tests; tests should not depend on private BEM-style CSS class names. - -## Accessibility Requirements - -- AXE checks pass on every migrated auth route. -- Stable selectors preserved (see the Stable Selectors section in `plan.md`). -- Error messages wired through `aria-describedby` and `aria-invalid`. -- Theme toggle and language switcher keyboard accessible; language menu closes on Escape and outside click. -- Reduced motion: disable strength meter transitions when `prefers-reduced-motion: reduce`. - -## Stable Selectors (e2e must not break) - -The e2e suite in `apps/web/app-e2e/src/auth/` and the unit specs under `apps/web/app/src/app/auth/` rely on a fixed set of selectors. The redesign must preserve all of them. - -| Surface | Stable selector | Notes | -| ---------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| sign-in / sign-up / reset | `getByLabel('Email')` | `