[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:
2026-06-10 21:05:55 +00:00
committed by frontend-engineer
parent 69488d2ef5
commit 6ad770eb16
4 changed files with 727 additions and 0 deletions
+174
View File
@@ -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)
+188
View File
@@ -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)
+212
View File
@@ -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)
+153
View File
@@ -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" }
}
}