xtablo-source/.planning/phases/17-chat-planning/17-UI-SPEC.md
Arthur Belleville b84503a54a
docs(17): add UI design contract for chat & planning phase
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-17 06:30:40 +02:00

10 KiB

phase slug status shadcn_initialized preset created
17 chat-planning draft false none 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

.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

.message-own {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

.message-other

.message-other {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.message-bubble

.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

.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

.message-other .message-bubble {
  background: var(--color-surface-default);
}

.message-meta

.message-meta {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.message-own .message-meta

.message-own .message-meta {
  justify-content: flex-end;
}

.message-meta .message-author

.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:

<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