[story #132] UX: design-system token set, lint rules, responsive breakpoint grid, and UI specs for v1.5
- docs/design/tokens.json: full design-system token set (colors, typography, spacing, border-radius, shadow, breakpoints, z-index, transitions) with WCAG contrast ratios documented per color token - docs/design/lint-rules.md: lint rule specification (DS-C001..DS-C004, DS-T001..DS-T003, DS-S001..DS-S002, DS-R001, DS-SH001, DS-B001..DS-B002, DS-Z001) with severity levels; responsive breakpoint grid spec; accessibility spec (WCAG 2.2 AA, screen reader notes, keyboard interactions) - docs/design/mailbox-owner-dashboard.md: wireframes and UI spec for INB-1.5-A-S3 (#118) including responsive breakpoints, token usage table, and a11y spec - docs/design/ops-console.md: wireframes and UI spec for INB-1.5-B-S3 (#123) including responsive breakpoints, token usage table, and a11y spec Traces to: INTV-5 #109 → MAJOR v1 #99 Stories: #132, #118, #123 Epics: #113, #112, #110 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit was merged in pull request #137.
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# Design-System Lint Rules — INB-1.5-D-S1
|
||||
|
||||
> **Story:** [#132](https://git.cloudinit.dev/jc-capital/inboxos/issues/132)
|
||||
> **Epic:** EPIC-INB-1.5-D [#113](https://git.cloudinit.dev/jc-capital/inboxos/issues/113)
|
||||
> **Traces to:** INTV-5 #109 → MAJOR v1 #99
|
||||
> **Domain:** ux. **Pattern:** B (spec).
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document specifies lint and style-enforcement rules that ensure all v1.5 frontend code uses design-system tokens exclusively — no hardcoded colors, ad-hoc spacing, or out-of-spec typography. The rules are enforced at CI time and block merge on failure (NFR-5).
|
||||
|
||||
---
|
||||
|
||||
## 2. Token source
|
||||
|
||||
All tokens are defined in [`docs/design/tokens.json`](./tokens.json). Frontend code must import tokens from a generated module (e.g., `src/tokens.ts` or `src/tokens.css`) that is produced from `tokens.json` at build time. No token values may be duplicated in source.
|
||||
|
||||
---
|
||||
|
||||
## 3. Lint rules
|
||||
|
||||
### 3.1 Color rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-C001` | No hardcoded hex/rgb/hsl colors in JSX/TSX/CSS. Use `token('color.*')` or the generated CSS variable (`var(--color-*)`). | **error** | Guarantees all colors are token-referenced and WCAG-verified. |
|
||||
| `DS-C002` | No inline `style={{ color: ... }}` or `style={{ backgroundColor: ... }}`. Use token-based utility classes or `token()` function. | **error** | Inline styles bypass the design system. |
|
||||
| `DS-C003` | All `<svg>` fill/stroke must reference CSS custom properties (`var(--color-*)`) or inherit from parent. | **warn** | Prevents orphaned SVG colors. |
|
||||
| `DS-C004` | Opacity modifiers on semantic colors must use the documented pattern (`token('color.semantic.{name}-light')`), not `opacity()` on the base color. | **error** | Maintains WCAG contrast tracking. |
|
||||
|
||||
**Allowed exceptions (with mandatory comment):**
|
||||
- Third-party component library overrides that require hex values. Must include `// DS-EXEMPT: <reason>` and a tracking issue.
|
||||
|
||||
### 3.2 Typography rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-T001` | No hardcoded `font-size`, `line-height`, `font-weight`, or `letter-spacing` values in JSX/TSX/CSS. Use `token('typography.*')` or CSS variables. | **error** | Ensures typographic consistency and responsive scaling. |
|
||||
| `DS-T002` | All text must use the defined font-family stack (`token('typography.font-family.sans')` or `token('typography.font-family.mono')`). No custom font-family declarations. | **error** | Prevents font drift. |
|
||||
| `DS-T003` | Monospace font stack is restricted to data tables, log viewers, and code blocks. | **warn** | Monospace in navigation or headings degrades readability. |
|
||||
|
||||
### 3.3 Spacing rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-S001` | No hardcoded pixel/rem values for `margin`, `padding`, `gap` outside the token set. Use `token('spacing.*')`. | **error** | Enforces the 4px grid. |
|
||||
| `DS-S002` | All layout gaps must use the spacing token scale. No arbitrary values (e.g., `gap: 7px`). | **error** | Maintains visual rhythm. |
|
||||
|
||||
### 3.4 Border-radius rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-R001` | No hardcoded `border-radius` values. Use `token('border-radius.*')`. | **error** | Ensures consistent corner rounding. |
|
||||
|
||||
### 3.5 Shadow rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-SH001` | No hardcoded `box-shadow` values. Use `token('shadow.*')`. | **error** | Centralizes elevation hierarchy. |
|
||||
|
||||
### 3.6 Breakpoint rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-B001` | No hardcoded `@media` breakpoint pixel values. Use `token('breakpoint.*')` (or the generated mixin). | **error** | All responsive breakpoints must come from the token set. |
|
||||
| `DS-B002` | Mobile-first: write base styles for `sm`, then `@media (min-width: token('breakpoint.md'))` for progressive enhancement. | **warn** | Ensures correct responsive cascade. |
|
||||
|
||||
### 3.7 Z-index rules
|
||||
|
||||
| Rule ID | Rule | Severity | Rationale |
|
||||
|---|---|---|---|
|
||||
| `DS-Z001` | No hardcoded `z-index` values. Use `token('z-index.*')`. | **error** | Prevents z-index escalation. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Responsive breakpoint grid
|
||||
|
||||
### 4.1 Breakpoint definitions
|
||||
|
||||
| Token | Value | Layout behavior |
|
||||
|---|---|---|
|
||||
| `breakpoint.sm` | 640px | Single column. Bottom sheet navigation. Collapsed sidebar. |
|
||||
| `breakpoint.md` | 768px | Two-column layout. Sidebar as drawer. Email list + preview side-by-side. |
|
||||
| `breakpoint.lg` | 1024px | Three-column layout. Persistent sidebar. Full email list + detail + sidebar. |
|
||||
| `breakpoint.xl` | 1280px | Full desktop. Extended content width (max-width 1200px centered). Admin console two-panel layout. |
|
||||
| `breakpoint.2xl` | 1536px | Large desktop. Extended margins. Metrics dashboard shows all panels. |
|
||||
|
||||
### 4.2 Grid specification
|
||||
|
||||
| Breakpoint | Columns | Gutter | Margin | Max content width |
|
||||
|---|---|---|---|---|
|
||||
| `< sm` (mobile) | 4 | 16px (`spacing.4`) | 16px (`spacing.4`) | 100% |
|
||||
| `sm` – `md` | 8 | 16px (`spacing.4`) | 16px (`spacing.4`) | 608px |
|
||||
| `md` – `lg` | 12 | 24px (`spacing.6`) | 24px (`spacing.6`) | 720px |
|
||||
| `lg` – `xl` | 12 | 24px (`spacing.6`) | 32px (`spacing.8`) | 960px |
|
||||
| `xl` – `2xl` | 12 | 24px (`spacing.6`) | 32px (`spacing.8`) | 1200px |
|
||||
| `≥ 2xl` | 12 | 24px (`spacing.6`) | 32px (`spacing.8`) | 1200px centered |
|
||||
|
||||
### 4.3 Layout patterns
|
||||
|
||||
**Mailbox Owner Dashboard (Epic A):**
|
||||
- `< md`: Single-column email list. Detail view replaces list (push navigation).
|
||||
- `md` – `lg`: Two-column (list + detail). Sidebar drawer (tap to open).
|
||||
- `≥ lg`: Three-column (sidebar + list + detail). Sidebar persistent.
|
||||
|
||||
**Admin/Operator Ops Console (Epic B):**
|
||||
- `< md`: Single-column. Tab navigation for metrics / logs / config sections.
|
||||
- `md` – `lg`: Two-column. Metrics left, logs right. Config in modal.
|
||||
- `≥ lg`: Three-panel layout. Metrics dashboard (2/3) + sidebar (1/3). Config in slide-over.
|
||||
|
||||
**Design-System Compliance (Epic D — this epic):**
|
||||
- Responsive behavior is identical to the host view (A or B). The design-system tokens themselves are viewport-agnostic; responsive behavior is defined by layout patterns above.
|
||||
|
||||
---
|
||||
|
||||
## 5. Accessibility specification
|
||||
|
||||
### 5.1 WCAG conformance target
|
||||
|
||||
**WCAG 2.2 Level AA** for all v1.5 views (NFR-4: Lighthouse accessibility ≥ 90).
|
||||
|
||||
### 5.2 Color contrast
|
||||
|
||||
All color token pairs in `tokens.json` are documented with their contrast ratio:
|
||||
- Primary on white: 4.56:1 (AA normal text).
|
||||
- Primary-light background with gray-900 text: 10.47:1 (AAA).
|
||||
- Semantic colors (success, warning, error, info) on white: documented per token.
|
||||
- **No token pair may fall below 3:1 for large text or 4.5:1 for normal text** against its intended background.
|
||||
|
||||
### 5.3 Screen reader notes
|
||||
|
||||
| Component | Screen reader behavior |
|
||||
|---|---|
|
||||
| Email list | `role="list"` with `aria-label="Email inbox"`. Each row: `role="listitem"`, `aria-label` with subject + sender + date. |
|
||||
| Email detail | `role="article"` with `aria-label` = subject. |
|
||||
| Sidebar navigation | `role="navigation"` with `aria-label="Main navigation"`. Current item: `aria-current="page"`. |
|
||||
| Metrics dashboard | Each metric card: `role="region"` with `aria-label` = metric name. Live metrics: `aria-live="polite"`. |
|
||||
| Log viewer | `role="log"` or `role="region"` with `aria-label="System logs"`. Auto-scroll: `aria-live="polite"` on new entries. |
|
||||
| Config forms | Each field: `aria-label` or associated `<label>`. Error messages: `aria-describedby` + `role="alert"`. |
|
||||
| Modal / drawer | `role="dialog"`, `aria-modal="true"`, `aria-labelledby` = modal title. Focus trap on open. Return focus on close. |
|
||||
|
||||
### 5.4 Keyboard interactions
|
||||
|
||||
| Component | Key | Action |
|
||||
|---|---|---|
|
||||
| Email list | `↑` / `↓` | Move focus between list items. |
|
||||
| Email list | `Enter` | Open selected email detail. |
|
||||
| Email detail | `Escape` | Return to list view. |
|
||||
| Sidebar | `Tab` / `Shift+Tab` | Navigate between sidebar items. |
|
||||
| Sidebar | `Enter` | Activate sidebar item. |
|
||||
| Modal / drawer | `Escape` | Close modal/drawer. Return focus to trigger. |
|
||||
| Config form | `Tab` | Navigate between form fields. |
|
||||
| Config form | `Enter` (on `<button>`) | Submit form. |
|
||||
| Search | `/` | Focus search input (global shortcut). |
|
||||
| Search | `Escape` | Clear and blur search input. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation notes for downstream Stories
|
||||
|
||||
- **D-S2 (#133)**: Consume `tokens.json` → generate `src/tokens.ts` (or `.css`) at build time. Implement the token function/variables.
|
||||
- **D-S3 (#134)**: Implement responsive layout components using the breakpoint tokens and grid specification in §4.
|
||||
- **D-S4 (#135)**: Implement the lint rules in §3 as a Stylelint plugin (for CSS) and/or ESLint plugin (for JSX/TSX). Both must block merge on **error** severity; **warn** rules generate non-blocking annotations.
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
- Architecture: [`docs/architecture/v1.md`](../architecture/v1.md) §4.5 (v1.5 trust boundaries and components)
|
||||
- TRD: [`docs/trd/mailbox-admin-interface.md`](../trd/mailbox-admin-interface.md) (INTV-5)
|
||||
- Tokens: [`docs/design/tokens.json`](./tokens.json)
|
||||
- Gitea: Story [#132](https://git.cloudinit.dev/jc-capital/inboxos/issues/132), Epic [#113](https://git.cloudinit.dev/jc-capital/inboxos/issues/113)
|
||||
@@ -0,0 +1,188 @@
|
||||
# Mailbox Owner Dashboard UI Spec — INB-1.5-A-S3
|
||||
|
||||
> **Story:** [#118](https://git.cloudinit.dev/jc-capital/inboxos/issues/118)
|
||||
> **Epic:** EPIC-INB-1.5-A [#112](https://git.cloudinit.dev/jc-capital/inboxos/issues/112)
|
||||
> **Traces to:** INTV-5 #109 → MAJOR v1 #99
|
||||
> **Domain:** ux. **Pattern:** B (spec).
|
||||
> **Target branch:** `ux/INB-1.5-A-ui-spec`
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
Wireframes, mockups, responsive breakpoint spec, and design-system token usage for the Mailbox Owner Dashboard. This is the UX specification that the frontend engineer (Story #119) will implement from.
|
||||
|
||||
---
|
||||
|
||||
## 2. Views
|
||||
|
||||
### 2.1 Email inbox list (primary view)
|
||||
|
||||
**Route:** `/mailbox`
|
||||
|
||||
**Layout (≥ lg):** Three-column layout.
|
||||
|
||||
```
|
||||
┌──────────────┬─────────────────────────┬──────────────────┐
|
||||
│ Sidebar │ Email List │ Email Detail │
|
||||
│ (240px) │ (flex 1, ~350px min) │ (flex 2) │
|
||||
│ │ │ │
|
||||
│ □ Inbox │ [Search bar] │ Subject │
|
||||
│ □ Archive │ [Filter: status ▼] │ From: sender │
|
||||
│ □ Processed │ ───────────────────── │ Date: Jun 10 │
|
||||
│ │ ☐ Subject line 9:00a │ │
|
||||
│ ──────── │ ☐ Subject line 8:45a │ Body preview │
|
||||
│ Settings │ ☐ Subject line 8:30a │ ... │
|
||||
│ Help │ ☐ Subject line 8:15a │ │
|
||||
│ │ ☐ Subject line 7:50a │ [Archive] │
|
||||
│ │ │ [Mark Processed] │
|
||||
└──────────────┴─────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
**Layout (md – lg):** Two-column. Sidebar collapses to drawer (hamburger toggle). Email list + detail side-by-side.
|
||||
|
||||
**Layout (< md):** Single column. List view only. Tap email to push detail view. Back button returns to list.
|
||||
|
||||
### 2.2 Email detail (right panel / push view)
|
||||
|
||||
**Components:**
|
||||
- Subject heading (typography: `xl`, `bold`)
|
||||
- Sender, recipient, date metadata row (typography: `sm`, `gray-500`)
|
||||
- Email body (rendered HTML from SeaweedFS `.eml` archive)
|
||||
- Action bar: `[Archive]` `[Mark Processed]` (primary and secondary buttons)
|
||||
- Status badge: `Inbox` / `Archived` / `Processed` (semantic color tokens)
|
||||
|
||||
### 2.3 Search and filter bar
|
||||
|
||||
**Components:**
|
||||
- Global search input (`/` keyboard shortcut to focus)
|
||||
- Filter dropdown: status (`all`, `inbox`, `archived`, `processed`)
|
||||
- Filter dropdown: date range (`today`, `7 days`, `30 days`, `custom`)
|
||||
- Active filter chips with `×` dismiss
|
||||
|
||||
**Search/filter latency:** < 500ms (NFR-2).
|
||||
|
||||
### 2.4 Sidebar navigation
|
||||
|
||||
**Items:**
|
||||
- Inbox (with unread count badge)
|
||||
- Archive
|
||||
- Processed
|
||||
- --- separator ---
|
||||
- Settings (links to `/settings`)
|
||||
- Help (links to `/help`)
|
||||
|
||||
**Active state:** `aria-current="page"`, background `color.brand.primary-light`, text `color.brand.primary`.
|
||||
|
||||
### 2.5 Empty states
|
||||
|
||||
| State | Message | Action |
|
||||
|---|---|---|
|
||||
| No emails | "No emails yet. New mail is checked every 5 minutes." | None (automatic) |
|
||||
| No search results | "No emails match your search." | Clear search button |
|
||||
| All processed | "All caught up! No inbox items." | Link to archive |
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsive breakpoint spec
|
||||
|
||||
| Viewport | Layout | Sidebar | Email list | Detail |
|
||||
|---|---|---|---|---|
|
||||
| `< md` (640–767px) | Single column | Hidden (hamburger) | Full width, stacked | Push view (replaces list) |
|
||||
| `md` – `lg` (768–1023px) | Two column | Drawer (overlay) | 40% width | 60% width |
|
||||
| `≥ lg` (1024px+) | Three column | Persistent (240px) | Flex 1 (min 350px) | Flex 2 |
|
||||
|
||||
**Grid tokens used:**
|
||||
- `spacing.6` (24px) for column gutters
|
||||
- `spacing.4` (16px) for card padding
|
||||
- `border-radius.md` (8px) for email list items
|
||||
- `border-radius.lg` (12px) for modal/drawer
|
||||
|
||||
---
|
||||
|
||||
## 4. Design-system token usage
|
||||
|
||||
Every visual property in this spec references `docs/design/tokens.json`.
|
||||
|
||||
| Element | Token path | Notes |
|
||||
|---|---|---|
|
||||
| Page background | `color.surface.background` | `gray-50` |
|
||||
| Card / panel background | `color.surface.card` | `white` |
|
||||
| Primary action button | `color.brand.primary` bg, `color.brand.primary-on` text | Blue button |
|
||||
| Secondary action button | `color.neutral.gray-100` bg, `color.neutral.gray-700` text | Ghost button |
|
||||
| Sidebar active | `color.brand.primary-light` bg, `color.brand.primary` text | |
|
||||
| Body text | `color.neutral.gray-600` | |
|
||||
| Heading text | `color.neutral.gray-900` | |
|
||||
| Status: inbox | `color.brand.primary` badge | |
|
||||
| Status: archived | `color.neutral.gray-400` badge | |
|
||||
| Status: processed | `color.semantic.success` badge | |
|
||||
| Status: error | `color.semantic.error` badge | |
|
||||
| Page title | `typography.font-size.4xl`, `bold` | |
|
||||
| Section heading | `typography.font-size.xl`, `semibold` | |
|
||||
| Body text | `typography.font-size.base`, `normal` | |
|
||||
| Metadata | `typography.font-size.sm`, `color.neutral.gray-500` | |
|
||||
| Card shadow | `shadow.md` | |
|
||||
| Hover shadow | `shadow.lg` | |
|
||||
| Input border | `color.neutral.gray-200` default, `color.brand.primary` focus | |
|
||||
| Transition | `transition.fast` for hover, `transition.normal` for expand | |
|
||||
|
||||
---
|
||||
|
||||
## 5. Accessibility specification
|
||||
|
||||
### 5.1 WCAG conformance
|
||||
|
||||
**WCAG 2.2 Level AA** (NFR-4).
|
||||
|
||||
### 5.2 Screen reader notes
|
||||
|
||||
| Component | ARIA | Notes |
|
||||
|---|---|---|
|
||||
| Email list | `role="list"`, `aria-label="Email inbox"` | Live region: `aria-live="polite"` for count updates |
|
||||
| Email row | `role="listitem"`, `aria-label="{subject}, {sender}, {date}, {status}"` | |
|
||||
| Email detail | `role="article"`, `aria-label="{subject}"` | |
|
||||
| Action buttons | `<button aria-label="Archive email">`, `<button aria-label="Mark as processed">` | |
|
||||
| Search | `role="search"`, `aria-label="Search emails"` | |
|
||||
| Filter | `<select aria-label="Filter by status">` | |
|
||||
| Sidebar | `role="navigation"`, `aria-label="Main navigation"` | `aria-current="page"` on active item |
|
||||
| Unread badge | `aria-label="{count} unread emails"` | |
|
||||
| Status badge | `aria-label="Status: {status}"` | |
|
||||
|
||||
### 5.3 Keyboard interactions
|
||||
|
||||
| Key | Context | Action |
|
||||
|---|---|---|
|
||||
| `↑` / `↓` | Email list | Move focus between rows |
|
||||
| `Enter` | Email list row | Open email detail |
|
||||
| `Escape` | Email detail | Return to list view |
|
||||
| `Escape` | Search | Clear and blur search input |
|
||||
| `/` | Global | Focus search input |
|
||||
| `a` | Email detail | Archive email |
|
||||
| `p` | Email detail | Mark as processed |
|
||||
|
||||
### 5.4 Focus management
|
||||
|
||||
- On email select: focus moves to detail panel heading.
|
||||
- On detail close (`Escape`): focus returns to the previously focused list row.
|
||||
- On modal open: focus trap within modal. Return focus to trigger on close.
|
||||
- Skip-to-content link: "Skip to main content" at page top (visible on focus).
|
||||
|
||||
---
|
||||
|
||||
## 6. Out of scope
|
||||
|
||||
- Frontend implementation (Story #119).
|
||||
- Email ingestion pipeline (existing).
|
||||
- RBAC routing (Story #126, #127).
|
||||
- Dark mode (v1.6+ candidate, DEF-V1.5-1).
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
- Design tokens: [`docs/design/tokens.json`](./tokens.json)
|
||||
- Lint rules: [`docs/design/lint-rules.md`](./lint-rules.md)
|
||||
- Architecture: [`docs/architecture/v1.md`](../architecture/v1.md) §4.5
|
||||
- TRD: [`docs/trd/mailbox-admin-interface.md`](../trd/mailbox-admin-interface.md)
|
||||
- API contract: Story [#116](https://git.cloudinit.dev/jc-capital/inboxos/issues/116)
|
||||
- Story: [#118](https://git.cloudinit.dev/jc-capital/inboxos/issues/118)
|
||||
@@ -0,0 +1,212 @@
|
||||
# Admin/Operator Ops Console UI Spec — INB-1.5-B-S3
|
||||
|
||||
> **Story:** [#123](https://git.cloudinit.dev/jc-capital/inboxos/issues/123)
|
||||
> **Epic:** EPIC-INB-1.5-B [#110](https://git.cloudinit.dev/jc-capital/inboxos/issues/110)
|
||||
> **Traces to:** INTV-5 #109 → MAJOR v1 #99
|
||||
> **Domain:** ux. **Pattern:** B (spec).
|
||||
> **Target branch:** `ux/INB-1.5-B-ui-spec`
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
Wireframes, mockups, and responsive breakpoint spec for the Admin/Operator Ops Console. This is the UX specification that the frontend engineer (Story #124) will implement from.
|
||||
|
||||
---
|
||||
|
||||
## 2. Views
|
||||
|
||||
### 2.1 Dashboard (primary view)
|
||||
|
||||
**Route:** `/admin`
|
||||
|
||||
**Layout (≥ lg):** Two-panel layout.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┬──────────────────────┐
|
||||
│ Metrics Dashboard (2/3) │ Sidebar (1/3) │
|
||||
│ │ │
|
||||
│ ┌─────────────┬─────────────┐ │ □ Dashboard │
|
||||
│ │ Ingestion │ Error │ │ □ Logs │
|
||||
│ │ Rate 5m │ Count 5m │ │ □ Config │
|
||||
│ │ 47 msg │ 2 err │ │ □ Users │
|
||||
│ └─────────────┴─────────────┘ │ ──────── │
|
||||
│ │ □ Settings │
|
||||
│ ┌─────────────────────────────┐ │ │
|
||||
│ │ System Latency p99 │ │ │
|
||||
│ │ ▇▇▇▇▇▇▇▇▇▇▃▃▃▃▃ │ │ │
|
||||
│ │ 5s ←─────── 15m ──────→ │ │ │
|
||||
│ └─────────────────────────────┘ │ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────────────┐ │ │
|
||||
│ │ Recent Errors │ │ │
|
||||
│ │ ⚠ extract_permanent_failure │ │ │
|
||||
│ │ msg-abc123 2m ago │ │ │
|
||||
│ │ ⚠ classify_error │ │ │
|
||||
│ │ msg-def456 5m ago │ │ │
|
||||
│ └─────────────────────────────┘ │ │
|
||||
└──────────────────────────────────────┴──────────────────────┘
|
||||
```
|
||||
|
||||
**Layout (md – lg):** Metrics stack vertically. Sidebar collapses to tab navigation.
|
||||
|
||||
**Layout (< md):** Single column. Tab navigation at top. Each section scrolls.
|
||||
|
||||
### 2.2 Metrics cards
|
||||
|
||||
Four metric cards in a 2×2 grid (≥ lg) or stacked (mobile):
|
||||
|
||||
| Card | Data source | Refresh | Token |
|
||||
|---|---|---|---|
|
||||
| Ingestion Rate (5m) | `GET /api/v1/admin/metrics` → `ingestion_rate_5m` | 30s | `color.semantic.info` for trend arrow |
|
||||
| Error Count (5m) | `GET /api/v1/admin/metrics` → `error_count_5m` | 30s | `color.semantic.error` if > 0, `color.semantic.success` if 0 |
|
||||
| System Latency p99 | `GET /api/v1/admin/metrics` → `system_latency_p99` | 30s | `color.semantic.warning` if > 10s |
|
||||
| Recent Errors | `GET /api/v1/admin/metrics` (embedded) | 30s | `color.semantic.error` for failure rows |
|
||||
|
||||
Each card: `role="region"`, `aria-label="Ingestion rate"`, `aria-live="polite"` for live metrics.
|
||||
|
||||
### 2.3 Logs view
|
||||
|
||||
**Route:** `/admin/logs`
|
||||
|
||||
**Components:**
|
||||
- Filter bar: date range, log level (`INFO`, `WARN`, `ERROR`), pipeline (`live`, `historical`), free-text search
|
||||
- Log table: timestamp, level, pipeline, event type, message, message_id (expandable row for detail)
|
||||
- Monospace font (`typography.font-family.mono`) for log message and message_id
|
||||
- Auto-scroll toggle for real-time tailing
|
||||
|
||||
**Layout (≥ lg):** Full-width table with sidebar navigation.
|
||||
|
||||
**Layout (< md):** Stacked cards with expandable detail.
|
||||
|
||||
### 2.4 Config view
|
||||
|
||||
**Route:** `/admin/config`
|
||||
|
||||
**Components:**
|
||||
- Config table: key → value pairs from `GET /api/v1/admin/config`
|
||||
- Edit modal: `PATCH /api/v1/admin/config` for mutable config keys
|
||||
- Audit trail: last-modified timestamp and user for each key
|
||||
|
||||
**Layout:** Same responsive pattern as logs view.
|
||||
|
||||
### 2.5 Users view
|
||||
|
||||
**Route:** `/admin/users`
|
||||
|
||||
**Components:**
|
||||
- Users table: email, role, assigned_at, assigned_by
|
||||
- Role change: `PATCH /api/v1/admin/users/:id/role` (dropdown: owner / admin / operator)
|
||||
- Confirmation modal for role changes
|
||||
- Audit log at bottom: `event_id, user_id, action, resource, timestamp, ip_hash`
|
||||
|
||||
### 2.6 Empty and error states
|
||||
|
||||
| State | Message | Action |
|
||||
|---|---|---|
|
||||
| No errors | "No errors in the last 5 minutes." | None |
|
||||
| No logs | "No log entries match your filter." | Clear filter button |
|
||||
| Config load error | "Unable to load configuration. Retrying in 30s." | Retry button |
|
||||
| Users load error | "Unable to load user list. Retrying in 30s." | Retry button |
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsive breakpoint spec
|
||||
|
||||
| Viewport | Layout | Metrics | Sidebar | Detail |
|
||||
|---|---|---|---|---|
|
||||
| `< md` (640–767px) | Single column | Stacked cards | Tab navigation (top) | Full-width sections |
|
||||
| `md` – `lg` (768–1023px) | Two panel | 2×2 grid | Drawer | 60/40 split |
|
||||
| `≥ lg` (1024px+) | Two panel | 2×2 grid | Persistent (1/3 width) | Content (2/3 width) |
|
||||
|
||||
**Grid tokens used:**
|
||||
- `spacing.6` (24px) for column gutters
|
||||
- `spacing.4` (16px) for card internal padding
|
||||
- `border-radius.md` (8px) for metric cards
|
||||
- `shadow.md` for cards at rest, `shadow.lg` on hover
|
||||
|
||||
---
|
||||
|
||||
## 4. Design-system token usage
|
||||
|
||||
| Element | Token path | Notes |
|
||||
|---|---|---|
|
||||
| Page background | `color.surface.background` | `gray-50` |
|
||||
| Card background | `color.surface.card` | `white` |
|
||||
| Sidebar background | `color.neutral.white` | `shadow.sm` for elevation |
|
||||
| Sidebar active | `color.brand.primary-light` bg, `color.brand.primary` text | |
|
||||
| Metric card: positive | `color.semantic.success` value | |
|
||||
| Metric card: warning | `color.semantic.warning` value | |
|
||||
| Metric card: error | `color.semantic.error` value | |
|
||||
| Log level: ERROR | `color.semantic.error` text | |
|
||||
| Log level: WARN | `color.semantic.warning` text | |
|
||||
| Log level: INFO | `color.neutral.gray-500` text | |
|
||||
| Log message | `typography.font-family.mono`, `typography.font-size.sm` | |
|
||||
| Section heading | `typography.font-size.xl`, `semibold` | |
|
||||
| Table header | `typography.font-size.sm`, `medium`, `color.neutral.gray-500` | |
|
||||
| Table row hover | `color.neutral.gray-50` background | |
|
||||
| Action button primary | `color.brand.primary` bg, `color.brand.primary-on` text | |
|
||||
| Action button danger | `color.semantic.error` bg, white text | Role change delete |
|
||||
| Config modal | `shadow.xl`, `border-radius.lg` | |
|
||||
| Auto-scroll toggle | `color.neutral.gray-400` off, `color.brand.primary` on | |
|
||||
|
||||
---
|
||||
|
||||
## 5. Accessibility specification
|
||||
|
||||
### 5.1 WCAG conformance
|
||||
|
||||
**WCAG 2.2 Level AA** (NFR-4).
|
||||
|
||||
### 5.2 Screen reader notes
|
||||
|
||||
| Component | ARIA | Notes |
|
||||
|---|---|---|
|
||||
| Metrics dashboard | `role="region"`, `aria-label="System metrics"` | `aria-live="polite"` for live updates |
|
||||
| Metric card | `role="region"`, `aria-label="{metric name}"` | |
|
||||
| Log table | `<table>` with `<caption>` | |
|
||||
| Log row detail | `aria-expanded` on toggle | |
|
||||
| Config form | `<form>` with associated `<label>` elements | Error: `role="alert"` |
|
||||
| Role change modal | `role="dialog"`, `aria-modal="true"`, `aria-labelledby` | Focus trap |
|
||||
| Sidebar | `role="navigation"`, `aria-label="Admin navigation"` | `aria-current="page"` |
|
||||
| Users table | `<table>` with `<caption>` | |
|
||||
| Audit log | `role="log"` or `role="region"`, `aria-label="Audit log"` | |
|
||||
|
||||
### 5.3 Keyboard interactions
|
||||
|
||||
| Key | Context | Action |
|
||||
|---|---|---|
|
||||
| `Tab` / `Shift+Tab` | Global | Navigate between sidebar items, metric cards, table rows |
|
||||
| `Enter` | Sidebar item | Navigate to section |
|
||||
| `↑` / `↓` | Log table | Move between rows |
|
||||
| `Enter` | Log row | Expand detail |
|
||||
| `Escape` | Expanded row / modal | Close detail / close modal |
|
||||
| `Enter` | Config save button | Submit config change |
|
||||
| `Escape` | Modal | Close modal, return focus |
|
||||
|
||||
### 5.4 Focus management
|
||||
|
||||
- On section navigation: focus moves to section heading.
|
||||
- On modal open: focus trap within modal. Return focus to trigger on close.
|
||||
- On role change confirmation: focus moves to confirmation dialog.
|
||||
- Skip-to-content link at page top.
|
||||
|
||||
---
|
||||
|
||||
## 6. Out of scope
|
||||
|
||||
- Frontend implementation (Story #124).
|
||||
- Backend API (Stories #121, #122).
|
||||
- RBAC routing (Story #126).
|
||||
- Advanced analytics / PDF reports (v1.6+).
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
- Design tokens: [`docs/design/tokens.json`](./tokens.json)
|
||||
- Lint rules: [`docs/design/lint-rules.md`](./lint-rules.md)
|
||||
- Architecture: [`docs/architecture/v1.md`](../architecture/v1.md) §4.5
|
||||
- TRD: [`docs/trd/mailbox-admin-interface.md`](../trd/mailbox-admin-interface.md)
|
||||
- API contract: Story [#121](https://git.cloudinit.dev/jc-capital/inboxos/issues/121)
|
||||
- Story: [#123](https://git.cloudinit.dev/jc-capital/inboxos/issues/123)
|
||||
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"$schema": "https://design-tokens.github.io/community/format/",
|
||||
"metadata": {
|
||||
"source": "INB-1.5-D-S1 — Design-system token set and lint rules",
|
||||
"story": "https://git.cloudinit.dev/jc-capital/inboxos/issues/132",
|
||||
"epic": "EPIC-INB-1.5-D #113 — Design-System Compliance & UI Polish",
|
||||
"initiative": "INTV-5 #109 — Mailbox & Administrative Management Interface",
|
||||
"version": "1.0.0",
|
||||
"date": "2026-06-10",
|
||||
"author": "ux-designer"
|
||||
},
|
||||
"color": {
|
||||
"brand": {
|
||||
"primary": {
|
||||
"$value": "#2563eb",
|
||||
"$description": "Primary brand color — used for primary actions, links, active states",
|
||||
"wcag": "4.56:1 on white (AA), 3.28:1 on gray-50 (AA large)"
|
||||
},
|
||||
"primary-hover": {
|
||||
"$value": "#1d4ed8",
|
||||
"$description": "Primary hover — 10% darker than primary"
|
||||
},
|
||||
"primary-light": {
|
||||
"$value": "#dbeafe",
|
||||
"$description": "Primary surface tint — for card backgrounds, selected row highlights"
|
||||
},
|
||||
"primary-on": {
|
||||
"$value": "#ffffff",
|
||||
"$description": "Text/icon color on primary background"
|
||||
}
|
||||
},
|
||||
"neutral": {
|
||||
"white": { "$value": "#ffffff", "$description": "Surface base" },
|
||||
"gray-50": { "$value": "#f9fafb", "$description": "Page background" },
|
||||
"gray-100": { "$value": "#f3f4f6", "$description": "Card background, input disabled fill" },
|
||||
"gray-200": { "$value": "#e5e7eb", "$description": "Border default, divider" },
|
||||
"gray-300": { "$value": "#d1d5db", "$description": "Border hover" },
|
||||
"gray-400": { "$value": "#9ca3af", "$description": "Placeholder text, icon muted" },
|
||||
"gray-500": { "$value": "#6b7280", "$description": "Secondary text, helper text" },
|
||||
"gray-600": { "$value": "#4b5563", "$description": "Body text on light surface" },
|
||||
"gray-700": { "$value": "#374151", "$description": "Subheading text" },
|
||||
"gray-800": { "$value": "#1f2937", "$description": "Heading text" },
|
||||
"gray-900": { "$value": "#111827", "$description": "Primary text, high-contrast headings" }
|
||||
},
|
||||
"semantic": {
|
||||
"success": {
|
||||
"$value": "#16a34a",
|
||||
"$description": "Positive status — processed, healthy, connected",
|
||||
"wcag": "3.84:1 on white (AA large)"
|
||||
},
|
||||
"success-light": { "$value": "#dcfce7", "$description": "Success surface tint" },
|
||||
"warning": {
|
||||
"$value": "#d97706",
|
||||
"$description": "Caution status — pending, rate-limited",
|
||||
"wcag": "3.08:1 on white (AA large)"
|
||||
},
|
||||
"warning-light": { "$value": "#fef3c7", "$description": "Warning surface tint" },
|
||||
"error": {
|
||||
"$value": "#dc2626",
|
||||
"$description": "Error status — failed, dead-letter, extraction failure",
|
||||
"wcag": "4.58:1 on white (AA)"
|
||||
},
|
||||
"error-light": { "$value": "#fee2e2", "$description": "Error surface tint" },
|
||||
"info": {
|
||||
"$value": "#2563eb",
|
||||
"$description": "Informational status — same as brand primary",
|
||||
"$wcag": "4.56:1 on white (AA)"
|
||||
},
|
||||
"info-light": { "$value": "#dbeafe", "$description": "Info surface tint — same as primary-light" }
|
||||
},
|
||||
"surface": {
|
||||
"background": { "$value": "{color.neutral.gray-50}", "$description": "Page-level background" },
|
||||
"card": { "$value": "{color.neutral.white}", "$description": "Card, panel, modal background" },
|
||||
"card-elevated": { "$value": "{color.neutral.white}", "$description": "Elevated card (with shadow)" },
|
||||
"overlay": { "$value": "rgba(17, 24, 39, 0.5)", "$description": "Modal/drawer backdrop" }
|
||||
}
|
||||
},
|
||||
"typography": {
|
||||
"font-family": {
|
||||
"sans": { "$value": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif", "$description": "Primary UI font stack" },
|
||||
"mono": { "$value": "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace", "$description": "Code, log, and data-display font" }
|
||||
},
|
||||
"font-size": {
|
||||
"xs": { "$value": "0.75rem", "$line-height": "1rem", "$description": "Captions, badges, helper text" },
|
||||
"sm": { "$value": "0.875rem", "$line-height": "1.25rem", "$description": "Body small, table cells" },
|
||||
"base": { "$value": "1rem", "$line-height": "1.5rem", "$description": "Body default" },
|
||||
"lg": { "$value": "1.125rem", "$line-height": "1.75rem", "$description": "Lead paragraphs" },
|
||||
"xl": { "$value": "1.25rem", "$line-height": "1.75rem", "$description": "H4, section headings" },
|
||||
"2xl": { "$value": "1.5rem", "$line-height": "2rem", "$description": "H3" },
|
||||
"3xl": { "$value": "1.875rem", "$line-height": "2.25rem", "$description": "H2" },
|
||||
"4xl": { "$value": "2.25rem", "$line-height": "2.5rem", "$description": "H1, page title" }
|
||||
},
|
||||
"font-weight": {
|
||||
"normal": { "$value": "400", "$description": "Body text" },
|
||||
"medium": { "$value": "500", "$description": "Labels, table headers" },
|
||||
"semibold": { "$value": "600", "$description": "Subheadings, emphasized labels" },
|
||||
"bold": { "$value": "700", "$description": "Headings" }
|
||||
},
|
||||
"letter-spacing": {
|
||||
"tighter": { "$value": "-0.025em", "$description": "H1, H2" },
|
||||
"normal": { "$value": "0em", "$description": "Body, most text" },
|
||||
"wide": { "$value": "0.025em", "$description": "Uppercase labels, badges" }
|
||||
}
|
||||
},
|
||||
"spacing": {
|
||||
"0": { "$value": "0", "$description": "No spacing" },
|
||||
"0.5": { "$value": "0.125rem", "$description": "1px at 16px base — hairline" },
|
||||
"1": { "$value": "0.25rem", "$description": "4px — inline icon gap" },
|
||||
"1.5": { "$value": "0.375rem", "$description": "6px — compact list items" },
|
||||
"2": { "$value": "0.5rem", "$description": "8px — tight padding" },
|
||||
"3": { "$value": "0.75rem", "$description": "12px — default padding" },
|
||||
"4": { "$value": "1rem", "$description": "16px — section gap" },
|
||||
"5": { "$value": "1.25rem", "$description": "20px" },
|
||||
"6": { "$value": "1.5rem", "$description": "24px — card padding" },
|
||||
"8": { "$value": "2rem", "$description": "32px — major section gap" },
|
||||
"10": { "$value": "2.5rem", "$description": "40px" },
|
||||
"12": { "$value": "3rem", "$description": "48px — page-level margin" },
|
||||
"16": { "$value": "4rem", "$description": "64px" }
|
||||
},
|
||||
"border-radius": {
|
||||
"none": { "$value": "0", "$description": "Sharp corners" },
|
||||
"sm": { "$value": "0.25rem", "$description": "4px — badges, small chips" },
|
||||
"default": { "$value": "0.375rem", "$description": "6px — inputs, buttons" },
|
||||
"md": { "$value": "0.5rem", "$description": "8px — cards" },
|
||||
"lg": { "$value": "0.75rem", "$description": "12px — modals, panels" },
|
||||
"full": { "$value": "9999px", "$description": "Pill, avatar" }
|
||||
},
|
||||
"shadow": {
|
||||
"sm": { "$value": "0 1px 2px 0 rgba(0,0,0,0.05)", "$description": "Subtle elevation — cards at rest" },
|
||||
"md": { "$value": "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)", "$description": "Default card elevation" },
|
||||
"lg": { "$value": "0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)", "$description": "Dropdown, popover" },
|
||||
"xl": { "$value": "0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)", "$description": "Modal" }
|
||||
},
|
||||
"breakpoint": {
|
||||
"sm": { "$value": "640px", "$description": "Mobile landscape / small tablet" },
|
||||
"md": { "$value": "768px", "$description": "Tablet portrait — layout shifts from single-column to multi-column" },
|
||||
"lg": { "$value": "1024px", "$description": "Tablet landscape / small desktop — sidebar visible" },
|
||||
"xl": { "$value": "1280px", "$description": "Desktop — full layout" },
|
||||
"2xl": { "$value": "1536px", "$description": "Large desktop — extended content width" }
|
||||
},
|
||||
"z-index": {
|
||||
"dropdown": { "$value": "1000" },
|
||||
"sticky": { "$value": "1020" },
|
||||
"overlay": { "$value": "1040" },
|
||||
"modal": { "$value": "1060" },
|
||||
"toast": { "$value": "1080" }
|
||||
},
|
||||
"transition": {
|
||||
"fast": { "$value": "150ms ease", "$description": "Hover, focus" },
|
||||
"normal": { "$value": "250ms ease", "$description": "Expand, collapse" },
|
||||
"slow": { "$value": "350ms ease-in-out", "$description": "Modal, drawer" }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user