` 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** `
` 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 `` 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: ``. 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 `| ` and ` | ` cells; a separate `border-b` on ` |
` is redundant and produces double borders.
+- **FR-005**: `defaultComponents` MUST add a `th` override: `| {children} | `.
+- **FR-006**: `defaultComponents` MUST add a `td` override: `{children} | `.
+- **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 `` 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-table-rendering/tasks.md b/specs/012b-markdown-table-rendering/tasks.md
new file mode 100644
index 000000000..d9dae8a5e
--- /dev/null
+++ b/specs/012b-markdown-table-rendering/tasks.md
@@ -0,0 +1,54 @@
+# Tasks: Markdown Table Rendering
+
+**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
+
+- [ ] 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-rendering/plan.md b/specs/012c-markdown-gfm-rendering/plan.md
new file mode 100644
index 000000000..90f9acc1a
--- /dev/null
+++ b/specs/012c-markdown-gfm-rendering/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-rendering/spec.md b/specs/012c-markdown-gfm-rendering/spec.md
new file mode 100644
index 000000000..5afbadabc
--- /dev/null
+++ b/specs/012c-markdown-gfm-rendering/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**: Visual styling requirements for blockquotes, horizontal rules, images, and extended heading levels in bot messages. Depends on spec 012a being merged first.
+
+## Overview
+
+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`** (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.
+
+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.
+
+**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 (``). 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 `` 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: `
`. 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-rendering/tasks.md b/specs/012c-markdown-gfm-rendering/tasks.md
new file mode 100644
index 000000000..50cea72ae
--- /dev/null
+++ b/specs/012c-markdown-gfm-rendering/tasks.md
@@ -0,0 +1,59 @@
+# Tasks: Complete GFM Element Support
+
+**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`.
+
+- **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 }) =>
`
+- [ ] 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