go-htmx-gsd #1

Merged
arthur merged 558 commits from go-htmx-gsd into main 2026-05-23 15:16:44 +00:00
2 changed files with 236 additions and 0 deletions
Showing only changes of commit 08e8fc1976 - Show all commits

View file

@ -0,0 +1,156 @@
# 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 1516 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 1015% — 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 1516)
- `.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 1316 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*

View file

@ -0,0 +1,80 @@
# Phase 17: Chat & Planning - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-05-17
**Phase:** 17-chat-planning
**Areas discussed:** Chat bubble layout
---
## Chat Bubble Layout
### Bubble Width and Alignment
| Option | Description | Selected |
|--------|-------------|----------|
| Full-width, background only | Own message fills full container width but has a tinted background. Others white/neutral. Simpler layout, no max-width clipping. Matches Slack highlight style. | |
| Constrained bubble, right-aligned | Own message max-width ~70-75%, floated right. Others at full-width or left-constrained. Classic WhatsApp/iMessage style. Requires more CSS. | ✓ |
**User's choice:** Constrained bubble, right-aligned
---
### Bubble Background Colors
| Option | Description | Selected |
|--------|-------------|----------|
| Brand primary tint for own, surface-default for others | Own: var(--color-brand-primary) at low opacity. Others: var(--color-surface-default) (white). Consistent with token palette. | ✓ |
| Brand accent (blue) for own, neutral surface for others | Own: var(--color-brand-accent) (#3b82f6) tint. Others: var(--color-surface-muted). Blue accent, less "brand" than purple. | |
**User's choice:** Brand primary tint (purple) for own, surface-default for others
---
### Author Label and Timestamp Position
| Option | Description | Selected |
|--------|-------------|----------|
| Above bubble, mirrored | Author + timestamp sit above bubble text, aligned to match bubble side. Own: right-aligned meta. Others: left-aligned meta. | ✓ |
| Below bubble, timestamp only | No inline author label. Timestamp shown below bubble. Very compact. | |
**User's choice:** Above bubble, mirrored — author + timestamp above, aligned to bubble side
---
### CSS Approach
| Option | Description | Selected |
|--------|-------------|----------|
| New CSS classes in app.css using design tokens | Add .message-row, .message-own, .message-other, .message-bubble, .message-meta — all using var(--...) tokens. Consistent with Phase 1316 pattern. | ✓ |
| Inline Tailwind classes in templ | Conditional Tailwind classes directly in DiscussionMessageRow. Simpler to write, but mixes presentation into template and uses hardcoded colors. | |
**User's choice:** New CSS classes in app.css with design tokens
---
## Areas NOT Selected
The user was presented with three gray areas and selected only "Chat bubble layout":
- **Composer + card integration** — not discussed; Claude's discretion (keep composer outside card, as currently)
- **Planning event row layout after date removal** — not discussed; Claude's discretion (row shows TimeRange → Title → TabloTitle + Location)
## Claude's Discretion
- Exact max-width value for own bubbles (70%)
- Border-radius on bubbles (asymmetric: own `0.75rem 0.75rem 0.25rem 0.75rem`, others `0.25rem 0.75rem 0.75rem 0.75rem`)
- Exact opacity for brand-primary tint (10-15%)
- Whether to keep `divide-y` inside the card or drop it now that bubbles have visual weight (recommend drop)
- Composer placement (keep outside card, unchanged)
- Planning day separator visual style (mirror discussion's `DiscussionDaySeparator` — gray strip, centered text)
- Empty state for planning (adopt `@ui.EmptyState(...)` consistent with Phase 16)
- Planning event row restructuring after removing DateLabel
## Deferred Ideas
- Hover-to-reveal timestamp (compact mode)
- Message edit or delete functionality
- Sidebar collapse toggle (JS interaction)
- Mobile-responsive kanban layout