` unchanged in automatic mode.
+- Measure the table and its header cells after layout. A `ResizeObserver` keeps
+ separator positions aligned while automatic content or container geometry
+ changes, and controlled column-width updates explicitly trigger another
+ measurement even when the table's outer box is unchanged. Observe character
+ data as well as child-list mutations so streamed text updates also remeasure.
+- Place focusable `role="separator"` controls in an overlay within the table's
+ scroll content. Each control is centered on an internal boundary, spans the
+ full table height, and scrolls with the table. Omit a boundary while either
+ adjacent measured column is below the 64-pixel minimum.
+- Give each separator an adjacent-column accessible name, vertical orientation,
+ a 64-pixel minimum, the rounded current left-column width, and a maximum equal
+ to the pair total minus the right-column minimum. Verify those rendered ARIA
+ semantics in Playwright alongside keyboard behavior.
+- On first adjustment, snapshot measured widths and apply them through a
+ `
` with fixed table layout. Keep the measured table width constant so
+ one adjacent column grows exactly as the other shrinks. Do not activate fixed
+ widths until pointer movement has a non-zero delta.
+- Use pointer capture for dragging. Restore the drag-start snapshot on
+ `pointercancel`, and clean up cursor and selection overrides on every exit
+ path.
+- Double-click or `Enter` clears the custom width state and returns the table to
+ CSS automatic layout. A changed column count does the same.
+- Render controls only when the responsive capability is non-phone and
+ fine-pointer. Tables that cannot provide valid multi-column geometry stay
+ automatic. A capability change during a drag also clears the active drag and
+ document-level cursor and selection overrides.
+
+### Styling and localization
+
+Add narrowly scoped Markdown-table classes in `apps/web/app/globals.css` for the
+relative stage, separator hit area, and hover/focus/active line. Preserve the
+existing `overflow-x-auto` wrapper and the two-/three-column wrapping and
+four-plus-column minimum-width policies.
+
+Add the separator's accessible name to the shared English locale and access it
+through `t()`; no visible copy or public documentation is added.
+
+## Responsive and mobile implementation
+
+The nearest mobile exemplar is the current task-chat Markdown table tested by
+`mobile-markdown-wrap.spec.ts`. The component shares parsing, measurement-safe
+markup, wrapping, and the local scroll owner at every viewport. It suppresses
+the separator overlay on phones and coarse pointers because a usable touch hit
+target would obstruct narrow table content. Mobile capability remains complete
+for reading and scrolling; only the optional precision adjustment is desktop
+specific.
+
+## Tests
+
+### Unit tests
+
+- RED: adjacent-pair resizing grows and shrinks by equal amounts.
+- RED: both drag directions clamp at 64 pixels without changing the pair total.
+- RED: keyboard deltas use the same geometry behavior.
+- Cover the resize hook's keyboard branches, capability-disable reset, and
+ column-count-change reset in a focused hook test.
+- GREEN: implement the geometry module and table component, then wire it into
+ `markdown-components.tsx`.
+- The repository does not add isolated React component tests for pure UI.
+ Playwright covers separator semantics, rendered geometry, reset behavior, and
+ responsive capability gating through the real shared renderer.
+
+Likely files:
+
+- `apps/web/lib/markdown/table-resize.ts`
+- `apps/web/lib/markdown/table-resize.test.ts`
+- `apps/web/components/shared/resizable-markdown-table.tsx`
+- `apps/web/components/shared/use-markdown-table-resize.ts`
+- `apps/web/components/shared/use-markdown-table-resize.test.ts`
+- `apps/web/components/shared/markdown-components.tsx`
+- `apps/web/app/globals.css`
+- `apps/web/src/locales/en/common.json`
+
+### Browser tests
+
+Extend the existing chat Markdown wrapping suites rather than creating a second
+fixture path.
+
+- Desktop: render a three-column table, start a drag at a body-row point on the
+ full-height boundary, and assert adjacent widths change, the untouched column
+ and total width remain stable, and minimum-width clamping works.
+- Desktop: assert double-click returns near the initial automatic proportions;
+ assert arrow keys resize and `Enter` resets.
+- Desktop wide table: scroll its local wrapper and assert separators remain
+ aligned and no chat/document overflow appears.
+- Mobile Chrome: assert no separators are exposed while the existing ordinary
+ wrapping and wide-table local scrolling checks still pass.
+
+Likely files:
+
+- `apps/web/e2e/tests/chat/markdown-wrap.spec.ts`
+- `apps/web/e2e/tests/chat/mobile-markdown-wrap.spec.ts`
+
+## Implementation waves
+
+Execution is sequential in the primary conversation. The E2E work depends on
+the renderer contract, and this package does not authorize subagent use.
+
+- [x] [Task 01: Build the ephemeral resize renderer](task-01-build-resize-renderer.md)
+- [x] [Task 02: Prove desktop interaction and mobile parity](task-02-prove-resize-interaction.md)
+
+## Verification
+
+```bash
+(cd apps && pnpm install --frozen-lockfile)
+(cd apps && pnpm --filter @kandev/web exec vitest run \
+ lib/markdown/table-resize.test.ts \
+ components/shared/use-markdown-table-resize.test.ts \
+ components/shared/markdown-components.test.tsx)
+(cd apps/web && pnpm run typecheck)
+(cd apps && pnpm --filter @kandev/web lint)
+make build-web
+(cd apps/web && pnpm e2e:run tests/chat/markdown-wrap.spec.ts)
+(cd apps/web && pnpm e2e:run --project mobile-chrome tests/chat/mobile-markdown-wrap.spec.ts)
+```
+
+## Documentation impact
+
+The durable UI spec and plan cover the new behavior. No public CLI, config, API,
+or workflow documentation changes are required.
+
+## Risks
+
+- Separator geometry must use the scroll content's coordinate system; viewport
+ coordinates would drift after horizontal scrolling.
+- Streaming updates can change row height or column count after initial render.
+ Observation must update positions, while structure changes must discard custom
+ widths.
+- Fixed widths must activate only after user adjustment. Applying them on first
+ render would regress responsive automatic wrapping.
+- Pointer cancellation and unmount must restore global cursor and text-selection
+ state to avoid a sticky resize cursor elsewhere in the app.
+- Component tests run in jsdom, so deterministic width math belongs in a pure
+ helper and real geometry assertions belong in Playwright.
+
+## Open questions
+
+None.
+
+## Verification results
+
+- `vitest`: 42 focused geometry/hook/shared-renderer tests passed.
+- `pnpm run typecheck`: passed.
+- `pnpm --filter @kandev/web lint`: passed with zero warnings.
+- `make build-web`: passed.
+- Desktop `markdown-wrap.spec.ts`: 8 tests passed.
+- Mobile Chrome `mobile-markdown-wrap.spec.ts`: 2 tests passed.
+- `git diff --check`: passed.
diff --git a/docs/plans/resizable-markdown-tables/task-01-build-resize-renderer.md b/docs/plans/resizable-markdown-tables/task-01-build-resize-renderer.md
new file mode 100644
index 0000000000..1e4a718c89
--- /dev/null
+++ b/docs/plans/resizable-markdown-tables/task-01-build-resize-renderer.md
@@ -0,0 +1,99 @@
+---
+id: "01-build-resize-renderer"
+title: "Build the ephemeral Markdown table resize renderer"
+status: done
+wave: 1
+depends_on: []
+plan: "plan.md"
+spec: "../../specs/ui/resizable-markdown-tables.md"
+---
+
+# Task 01: Build the Ephemeral Resize Renderer
+
+## Acceptance
+
+- Shared Markdown tables render accessible full-height internal separators only
+ on non-phone fine-pointer layouts with valid multi-column geometry.
+- Every separator exposes its adjacent-column accessible name, vertical
+ orientation, 64-pixel minimum, rounded current left-column width, and the
+ pair-derived maximum through the complete ARIA separator contract.
+- Pointer and keyboard adjustment resize only adjacent columns, preserve the
+ table width, and enforce a 64-pixel minimum.
+- Boundaries with either adjacent measured column below 64 pixels are omitted;
+ adjacent pairs below 128 pixels therefore remain unchanged without exposing
+ an invalid or immovable separator.
+- Double-click and `Enter` clear all custom widths and restore CSS automatic
+ layout; unmount, reload, and column-count changes retain no state.
+- Existing two-/three-column wrapping and four-plus-column local scrolling CSS
+ remains authoritative before the first user adjustment.
+- Pointer cancellation restores drag-start widths and all pointer/cursor/
+ selection cleanup paths are covered.
+
+## TDD sequence
+
+1. RED: add pure tests for equal-and-opposite width changes, both clamp
+ directions, unchanged pair totals, and 8-pixel keyboard deltas.
+2. RED: add a browser regression proving the shared renderer has no resize
+ separator before production behavior changes.
+3. GREEN: implement the geometry helper and resizable table component. Keep
+ pure UI behavior in Playwright instead of adding isolated React tests.
+4. GREEN: replace the shared Markdown table wrapper, add scoped styles and the
+ localized accessible name, then rerun the existing renderer suite.
+5. REFACTOR: keep DOM measurement and pointer lifecycle local to the component;
+ do not leak resizing concerns into Markdown normalization or message stores.
+
+## Verification
+
+```bash
+(cd apps && pnpm install --frozen-lockfile)
+(cd apps && pnpm --filter @kandev/web exec vitest run \
+ lib/markdown/table-resize.test.ts \
+ components/shared/use-markdown-table-resize.test.ts \
+ components/shared/markdown-components.test.tsx)
+(cd apps/web && pnpm run typecheck)
+(cd apps && pnpm --filter @kandev/web lint)
+git diff --check
+```
+
+## Files likely touched
+
+- `apps/web/lib/markdown/table-resize.ts`
+- `apps/web/lib/markdown/table-resize.test.ts`
+- `apps/web/components/shared/resizable-markdown-table.tsx`
+- `apps/web/components/shared/use-markdown-table-resize.ts`
+- `apps/web/components/shared/markdown-components.tsx`
+- `apps/web/app/globals.css`
+- `apps/web/src/locales/en/common.json`
+
+## Dependencies
+
+None.
+
+## Parallelism
+
+`sequential`. Measurement behavior, component semantics, and shared renderer
+wiring must stay in one RED-GREEN cycle.
+
+## Inputs
+
+- `docs/specs/ui/resizable-markdown-tables.md`
+- Existing table wrapper in `apps/web/components/shared/markdown-components.tsx`
+- Existing wrapping rules in `apps/web/app/globals.css`
+- Fine-pointer and phone capability in `apps/web/hooks/use-responsive-breakpoint.ts`
+- Pointer-capture cleanup patterns in `apps/web/components/app-status-bar/`
+
+## Output contract
+
+Record the expected RED failures, focused GREEN command results, files changed,
+and the rendered geometry proven in Playwright.
+
+## Result
+
+- RED: three geometry assertions failed against the no-op width helper; the
+ desktop Playwright regression failed because no separator existed.
+- GREEN: 32 focused geometry/shared-renderer tests passed; TypeScript, frontend
+ lint, and `git diff --check` passed.
+- The renderer now provides fine-pointer separators, pointer capture, 64-pixel
+ adjacent clamping, keyboard adjustment, ephemeral reset, structure-change
+ invalidation, and cleanup on cancellation/unmount.
+- Real table geometry and responsive behavior are covered by Task 02.
diff --git a/docs/plans/resizable-markdown-tables/task-02-prove-resize-interaction.md b/docs/plans/resizable-markdown-tables/task-02-prove-resize-interaction.md
new file mode 100644
index 0000000000..69ac009d33
--- /dev/null
+++ b/docs/plans/resizable-markdown-tables/task-02-prove-resize-interaction.md
@@ -0,0 +1,86 @@
+---
+id: "02-prove-resize-interaction"
+title: "Prove Markdown table resizing and mobile parity"
+status: done
+wave: 2
+depends_on: ["01-build-resize-renderer"]
+plan: "plan.md"
+spec: "../../specs/ui/resizable-markdown-tables.md"
+---
+
+# Task 02: Prove Resize Interaction and Mobile Parity
+
+## Acceptance
+
+- A desktop browser test drags a separator from its body-row hit area and proves
+ equal-and-opposite adjacent width changes, an unchanged third column, a stable
+ table width, and 64-pixel clamping.
+- Desktop tests prove double-click reset, arrow-key adjustment, and `Enter` reset.
+- A locally scrolling wide table keeps separators aligned after scrolling and
+ creates no chat- or document-level horizontal overflow.
+- Mobile Chrome exposes no separator controls and preserves ordinary table
+ wrapping plus wide-table local scrolling.
+- Existing Markdown wrapping regressions remain green.
+
+## TDD sequence
+
+1. RED: extend the desktop wrapping fixture with a three-column interaction case
+ and prove current tables have no separator.
+2. GREEN: drive pointer, double-click, and keyboard behavior against the Task 01
+ implementation; refine only externally observable defects.
+3. RED/GREEN: add mobile absence and parity assertions to the existing phone
+ fixture.
+4. Run both complete focused files to catch wrapping or scroll-owner regressions.
+
+## Verification
+
+```bash
+make build-web
+(cd apps/web && pnpm e2e:run tests/chat/markdown-wrap.spec.ts)
+(cd apps/web && pnpm e2e:run --project mobile-chrome tests/chat/mobile-markdown-wrap.spec.ts)
+(cd apps/web && pnpm run typecheck)
+(cd apps && pnpm --filter @kandev/web lint)
+git diff --check
+```
+
+## Files likely touched
+
+- `apps/web/e2e/tests/chat/markdown-wrap.spec.ts`
+- `apps/web/e2e/tests/chat/mobile-markdown-wrap.spec.ts`
+- Task 01 production files only if browser evidence reveals an interaction bug
+
+## Dependencies
+
+- `01-build-resize-renderer`
+
+## Parallelism
+
+`sequential`. Browser assertions depend on the completed renderer contract and
+may legitimately feed a defect back into Task 01 files.
+
+## Inputs
+
+- Existing exact pnpm-table and wide-table fixtures in both wrapping suites
+- `docs/specs/ui/resizable-markdown-tables.md`, especially desktop interaction,
+ local-scroll ownership, and mobile capability scenarios
+
+## Output contract
+
+Report desktop and mobile command results, before/after column and table widths,
+the tested hit-point location, local/document overflow measurements, files
+changed, and any unsupported browser behavior.
+
+## Result
+
+- RED: the desktop user-flow test failed because the shared renderer exposed no
+ resize separator.
+- GREEN: the rebuilt production bundle passed all 8 desktop Markdown wrapping
+ tests and both Mobile Chrome Markdown tests.
+- The desktop test drags from the first body row, verifies equal-and-opposite
+ 60-pixel adjacent changes, an unchanged third column and table width, the
+ 64-pixel clamp, double-click reset, 8-pixel keyboard adjustment, and `Enter`
+ reset.
+- A wide desktop table keeps its separator aligned after 160 pixels of local
+ scroll. Chat and document overflow checks remain clean.
+- Mobile Chrome exposes no separators while ordinary tables wrap and wide tables
+ retain local scrolling. No unsupported browser behavior remains in scope.
diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md
index 425672479a..f01093f8a3 100644
--- a/docs/specs/INDEX.md
+++ b/docs/specs/INDEX.md
@@ -142,6 +142,7 @@ Subscription quota tracking and per-agent cheap-model profile routing.
| [ci-pr-automation](ui/ci-pr-automation.md) | building |
| [github-pr-review-actions](ui/github-pr-review-actions.md) | shipped |
| [comment-markdown](ui/comment-markdown.md) | shipped |
+| [resizable-markdown-tables](ui/resizable-markdown-tables.md) | building |
| [transcript-auto-scroll](ui/transcript-auto-scroll.md) | building |
| [empty-turn-notice](ui/empty-turn-notice.md) | shipped |
| [acp-shell-command-output](ui/acp-shell-command-output.md) | shipped |
diff --git a/docs/specs/ui/resizable-markdown-tables.md b/docs/specs/ui/resizable-markdown-tables.md
new file mode 100644
index 0000000000..58030f4e49
--- /dev/null
+++ b/docs/specs/ui/resizable-markdown-tables.md
@@ -0,0 +1,122 @@
+---
+status: building
+created: 2026-08-05
+owner: kandev
+---
+
+# Resizable Markdown Table Columns
+
+## Why
+
+Model-generated Markdown tables often choose column proportions that are valid
+but inconvenient for the current chat width. Automatic wrapping must remain the
+safe default, but desktop users need a quick way to rebalance a table without
+editing the source Markdown or opening another view.
+
+## What
+
+- Tables rendered through Kandev's shared GitHub-flavored Markdown renderer
+ expose a resize separator at every internal column boundary on non-phone,
+ fine-pointer layouts.
+- Each separator has a narrow visual line and a forgiving hit area spanning the
+ full rendered table height, including the header and body.
+- Dragging a separator changes only the two adjacent columns. Their combined
+ width and the table's total width remain unchanged, and neither column may be
+ reduced below 64 CSS pixels. Separator positions are re-measured after every
+ controlled width change and streamed cell-text mutation so the drag target
+ remains on the rendered boundary.
+- A separator is exposed only while both adjacent measured columns meet the
+ 64-pixel minimum. This omits every pair measuring less than 128 CSS pixels and
+ keeps each exposed separator's current ARIA value inside its declared range.
+- The first resize begins from the browser's measured automatic layout. Custom
+ widths activate only after a non-zero pointer movement, then remain in
+ component memory for that mounted table only. Clicking a separator solely to
+ focus it leaves automatic layout active.
+- Double-clicking any separator clears every custom width on that table and
+ returns it to the current automatic layout. It does not restore a stale pixel
+ snapshot.
+- A focused separator supports `ArrowLeft` and `ArrowRight` in 8-pixel steps.
+ `Enter` resets the whole table. Each control has `role="separator"`, an
+ accessible name identifying its adjacent one-based column numbers,
+ `aria-orientation="vertical"`, `aria-valuemin="64"`, `aria-valuenow` equal to
+ the rounded current left-column width, and `aria-valuemax` equal to the
+ adjacent pair total minus the 64-pixel right-column minimum.
+- Hover, keyboard focus, and active dragging make the complete boundary line
+ visible. Normal links, selection, and scrolling remain interactive outside
+ the separator's narrow hit area.
+- Resize state is ephemeral: it is not stored in local storage, backend state,
+ message content, or shared with another rendering of the same Markdown.
+- Phone and coarse-pointer layouts do not show or activate resize separators.
+ They keep the existing automatic wrapping for ordinary tables and local
+ horizontal scrolling for wide tables.
+- If resize capability becomes unavailable during a drag, the drag ends and
+ any document-level cursor or text-selection override is cleared.
+- Table-local scrolling remains the only horizontal scroll owner. Resizing must
+ not create document-level or chat-level horizontal overflow.
+
+## Scenarios
+
+- **GIVEN** a three-column Markdown table on desktop, **WHEN** the user drags the
+ first separator from a point beside a body row, **THEN** the first column grows,
+ the second shrinks by the same amount, and the third column is unchanged.
+- **GIVEN** a drag has started, **WHEN** the pointer leaves the table before
+ release, **THEN** pointer capture keeps the resize active until release.
+- **GIVEN** an adjacent column reaches 64 pixels, **WHEN** the user continues
+ dragging toward it, **THEN** that boundary stops moving while the table width
+ stays constant.
+- **GIVEN** a resized table, **WHEN** the user double-clicks any separator,
+ **THEN** all custom widths are removed and the browser recalculates the table's
+ automatic layout from current content and available width.
+- **GIVEN** a focused separator, **WHEN** the user presses an arrow key,
+ **THEN** the adjacent widths change by 8 pixels subject to the same minimum;
+ **WHEN** the user presses `Enter`, **THEN** the table resets.
+- **GIVEN** a resize is interrupted by pointer cancellation, **WHEN** cancellation
+ is received, **THEN** the table returns to the widths captured at drag start.
+- **GIVEN** a resized streamed message, **WHEN** its table column count changes,
+ **THEN** custom widths are discarded and the new table uses automatic layout.
+- **GIVEN** a resized table, **WHEN** it unmounts or the page reloads, **THEN** no
+ width preference survives.
+- **GIVEN** the same Markdown table on a phone or coarse-pointer device, **WHEN**
+ it renders, **THEN** no resize control obstructs the cells and the existing
+ wrapping or table-local scrolling behavior remains intact.
+- **GIVEN** a wide table that already scrolls locally, **WHEN** it is resized,
+ **THEN** separators stay aligned with their boundaries while the table scrolls,
+ and the chat and document do not overflow.
+
+## Responsive and mobile contract
+
+- **Desktop entry point:** inline separators on the rendered table; no menu or
+ configuration panel.
+- **Mobile counterpart:** the existing readable automatic layout. Resizing is an
+ optional precision adjustment, so touch users retain full content access
+ without controls that would cover narrow cells.
+- **Presentation:** the existing inline Markdown table and its local scroll
+ wrapper remain authoritative at every breakpoint.
+- **Shared behavior:** Markdown parsing, table content, automatic wrapping, and
+ wide-table scroll policy are shared. Only the separator affordance is gated to
+ non-phone fine pointers.
+- **Mobile verification:** phone tests assert that controls are absent, ordinary
+ two-column tables wrap, wide tables scroll locally, and neither chat nor the
+ document overflows.
+
+## Failure modes
+
+- If column geometry cannot be measured or fewer than two columns are present,
+ the table remains in automatic layout and exposes no separators.
+- Pointer capture, document cursor, and text-selection overrides are always
+ cleaned up after release, cancellation, or unmount.
+- A content update that changes the detected column count clears custom widths
+ instead of applying stale dimensions to a different structure.
+
+## Out of scope
+
+- Persisting widths across reloads, sessions, messages, or render locations.
+- Encoding widths in Markdown or modifying stored message content.
+- Touch dragging, a mobile column-width editor, or large touch resize targets.
+- Resizing rows, reordering columns, hiding columns, or resizing non-Markdown
+ data grids.
+- Choosing an exact width from a numeric input.
+
+## Open questions
+
+None.