Skip to content

fix: say less, and say the true thing - #23

Merged
dspv merged 4 commits into
masterfrom
fix/say-less
Sep 2, 2026
Merged

fix: say less, and say the true thing#23
dspv merged 4 commits into
masterfrom
fix/say-less

Conversation

@dspv

@dspv dspv commented Sep 2, 2026

Copy link
Copy Markdown
Owner

A read of every string a user sees, on the brief that the product should read as if a person wrote it for a person.

Three were wrong, not merely long

The answers screen showed raw Markdown. Claude writes bold text, lists and tables; the screen preserved line breaks and rendered nothing, so a reader got asterisks around every bold phrase and a table collapsed into | | |.

This is the same defect caught in the release-notes dialog an hour earlier — the fix existed and had not been applied here. The renderer is now shared (and learned tables and block quotes on the way). Renamed ReleaseNotesProse, since that is what it renders.

A Gemini error sent people somewhere they no longer need to go. "Set GEMINI_API_KEY in the daemon's environment and restart it" — but the key has had a field on the Cost screen since this morning and takes effect at once. A wrong instruction costs more than a long one.

A Go package prefix reached the dialog box. agents: "/x" is not an absolute path is a message written for whoever was debugging it.

The rest is length

Several tooltips explained the attribution algorithm where the reader had asked what a number meant. The per-directory rule was four sentences of specification on hover:

A turn counts toward the directory of the most recent file it touched, and keeps counting there until it touches a file somewhere else — so the commands, tests and searches between two edits count toward the directory being worked on. Reading, editing or writing a file counts as touching it; running a command does not. Each turn goes whole to one row, never split between two, so the rows add up to the repository total exactly.

Now: "Cost is charged to the folder Claude was last working in. Rows add up to the repository total."

Several paragraphs defended decisions nobody had questioned — why the Telegram bot is yours, why Caprock will not type into a session it did not start, why a connection timer counts up when idle. Each of those teaches a reader to be suspicious of something they had accepted.

"Everything here is measured — no invented numbers" is gone. That is project rule 6 leaking onto the display: readers assume figures are real until told otherwise, so the sentence created the doubt it answered.

The tests that broke are worth noting

Four failed. Three asserted exact words rather than the claim underneath — toContain('approximately') where the text now says "roughly" — and now assert the claim.

The fourth asserted that answer text survives with its line breaks intact. That is the behaviour this change deliberately removes: it passed for months while readers saw markup. It now asserts the structure is rendered.

https://claude.ai/code/session_01DR8fggA2LRHcjNWUsqtDcF

Then: say what each number is

The copy pass ended at the words. Three screens later the same brief caught the numbers.

Five tables had no column headings — a value, a bar and a percentage, with nothing naming what the percentage was of. Each now names its columns on the same line as its title, so the header row costs no extra line.

Most-used tools gained a returned column, and it inverts the story the call count tells. Bash is 46,343 calls that hand back 13.4MB; Read is 11,767 calls that hand back 58.7MB. Four times fewer calls, four times more context. The loud tool is the cheap one — which is not visible anywhere else in the product.

Bytes and not tokens, on purpose. Tool results arrive in the transcript with no token attribution of their own. The only way to print a token figure here is to invent one from a bytes-per-token ratio, which would understate Bash against Read by an order of magnitude — in the one table whose whole job is comparing them. Rule 6, in its most harmful form. Bytes are measured.

Measured at write time, not per request. A new tool_bytes column, filled from the response as the event is stored and backfilled once, plus a covering index. GET /v1/history?range=all went 2.1s → 0.05s on a 641MB database, so the panel that took a visible beat to appear no longer does. No cache, no new moving part.

Migration 0019 drops idx_events_kind_ts_tool, which 0018's index now supersedes — as its own migration, because 0018 had already run where it mattered, and an edit to an applied migration is an edit nobody receives.

And the plan picker. Once a plan was set the button went grey and borderless — exactly what an unset control looks like — so every session read as though the plan had never been chosen. It now carries a check and keeps its border.

dspv added 4 commits September 2, 2026 18:21
A read of every string a user sees, on the brief that the product should
read as if a person wrote it for a person.

Three were wrong rather than merely long.

The answers screen showed raw Markdown. Claude writes bold text, lists
and tables; the screen preserved the line breaks and rendered nothing, so
a reader got asterisks around every bold phrase and a table collapsed
into `| | |`. This is the same defect a user caught in the release-notes
dialog an hour earlier — the fix existed and had not been applied here.
The renderer is now shared, and learned tables and block quotes on the
way. It is called Prose rather than ReleaseNotes, since that is what it
renders.

A Gemini error told people to set an environment variable and restart the
daemon. The key has had a field on the Cost screen since this morning and
takes effect immediately. A wrong instruction costs more than a long one.

And a Go package prefix reached the dialog box: `agents: "/x" is not an
absolute path` is a message written for whoever was debugging it.

The rest is length. Several tooltips explained the attribution algorithm
where the reader had asked what a number meant — the per-directory rule
was four sentences of specification on hover, and is now one. Several
paragraphs defended decisions nobody had questioned: why the Telegram bot
is yours, why Caprock will not type into a session it did not start, why
a connection timer counts up when nothing is happening. Each of those
teaches a reader to be suspicious of something they had accepted.

"Everything here is measured — no invented numbers" is gone from the Cost
screen. It is project rule 6 leaking onto the display: readers assume the
figures are real until told otherwise, so the sentence created the doubt
it was answering.

Four tests broke. Three were asserting exact words rather than the claim
underneath, and now assert the claim. The fourth asserted that answer
text survives with its line breaks intact — the behaviour this change
deliberately removes — and now asserts that the structure is rendered.

Claude-Session: https://claude.ai/code/session_01DR8fggA2LRHcjNWUsqtDcF
…measured

Three things a user pointed at in one sitting.

Sharing was locked to all time. The picker had been removed on the
reasoning that a card showing today, the week, the month and all time at
once made choosing redundant — but somebody sharing a working week does
not want their lifetime total to be the headline, and a card that answers
four questions answers none of them loudly. The period is back and
decides what the heading claims and which figure is lit; every other one
stays on the card, because a week means nothing without knowing whether
it was a normal week. It opens on the week.

You could not see the card before sending it. The two buttons said where
the picture would go and nothing said what was in it: you pressed Save,
opened your downloads, and only then found out what you had chosen. The
card is drawn in the sheet now, at the size it will be seen, redrawing as
the period changes — which is what makes the choice worth offering at
all.

The Share button could not be found. It was outlined in the accent colour
in a row of 11px text, beside a premium button that is a solid block of
it; an outline cannot win an argument with a fill. Filled, a size up, and
carrying an icon. It earns that: sharing is the only thing on this
dashboard that leaves the machine.

And the first screen told a lie on every load. `measured` could not tell
"no answer yet" from "the answer is nothing", so for the second it took
the first response to land it announced that a machine with thousands of
turns had never run anything — reported as "the data took very long to
load" by someone whose data was there the whole time. Measured on the
641 MB database that produced the report: nothing on that screen takes
longer than 0.7s. The delay was never the problem; the confident wrong
answer during it was.

Claude-Session: https://claude.ai/code/session_01DR8fggA2LRHcjNWUsqtDcF
Three figures in a row with nothing over them is a puzzle. "$7,587.14 ·
12.01B · 58%" reads as three unrelated numbers until you have worked out
which is which, and the "by cost" sitting in the corner describes the
sort order rather than the columns. Every breakdown on Now, Cost and
Lifetime names them now, on the same line as the table's own title so it
costs no vertical space and leaves no gap between a heading and the row
it heads.

The tool table has two figures rather than three, and now says so instead
of leaving a hole where a third would go.

It could have had tokens. It does not, and that is the interesting part:
tool events carry no tokens at all — checked, and they are exactly zero
across every tool. Tokens live on the assistant turn. A tool does not
spend tokens; the turn that called it and read its output does.

So the column could only be attributed, and every way of attributing it
is wrong in a way that would not show. Give the whole turn to each call
and the totals exceed what was spent. Split it evenly and a Read pulling
a 200KB file scores the same as an `ls` in the same turn, when the file
is the entire reason that turn was expensive. On top of that, 14% of Bash
calls cannot be linked to a paying turn against 1% of Read's, so whatever
split was chosen would understate Bash specifically — which is the
comparison this table exists to make. A figure that looks measured and is
quietly skewed is worse than a column that is not there.

Separately, the all-time breakdown moved up to sit under the live pulse.
It was last, below a tall activity feed, so the most legible thing on the
screen was the thing you had to scroll to find. It reads as a
continuation of the pulse: that shows the shape of the work, this shows
what the work was.

Claude-Session: https://claude.ai/code/session_01DR8fggA2LRHcjNWUsqtDcF
Five tables showed numbers with no headings and a percentage with nothing
naming what it was a percentage of. Every one of them now names its columns
on the same line as its title, so the row is a header rather than an extra
line of chrome above one.

Most-used tools gains a "returned" column, and it inverts the story the
call count tells: Bash is 46,343 calls that hand back 13.4MB, Read is
11,767 calls that hand back 58.7MB. Four times fewer calls, four times more
context. The loud tool is the cheap one.

Bytes and not tokens on purpose. Tool results arrive in the transcript with
no token attribution of their own, and the only way to show a token figure
here is to invent one from a bytes-per-token ratio — which would understate
Bash against Read by an order of magnitude, in a table whose whole job is
comparing them. Bytes are measured; a converted token count would not be.

Measured rather than computed per request: a new `tool_bytes` column, filled
from the response at write time and backfilled once, plus a covering index.
The ALL TIME query went 2.1s to 0.05s on a 641MB database, so the panel
that took a visible beat to appear now doesn't — no cache, no extra moving
part. Migration 0019 drops the index 0016 added for this same query, which
0018's index now supersedes.

And the plan picker: once a plan was set the button went grey and
borderless, which is exactly what an unset control looks like, so it read
every session as though the plan had never been chosen. It now carries a
check and keeps its border.

Claude-Session: https://claude.ai/code/session_01DR8fggA2LRHcjNWUsqtDcF
@dspv
dspv merged commit d83f3ff into master Sep 2, 2026
9 checks passed
@dspv
dspv deleted the fix/say-less branch September 2, 2026 18:17
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