Skip to content

Show a tabular file as a picture, drawn the way the export writes it - #1

Closed
JackTelford wants to merge 2 commits into
mainfrom
worktree-tabular-preview-tool
Closed

JackTelford wants to merge 2 commits into
mainfrom
worktree-tabular-preview-tool

Conversation

@JackTelford

Copy link
Copy Markdown
Owner

Previews a spreadsheet in the answer instead of making the reader download it first. Each worksheet is drawn as a figure — header band, lettered column strip, numbered row gutter, and a caption saying what was left out of the window.

The picture is drawn from the formatting the file is written with

Resolving formatting in two places is how a preview ends up showing a different header colour, or a raw 74612.15999999999 where the file shows $74,612.16. The resolution both tools need now lives in TabularFormattingResolver, and ExportTabularDataTool delegates to it — so the two cannot drift. Column formats inherited from the uploaded workbook are applied to the drawing too, which is what makes a column that was currency in the upload read as currency in the preview.

A workbook stores a number and a format code and leaves the presenting to whatever opens it, so the codes are read in TabularPreviewValueFormat. Currency, accounting, plain number, percent and date are covered; anything outside that vocabulary falls back to the general presentation rather than being guessed at.

The general presentation now rounds to the significant digits a spreadsheet shows, instead of printing a double to full binary precision — 74612.15999999999 was wrong even with no format applied.

Two faults found while building it

Read-your-own-write. Figures were addressed before the rows backing them were committed, so every picture arrived at an address that did not resolve yet. The document is now committed before its address is handed out.

The model would not write the markers that place the pictures, through three rounds of prompt hardening. The host now appends a marker for any servable image the answer did not name — the rule this codebase already applies to generated files, which are surfaced even when the model does not cite them.

Reading the picture

A figure is bounded to a fraction of its drawn size so a stack of sheets does not push the conversation off screen, which leaves a grid at roughly half size. Clicking one enlarges it (~45% → ~72%, undistorted).

The download sits over the picture's corner rather than on a line of its own — that band cost a row of vertical space per sheet. It saves a PNG, converted in the browser from the picture already loaded: no second fetch, and nothing converted for a picture nobody saves. Every failure path falls back to the SVG the server holds.

Verification

  • TabularPreviewValueFormat covered by 15 cases against the format codes this corpus actually carries, including the values from a real workbook: $74,612.16, $0.00, -$81,065.04, (1,234.50), 12.3%, 09-01-26 — all pass.
  • Zoom measured across all three sheets at 1280×900: undistorted, 71–73%, opens/closes, thumbnail unchanged.
  • Hover download: 0px added height, 51 ms conversion, real PNG magic bytes, canvas untainted.
  • Generated .xlsx validated with OpenXmlValidator — 0 errors; bytes served match bytes on disk by SHA-256.

Not included

appsettings.json is deliberately left out of this branch — it carries live credentials in the working tree.

Known gaps: the SQL-query preview path gets the correct header colour and rounding but not per-column formats; and ai-chat.js / chat-interaction.js render the same markup for the other chat surfaces and still need the zoom/download treatment, which requires an asset rebuild.

🤖 Generated with Claude Code

A reader handed a spreadsheet had no way to see it without downloading it
first. Each worksheet is now drawn as a figure in the answer: a header band,
a lettered column strip, a numbered row gutter, and a caption saying plainly
what was left out of the window.

The picture is drawn from the same formatting the workbook is written with.
Resolving that in two places is how a preview ends up showing a different
header colour, or a raw 74612.15999999999 where the file shows $74,612.16, so
the resolution both tools need now lives in TabularFormattingResolver and the
export delegates to it. Column formats inherited from the uploaded file are
applied to the drawing too, which is what makes a column that was currency in
the upload read as currency in the preview.

The value a workbook stores is a number and a format code, and the presenting
is left to whatever opens it, so the codes are read here. Currency, accounting,
plain number, percent and date are covered; a code outside that vocabulary
falls back to the general presentation rather than being guessed at. That
general presentation now rounds to the significant digits a spreadsheet shows
instead of printing a double to full binary precision.

Two faults found while building it:

The figures were addressed before the rows that back them were committed, so
every picture arrived at an address that did not resolve yet. The document is
now committed before its address is handed out.

The model would not write the markers that place the pictures, through three
rounds of asking. The host now appends a marker for any servable image the
answer did not name, which is the rule this codebase already applies to a
generated file: it is surfaced even when the model does not cite it.

A picture is bounded to a fraction of its drawn size so a stack of sheets does
not push the conversation off screen, which leaves a spreadsheet grid at about
half size. Clicking one enlarges it. The download sits over the picture's
corner rather than on a line of its own, and saves a PNG converted from the
picture the page has already loaded, so nothing is fetched twice and nothing
is converted for a picture nobody saves.
@JackTelford JackTelford self-assigned this Sep 21, 2026
…f its name

The check reads back which element a figure is wrapped in, because a marker is
often written mid-sentence and a <div> tears the paragraph apart. It located
that element by the first mention of the class name in the file, which is the
markup only for as long as no rule or selector is written above it. Adding a
stylesheet for the figures moved the first mention into CSS and the check
started reading a selector, so it failed on a file whose markup it had never
looked at.

It now searches for the opening tag itself, and checks every figure the file
emits rather than only the one that happens to come first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant