Files
kandev/docs/specs/ui/resizable-markdown-tables.md
T

123 lines
6.4 KiB
Markdown

---
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.