docs(17): add UI design contract for chat & planning phase
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
08e8fc1976
commit
b84503a54a
1 changed files with 278 additions and 0 deletions
278
.planning/phases/17-chat-planning/17-UI-SPEC.md
Normal file
278
.planning/phases/17-chat-planning/17-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
---
|
||||
phase: 17
|
||||
slug: chat-planning
|
||||
status: draft
|
||||
shadcn_initialized: false
|
||||
preset: none
|
||||
created: 2026-05-17
|
||||
---
|
||||
|
||||
# Phase 17 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for Phase 17: Chat & Planning.
|
||||
> Generated by gsd-ui-researcher, verified by gsd-ui-checker.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Tool | none — custom CSS design system via `backend/internal/web/ui/` |
|
||||
| Preset | not applicable |
|
||||
| Component library | templ components + design-token CSS classes |
|
||||
| Icon library | Heroicons (SVG, inline) |
|
||||
| Font | ui-sans-serif / system-ui stack (declared in `base.css` body rule) |
|
||||
|
||||
Source: `backend/internal/web/ui/base.css`, `backend/internal/web/ui/app.css`, `backend/internal/web/ui/card.css`
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
All values multiples of 4. Tokens are used in CSS via explicit `px`/`rem` values that map to this scale.
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px (0.25rem) | Icon gaps, inline tight padding |
|
||||
| sm | 8px (0.5rem) | `.message-meta` gap, separator padding |
|
||||
| md | 16px (1rem) | Default element padding (message bubble horizontal) |
|
||||
| lg | 24px (1.5rem) | `.overview-section-heading` margin-bottom |
|
||||
| xl | 32px (2rem) | `dashboard-main` padding |
|
||||
| 2xl | 48px (3rem) | Major section breaks |
|
||||
| 3xl | 64px (4rem) | Page-level spacing |
|
||||
|
||||
Exceptions:
|
||||
- Message bubble vertical padding: 12px (0.75rem) — between sm and md, acceptable for chat density
|
||||
- `.ui-card-header` / `.ui-card-body` / `.ui-card-footer` padding: 20px 24px (1.25rem 1.5rem) — defined by `card.css`, must not change
|
||||
- Touch targets for tab nav items: 44px min-height — already declared in `.tab-nav-item`
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
All sizes match existing system. No new sizes are introduced.
|
||||
|
||||
| Role | Size | Weight | Line Height | Token / Class |
|
||||
|------|------|--------|-------------|---------------|
|
||||
| Page heading (Planning h1) | 28px (1.75rem) | 600 | 1.25 | Matches `text-[28px] font-semibold` already in planning.templ |
|
||||
| Section heading (Discussion h2) | 24px (1.5rem) | 600 | 1.25 | Matches `text-2xl font-semibold` already in discussion.templ |
|
||||
| Body / message text | 16px (1rem) | 400 | 1.5 (leading-6) | `.message-bubble p` — inherits base |
|
||||
| Label / meta (author, timestamp, date separator) | 14px (0.875rem) | 400 (author: 600) | 1.4 | `.message-meta` |
|
||||
|
||||
Source: `discussion.templ` existing type scale, `planning.templ` existing type scale — phase 17 must not introduce additional sizes.
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
All values use design tokens from `backend/internal/web/ui/base.css`. No hardcoded hex in new CSS.
|
||||
|
||||
| Role | Token | Resolved value | Usage |
|
||||
|------|-------|----------------|-------|
|
||||
| Dominant (60%) | `--color-surface-default` | `#ffffff` | Page background, `.ui-card` surface, others' message bubbles |
|
||||
| Secondary (30%) | `--color-surface-subtle` / `--color-surface-muted` | `hsl(0 0% 96.1%)` / `#f3f4f6` | Day separators (`bg-slate-50` equivalent), event list backgrounds, composer border area |
|
||||
| Accent (10%) | `--color-brand-primary` | `#804eec` | Own message bubble tint background only |
|
||||
| Destructive | `--color-status-danger-strong` | `#dc2626` | Not applicable in this phase — no destructive actions |
|
||||
|
||||
**Accent reserved for:** Own message bubble background tint only (`rgba(128, 78, 236, 0.10)` — use the resolved value directly in `.message-own .message-bubble`). The brand accent must NOT appear on borders, nav items, or other phase 17 elements.
|
||||
|
||||
**Border tokens:**
|
||||
- Message bubbles: `--color-border-subtle` (`#d0d5dd`) — 1px solid
|
||||
- Discussion card container (`.ui-card`): `--color-border-default` (`hsl(0 0% 90.9%)`)
|
||||
- Day separators: no explicit border — background differentiation only
|
||||
|
||||
**Text tokens:**
|
||||
- Message body: `--color-text-primary` (`hsl(0 0% 9%)`)
|
||||
- Meta (author email, timestamp, date separator): `--color-text-muted` (`hsl(0 0% 43.5%)`)
|
||||
- Author email own message: `--color-text-brand` (`#804eec`) — weight 600
|
||||
- Planning event title: `--color-surface-muted-inverse` (`#111827`)
|
||||
- Planning event time/tablo label: `--color-text-secondary` (`#475467`)
|
||||
|
||||
---
|
||||
|
||||
## Component Inventory
|
||||
|
||||
### New CSS classes (all go into `backend/internal/web/ui/app.css`)
|
||||
|
||||
#### `.message-row`
|
||||
```css
|
||||
.message-row {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
```
|
||||
Flex container for a single message. No divider — drop `divide-y divide-slate-100` from the parent; bubbles carry their own visual weight.
|
||||
|
||||
#### `.message-own`
|
||||
```css
|
||||
.message-own {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-other`
|
||||
```css
|
||||
.message-other {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-bubble`
|
||||
```css
|
||||
.message-bubble {
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: 0.25rem 0.75rem 0.75rem 0.75rem;
|
||||
max-width: 70%;
|
||||
padding: 0.75rem 1rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-words;
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-own .message-bubble`
|
||||
```css
|
||||
.message-own .message-bubble {
|
||||
background: rgba(128, 78, 236, 0.10);
|
||||
border-radius: 0.75rem 0.75rem 0.25rem 0.75rem;
|
||||
}
|
||||
```
|
||||
Own messages: top-right corner is tight (0.25rem) — classic outgoing bubble cue.
|
||||
|
||||
#### `.message-other .message-bubble`
|
||||
```css
|
||||
.message-other .message-bubble {
|
||||
background: var(--color-surface-default);
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-meta`
|
||||
```css
|
||||
.message-meta {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-own .message-meta`
|
||||
```css
|
||||
.message-own .message-meta {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
```
|
||||
|
||||
#### `.message-meta .message-author`
|
||||
```css
|
||||
.message-meta .message-author {
|
||||
font-weight: 600;
|
||||
}
|
||||
```
|
||||
|
||||
### Existing classes applied to new elements
|
||||
|
||||
| Element | Class | Source |
|
||||
|---------|-------|--------|
|
||||
| Discussion messages container `#discussion-messages` | `.ui-card` | `backend/internal/web/ui/card.css` |
|
||||
| Planning page header wrapper | `.overview-section` | `backend/internal/web/ui/app.css` section 13 |
|
||||
| Planning heading row | `.overview-section-heading` | `backend/internal/web/ui/app.css` section 13 |
|
||||
| Planning empty state | `@ui.EmptyState(...)` | `backend/internal/web/ui/empty_state.templ` |
|
||||
|
||||
### Planning day separator element
|
||||
|
||||
Use the same visual approach as `DiscussionDaySeparator` for cross-surface consistency:
|
||||
|
||||
```html
|
||||
<div class="bg-slate-50 px-4 py-2 text-center text-sm text-slate-500">
|
||||
{ date label string }
|
||||
</div>
|
||||
```
|
||||
|
||||
Note: `bg-slate-50` and `text-slate-500` resolve to `--color-surface-muted` / `--color-text-muted` territory. This is an inline Tailwind usage that matches existing `DiscussionDaySeparator` exactly — acceptable because it mirrors an already-established pattern rather than introducing new values.
|
||||
|
||||
---
|
||||
|
||||
## Interaction Contract
|
||||
|
||||
### Discussion tab
|
||||
|
||||
| State | Behavior |
|
||||
|-------|---------|
|
||||
| Messages list — own | `.message-row.message-own` — bubble right-aligned, brand-tinted background, meta right-aligned |
|
||||
| Messages list — others | `.message-row.message-other` — bubble left-aligned, white background, meta left-aligned |
|
||||
| Day boundary | `DiscussionDaySeparator` renders above first message of each new day (existing logic unchanged) |
|
||||
| Empty state | `DiscussionEmptyState` — existing templ component, no change |
|
||||
| Composer submit | HTMX POST → `hx-swap="beforeend"` appends new `.message-row.message-own` row. New row must use `message.IsOwn = true` |
|
||||
| SSE new message (others) | SSE stream appends `.message-row.message-other` row. `IsOwn = false` (default) |
|
||||
|
||||
### Planning page
|
||||
|
||||
| State | Behavior |
|
||||
|-------|---------|
|
||||
| Page heading | `.overview-section` > `.overview-section-heading` with h1 + date-range label, nav buttons right side |
|
||||
| Events with multiple dates | `PlanningShowDaySeparator` helper — day separator rendered above first event of each new date |
|
||||
| Events same date | No separator between them |
|
||||
| Empty (no events) | `@ui.EmptyState(...)` — consistent with Phase 16 files section |
|
||||
| Event row | TimeRange → Title → TabloTitle + Location (right column). `DateLabel` removed from row body |
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Primary CTA — discussion composer | "Send message" (existing, no change) |
|
||||
| Discussion empty state heading | "No messages yet" (existing `DiscussionEmptyState`) |
|
||||
| Discussion empty state body | "Start the discussion for this tablo." (existing) |
|
||||
| Planning empty state heading | "No events in this range" (adopt via `@ui.EmptyState`) |
|
||||
| Planning empty state body | "Use the navigation controls to browse another 14-day window." (adopt via `@ui.EmptyState`) |
|
||||
| Planning nav — prev | "Previous 14 days" (existing, no change) |
|
||||
| Planning nav — today | "Today" (existing, no change) |
|
||||
| Planning nav — next | "Next 14 days" (existing, no change) |
|
||||
| Error state | Not applicable — this phase has no form validation changes |
|
||||
| Destructive confirmation | Not applicable — no destructive actions in scope |
|
||||
|
||||
Source: CONTEXT.md decisions + existing templ files. No copy changes required; existing strings are correct.
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | none | not required — project uses custom Go templ design system |
|
||||
| third-party | none | not applicable |
|
||||
|
||||
No registry components. All UI is implemented via Go templ templates + CSS classes. No npm packages are installed.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Evidence Map
|
||||
|
||||
| Acceptance criterion | Spec ref | Visual contract |
|
||||
|----------------------|----------|----------------|
|
||||
| Own messages right-aligned, distinct background | REQ-1, REQ-2 | `.message-own` + `rgba(128,78,236,0.10)` tint |
|
||||
| Others left-aligned | REQ-1 | `.message-other` default |
|
||||
| SSE new message right-aligned for poster | REQ-2 | `message.IsOwn = true` → `.message-own` |
|
||||
| Discussion container = `.ui-card` surface | REQ-3 | `.ui-card` on `#discussion-messages` |
|
||||
| Planning heading = `.overview-section` | REQ-4 | `.overview-section` + `.overview-section-heading` |
|
||||
| Events grouped under date separators | REQ-5 | `PlanningShowDaySeparator` + separator element |
|
||||
| `go test ./...` exits 0 | REQ-6 | No new constraints from UI contract |
|
||||
|
||||
---
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
Loading…
Reference in a new issue