From 7c30e8b2d76ea0df99cfed6a74c7e3c207b72353 Mon Sep 17 00:00:00 2001 From: jsell-rh Date: Tue, 28 Apr 2026 21:07:41 +0000 Subject: [PATCH 1/4] spec: add markdown rendering specs 012a, 012b, 012c Three-part spec for fixing markdown rendering in message.tsx and tool-message.tsx. Covers core layout bugs (inline wrapper, font-mono, paragraph spacing), GFM table rendering with shared component overrides, and remaining GFM elements (blockquote, hr, img, h4-h6). Co-Authored-By: Claude Sonnet 4.6 --- specs/012a-markdown-layout-bugs/plan.md | 34 ++++++ specs/012a-markdown-layout-bugs/spec.md | 125 +++++++++++++++++++++ specs/012a-markdown-layout-bugs/tasks.md | 53 +++++++++ specs/012b-markdown-tables/plan.md | 32 ++++++ specs/012b-markdown-tables/spec.md | 109 ++++++++++++++++++ specs/012b-markdown-tables/tasks.md | 54 +++++++++ specs/012c-markdown-gfm-elements/plan.md | 29 +++++ specs/012c-markdown-gfm-elements/spec.md | 129 ++++++++++++++++++++++ specs/012c-markdown-gfm-elements/tasks.md | 59 ++++++++++ 9 files changed, 624 insertions(+) create mode 100644 specs/012a-markdown-layout-bugs/plan.md create mode 100644 specs/012a-markdown-layout-bugs/spec.md create mode 100644 specs/012a-markdown-layout-bugs/tasks.md create mode 100644 specs/012b-markdown-tables/plan.md create mode 100644 specs/012b-markdown-tables/spec.md create mode 100644 specs/012b-markdown-tables/tasks.md create mode 100644 specs/012c-markdown-gfm-elements/plan.md create mode 100644 specs/012c-markdown-gfm-elements/spec.md create mode 100644 specs/012c-markdown-gfm-elements/tasks.md diff --git a/specs/012a-markdown-layout-bugs/plan.md b/specs/012a-markdown-layout-bugs/plan.md new file mode 100644 index 000000000..dcc5a7910 --- /dev/null +++ b/specs/012a-markdown-layout-bugs/plan.md @@ -0,0 +1,34 @@ +# Implementation Plan: Fix Core Markdown Layout Bugs + +**Branch**: `feat/markdown-layout-bugs` | **Date**: 2026-04-28 | **Spec**: [spec.md](spec.md) + +## Summary + +Five targeted changes to `message.tsx` fixing structural rendering bugs. One file, ~10 lines changed. No new dependencies. Must ship before 012b (tables) and 012c (GFM elements). + +## Technical Context + +**Language**: TypeScript/React +**Target file**: `components/frontend/src/components/ui/message.tsx` +**Testing**: vitest (`npx vitest run`) +**Risk**: Medium — outer wrapper changes affect every bot message; scroll-position preservation in `MessagesTab.tsx` must be verified + +## Files + +``` +components/frontend/src/components/ui/ +└── message.tsx # MODIFY: 5 targeted changes (lines 78, 90, 93, 314, 321, + new overrides) +``` + +## Change Summary + +| Line | Before | After | +|------|--------|-------| +| 314 | `"text-sm text-foreground font-mono"` | `"text-sm text-foreground"` | +| 321 | `
` | `
` | +| 78 | `mb-[0.2rem]` | `mb-2` | +| 90 | `space-y-1` (ul) | `space-y-1.5` | +| 93 | `space-y-1` (ol) | `space-y-1.5` | +| new | — | `strong`, `em`, `del` component overrides | + +The streaming cursor `` at line 329 is **not modified**. Its `inline-block align-middle` classes keep it at the text baseline regardless of parent display type. diff --git a/specs/012a-markdown-layout-bugs/spec.md b/specs/012a-markdown-layout-bugs/spec.md new file mode 100644 index 000000000..30ca66dd8 --- /dev/null +++ b/specs/012a-markdown-layout-bugs/spec.md @@ -0,0 +1,125 @@ +# Feature Specification: Fix Core Markdown Layout Bugs + +**Feature Branch**: `feat/markdown-layout-bugs` +**Created**: 2026-04-28 +**Status**: Draft +**Input**: Three structural bugs in `message.tsx` that break all markdown rendering: an inline wrapper forcing block content into inline flow, `font-mono` applied to all prose, and a near-zero paragraph margin. Prerequisite for specs 012b and 012c. + +## Overview + +`components/frontend/src/components/ui/message.tsx` has three bugs that degrade every bot message regardless of content: + +1. **`className="inline"` on the ReactMarkdown wrapper** (line 321) — a `
` with `display:inline` wraps all `` output. Block-level elements (paragraphs, tables, lists, headings) collapse into inline flow, breaking margins, table row layout, and list indentation. The streaming cursor `` on line 329 is already `inline-block` with `align-middle` and positions correctly in either inline or block context — the wrapper's `inline` class serves no purpose. + +2. **`font-mono` on the message content wrapper** (line 314) — applies monospace to all prose: headings, paragraphs, bold, and italic. Only `` and `
` should be monospace. The existing `code` component override (line 56) already sets `font-mono`; the wrapper-level class is redundant and harmful.
+
+3. **`mb-[0.2rem]` on the paragraph override** (line 78) — 3.2px between paragraphs at `text-sm` (14px line height ~22.75px). The margin is 14% of one line height, visually indistinguishable from no margin at all. Standard readable prose uses `mb-2` (8px, ~35%).
+
+Two additional gaps surface once the `font-mono` bug is fixed: `` and `` have no component overrides, so while browser defaults render them correctly in a sans-serif context, they need explicit overrides to receive the correct color (`text-foreground`) and confirm the intended weight/style. The `
    `/`
      ` item gap (`space-y-1`, 4px) is also tighter than the corrected paragraph spacing and should be harmonized. + +This spec must be merged before 012b (tables) or 012c (GFM elements), both of which depend on block-level markdown working correctly. + +--- + +## User Scenarios & Testing + +### Prerequisite Fix — Remove Inline Wrapper (Blocking All Markdown) + +This is not a user story with business value of its own; it is a structural fix that unblocks all other markdown user stories. Without it, block-level elements (paragraphs with correct margins, tables, lists, blockquotes) cannot render correctly. + +**Fix**: Change `message.tsx:321` from `
      ` to `
      `. The streaming cursor `` (line 329) requires no changes — its `inline-block` and `align-middle` classes keep it at the text baseline regardless of parent display type. + +**Acceptance**: Given any bot message with multiple block-level markdown elements, when rendered, the elements stack vertically (block flow), table rows are horizontal, and the streaming cursor appears at the text baseline of the last rendered character. + +--- + +### User Story 1 — Prose Text Is Readable (Priority: P1) + +A user reads a multi-paragraph bot response. Paragraphs are clearly separated. Headings, bold, and italic text render in sans-serif, not monospace. Inline code is the only monospace element. + +**Why this priority**: The `font-mono` wrapper affects every bot message. Its removal is the highest-impact single-line change in this file. + +**Independent Test**: Send a message with two paragraphs, `**bold**`, `*italic*`, and `` `code` ``. Verify paragraphs are in a proportional font, bold is heavier, italic is slanted, only inline code is monospace. + +**Acceptance Scenarios**: + +1. **Given** a bot message with body text, **When** rendered, **Then** the message content wrapper (`div` at line 314) does NOT carry the `font-mono` CSS class; `font-family` resolves to the project's sans-serif stack (Inter / system-ui) +2. **Given** `**bold**` in a bot message, **When** rendered, **Then** the `` element has `font-weight: 600` or higher and `font-family` resolves to sans-serif +3. **Given** `*italic*` in a bot message, **When** rendered, **Then** the `` element has `font-style: italic` and `font-family` resolves to sans-serif +4. **Given** inline `` `code` `` in a bot message, **When** rendered, **Then** the `` element has `font-family` resolving to a monospace stack — the only element in the message that does + +--- + +### User Story 2 — Paragraphs Have Readable Spacing (Priority: P1) + +A user reads a bot message with three or more paragraphs. Each paragraph is visually distinct from the next with clear vertical separation. + +**Why this priority**: `mb-[0.2rem]` (3.2px) affects every multi-paragraph response. At `text-sm` this is imperceptible. + +**Independent Test**: Send a message with three short paragraphs. Verify each paragraph's bottom margin is at least 8px (Tailwind `mb-2`). + +**Acceptance Scenarios**: + +1. **Given** a bot message with 3+ paragraphs, **When** rendered, **Then** each `

      ` (rendered as a `

      ` by the component override) has `margin-bottom` of exactly `0.5rem` (8px, `mb-2`) +2. **Given** a bot message with 3+ paragraphs in a scroll container with `max-h-96`, **When** rendered, **Then** the container scrolls correctly and does not clip the last paragraph's bottom margin +3. **Given** the session messages list (`MessagesTab.tsx`) with 20+ messages already rendered, **When** a new bot message arrives with the corrected paragraph spacing, **Then** the viewport position of previously rendered messages is unaffected — the layout change to the new message does not cause a visible scroll jump in content above it + +--- + +### User Story 3 — Bold, Italic, and Strikethrough Render Correctly (Priority: P1) + +A user receives a message using `**bold**`, `*italic*`, and `~~strikethrough~~`. All three render with correct visual style in sans-serif. + +**Why this priority**: These are the most common inline formatting elements. They were broken by the `font-mono` wrapper — once the wrapper is fixed, explicit overrides ensure the correct color and weight. + +**Independent Test**: Send a message with all three inline formats. Verify each is visually distinct from plain text. + +**Acceptance Scenarios**: + +1. **Given** `**bold text**` in a bot message, **When** rendered, **Then** the `` element uses `font-semibold` (`font-weight: 600`) and `text-foreground` color +2. **Given** `*italic text*` in a bot message, **When** rendered, **Then** the `` element uses `italic` font style and `text-foreground` color +3. **Given** `~~strikethrough~~` in a bot message, **When** rendered, **Then** the `` element has `text-decoration: line-through` and `opacity: 0.7` (`opacity-70`) + +--- + +### Edge Cases + +- When a bot message contains only a code block (no paragraphs), the `font-mono` removal must not affect code rendering — the `code` override already sets `font-mono` explicitly. +- When the message is actively streaming and the inline wrapper changes to block, the animated cursor `` must appear at the baseline of the last rendered character on the same line (not on a new line below). Verify by streaming a multi-word sentence and confirming cursor stays inline. +- When a message contains nested formatting (`**bold with _italic_ inside**`), both overrides must compose correctly. +- When a scroll container loads earlier messages above the current viewport, paragraph spacing increase must not cause visible scroll jump (MessagesTab scroll-preservation logic). + +--- + +## Requirements + +### Functional Requirements + +- **FR-001**: `message.tsx` line 321 MUST change `className="inline"` to `className="w-full"`. The streaming cursor `` at line 329 MUST NOT be modified — it already positions correctly with `inline-block` and `align-middle`. +- **FR-002**: `message.tsx` line 314 MUST remove `font-mono` from the class list. The resulting class string must be `"text-sm text-foreground"` (plus the existing conditional `!isBot && "py-2 px-4"`). `font-mono` must remain on the `code` component override (lines 57 and 68) and nowhere else in the message content wrapper tree. +- **FR-003**: The `p` component override MUST change `mb-[0.2rem]` to `mb-2`. The `leading-relaxed` class and `text-muted-foreground` color MUST be retained. +- **FR-004**: `defaultComponents` MUST include a `strong` override: `{children}`. +- **FR-005**: `defaultComponents` MUST include an `em` override: `{children}`. +- **FR-006**: `defaultComponents` MUST include a `del` override: `{children}`. +- **FR-007**: The `ul` and `ol` overrides MUST change `space-y-1` to `space-y-1.5` to harmonize list item gap with corrected paragraph spacing. +- **FR-008**: All Tailwind classes using theme colors MUST reference Tailwind utilities (`bg-muted`, `text-muted-foreground`, `text-foreground`, `border-border`) — not raw CSS variable names. + +### Key Entities + +- **`defaultComponents`** (`message.tsx:38`): The `Components` map passed to ``; primary target for all inline-element overrides in this spec. +- **Content wrapper div** (`message.tsx:314`): The outer `
      ` applying `font-mono` and `text-sm` to all markdown output. FR-002 targets this line. +- **ReactMarkdown wrapper div** (`message.tsx:321`): The `
      ` immediately wrapping ``. FR-001 targets this line. +- **Streaming cursor ``** (`message.tsx:329`): The blinking cursor rendered during streaming. Not modified by this spec. + +--- + +## Success Criteria + +### Measurable Outcomes + +- **SC-001**: The content wrapper div at line 314 does not contain `font-mono` in its `className` (grep-verifiable) +- **SC-002**: The ReactMarkdown wrapper div at line 321 has `className="w-full"` (grep-verifiable) +- **SC-003**: `defaultComponents` contains entries for `strong`, `em`, and `del` (grep-verifiable) +- **SC-004**: The `p` override contains `mb-2` not `mb-[0.2rem]` (grep-verifiable) +- **SC-005**: A streaming bot message with paragraph text shows the animated cursor on the same line as the last rendered character, not on a new line below it (manual visual test during streaming) +- **SC-006**: `cd components/frontend && npx vitest run` passes with no new failures diff --git a/specs/012a-markdown-layout-bugs/tasks.md b/specs/012a-markdown-layout-bugs/tasks.md new file mode 100644 index 000000000..580a15137 --- /dev/null +++ b/specs/012a-markdown-layout-bugs/tasks.md @@ -0,0 +1,53 @@ +# Tasks: Fix Core Markdown Layout Bugs + +**Input**: Design documents from `/specs/012a-markdown-layout-bugs/` + +## Phase 1: Structural Wrapper Fixes + +- [ ] T001 [Prereq] `message.tsx:321` — change `className="inline"` to `className="w-full"`. Do NOT touch line 329 (streaming cursor). +- [ ] T002 [FR-002] `message.tsx:314` — remove `font-mono` from the class string. Result: `cn("text-sm text-foreground", !isBot && "py-2 px-4")`. + +### Commit: `fix(frontend): remove inline wrapper and font-mono from message content` + +--- + +## Phase 2: Spacing Fixes + +- [ ] T010 [FR-003] `message.tsx` `p` override — change `mb-[0.2rem]` to `mb-2` +- [ ] T011 [FR-007] `message.tsx` `ul` override — change `space-y-1` to `space-y-1.5` +- [ ] T012 [FR-007] `message.tsx` `ol` override — change `space-y-1` to `space-y-1.5` + +### Commit: `fix(frontend): increase paragraph and list spacing in markdown renderer` + +--- + +## Phase 3: Inline Element Overrides + +- [ ] T020 [FR-004] Add `strong` to `defaultComponents`: `strong: ({ children }) => {children}` +- [ ] T021 [FR-005] Add `em` to `defaultComponents`: `em: ({ children }) => {children}` +- [ ] T022 [FR-006] Add `del` to `defaultComponents`: `del: ({ children }) => {children}` + +### Commit: `feat(frontend): add strong, em, del component overrides to markdown renderer` + +--- + +## Phase 4: Verify + +- [ ] T030 Run `cd components/frontend && npx vitest run` — confirm no failures +- [ ] T031 Manually stream a bot message with `**bold** *italic* ~~strike~~` — confirm sans-serif, no monospace leak +- [ ] T032 Manually send a 3-paragraph bot message — confirm 8px margin visible between paragraphs +- [ ] T033 Visually confirm streaming cursor stays on same line as last character during active streaming +- [ ] T034 Check `MessagesTab` scroll behavior: load a session with 20+ messages, scroll to a middle message, trigger a new message — confirm viewport does not jump + +### Commit (if lint fixes needed): `chore: lint fixes` + +--- + +## Dependencies + +- Phase 1 → independent (start here) +- Phase 2 → independent of Phase 1 (can be parallel) +- Phase 3 → independent of Phases 1 and 2 +- Phase 4 → depends on Phases 1–3 +- Spec 012b → depends on this spec being merged +- Spec 012c → depends on this spec being merged diff --git a/specs/012b-markdown-tables/plan.md b/specs/012b-markdown-tables/plan.md new file mode 100644 index 000000000..35b7c42c1 --- /dev/null +++ b/specs/012b-markdown-tables/plan.md @@ -0,0 +1,32 @@ +# Implementation Plan: Markdown Table Rendering + +**Branch**: `feat/markdown-tables` | **Date**: 2026-04-28 | **Spec**: [spec.md](spec.md) +**Depends on**: `feat/markdown-layout-bugs` (spec 012a) merged to main + +## Summary + +Add six table component overrides to `message.tsx` and share them with `tool-message.tsx` via a new shared constant file. Move `markdownComponents` in `tool-message.tsx` from inside the `ExpandableMarkdown` function body to module level. No new npm dependencies; uses existing Tailwind utilities and react-markdown API. + +## Technical Context + +**Language**: TypeScript/React +**Target files**: `components/frontend/src/components/ui/message.tsx`, `tool-message.tsx`, new `markdown-components.ts` +**Testing**: vitest + manual visual verification +**Risk**: Low — new component overrides do not affect existing elements; `prose-sm` on ExpandableMarkdown may conflict (verify with devtools) + +## Files + +``` +components/frontend/src/ +├── lib/ +│ └── markdown-components.ts # NEW: exports sharedMarkdownComponents (table overrides; extended by 012c) +└── components/ui/ + ├── message.tsx # MODIFY: import and spread sharedMarkdownComponents into defaultComponents + └── tool-message.tsx # MODIFY: move markdownComponents to module level; spread sharedMarkdownComponents +``` + +## Architecture Note + +Extracting table overrides into `src/lib/markdown-components.ts` (not `src/components/ui/`) keeps library-style shared utilities separate from UI components. The exported constant is named `sharedMarkdownComponents` — not `tableComponents` — because spec 012c adds blockquote, hr, img, and h4–h6 overrides to the same constant; a table-specific name would be misleading. Both `message.tsx` and `tool-message.tsx` spread the shared map into their local component definitions, preserving any component-specific overrides. + +`markdownComponents` in `tool-message.tsx` is currently defined inside the `ExpandableMarkdown` function body (lines 66–93). It must be lifted to module level so it can be defined once and reference the imported `sharedMarkdownComponents` without re-instantiating the object on every render. diff --git a/specs/012b-markdown-tables/spec.md b/specs/012b-markdown-tables/spec.md new file mode 100644 index 000000000..2644c243b --- /dev/null +++ b/specs/012b-markdown-tables/spec.md @@ -0,0 +1,109 @@ +# Feature Specification: Markdown Table Rendering + +**Feature Branch**: `feat/markdown-tables` +**Created**: 2026-04-28 +**Status**: Draft +**Input**: GFM tables render as completely unstyled HTML in session messages — no borders, no padding, no header distinction. Depends on spec 012a (layout bugs) being merged first. + +## Overview + +`react-markdown` with `remark-gfm` emits standard ``, ``, ``, ``, `` rows in ``, **When** rendered, **Then** alternating rows do NOT require background striping (out of scope); rows are separated by cell borders alone + +--- + +### User Story 2 — Wide Tables Do Not Break Layout (Priority: P1) + +A user receives a table with 6+ columns whose total width exceeds the message container. The page does not scroll horizontally; the table scrolls independently within its container. + +**Why this priority**: An unstyled wide table causes horizontal page overflow, breaking the entire session layout. + +**Independent Test**: Send a bot message with an 8-column table. Verify the page body has no horizontal scrollbar; the table itself is scrollable. + +**Acceptance Scenarios**: + +1. **Given** a table wider than the message container, **When** rendered, **Then** the `
      `, `` elements for GFM tables. Neither `message.tsx`'s `defaultComponents` nor `tool-message.tsx`'s `markdownComponents` define overrides for any of these elements. The result is bare, browser-default table rendering: no borders, zero padding, headers indistinguishable from data cells, no overflow protection. + +The fix is custom component overrides using theme-aware Tailwind utilities (`border-border`, `bg-muted`, `text-muted-foreground`) so tables adapt to light and dark mode automatically. Wide tables must be wrapped in `overflow-x-auto` to prevent horizontal page scroll. + +**Prerequisite**: Spec 012a must be merged. The `className="inline"` wrapper removed in 012a is what allows tables to render in block flow — without that fix, table rows collapse into inline content regardless of component overrides. + +**Design note on prose vs. custom overrides**: The project uses `prose prose-sm dark:prose-invert` in `file-content-viewer.tsx` and `recent-updates-dialog.tsx`. The custom-overrides approach chosen here for `message.tsx` and `tool-message.tsx` is a pre-existing architectural inconsistency, not a new decision. This spec does not resolve that inconsistency; it extends the custom-overrides pattern already established in `message.tsx` since introducing `prose` would require removing the existing component map, re-testing all message rendering, and aligning `tool-message.tsx`'s `prose-sm` usage — scope for a separate architectural decision. + +--- + +## User Scenarios & Testing + +### User Story 1 — Tables Are Readable (Priority: P1) + +A user asks the agent to summarize data. The agent responds with a GFM table. The user can distinguish columns, read cells, and identify headers. + +**Why this priority**: Tables are currently the most visually broken element — completely unstyled. This is the primary trigger for this spec set. + +**Independent Test**: Send a bot message containing a 3-column, 4-row GFM table. Verify borders, padding, and header background are visible. + +**Acceptance Scenarios**: + +1. **Given** a bot message containing a GFM table, **When** rendered in light mode, **Then** every `` and `` element has a `1px solid` border using the `border-border` Tailwind utility, and padding of `px-3 py-2` +2. **Given** the same table in dark mode, **When** the theme is toggled, **Then** borders and header backgrounds use the same `border-border`/`bg-muted` utilities and remain visible without hardcoded colors +3. **Given** a table's `` cells, **When** rendered, **Then** headers have `bg-muted` background, `font-medium` weight, and `text-left` alignment — visually distinct from `` data cells +4. **Given** a table with `
      ` element is wrapped in a `
      ` and the page body does not scroll horizontally +2. **Given** the overflow wrapper, **When** the viewport is 375px wide (mobile), **Then** the table is independently scrollable and no content is clipped + +--- + +### User Story 3 — Tables Render Consistently in Tool Messages (Priority: P1) + +The same GFM table renders identically whether it appears in a bot message or inside a tool result's `ExpandableMarkdown`. + +**Why this priority**: `tool-message.tsx` uses a separate `markdownComponents` map with no table overrides, so tool result tables are completely unstyled even after message.tsx is fixed. + +**Independent Test**: Render the same table markdown in a bot message and in a tool result. Compare side-by-side — borders, padding, and header styling must match. + +**Acceptance Scenarios**: + +1. **Given** a tool result containing a GFM table rendered via `ExpandableMarkdown`, **When** displayed, **Then** `
      ` section (some GFM parsers allow this) must still render with bordered `` cells. +- A table immediately followed by a paragraph must have `my-2` (8px vertical margin) to separate it from adjacent content. + +--- + +## Requirements + +### Functional Requirements + +- **FR-001**: `defaultComponents` in `message.tsx` MUST add a `table` override: `
      ` and `` elements have the same `border-border`, `px-3 py-2`, and `bg-muted` (for headers) as bot message tables +2. **Given** `tool-message.tsx` applies `prose-sm` as a class on the `ExpandableMarkdown` wrapper, **When** the custom table overrides are applied, **Then** the Tailwind component utility classes on ``/`` take precedence over any `prose-sm` base styles (verified by inspecting computed styles in browser devtools) + +--- + +### Edge Cases + +- A table cell containing inline code, bold text, or a link must render correctly — nested inline elements inside cells inherit the table override's `text-sm text-foreground`. +- A single-column table must render with borders on both sides of the single column. +- A table with no `
      {children}
      `. The `overflow-x-auto` wrapper is on the containing `
      `, not the `` element itself. The `
      ` element itself does NOT carry `border border-border` — with `border-collapse: collapse`, the outermost cell borders already form the table's outer edge; a separate table border is redundant. +- **FR-002**: `defaultComponents` MUST add a `thead` override: `{children}`. +- **FR-003**: `defaultComponents` MUST add a `tbody` override: `{children}` (no additional classes required; row separation comes from cell borders). +- **FR-004**: `defaultComponents` MUST add a `tr` override: `{children}`. The `` element does NOT carry border classes — with `border-collapse: collapse`, row separation comes from the `border border-border` on `` is redundant and produces double borders. +- **FR-005**: `defaultComponents` MUST add a `th` override: ``. +- **FR-006**: `defaultComponents` MUST add a `td` override: ``. +- **FR-007**: `tool-message.tsx`'s `markdownComponents` MUST receive the identical `table`, `thead`, `tbody`, `tr`, `th`, and `td` overrides defined in FR-001 through FR-006. The shared overrides MUST be extracted into a named export `sharedMarkdownComponents` in `src/lib/markdown-components.ts`, importable by both files to prevent drift. The name `sharedMarkdownComponents` (not `tableComponents`) is required because spec 012c adds non-table overrides to the same constant. Additionally, `markdownComponents` in `tool-message.tsx` is currently defined inside the `ExpandableMarkdown` function body — it MUST be moved to module level before the spread can import the shared constant without re-instantiating it on every render. +- **FR-008**: All border, background, and text colors MUST use Tailwind utilities that reference theme variables (`border-border`, `bg-muted`, `text-foreground`, `text-muted-foreground`) — no hardcoded colors. +- **FR-009**: The `prose-sm` class applied to `ExpandableMarkdown` wrappers in `tool-message.tsx` (lines 467, 643, 692) MUST NOT override the custom table component classes. If computed-style conflicts are found during implementation, the `markdownComponents` table overrides take precedence (add `!important` via arbitrary Tailwind values only as a last resort; prefer removing conflicting `prose-sm` classes first). + +### Key Entities + +- **`defaultComponents`** (`message.tsx:38`): Primary target; receives table/thead/tbody/tr/th/td overrides. +- **`markdownComponents`** (`tool-message.tsx:66`): Parallel map that must receive identical table overrides via shared constant. Currently defined inside the `ExpandableMarkdown` function body — must be moved to module level as part of this spec. +- **`sharedMarkdownComponents`** (new shared constant): Extracted component override map to be imported by both `message.tsx` and `tool-message.tsx`. Location: `components/frontend/src/lib/markdown-components.ts`. Named `sharedMarkdownComponents` (not `tableComponents`) to accommodate the non-table entries added in spec 012c. +- **`ExpandableMarkdown`** (`tool-message.tsx`): The component applying `prose-sm` wrapper class; must not conflict with table overrides. + +--- + +## Success Criteria + +### Measurable Outcomes + +- **SC-001**: A 3-column, 4-row GFM table in a bot message renders with visible borders on all cells in both light and dark mode (manual visual test; Cypress: `cy.get('table td').should('have.css', 'border-width', '1px')`) +- **SC-002**: A table with 8 columns in a 1280px viewport does not cause `document.documentElement.scrollWidth > document.documentElement.clientWidth` (Cypress automatable) +- **SC-003**: The same table markdown in a bot message and in a tool result `ExpandableMarkdown` produces visually identical border, padding, and header styling (manual side-by-side comparison) +- **SC-004**: `border-collapse` is applied to the `
      ` and `` cells; a separate `border-b` on `
      {children}{children}
      ` element so adjacent cell borders do not double up (Cypress: `cy.get('table').should('have.css', 'border-collapse', 'collapse')`) +- **SC-005**: `cd components/frontend && npx vitest run` passes with no new failures diff --git a/specs/012b-markdown-tables/tasks.md b/specs/012b-markdown-tables/tasks.md new file mode 100644 index 000000000..d422bc24c --- /dev/null +++ b/specs/012b-markdown-tables/tasks.md @@ -0,0 +1,54 @@ +# Tasks: Markdown Table Rendering + +**Input**: Design documents from `/specs/012b-markdown-tables/` +**Prerequisite**: Spec 012a (`feat/markdown-layout-bugs`) merged to main and this branch rebased on it + +## Phase 1: Shared Component File + +- [ ] T001 [FR-007] Create `components/frontend/src/lib/markdown-components.ts` +- [ ] T002 [FR-001–006] Add `sharedMarkdownComponents` export containing overrides for `table`, `thead`, `tbody`, `tr`, `th`, `td` using the exact Tailwind classes from spec FR-001 through FR-006 +- [ ] T003 Verify the file has no `any` types and exports a `Components`-compatible type from `react-markdown` + +### Commit: `feat(frontend): add shared markdown table component overrides` + +--- + +## Phase 2: Wire into message.tsx + +- [ ] T010 [FR-001] In `message.tsx`, import `sharedMarkdownComponents` from `../../lib/markdown-components` +- [ ] T011 Spread `sharedMarkdownComponents` into `defaultComponents`: `const defaultComponents: Components = { ...sharedMarkdownComponents, code: ..., p: ..., ... }` + +### Commit: `feat(frontend): wire table components into message.tsx markdown renderer` + +--- + +## Phase 3: Wire into tool-message.tsx + +- [ ] T020 [FR-007] In `tool-message.tsx`, move the `markdownComponents` definition from inside the `ExpandableMarkdown` function body to module level (currently lines 66–93, inside the function). This is required before the shared import can work without re-instantiating on every render. +- [ ] T021 [FR-007] Import `sharedMarkdownComponents` from `../../lib/markdown-components` and spread into the now-module-level `markdownComponents`: `const markdownComponents: Components = { ...sharedMarkdownComponents, ... }` +- [ ] T022 [FR-009] Open browser devtools and inspect a rendered tool-result table: verify `th`/`td` computed styles show `border-width: 1px` and `padding-left: 12px` (px-3). Note: `prose-sm` is declared via `@plugin "@tailwindcss/typography"` in `globals.css` but NOT registered in `tailwind.config.js` plugins array — if computed styles show no `prose-sm` interference, that may be why (no action needed). If conflicts ARE observed, remove the `prose-sm` class from the relevant `ExpandableMarkdown` wrappers (lines 467, 643, 692) rather than using `!important` overrides. + +### Commit: `feat(frontend): wire table components into tool-message.tsx markdown renderer` + +--- + +## Phase 4: Verify + +- [ ] T030 Run `cd components/frontend && npx vitest run` — confirm no failures +- [ ] T031 Manually send a bot message with a 3-column, 4-row GFM table in light mode — confirm borders, padding, header background visible +- [ ] T032 Toggle to dark mode — confirm borders and header remain visible (no hardcoded colors) +- [ ] T033 Send a bot message with an 8-column table — confirm no horizontal page scroll (`scrollWidth === clientWidth`) +- [ ] T034 Send a tool-use message that produces a table in the result — confirm same styling as bot message table +- [ ] T035 Check mobile viewport (375px) — confirm table scrolls horizontally within its container, page body does not scroll + +### Commit (if lint fixes needed): `chore: lint fixes` + +--- + +## Dependencies + +- Phase 1 → start here (no dependencies on other phases) +- Phase 2 → depends on Phase 1 +- Phase 3 → depends on Phase 1 (independent of Phase 2) +- Phase 4 → depends on Phases 1–3 +- Spec 012c → can share the `markdown-components.ts` file introduced here diff --git a/specs/012c-markdown-gfm-elements/plan.md b/specs/012c-markdown-gfm-elements/plan.md new file mode 100644 index 000000000..90f9acc1a --- /dev/null +++ b/specs/012c-markdown-gfm-elements/plan.md @@ -0,0 +1,29 @@ +# Implementation Plan: Complete GFM Element Support + +**Branch**: `feat/markdown-gfm-elements` | **Date**: 2026-04-28 | **Spec**: [spec.md](spec.md) +**Depends on**: `feat/markdown-layout-bugs` (spec 012a) merged to main +**Extends**: `markdown-components.ts` introduced in spec 012b (if merged; otherwise create it here) + +## Summary + +Add four element overrides to the shared `markdown-components.ts` constant: `blockquote`, `hr`, `img`, and `h4`/`h5`/`h6`. Both `message.tsx` and `tool-message.tsx` inherit them automatically through the spread pattern introduced in 012b. No new npm dependencies. + +## Technical Context + +**Language**: TypeScript/React +**Target files**: `components/frontend/src/components/ui/markdown-components.ts` (primary), `message.tsx` and `tool-message.tsx` only if 012b is not yet merged +**Testing**: vitest + manual visual verification +**Risk**: Low — all additions are new component overrides that do not interact with existing overrides + +## Files + +``` +components/frontend/src/components/ui/ +└── markdown-components.ts # MODIFY: add blockquote, hr, img, h4, h5, h6 overrides +``` + +If spec 012b has not been merged when this branch is cut, `markdown-components.ts` must be created here and wired into both `message.tsx` and `tool-message.tsx` following the same pattern described in 012b's plan. + +## H4–H6 Decision Gate + +Before implementing T020–T022, confirm with the team whether LLM output in this product ever generates H4–H6 headings. If not, skip those tasks and note the decision in the PR description. The spec marks this story P3 and explicitly allows it to be cut. diff --git a/specs/012c-markdown-gfm-elements/spec.md b/specs/012c-markdown-gfm-elements/spec.md new file mode 100644 index 000000000..94f9566d2 --- /dev/null +++ b/specs/012c-markdown-gfm-elements/spec.md @@ -0,0 +1,129 @@ +# Feature Specification: Complete GFM Element Support + +**Feature Branch**: `feat/markdown-gfm-elements` +**Created**: 2026-04-28 +**Status**: Draft +**Input**: Four GFM markdown elements — blockquotes, horizontal rules, images, and H4–H6 headings — have no component overrides in `message.tsx` or `tool-message.tsx`, rendering with browser defaults that are unstyled or layout-breaking. Depends on spec 012a (layout bugs) being merged first. + +## Overview + +After specs 012a and 012b, `defaultComponents` in `message.tsx` will handle: code, p, h1–h3, ul, ol, li, a, strong, em, del, table, thead, tbody, tr, th, td. This spec adds the remaining common GFM elements: + +- **`blockquote`**: No override — renders as browser-default indented block with no visual distinction from prose +- **`hr`**: No override — renders as browser-default `
      ` with potentially incorrect color in dark mode +- **`img`**: No override — images have no `max-width` constraint and can break the message layout +- **`h4`/`h5`/`h6`**: Only h1–h3 are styled; lower heading levels fall back to browser defaults that may be larger than h3 in some resets + +Blockquotes are elevated to P1 because AI responses use `>` quoting frequently (citations, emphasis, user input echoes). Images and horizontal rules are P2. H4–H6 are P3 and may be cut if the team determines LLM output rarely generates them. + +**Prerequisite**: Spec 012a must be merged. The `font-mono` and `inline` wrapper fixes in 012a are required for blockquotes and other block-level elements to render in proper block flow with correct typography. + +**Parallel development risk**: Both this spec and spec 012b add entries to `src/lib/markdown-components.ts` and export `sharedMarkdownComponents`. If 012b and 012c are developed on concurrent branches, the shared file is a merge conflict surface. The recommended sequence is to merge 012b first and rebase this branch on top. If parallel development is unavoidable, see tasks.md for the conflict resolution protocol. + +--- + +## User Scenarios & Testing + +### User Story 1 — Blockquotes Are Visually Distinct (Priority: P1) + +A user receives a bot response that quotes a user's earlier message or cites a source using `>`. The quoted text is immediately recognizable as quoted content, distinct from prose. + +**Why this priority**: Blockquotes appear in nearly every multi-turn conversation where the agent references prior context. Without styling, quoted text is indistinguishable from regular prose — users lose the attribution signal entirely. + +**Independent Test**: Send a bot message with `> quoted text`. Verify a left border and indentation appear. + +**Acceptance Scenarios**: + +1. **Given** a blockquote (`> text`) in a bot message, **When** rendered, **Then** the `
      ` element has a `border-l-4` left border using `border-border` color and `pl-4` left padding +2. **Given** a blockquote, **When** rendered, **Then** the blockquote text uses `text-muted-foreground` color and `italic` style, visually lighter than surrounding prose +3. **Given** a multi-line blockquote (`> line 1\n> line 2`), **When** rendered, **Then** both lines are contained within the same styled blockquote block with consistent border and padding +4. **Given** a blockquote in dark mode, **When** the theme is toggled, **Then** the border uses `border-border` (theme-aware) and remains visible + +--- + +### User Story 2 — Horizontal Rules Separate Content (Priority: P2) + +A user receives a response using `---` to separate sections. The separator renders as a full-width horizontal line in the theme's border color. + +**Why this priority**: Section separators are a common document structure element. Browser-default `
      ` may render with incorrect color in dark mode. + +**Independent Test**: Send a bot message containing `---`. Verify a horizontal line appears across the message width. + +**Acceptance Scenarios**: + +1. **Given** a horizontal rule (`---`) in a bot message, **When** rendered, **Then** an `
      ` element appears with `border-t border-border my-4` — full-width, theme-colored, with vertical margin +2. **Given** a horizontal rule in dark mode, **When** the theme is toggled, **Then** the border color resolves to the dark-mode `--border` value (no hardcoded color) + +--- + +### User Story 3 — Images Do Not Break Layout (Priority: P2) + +A bot message contains a markdown image (`![alt](url)`). The image renders within the message container without causing horizontal overflow or covering adjacent content. + +**Why this priority**: Without `max-width`, a large image expands beyond the message container and breaks the session layout. This is a layout-safety requirement, not an aesthetic one. + +**Independent Test**: Render a bot message with an image URL pointing to a 2000px-wide image. Verify the image does not cause horizontal overflow. + +**Acceptance Scenarios**: + +1. **Given** a bot message containing `![alt](url)` where the image is wider than the container, **When** rendered, **Then** the `` element has `max-w-full` and `rounded` classes and does not cause `scrollWidth > clientWidth` on the page +2. **Given** a broken image URL, **When** rendered, **Then** the `alt` text is visible and the element does not stretch beyond its container + +--- + +### User Story 4 — H4–H6 Headings Follow the Size Scale (Priority: P3) + +A bot message uses four or more heading levels. H4–H6 are progressively smaller than H3 and larger than body text. + +**Why this priority**: Lower heading levels are rarely generated by LLMs. This is a completeness item. If the team determines H4–H6 never appear in practice, this story may be cut without affecting P1 or P2 deliverables. + +**Independent Test**: Send a message with `####`, `#####`, `######`. Verify each is smaller than the one above and larger than `text-sm` body text. + +**Acceptance Scenarios**: + +1. **Given** an H4 heading, **When** rendered, **Then** the element has `text-xs font-medium text-foreground mb-1` — visually smaller than H3 (`text-sm font-medium`) and larger than `text-xs text-muted-foreground` body text by weight contrast +2. **Given** H4, H5, H6 in the same message, **When** rendered, **Then** each level's `font-size` (computed) is less than or equal to the level above it + +--- + +### Edge Cases + +- A blockquote containing a nested blockquote (`>> nested`) must render with correct nesting (the inner blockquote gets its own border-l-4 and pl-4 inside the outer one). +- A blockquote containing inline code must not apply `font-mono` to the blockquote prose — only to the `` element within it. +- An `` rendered inside a table cell must still obey `max-w-full` to avoid breaking the cell. +- H4–H6 immediately following H1–H3 must have consistent `mb-*` spacing so the heading hierarchy feels uniform. +- A `---` rule following a paragraph with `mb-2` must not produce excessive whitespace — the `my-4` on `
      ` accounts for both top and bottom gap. + +--- + +## Requirements + +### Functional Requirements + +- **FR-001**: `defaultComponents` in `message.tsx` MUST add a `blockquote` override: `
      {children}
      `. +- **FR-002**: `defaultComponents` MUST add an `hr` override: `
      `. +- **FR-003**: `defaultComponents` MUST add an `img` override: `{alt`. The `??` fallback is required because react-markdown types `src` and `alt` as `string | undefined`; omitting the fallback produces a TypeScript error. The override MUST pass through `src` and `alt` props from the markdown source. +- **FR-004**: `defaultComponents` MUST add `h4`, `h5`, and `h6` overrides following the existing h1–h3 scale: + - `h4`: `

      {children}

      ` + - `h5`: `
      {children}
      ` + - `h6`: `
      {children}
      ` +- **FR-005**: The identical `blockquote`, `hr`, `img`, and `h4`/`h5`/`h6` overrides MUST be applied to `tool-message.tsx`'s `markdownComponents`. These SHOULD be extracted into the shared `markdown-components.ts` constant introduced in spec 012b (FR-007) to avoid duplication. +- **FR-006**: All colors MUST use Tailwind utilities referencing theme variables (`border-border`, `text-muted-foreground`, `text-foreground`) — no hardcoded hex or `rgba` values. + +### Key Entities + +- **`defaultComponents`** (`message.tsx:38`): Receives all new element overrides in this spec. +- **`markdownComponents`** (`tool-message.tsx:66`): Receives the same overrides via the shared constant from 012b. +- **`markdown-components.ts`** (shared constant from spec 012b): The canonical source for all shared component overrides; extended in this spec with blockquote, hr, img, h4–h6. + +--- + +## Success Criteria + +### Measurable Outcomes + +- **SC-001**: A bot message with `> blockquote` renders with a visible left border and indented italic text in both light and dark mode (manual visual test; Cypress: `cy.get('blockquote').should('have.css', 'border-left-width', '4px')`) +- **SC-002**: A bot message with `---` renders a full-width `
      ` element with no hardcoded color (inspect computed `border-color` resolves to the theme's `--border` value) +- **SC-003**: A bot message with a 2000px-wide image does not cause horizontal page scroll (`document.documentElement.scrollWidth === document.documentElement.clientWidth`) +- **SC-004**: A bot message with `####` renders an element with `font-size` smaller than the H3 override's `text-sm` (14px) — verifiable with `cy.get('h4').invoke('css', 'font-size').then(parseFloat).should('be.lte', 14)` — or the story is cut if team confirms LLMs never generate H4–H6 +- **SC-005**: `cd components/frontend && npx vitest run` passes with no new failures diff --git a/specs/012c-markdown-gfm-elements/tasks.md b/specs/012c-markdown-gfm-elements/tasks.md new file mode 100644 index 000000000..62e5f055c --- /dev/null +++ b/specs/012c-markdown-gfm-elements/tasks.md @@ -0,0 +1,59 @@ +# Tasks: Complete GFM Element Support + +**Input**: Design documents from `/specs/012c-markdown-gfm-elements/` +**Prerequisite**: Spec 012a (`feat/markdown-layout-bugs`) merged to main and this branch rebased on it +**Coordination with 012b**: Both 012b and 012c write to `components/frontend/src/lib/markdown-components.ts` and export `sharedMarkdownComponents`. + +- **Option A (preferred)**: Merge spec 012b to main first, then rebase this branch. `markdown-components.ts` already exists with the `sharedMarkdownComponents` export — add the new GFM element overrides directly to the same object. No conflict possible. +- **Option B (parallel development)**: Create `markdown-components.ts` at `src/lib/markdown-components.ts` with only the 012c overrides under the same export name. When 012b merges first and you rebase: git will report a conflict in `markdown-components.ts`. Resolve by merging BOTH sets of overrides into the single `sharedMarkdownComponents` object — keep all table entries from 012b AND all GFM element entries from 012c. Do NOT discard either set. + +## Phase 1: Blockquote (P1) + +- [ ] T001 [FR-001] Add `blockquote` to the shared component map in `markdown-components.ts`: + `blockquote: ({ children }) =>
      {children}
      ` +- [ ] T002 Manual visual test: bot message with `> quoted text` — confirm left border, italic, muted color in light and dark mode + +### Commit: `feat(frontend): add blockquote override to markdown renderer` + +--- + +## Phase 2: Horizontal Rule and Image (P2) + +- [ ] T010 [FR-002] Add `hr` to the shared component map: + `hr: () =>
      ` +- [ ] T011 [FR-003] Add `img` to the shared component map: + `img: ({ src, alt }) => {alt` +- [ ] T012 Manual visual test: bot message with `---` — confirm full-width themed separator +- [ ] T013 Manual visual test: bot message with a wide image — confirm no horizontal page scroll + +### Commit: `feat(frontend): add hr and img overrides to markdown renderer` + +--- + +## Phase 3: H4–H6 Headings (P3 — confirm before implementing) + +- [ ] T020 [Decision] Confirm with team: do LLM responses in this product ever generate H4–H6? If NO, skip T021–T023 and document the decision in the PR. +- [ ] T021 [FR-004] Add `h4` override: `h4: ({ children }) =>

      {children}

      ` +- [ ] T022 [FR-004] Add `h5` override: `h5: ({ children }) =>
      {children}
      ` +- [ ] T023 [FR-004] Add `h6` override: `h6: ({ children }) =>
      {children}
      ` + +### Commit: `feat(frontend): add h4-h6 overrides to markdown renderer` + +--- + +## Phase 4: Verify + +- [ ] T030 Run `cd components/frontend && npx vitest run` — confirm no failures +- [ ] T031 Confirm `tool-message.tsx` tables, blockquotes, and separators pick up the new overrides automatically (they spread `markdown-components.ts` — no file change needed if 012b is merged) +- [ ] T032 Grep for `any` types in changed files: `grep -n ': any' components/frontend/src/lib/markdown-components.ts` + +### Commit (if lint fixes needed): `chore: lint fixes` + +--- + +## Dependencies + +- Phase 1 (blockquote) → independent; start here +- Phase 2 (hr, img) → independent of Phase 1 +- Phase 3 (h4–h6) → independent; gated on team decision +- Phase 4 → depends on Phases 1–3 From f5aea38e30bb186450f5aa0fcdf9ac36cb825648 Mon Sep 17 00:00:00 2001 From: jsell-rh Date: Tue, 28 Apr 2026 21:25:10 +0000 Subject: [PATCH 2/4] spec(markdown): reframe specs as desired state Replace bug-fix framing in Overview and Input sections with desired-state descriptions of the expected rendering behavior. Implementation details (what changes) remain in plan.md files. Co-Authored-By: Claude Sonnet 4.6 --- specs/012a-markdown-layout-bugs/spec.md | 22 +++++++++------------- specs/012b-markdown-tables/spec.md | 6 +++--- specs/012c-markdown-gfm-elements/spec.md | 14 +++++++------- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/specs/012a-markdown-layout-bugs/spec.md b/specs/012a-markdown-layout-bugs/spec.md index 30ca66dd8..13ef15317 100644 --- a/specs/012a-markdown-layout-bugs/spec.md +++ b/specs/012a-markdown-layout-bugs/spec.md @@ -3,33 +3,29 @@ **Feature Branch**: `feat/markdown-layout-bugs` **Created**: 2026-04-28 **Status**: Draft -**Input**: Three structural bugs in `message.tsx` that break all markdown rendering: an inline wrapper forcing block content into inline flow, `font-mono` applied to all prose, and a near-zero paragraph margin. Prerequisite for specs 012b and 012c. +**Input**: Desired prose rendering for bot messages: block layout, proportional typography, paragraph spacing, and inline element styling. Prerequisite for specs 012b and 012c. ## Overview -`components/frontend/src/components/ui/message.tsx` has three bugs that degrade every bot message regardless of content: +Bot message prose renders in the application's proportional sans-serif font with clear vertical rhythm. All block-level elements — paragraphs, headings, lists, tables — stack vertically in normal document flow and occupy the full message container width. Paragraph spacing is visually distinct. Bold, italic, and strikethrough text carry the correct weight, style, and `text-foreground` color. Inline code is the only monospace element in a message. -1. **`className="inline"` on the ReactMarkdown wrapper** (line 321) — a `
      ` with `display:inline` wraps all `` output. Block-level elements (paragraphs, tables, lists, headings) collapse into inline flow, breaking margins, table row layout, and list indentation. The streaming cursor `` on line 329 is already `inline-block` with `align-middle` and positions correctly in either inline or block context — the wrapper's `inline` class serves no purpose. +The animated streaming cursor appears at the text baseline of the last rendered character, staying on the same line via `inline-block` and `align-middle`. -2. **`font-mono` on the message content wrapper** (line 314) — applies monospace to all prose: headings, paragraphs, bold, and italic. Only `` and `
      ` should be monospace. The existing `code` component override (line 56) already sets `font-mono`; the wrapper-level class is redundant and harmful.
      +Explicit component overrides for ``, ``, and `` ensure these elements receive the correct Tailwind color utilities rather than relying on browser defaults. List item gap (`space-y-1.5`) is harmonized with paragraph spacing (`mb-2`) for consistent vertical rhythm throughout a message.
       
      -3. **`mb-[0.2rem]` on the paragraph override** (line 78) — 3.2px between paragraphs at `text-sm` (14px line height ~22.75px). The margin is 14% of one line height, visually indistinguishable from no margin at all. Standard readable prose uses `mb-2` (8px, ~35%).
      -
      -Two additional gaps surface once the `font-mono` bug is fixed: `` and `` have no component overrides, so while browser defaults render them correctly in a sans-serif context, they need explicit overrides to receive the correct color (`text-foreground`) and confirm the intended weight/style. The `
        `/`
          ` item gap (`space-y-1`, 4px) is also tighter than the corrected paragraph spacing and should be harmonized. - -This spec must be merged before 012b (tables) or 012c (GFM elements), both of which depend on block-level markdown working correctly. +This spec is prerequisite for specs 012b (table rendering) and 012c (GFM elements) — block-level elements require vertical document flow to render correctly. --- ## User Scenarios & Testing -### Prerequisite Fix — Remove Inline Wrapper (Blocking All Markdown) +### Block Layout for ReactMarkdown Output -This is not a user story with business value of its own; it is a structural fix that unblocks all other markdown user stories. Without it, block-level elements (paragraphs with correct margins, tables, lists, blockquotes) cannot render correctly. +The `` output wrapper uses block layout (`w-full`) so that block-level elements — paragraphs, tables, lists, headings — stack vertically and fill the container. This is the structural foundation that all other markdown rendering (tables, blockquotes, list indentation) depends on. -**Fix**: Change `message.tsx:321` from `
          ` to `
          `. The streaming cursor `` (line 329) requires no changes — its `inline-block` and `align-middle` classes keep it at the text baseline regardless of parent display type. +The streaming cursor `` uses `inline-block` and `align-middle`, which positions it at the text baseline regardless of the parent wrapper's display type — no special handling is required. -**Acceptance**: Given any bot message with multiple block-level markdown elements, when rendered, the elements stack vertically (block flow), table rows are horizontal, and the streaming cursor appears at the text baseline of the last rendered character. +**Acceptance**: Given any bot message with multiple block-level markdown elements, when rendered, the elements stack vertically, table rows are horizontal, and the streaming cursor appears at the text baseline of the last rendered character. --- diff --git a/specs/012b-markdown-tables/spec.md b/specs/012b-markdown-tables/spec.md index 2644c243b..cf01fd86b 100644 --- a/specs/012b-markdown-tables/spec.md +++ b/specs/012b-markdown-tables/spec.md @@ -3,13 +3,13 @@ **Feature Branch**: `feat/markdown-tables` **Created**: 2026-04-28 **Status**: Draft -**Input**: GFM tables render as completely unstyled HTML in session messages — no borders, no padding, no header distinction. Depends on spec 012a (layout bugs) being merged first. +**Input**: GFM table rendering requirements for bot messages and tool results. Depends on spec 012a being merged first. ## Overview -`react-markdown` with `remark-gfm` emits standard `
      `, ``, ``, ``, `
      `, `` elements for GFM tables. Neither `message.tsx`'s `defaultComponents` nor `tool-message.tsx`'s `markdownComponents` define overrides for any of these elements. The result is bare, browser-default table rendering: no borders, zero padding, headers indistinguishable from data cells, no overflow protection. +GFM tables in bot messages and tool results render with visible borders on all cells, padded content, a visually distinct header row, and horizontal overflow protection for wide tables. All colors use theme-aware Tailwind utilities (`border-border`, `bg-muted`, `text-foreground`, `text-muted-foreground`) so tables adapt automatically to light and dark mode without hardcoded values. -The fix is custom component overrides using theme-aware Tailwind utilities (`border-border`, `bg-muted`, `text-muted-foreground`) so tables adapt to light and dark mode automatically. Wide tables must be wrapped in `overflow-x-auto` to prevent horizontal page scroll. +Both `message.tsx` and `tool-message.tsx` share identical table component overrides via `sharedMarkdownComponents` in `src/lib/markdown-components.ts`, ensuring consistent table rendering wherever markdown appears in the UI. **Prerequisite**: Spec 012a must be merged. The `className="inline"` wrapper removed in 012a is what allows tables to render in block flow — without that fix, table rows collapse into inline content regardless of component overrides. diff --git a/specs/012c-markdown-gfm-elements/spec.md b/specs/012c-markdown-gfm-elements/spec.md index 94f9566d2..5afbadabc 100644 --- a/specs/012c-markdown-gfm-elements/spec.md +++ b/specs/012c-markdown-gfm-elements/spec.md @@ -3,18 +3,18 @@ **Feature Branch**: `feat/markdown-gfm-elements` **Created**: 2026-04-28 **Status**: Draft -**Input**: Four GFM markdown elements — blockquotes, horizontal rules, images, and H4–H6 headings — have no component overrides in `message.tsx` or `tool-message.tsx`, rendering with browser defaults that are unstyled or layout-breaking. Depends on spec 012a (layout bugs) being merged first. +**Input**: Visual styling requirements for blockquotes, horizontal rules, images, and extended heading levels in bot messages. Depends on spec 012a being merged first. ## Overview -After specs 012a and 012b, `defaultComponents` in `message.tsx` will handle: code, p, h1–h3, ul, ol, li, a, strong, em, del, table, thead, tbody, tr, th, td. This spec adds the remaining common GFM elements: +Bot messages containing blockquotes, horizontal rules, images, or extended heading levels render with appropriate visual treatment consistent with the rest of the message design: -- **`blockquote`**: No override — renders as browser-default indented block with no visual distinction from prose -- **`hr`**: No override — renders as browser-default `
      ` with potentially incorrect color in dark mode -- **`img`**: No override — images have no `max-width` constraint and can break the message layout -- **`h4`/`h5`/`h6`**: Only h1–h3 are styled; lower heading levels fall back to browser defaults that may be larger than h3 in some resets +- **`blockquote`** (P1): Themed left border (`border-l-4 border-border`) with indented italic text in `text-muted-foreground`, making quoted content immediately distinguishable from prose. Elevated to P1 because AI responses use `>` quoting frequently — citations, emphasis, user input echoes. +- **`hr`** (P2): Full-width line in the theme's border color (`border-t border-border`) with vertical margin, providing visual section separation in dark and light mode. +- **`img`** (P2): Constrained to container width (`max-w-full`) with rounded corners, preventing layout overflow regardless of source image dimensions. +- **`h4`/`h5`/`h6`** (P3): Follow the established heading scale — progressively smaller than H3 (`text-sm font-medium`). May be cut if the team confirms LLM output does not generate these levels. -Blockquotes are elevated to P1 because AI responses use `>` quoting frequently (citations, emphasis, user input echoes). Images and horizontal rules are P2. H4–H6 are P3 and may be cut if the team determines LLM output rarely generates them. +All overrides apply identically to `tool-message.tsx` via the shared `sharedMarkdownComponents` constant from spec 012b. After this spec, `sharedMarkdownComponents` covers all common GFM elements: tables, blockquote, hr, img, and h4–h6. **Prerequisite**: Spec 012a must be merged. The `font-mono` and `inline` wrapper fixes in 012a are required for blockquotes and other block-level elements to render in proper block flow with correct typography. From a696ea0e07b955e5fcad8c4a07ce282be5475454 Mon Sep 17 00:00:00 2001 From: jsell-rh Date: Tue, 28 Apr 2026 21:26:14 +0000 Subject: [PATCH 3/4] spec(markdown): rename dirs to reflect desired-state framing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 012a-markdown-layout-bugs → 012a-markdown-prose-rendering 012b-markdown-tables → 012b-markdown-table-rendering 012c-markdown-gfm-elements → 012c-markdown-gfm-rendering Co-Authored-By: Claude Sonnet 4.6 --- specs/001-auto-doc-screenshots/checklists/requirements.md | 0 specs/001-auto-doc-screenshots/plan.md | 0 specs/001-auto-doc-screenshots/quickstart.md | 0 specs/001-auto-doc-screenshots/research.md | 0 specs/001-auto-doc-screenshots/spec.md | 0 specs/001-auto-doc-screenshots/tasks.md | 0 specs/001-coderabbit-integration/checklists/requirements.md | 0 specs/001-coderabbit-integration/spec.md | 0 specs/010-advanced-sdk-options/plan.md | 0 specs/010-advanced-sdk-options/spec.md | 0 specs/010-advanced-sdk-options/tasks.md | 0 specs/011-session-options-menu/plan.md | 0 specs/011-session-options-menu/spec.md | 0 specs/011-session-options-menu/tasks.md | 0 .../plan.md | 0 .../spec.md | 0 .../tasks.md | 2 +- .../plan.md | 0 .../spec.md | 0 .../tasks.md | 2 +- .../plan.md | 0 .../spec.md | 0 .../tasks.md | 2 +- 23 files changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 specs/001-auto-doc-screenshots/checklists/requirements.md mode change 100644 => 100755 specs/001-auto-doc-screenshots/plan.md mode change 100644 => 100755 specs/001-auto-doc-screenshots/quickstart.md mode change 100644 => 100755 specs/001-auto-doc-screenshots/research.md mode change 100644 => 100755 specs/001-auto-doc-screenshots/spec.md mode change 100644 => 100755 specs/001-auto-doc-screenshots/tasks.md mode change 100644 => 100755 specs/001-coderabbit-integration/checklists/requirements.md mode change 100644 => 100755 specs/001-coderabbit-integration/spec.md mode change 100644 => 100755 specs/010-advanced-sdk-options/plan.md mode change 100644 => 100755 specs/010-advanced-sdk-options/spec.md mode change 100644 => 100755 specs/010-advanced-sdk-options/tasks.md mode change 100644 => 100755 specs/011-session-options-menu/plan.md mode change 100644 => 100755 specs/011-session-options-menu/spec.md mode change 100644 => 100755 specs/011-session-options-menu/tasks.md rename specs/{012a-markdown-layout-bugs => 012a-markdown-prose-rendering}/plan.md (100%) rename specs/{012a-markdown-layout-bugs => 012a-markdown-prose-rendering}/spec.md (100%) rename specs/{012a-markdown-layout-bugs => 012a-markdown-prose-rendering}/tasks.md (96%) rename specs/{012b-markdown-tables => 012b-markdown-table-rendering}/plan.md (100%) rename specs/{012b-markdown-tables => 012b-markdown-table-rendering}/spec.md (100%) rename specs/{012b-markdown-tables => 012b-markdown-table-rendering}/tasks.md (97%) rename specs/{012c-markdown-gfm-elements => 012c-markdown-gfm-rendering}/plan.md (100%) rename specs/{012c-markdown-gfm-elements => 012c-markdown-gfm-rendering}/spec.md (100%) rename specs/{012c-markdown-gfm-elements => 012c-markdown-gfm-rendering}/tasks.md (98%) diff --git a/specs/001-auto-doc-screenshots/checklists/requirements.md b/specs/001-auto-doc-screenshots/checklists/requirements.md old mode 100644 new mode 100755 diff --git a/specs/001-auto-doc-screenshots/plan.md b/specs/001-auto-doc-screenshots/plan.md old mode 100644 new mode 100755 diff --git a/specs/001-auto-doc-screenshots/quickstart.md b/specs/001-auto-doc-screenshots/quickstart.md old mode 100644 new mode 100755 diff --git a/specs/001-auto-doc-screenshots/research.md b/specs/001-auto-doc-screenshots/research.md old mode 100644 new mode 100755 diff --git a/specs/001-auto-doc-screenshots/spec.md b/specs/001-auto-doc-screenshots/spec.md old mode 100644 new mode 100755 diff --git a/specs/001-auto-doc-screenshots/tasks.md b/specs/001-auto-doc-screenshots/tasks.md old mode 100644 new mode 100755 diff --git a/specs/001-coderabbit-integration/checklists/requirements.md b/specs/001-coderabbit-integration/checklists/requirements.md old mode 100644 new mode 100755 diff --git a/specs/001-coderabbit-integration/spec.md b/specs/001-coderabbit-integration/spec.md old mode 100644 new mode 100755 diff --git a/specs/010-advanced-sdk-options/plan.md b/specs/010-advanced-sdk-options/plan.md old mode 100644 new mode 100755 diff --git a/specs/010-advanced-sdk-options/spec.md b/specs/010-advanced-sdk-options/spec.md old mode 100644 new mode 100755 diff --git a/specs/010-advanced-sdk-options/tasks.md b/specs/010-advanced-sdk-options/tasks.md old mode 100644 new mode 100755 diff --git a/specs/011-session-options-menu/plan.md b/specs/011-session-options-menu/plan.md old mode 100644 new mode 100755 diff --git a/specs/011-session-options-menu/spec.md b/specs/011-session-options-menu/spec.md old mode 100644 new mode 100755 diff --git a/specs/011-session-options-menu/tasks.md b/specs/011-session-options-menu/tasks.md old mode 100644 new mode 100755 diff --git a/specs/012a-markdown-layout-bugs/plan.md b/specs/012a-markdown-prose-rendering/plan.md similarity index 100% rename from specs/012a-markdown-layout-bugs/plan.md rename to specs/012a-markdown-prose-rendering/plan.md diff --git a/specs/012a-markdown-layout-bugs/spec.md b/specs/012a-markdown-prose-rendering/spec.md similarity index 100% rename from specs/012a-markdown-layout-bugs/spec.md rename to specs/012a-markdown-prose-rendering/spec.md diff --git a/specs/012a-markdown-layout-bugs/tasks.md b/specs/012a-markdown-prose-rendering/tasks.md similarity index 96% rename from specs/012a-markdown-layout-bugs/tasks.md rename to specs/012a-markdown-prose-rendering/tasks.md index 580a15137..2dc434e49 100644 --- a/specs/012a-markdown-layout-bugs/tasks.md +++ b/specs/012a-markdown-prose-rendering/tasks.md @@ -1,6 +1,6 @@ # Tasks: Fix Core Markdown Layout Bugs -**Input**: Design documents from `/specs/012a-markdown-layout-bugs/` +**Input**: Design documents from `/specs/012a-markdown-prose-rendering/` ## Phase 1: Structural Wrapper Fixes diff --git a/specs/012b-markdown-tables/plan.md b/specs/012b-markdown-table-rendering/plan.md similarity index 100% rename from specs/012b-markdown-tables/plan.md rename to specs/012b-markdown-table-rendering/plan.md diff --git a/specs/012b-markdown-tables/spec.md b/specs/012b-markdown-table-rendering/spec.md similarity index 100% rename from specs/012b-markdown-tables/spec.md rename to specs/012b-markdown-table-rendering/spec.md diff --git a/specs/012b-markdown-tables/tasks.md b/specs/012b-markdown-table-rendering/tasks.md similarity index 97% rename from specs/012b-markdown-tables/tasks.md rename to specs/012b-markdown-table-rendering/tasks.md index d422bc24c..d9dae8a5e 100644 --- a/specs/012b-markdown-tables/tasks.md +++ b/specs/012b-markdown-table-rendering/tasks.md @@ -1,6 +1,6 @@ # Tasks: Markdown Table Rendering -**Input**: Design documents from `/specs/012b-markdown-tables/` +**Input**: Design documents from `/specs/012b-markdown-table-rendering/` **Prerequisite**: Spec 012a (`feat/markdown-layout-bugs`) merged to main and this branch rebased on it ## Phase 1: Shared Component File diff --git a/specs/012c-markdown-gfm-elements/plan.md b/specs/012c-markdown-gfm-rendering/plan.md similarity index 100% rename from specs/012c-markdown-gfm-elements/plan.md rename to specs/012c-markdown-gfm-rendering/plan.md diff --git a/specs/012c-markdown-gfm-elements/spec.md b/specs/012c-markdown-gfm-rendering/spec.md similarity index 100% rename from specs/012c-markdown-gfm-elements/spec.md rename to specs/012c-markdown-gfm-rendering/spec.md diff --git a/specs/012c-markdown-gfm-elements/tasks.md b/specs/012c-markdown-gfm-rendering/tasks.md similarity index 98% rename from specs/012c-markdown-gfm-elements/tasks.md rename to specs/012c-markdown-gfm-rendering/tasks.md index 62e5f055c..50cea72ae 100644 --- a/specs/012c-markdown-gfm-elements/tasks.md +++ b/specs/012c-markdown-gfm-rendering/tasks.md @@ -1,6 +1,6 @@ # Tasks: Complete GFM Element Support -**Input**: Design documents from `/specs/012c-markdown-gfm-elements/` +**Input**: Design documents from `/specs/012c-markdown-gfm-rendering/` **Prerequisite**: Spec 012a (`feat/markdown-layout-bugs`) merged to main and this branch rebased on it **Coordination with 012b**: Both 012b and 012c write to `components/frontend/src/lib/markdown-components.ts` and export `sharedMarkdownComponents`. From 866d99c095d1f2b69c2b2f66c12304729759c4b5 Mon Sep 17 00:00:00 2001 From: jsell-rh Date: Tue, 28 Apr 2026 21:32:37 +0000 Subject: [PATCH 4/4] chore: restore file modes on non-markdown specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accidentally included pre-existing mode changes (100644 → 100755) on 001, 010, and 011 specs in the previous rename commit. No content changed. Co-Authored-By: Claude Sonnet 4.6 --- specs/001-auto-doc-screenshots/checklists/requirements.md | 0 specs/001-auto-doc-screenshots/plan.md | 0 specs/001-auto-doc-screenshots/quickstart.md | 0 specs/001-auto-doc-screenshots/research.md | 0 specs/001-auto-doc-screenshots/spec.md | 0 specs/001-auto-doc-screenshots/tasks.md | 0 specs/001-coderabbit-integration/checklists/requirements.md | 0 specs/001-coderabbit-integration/spec.md | 0 specs/010-advanced-sdk-options/plan.md | 0 specs/010-advanced-sdk-options/spec.md | 0 specs/010-advanced-sdk-options/tasks.md | 0 specs/011-session-options-menu/plan.md | 0 specs/011-session-options-menu/spec.md | 0 specs/011-session-options-menu/tasks.md | 0 14 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 specs/001-auto-doc-screenshots/checklists/requirements.md mode change 100755 => 100644 specs/001-auto-doc-screenshots/plan.md mode change 100755 => 100644 specs/001-auto-doc-screenshots/quickstart.md mode change 100755 => 100644 specs/001-auto-doc-screenshots/research.md mode change 100755 => 100644 specs/001-auto-doc-screenshots/spec.md mode change 100755 => 100644 specs/001-auto-doc-screenshots/tasks.md mode change 100755 => 100644 specs/001-coderabbit-integration/checklists/requirements.md mode change 100755 => 100644 specs/001-coderabbit-integration/spec.md mode change 100755 => 100644 specs/010-advanced-sdk-options/plan.md mode change 100755 => 100644 specs/010-advanced-sdk-options/spec.md mode change 100755 => 100644 specs/010-advanced-sdk-options/tasks.md mode change 100755 => 100644 specs/011-session-options-menu/plan.md mode change 100755 => 100644 specs/011-session-options-menu/spec.md mode change 100755 => 100644 specs/011-session-options-menu/tasks.md diff --git a/specs/001-auto-doc-screenshots/checklists/requirements.md b/specs/001-auto-doc-screenshots/checklists/requirements.md old mode 100755 new mode 100644 diff --git a/specs/001-auto-doc-screenshots/plan.md b/specs/001-auto-doc-screenshots/plan.md old mode 100755 new mode 100644 diff --git a/specs/001-auto-doc-screenshots/quickstart.md b/specs/001-auto-doc-screenshots/quickstart.md old mode 100755 new mode 100644 diff --git a/specs/001-auto-doc-screenshots/research.md b/specs/001-auto-doc-screenshots/research.md old mode 100755 new mode 100644 diff --git a/specs/001-auto-doc-screenshots/spec.md b/specs/001-auto-doc-screenshots/spec.md old mode 100755 new mode 100644 diff --git a/specs/001-auto-doc-screenshots/tasks.md b/specs/001-auto-doc-screenshots/tasks.md old mode 100755 new mode 100644 diff --git a/specs/001-coderabbit-integration/checklists/requirements.md b/specs/001-coderabbit-integration/checklists/requirements.md old mode 100755 new mode 100644 diff --git a/specs/001-coderabbit-integration/spec.md b/specs/001-coderabbit-integration/spec.md old mode 100755 new mode 100644 diff --git a/specs/010-advanced-sdk-options/plan.md b/specs/010-advanced-sdk-options/plan.md old mode 100755 new mode 100644 diff --git a/specs/010-advanced-sdk-options/spec.md b/specs/010-advanced-sdk-options/spec.md old mode 100755 new mode 100644 diff --git a/specs/010-advanced-sdk-options/tasks.md b/specs/010-advanced-sdk-options/tasks.md old mode 100755 new mode 100644 diff --git a/specs/011-session-options-menu/plan.md b/specs/011-session-options-menu/plan.md old mode 100755 new mode 100644 diff --git a/specs/011-session-options-menu/spec.md b/specs/011-session-options-menu/spec.md old mode 100755 new mode 100644 diff --git a/specs/011-session-options-menu/tasks.md b/specs/011-session-options-menu/tasks.md old mode 100755 new mode 100644