Files
galaxy/specs/zachlloyd/markdown-table-consistency/TECH.md
T

13 KiB
Raw Blame History

Markdown Table Styling Consistency — Tech Spec

Product spec: specs/zachlloyd/markdown-table-consistency/PRODUCT.md

Problem

PR #23908 updated AI block list Markdown tables to the new blockless visual treatment, but the rest of Warps Markdown table renderers still use the older boxed table styling. On the current branch, Markdown table appearance is owned by two separate rendering paths:

  • editor-backed Markdown surfaces use RichTextStyles.table_style
  • AI block list tables build a separate WarpUI TableConfig inline

That split creates two technical problems:

  1. the shared editor-backed renderer still paints outer borders, vertical dividers, filled header backgrounds, and alternating row backgrounds
  2. future surfaces can drift again because there is no single shared source for Markdown table chrome

The implementation should make every current Markdown table renderer inherit the same structural style while preserving surface-specific typography and existing editing/selection behavior.

Relevant code

  • specs/zachlloyd/markdown-table-consistency/PRODUCT.md — approved product behavior
  • specs/blocklist-markdown-table-rendering/TECH.md — prior AI block list table implementation plan
  • app/src/notebooks/editor/mod.rs (145-215)rich_text_styles; current shared editor-backed table style source
  • crates/editor/src/render/model/mod.rs (424-568)RichTextStyles and TableStyle
  • crates/editor/src/render/element/table.rs:1-220 — editor-backed Markdown table painting path
  • app/src/ai/blocklist/block/view_impl/common.rs (1140-1270)render_table_section; current AI block list table renderer and inline TableConfig
  • crates/warpui_core/src/elements/table/mod.rs (117-239) — shared TableConfig, RowBackground, and TableVerticalSizing
  • crates/warpui_core/src/elements/table/mod.rs (537-759) — intrinsic width measurement logic
  • crates/warpui_core/src/elements/table/mod.rs (1013-1211) — viewported row layout and painting behavior
  • app/src/notebooks/file/mod.rs (230-278) — file notebook view using rich_text_styles
  • app/src/ai/ai_document_view.rs (292-321) — AI document view fallback editor using rich_text_styles
  • app/src/ai/document/ai_document_model.rs (783-801) — AI document model creating notebook-backed editors with rich_text_styles
  • app/src/code/editor/comment_editor.rs (505-544) — comment editor using rich_text_styles
  • app/src/code/editor/view.rs (2357-2381)code_text_styles; clones rich_text_styles before overriding non-table text settings

Current state

Editor-backed Markdown tables

Most rendered Markdown surfaces in the app are backed by NotebooksEditorModel and the shared editor renderer. Their table appearance comes from rich_text_styles() in app/src/notebooks/editor/mod.rs, which currently sets:

  • border_color = theme.surface_3()
  • header_background = theme.surface_2()
  • cell_background = theme.background()
  • alternate_row_background = Some(theme.surface_2())
  • cell_padding = 6.

That style is then consumed by RenderableTable in crates/editor/src/render/element/table.rs. The renderer currently always paints:

  • row backgrounds for header and body
  • an outer top/bottom/left/right border
  • horizontal row dividers
  • vertical column dividers

This is the old notebook/editor table treatment that the new product spec wants to replace.

Surfaces that inherit the editor-backed table style

The old table style is not limited to notebooks. The same rich_text_styles() entry point is reused by multiple surfaces:

  • notebooks
  • file notebooks
  • AI documents / planning docs
  • Markdown comment editors

In addition, code_text_styles() starts from rich_text_styles() before overriding paragraph-level settings, so any code-editor surface that ends up rendering Markdown tables also inherits the same table style unless it explicitly overrides it.

This means a change to the shared editor-backed style source will propagate to multiple surfaces automatically.

AI block list tables

The AI block list no longer uses the old style. render_table_section() in app/src/ai/blocklist/block/view_impl/common.rs builds a WarpUI Table with an inline TableConfig that already matches the desired structural treatment:

  • outer_border: false
  • column_dividers: false
  • row_dividers: true
  • transparent header/background fills
  • cell_padding: 12.
  • TableVerticalSizing::ExpandToContent
  • measure_body_cells_for_intrinsic_widths: true

That renderer is already the visual baseline from PR #23908, but it is defined inline inside the block list path rather than shared with editor-backed renderers.

Ownership problem

Today there is no shared “Markdown table appearance” abstraction. The editor-backed path and block list path both describe the same visual decisions in different structures:

  • TableStyle for the editor renderer
  • TableConfig plus per-cell text styling for WarpUI Table

As a result:

  • the two renderers can drift
  • future surfaces may pick one path and forget to copy the latest style
  • structural decisions like “no outer border” or “no column dividers” are not represented in the editor-backed style model yet

Proposed changes

1. Introduce a shared Markdown table appearance helper

Add a small shared helper at the app layer that defines the canonical Markdown table chrome for the new blockless treatment.

The helper should represent the structural and color decisions that must stay aligned across renderers:

  • divider color
  • header text color
  • body text color
  • transparent vs filled backgrounds
  • whether outer borders are shown
  • whether column dividers are shown
  • whether row dividers are shown
  • row striping behavior
  • cell padding

Typography should remain surface-specific. The helper should define the table chrome and text-color hierarchy, not force every surface to use the same font family or font size.

This gives us one source of truth that both renderers can map from.

2. Extend TableStyle so the editor renderer can express the blockless treatment

crates/editor/src/render/model/mod.rs currently cannot represent some of the structural choices already used by the block list renderer.

Extend TableStyle with the missing structural controls needed by the product spec:

  • outer_border: bool
  • column_dividers: bool
  • row_dividers: bool

Keep the existing color and typography fields. The important change is letting the editor-backed renderer express “only horizontal separators, no outer border, no vertical dividers” directly instead of baking those assumptions into painting logic.

3. Update the editor-backed table painter to honor the expanded style model

Update crates/editor/src/render/element/table.rs so painting follows TableStyle rather than hardcoded table chrome assumptions.

Specifically:

  • paint_backgrounds() should respect transparent header/body backgrounds and the absence of alternating row backgrounds
  • paint_borders() should paint only the borders/dividers enabled by TableStyle
  • the renderer should continue using the existing text layout, selection, cursor, and alignment logic unchanged

This keeps the behavioral parts of the editor renderer stable while changing only the visual treatment.

4. Make rich_text_styles() produce the new Markdown table style

Update app/src/notebooks/editor/mod.rs so rich_text_styles() returns the blockless Markdown table style from the new shared helper.

That means the shared editor-backed path should move from the current boxed style to:

  • no outer border
  • no column dividers
  • row dividers only
  • transparent header background
  • transparent body background
  • no alternating row backgrounds
  • header/body text colors matching the block list hierarchy
  • cell padding aligned with the block list treatment

This change is the main propagation point for notebooks, file notebooks, AI documents, comment editors, and any other surface using the shared editor-backed Markdown renderer.

5. Refactor the block list table renderer to consume the same shared appearance helper

Update app/src/ai/blocklist/block/view_impl/common.rs so the block list no longer constructs its structural table chrome entirely inline.

The block list should still keep its surface-specific text settings where needed:

  • AI font family
  • AI font size
  • AI font weight
  • AI selection color

But the structural table configuration should come from the same shared appearance helper used by rich_text_styles(). That keeps the existing block list result visually unchanged while preventing future drift.

6. Preserve surface-specific typography

The product goal is shared styling, not identical typography across unrelated surfaces.

The shared appearance helper should therefore be mapped differently by each renderer:

  • editor-backed surfaces continue using their surfaces font_family / font_size in TableStyle
  • block list continues using its AI-output text settings

What must remain shared is the blockless table chrome and the text hierarchy relationship, not every literal font token.

7. Treat the shared helper as the default for future Markdown table renderers

Document in the code by naming and placement that this helper is the default source for Markdown table appearance in Warp.

The goal is that a new Markdown-rendering surface should not invent its own TableConfig or TableStyle values for tables unless it has a clear product reason to diverge.

End-to-end flow

  1. A surface creates a rendered Markdown editor or table view.
  2. If it is editor-backed, it obtains RichTextStyles from rich_text_styles() or code_text_styles().
  3. rich_text_styles() builds table_style from the shared Markdown table appearance helper.
  4. The editor renderer lays out and paints table content using the updated TableStyle, which now supports the blockless chrome.
  5. If it is the AI block list path, render_table_section() builds its WarpUI Table using the same shared appearance helper, while keeping block-list-specific typography and selection settings.
  6. The user sees the same structural Markdown table treatment across surfaces, with existing interaction behavior preserved.

Risks and mitigations

Risk: editor-backed behavior regressions

Changing RenderableTable painting could accidentally affect selection visibility, cursor readability, or perceived cell hit areas.

Mitigation:

  • keep layout, selection, cursor, and alignment code unchanged
  • scope the change to styling and border/background painting
  • validate editable surfaces manually after the visual update

Risk: block list drifts again later

If the block list keeps hand-authoring its own TableConfig, the two renderers can diverge again even after this change.

Mitigation:

  • refactor both paths to read from the same shared appearance helper
  • avoid leaving structural table chrome duplicated inline

Risk: future surfaces bypass the shared style

Even after current surfaces are fixed, a new renderer could hardcode another table style.

Mitigation:

  • make the helper discoverable and clearly named as the canonical Markdown table appearance
  • reference it directly from both existing rendering paths so future work sees the pattern

Risk: typography becomes unintentionally identical everywhere

If the shared helper carries too much font data, it could flatten legitimate surface-specific typography differences.

Mitigation:

  • keep the helper focused on chrome and text hierarchy
  • let each renderer keep its own font family, size, and weight choices where appropriate

Testing and validation

Shared-style tests

  • Add focused tests for the shared Markdown table appearance helper so its structural defaults are explicit:
    • no outer border
    • no column dividers
    • row dividers enabled
    • transparent backgrounds
    • no alternating row striping
    • updated padding

Editor-backed renderer coverage

  • Add or update tests around editor-backed Markdown tables to ensure table rendering still occurs and existing Markdown table behavior does not regress.
  • Keep existing geometry/selection-oriented table tests in crates/editor/src/render/element/table_tests.rs passing after the style changes.

Surface propagation checks

  • Manual validation in:
    • Markdown notebook
    • Markdown editor
    • file notebook
    • AI document / planning document
    • Markdown comment editor
    • AI block list
  • Confirm these surfaces all show the same blockless table chrome.

Behavior regression checks

  • Manual validation of:
    • left/center/right alignment
    • inline formatting inside cells
    • cursor placement in editable contexts
    • selection within and across cells
    • link rendering and interaction
    • wide-table overflow handling

Visual validation

  • Screenshot-based comparison against the Figma node and the AI block list implementation from PR #23908
  • Manual confirmation that the older notebook/editor chrome is gone everywhere in scope

Follow-ups

  • If additional Markdown table renderers appear outside the current editor-backed path and block list path, route them through the shared appearance helper rather than creating another table style definition.
  • If we later want stricter visual parity between surfaces, we can consider a deeper shared text-style adapter, but that should be a follow-up after the chrome is unified.