# Phase 17: Chat & Planning - Context

**Gathered:** 2026-05-17
**Status:** Ready for planning

<domain>
## Phase Boundary

Restyle the discussion view and planning page using design system components to make them visually consistent with the Phases 15–16 restyled surfaces. Phase 17 delivers:
- Discussion view: constrained chat bubbles with own-vs-others alignment (own right, others left), brand-tinted backgrounds, new CSS classes in app.css using design tokens, `.ui-card` surface for message container
- Planning page: `.overview-section` / `.overview-section-heading` header layout, day-grouped events with date separators, `DateLabel` removed from individual event rows

Does NOT include: avatars or profile pictures, message reactions or threading, planning sorting/filtering controls, new routes or data models, schema changes.

</domain>

<spec_lock>
## Requirements (locked via SPEC.md)

**6 requirements are locked.** See `17-SPEC.md` for full requirements, boundaries, and acceptance criteria.

Downstream agents MUST read `17-SPEC.md` before planning or implementing. Requirements are not duplicated here.

**In scope (from SPEC.md):**
- `DiscussionMessageView` — add `IsOwn bool` field
- `DiscussionMessagesFromRows` — add `currentUserID uuid.UUID` parameter; set `IsOwn` by comparing to each message's author
- `DiscussionMessageFromRow` — handle `isOwn` appropriately for SSE vs. HTMX paths
- `DiscussionTabFragment` / `DiscussionMessageRow` — restyle as right/left-aligned chat bubbles; message container adopts `.ui-card` surface
- `PlanningPage` / `PlanningEventListItem` — adopt `.overview-section` heading; add `PlanningShowDaySeparator` helper; add day-separator element; remove `DateLabel` from event row body
- All call sites for changed function signatures updated (handlers, test files where necessary to compile)
- Browser walkthrough confirming visual consistency on both pages

**Out of scope (from SPEC.md):**
- Avatars or profile pictures in message rows
- Message reactions or reply threading
- Planning page sorting or filtering controls
- New database schema, migrations, or sqlc queries
- New routes or HTTP handlers

</spec_lock>

<decisions>
## Implementation Decisions

### Chat Bubble Visual Design

- **D-M01:** Own messages render as constrained bubbles — max-width ~70%, right-aligned (use `margin-left: auto`). Others render at full width, left-aligned. Each message row is a flex container; own uses `.message-own` class, others use `.message-other`.
- **D-M02:** Bubble backgrounds use design tokens: own messages use `var(--color-brand-primary)` at low opacity (tint — e.g., `rgba(128, 78, 236, 0.1)` or a CSS custom property layered on `--color-brand-primary`). Others use `var(--color-surface-default)` (white). Border: subtle `var(--color-border-subtle)`.
- **D-M03:** Author email + timestamp appear ABOVE the bubble text, mirrored to match bubble alignment. Own: author + time right-aligned. Others: author + time left-aligned.
- **D-M04:** New CSS classes in `backend/internal/web/ui/app.css` using `var(--...)` tokens only — no Tailwind inline, no hardcoded hex values. Classes: `.message-row`, `.message-own`, `.message-other`, `.message-bubble`, `.message-meta`.

### Discussion Data Model

- **D-D01:** `DiscussionMessageView` gains `IsOwn bool`. Set via `DiscussionMessagesFromRows(rows, currentUserID)` — the handler already has `user.ID` from `loadOwnedTablo`.
- **D-D02:** `loadDiscussionTabData` receives the current user ID and passes it to `DiscussionMessagesFromRows`. The `TabloDiscussionTabHandler` already has `user` — thread `user.ID` through.
- **D-D03:** For the HTMX POST response (posting user): `message.IsOwn = true`. For `renderDiscussionMessageHTML` (SSE broadcast to other viewers): `IsOwn = false`. Since `DiscussionMessageView` is a value type in Go, set `IsOwn = false` before SSE publish and `IsOwn = true` before HTMX render — two sequential mutations on the same local copy work cleanly.
- **D-D04:** `DiscussionMessageFromRow` no longer needs an `isOwn bool` parameter — use the struct field mutation approach in the handler instead. The function returns a view with `IsOwn: false` by default; the handler sets `.IsOwn = true` for the HTMX path.

### Discussion Container

- **D-C01:** The `#discussion-messages` container div changes from `class="rounded border border-slate-200 bg-white"` to use the `.ui-card` CSS class. The composer form remains outside the card (below it), as currently — no structural change needed.

### Planning Page

- **D-P01:** `PlanningPage` heading section wraps in `<section class="overview-section">` + `<div class="overview-section-heading">` with `<h1>Planning</h1>` and the date range label below — matching the `.overview-section` pattern from tablos.templ.
- **D-P02:** Add `PlanningShowDaySeparator(events []PlanningEventRow, index int) bool` helper in `planning_forms.go` — compares `events[index].DateLabel != events[index-1].DateLabel` (string comparison, returns true for index 0 or label change).
- **D-P03:** Day separator element mirrors the discussion day separator style — a centered strip with the date label (e.g., `<div class="bg-slate-50 px-4 py-2 text-center text-sm text-slate-500">`).
- **D-P04:** `DateLabel` field is removed from `PlanningEventListItem` row body — it's now rendered in the separator. Event row shows: TimeRange → Title → TabloTitle + Location (existing right column).

### Claude's Discretion

- Exact `max-width` value for own message bubbles (recommend 70%)
- Border-radius on bubbles (recommend `0.75rem` for all corners, or asymmetric: own `0.75rem 0.75rem 0.25rem 0.75rem`)
- Exact opacity/alpha for the brand-primary bubble tint (recommend 10–15% — subtle, readable)
- Divider between messages: keep `divide-y divide-slate-100` inside the card or drop it now that bubbles have their own visual weight (recommend dropping the divider)
- Empty state for planning when no events: adopt `@ui.EmptyState(...)` to match Phase 16 files section (consistent with established pattern)
- Navigation controls in planning heading: keep using `.ui-button` inline utility classes (no change needed)

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Locked Requirements
- `.planning/phases/17-chat-planning/17-SPEC.md` — Locked requirements (6), boundaries, acceptance criteria — MUST read before planning

### Product Scope
- `.planning/ROADMAP.md` — Phase 17 goal, success criteria (4 criteria), requirements CHAT-UI-01, PLAN-UI-01
- `.planning/REQUIREMENTS.md` — CHAT-UI-01 (card/surface design, own-vs-others), PLAN-UI-01 (overview-section layout)
- `.planning/PROJECT.md` — v3.0 scope: visual polish only; no new data models or routes

### Current Backend (files being changed)
- `backend/templates/discussion.templ` — `DiscussionTabFragment`, `DiscussionMessageRow`, `DiscussionEmptyState`, `DiscussionDaySeparator`, `DiscussionComposer` — all being restyled
- `backend/templates/discussion_forms.go` — `DiscussionMessageView` (add `IsOwn bool`), `DiscussionMessagesFromRows` (add `currentUserID uuid.UUID`), `DiscussionMessageFromRow` (returns `IsOwn: false`; handler sets `IsOwn: true` for HTMX path)
- `backend/templates/planning.templ` — `PlanningPage`, `PlanningEventListItem` — adopt overview-section and day separators
- `backend/templates/planning_forms.go` — add `PlanningShowDaySeparator` helper
- `backend/internal/web/handlers_discussion.go` — `loadDiscussionTabData`, `DiscussionMessageCreateHandler` — thread `user.ID` through; set `IsOwn` for HTMX vs. SSE paths

### Design System (Phase 13 — already in backend)
- `backend/internal/web/ui/app.css` — receives new `.message-row`, `.message-own`, `.message-other`, `.message-bubble`, `.message-meta` CSS classes using design tokens
- `backend/internal/web/ui/base.css` — design tokens; all new CSS uses `var(--...)` tokens. Key tokens: `--color-brand-primary`, `--color-surface-default`, `--color-border-subtle`, `--color-text-muted`, `--color-surface-muted-inverse`
- `backend/internal/web/ui/card.templ` + `backend/internal/web/ui/card.css` — `.ui-card` class to apply to discussion message container
- `backend/internal/web/ui/empty_state.templ` — `@ui.EmptyState(...)` for planning empty state (Claude's discretion)

### Established Patterns (Phase 15–16)
- `.planning/phases/16-tablo-detail/16-CONTEXT.md` — overview-section heading pattern, design token constraints, all-CSS-in-app.css rule
- `.planning/phases/15-dashboard-tablos/15-CONTEXT.md` — AppLayout integration, sidebar tablos list pattern

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `backend/internal/web/ui/card.css` `.ui-card`: Apply to `#discussion-messages` container — swap `class="rounded border border-slate-200 bg-white"` with `class="ui-card"`
- `backend/templates/discussion.templ` `DiscussionDaySeparator`: Day separator pattern already exists for discussion — use the same visual approach for planning day separators
- `backend/internal/web/ui/empty_state.templ` `@ui.EmptyState(...)`: Ready for planning empty state to replace the current raw div

### Established Patterns
- All new CSS in `app.css` uses `var(--...)` tokens only — no hardcoded hex, no Tailwind inline in CSS files (Phases 13–16 convention)
- Value-type Go structs: `DiscussionMessageView` is a struct (not pointer) — handler can mutate `.IsOwn` between SSE publish and HTMX render without affecting the published copy
- `loadOwnedTablo` returns `tablo, user, ok` — `user.ID` is already available in all discussion handlers without additional queries

### Integration Points
- `backend/internal/web/handlers_discussion.go` `loadDiscussionTabData`: needs `userID uuid.UUID` as a new parameter; one caller (`TabloDiscussionTabHandler`) passes `user.ID`
- `backend/internal/web/handlers_discussion.go` `DiscussionMessageCreateHandler`: sets `message.IsOwn = false` before SSE publish; `message.IsOwn = true` before HTMX render (line ~141)
- `backend/templates/tablos.templ` `TabloDetailPage`: discussion tab passes `discussion DiscussionTabData` unchanged — no signature change needed at this level
- All non-discussion handlers that pass empty `templates.DiscussionTabData{}` are unaffected — `IsOwn` defaults to `false` which is correct for empty data

</code_context>

<specifics>
## Specific Ideas

- Message bubble CSS: `.message-own .message-bubble { margin-left: auto; max-width: 70%; background: rgba(128, 78, 236, 0.10); border: 1px solid var(--color-border-subtle); border-radius: 0.75rem 0.75rem 0.25rem 0.75rem; }` — asymmetric radius gives the classic chat feel for own messages; others get `border-radius: 0.25rem 0.75rem 0.75rem 0.75rem`
- `.message-meta` for own: `text-align: right; display: flex; justify-content: flex-end; gap: 0.5rem; margin-bottom: 0.25rem`
- Planning day separator: reuse the `DiscussionDaySeparator` visual pattern — same gray-tinted strip (`bg-slate-50 px-4 py-2 text-center text-sm text-slate-500`) for visual consistency between the two restyled surfaces

</specifics>

<deferred>
## Deferred Ideas

- Hover-to-reveal timestamp (compact mode) — not in v3.0 requirements
- Message edit or delete — not in v3.0 requirements
- Sidebar collapse toggle (JS) — deferred since Phase 15; still deferred
- Mobile-responsive kanban — still deferred (RESP-01 future)

</deferred>

---

*Phase: 17-chat-planning*
*Context gathered: 2026-05-17*
