Skip to content

The sheet quotes the row's cut instead of guessing at it - #618

Merged
Georg-Klock merged 2 commits into
mainfrom
fix/editor-shows-the-rows-cut
Sep 9, 2026
Merged

The sheet quotes the row's cut instead of guessing at it#618
Georg-Klock merged 2 commits into
mainfrom
fix/editor-shows-the-rows-cut

Conversation

@Georg-Klock

Copy link
Copy Markdown
Owner

Closes #615.

Typing "Crossbowww" showed all ten letters in the editor, under a warning
saying the name would be cut, and saved to a row reading "Crossbow…".

No width formula can fix this

The field drew the name at 17pt in a box nameMaxWidth × 17/12 wide and
truncated it there, on the theory that enlarging both sides of the ratio
keeps the ellipsis on the same character (#482). It does not. A line is
size × Σ(advances) + (count − 1) × tracking(size), and SF's tracking table
is per-size, so 17pt type is proportionally narrower than the ratio
predicts and the field is systematically the more generous of the two.

Measured over 1,296 names, against the row's own cut:

shipping   nameMaxWidth × 17/12                  597 disagreements
measured   scaled by the string's own 17/12      35
measured   ...with the ellipsis included         29
quoting    draw the row's own visible string     0   (exact by construction)

"Crossbowww" is 74.63pt against a 71.75pt column and 101.40pt against a
101.65pt field — cut on one surface, fitting on the other, by a quarter of a
point. Every remaining candidate is still a second truncation asked to agree
with the first by arithmetic, so the second truncation goes: NameTruncation
resolves the row's visible text once, at the row's font, size and column, and
the field draws that string.

isNameCut comes off the same string, replacing a comparison of two
.onGeometryChange widths landing in different render passes — which is why
the warning could be seen switched on over a name still drawn in full.

The issue proposed instantiating HabitLabelView in the probe instead of
hand-copying it. That is a real mirror-copy risk, but it is not this bug:
both probes ran at the row's 12pt and agreed with the row. The disagreement
was between 12pt and 17pt, which no amount of sharing the 12pt arrangement
would have reconciled. The probes are gone rather than unified.

A second defect, found while verifying

.foregroundStyle(.clear) does not clear a TextField's text — the
style reaches the placeholder and the insertion point, not the glyphs UIKit
draws. The truncated copy has been drawn over a field still showing the
full name since #456, invisible for exactly as long as the two strings were
identical. The moment the copy became the row's shorter answer it read as an
ellipsis stamped across "ww". It is .opacity now.

sheetWidth's initial value was WidgetMetrics.largeWidth — the width of
the panel, where this measures the window the panel is laid out in —
making the first pass a row at 88% scale. It is now the narrowest window that
draws the grid at full scale, written as the sum it is.

Testing

L1 790/790

Verified in the running app, which is the only witness that counts here: the
editor and the saved row both read "Crossbow…", and both read "Meditation…"
for a longer name.

The obvious unit test was written and removed. Comparing the pixels of
SwiftUI's own truncation against the model's prediction fails: asked for
"Crossbowww" in a 71.75pt column, ImageRenderer draws "Crossboww…" — a
string measuring 74.11pt, wider than the column it was given — while the
running app draws "Crossbow…". This repository already knows not to believe
ImageRenderer about layout (#386), and a test that fails when the app is
right is worse than no test. What stays is arithmetic, including the
invariant the overlay depends on: the row's visible text always fits the
field that quotes it, checked across 288 names.

🤖 Generated with Claude Code

Typing "Crossbowww" showed all ten letters in the editor, under a warning
saying the name would be cut, and saved to a row reading "Crossbow…".

The field drew the name at 17pt in a box `nameMaxWidth × 17/12` wide and
truncated it there, on the theory that enlarging both sides of the ratio
keeps the ellipsis on the same character. It does not. A line is
`size × Σ(advances) + (count − 1) × tracking(size)`, and SF's tracking table
is per-size, so 17pt type is proportionally narrower than the ratio predicts
and the field is systematically the more generous of the two. Over 1,296
names the two cuts differed 597 times — half of them — always with the field
keeping a character the row drops. "Crossbowww" is 74.63pt against a 71.75pt
column and 101.40pt against a 101.65pt field: cut on one surface, fitting on
the other, by a quarter of a point.

No width formula fixes this. Scaling by the measured ratio of the same string
at both sizes gets 597 down to 35, and with the ellipsis included to 29; each
is still a second truncation asked to agree with the first by arithmetic. So
the second truncation goes. `NameTruncation` resolves the row's own visible
text once, at the row's font, size and column, and the field draws that
string. There is one answer, so there is nothing to disagree.

`isNameCut` comes off the same string, replacing a comparison of two
`.onGeometryChange` widths that landed in different render passes — which is
why the warning could be seen switched on over a name still drawn in full.

**A second defect surfaced while verifying this.** `.foregroundStyle(.clear)`
does not clear a `TextField`'s text; the style reaches the placeholder and
the insertion point, not the glyphs UIKit draws. The truncated copy has been
drawn *over* a field still showing the full name since #456, invisible for
exactly as long as the two strings were identical. The moment the copy
became the row's shorter answer it read as an ellipsis stamped across "ww".
It is `.opacity` now.

The initial `sheetWidth` was `WidgetMetrics.largeWidth` — the width of the
panel, where this measures the window the panel is laid out in — making the
first pass a row at 88% scale. It is now the narrowest window that draws the
grid at full scale, written as the sum it is.

Verified on a simulator, which is the only witness that counts here: typed
into the running app, the editor and the saved row both read "Crossbow…",
and both read "Meditation…" for a longer name. The obvious unit test —
compare the pixels of SwiftUI's truncation against the model's prediction —
was written and removed: `ImageRenderer` drew "Crossboww…", a string 74.11pt
wide, into a 71.75pt column, and this repository already knows not to
believe it about layout (#386). A test that fails when the app is right is
worse than no test.

L1 790/790
`.foregroundStyle(.clear)` not hiding a TextField's text, and the reason no
width formula can make one string truncate alike at two point sizes. Both
cost this issue real time, and both are invisible to the obvious test: the
first only shows when the two strings differ, the second only at a boundary.
@Georg-Klock
Georg-Klock merged commit 2f7734f into main Sep 9, 2026
7 of 9 checks passed
@Georg-Klock
Georg-Klock deleted the fix/editor-shows-the-rows-cut branch September 9, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants