Skip to content

Structured summary segments#91

Merged
jcushman merged 1 commit into
mainfrom
pr/structured-summary-segments
Jun 3, 2026
Merged

Structured summary segments#91
jcushman merged 1 commit into
mainfrom
pr/structured-summary-segments

Conversation

@jcushman
Copy link
Copy Markdown
Contributor

@jcushman jcushman commented Jun 3, 2026

Trying to add commas to long numbers in markdown output exposed a basic issue -- by the time log strings get from transformers to renderers, there isn't enough type information to format numbers properly. "300000 rows" should have a comma but "02-04-2000" shouldn't, and adding special cases to try to guess type info is unworkable.

This patch lets transformers emit structured messages with text, path, int, and float components, so they can emit [pluralize(300000, "row), " moved"] or [path(foo, left), " moved to ", path(bar, right)] or whatever. The renderer can apply whatever humanization or localization or link rendering or whatever it wants.

We can add additional types if upstream plugins need to support some other type of data, like dates or file sizes or whatever.

--

Summary

  • Replace DiffNode.summary: Option<String> with Option<Summary> — an ordered list of typed Segments (Text, Path { value, snapshot }, Uint, Float). Renderers format each segment by its type instead of scanning prose to decide which digit runs to group.
  • A Uint is always digit-grouped; Text/Path values are verbatim, so years in folder names and other non-count digits are never mangled (the humanize_numbers prose scan is gone for summaries).
  • Rename/move/copy detectors emit Path segments with a snapshot side. The renderer never recognizes the "move" concept or string-matches "Moved from " prefixes — producers own their concept wording, the renderer owns typography, and this composes with plugin-defined actions.

Ergonomics

  • Plain strings still work via impl Into<Summary> (one Text segment), so summary-less producers are untouched.
  • Summary::count(n, noun) builds the common "{n} rows" shape with grouping + pluralization; text() coalesces adjacent text so count() costs nothing on the wire.
  • Annotation trailers (tabular_summary/content_summary) stay plain text by design (read with .as_str() by the folder-move detector); documented in the ADR.

Notes

  • The persisted changeset JSON now exposes summary as a typed segment array (schema doc regenerated); machine consumers can read segments directly or flatten via plain text.
  • Rationale and the rejected alternatives (hardening the prose scanner, generating headlines in the renderer, a semantic Currency/Percent enum) are in docs/adr/2026-06-03-structured-summary-segments.md.

DiffNode.summary becomes Option<Summary>: an ordered list of typed
Segments (Text, Path{value, snapshot}, Uint, Float). Renderers format
each segment by its type instead of scanning prose for numbers to group.
A Uint is always digit-grouped; Text and Path values are verbatim, so
years in folder names and other non-count digits are never mangled.

Rename/move/copy detectors emit Path segments carrying a snapshot side,
so the renderer never recognizes the "move" concept or string-matches
path-statement prefixes (the prior `humanize_numbers` prose scan and the
path-statement detection are gone). Producers own their concept wording;
the renderer owns typography. This composes with plugin-defined actions.

A plain string still works via `impl Into<Summary>` (one Text segment);
`Summary::count(n, noun)` builds the common "{n} rows" shape with grouping
and pluralization, and `text()` coalesces so it costs nothing on the wire.
Annotation trailers (tabular_summary/content_summary) stay plain text.

See docs/adr/2026-06-03-structured-summary-segments.md.

Co-Authored-By: Claude <noreply@anthropic.com>
@jcushman jcushman merged commit 0da960e into main Jun 3, 2026
10 checks passed
@jcushman jcushman deleted the pr/structured-summary-segments branch June 3, 2026 19:00
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