Skip to content

App review, and the work it proposed: v3.7.0 → v3.16.2 - #44

Merged
nedimperva merged 28 commits into
masterfrom
claude/app-improvements-review-2j0x69
Aug 11, 2026
Merged

App review, and the work it proposed: v3.7.0 → v3.16.2#44
nedimperva merged 28 commits into
masterfrom
claude/app-improvements-review-2j0x69

Conversation

@nedimperva

@nedimperva nedimperva commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Started as a review of the app at v3.7.0 and became the work that review
proposed. The branch now carries v3.7.0 → v3.16.2: nine feature
releases and two fix releases, plus docs/REVIEW_2026-08.md, which
tracks what shipped and — for the one item that didn't — why not.

93 files changed. The review document and the changelogs account for a
large share of the additions; packages/metal-core gained four new
modules and the command shell gained six.

What shipped

Saved entries became objects (3.8.0) — cross-section thumbnail,
spec-first title, live re-pricing against today's rate, search / sort /
tags / pin / duplicate. About ten functions in useSaved.ts were
implemented and called by nothing; they have a UI now. Saved identity
moved to a geometry-only fingerprint so a re-priced entry can't
duplicate itself.

The bar got faster (3.9.0) — refine chips at the finished stage,
19 to insert a chip, one Enter rule, a visible key map, ? for the
reference, last-query restore for returning users, memoized suggestions.

Quoting (3.10.0) — a rate per material grade (one global €/kg priced
stainless like mild steel), margin → sell price, session → project in
one press, assemblies, and a printable quote.

Design pass (3.11.0) — the 640–1023 range became the real workspace
instead of a 560px card on a background; one numeric type rule; accent
discipline; a shared motion vocabulary that honours reduced motion.

Target queries (3.12.0) — hea120 6m =500kg answers how many of
these make 500 kg
; with a quantity fixed it solves the length. The
solver probes the engine rather than restating the pricing rules, so
waste, VAT and price basis count in for free, and a per-piece rate —
flat with respect to length — is left unsolved rather than answered
wrongly. Pieces round up and the badge states the overshoot.

Habits sync (3.13.0) — usage stats travel with the encrypted
snapshot as a grow-only counter per device. A shared total would
double-count on every round trip; each device pushes only what it
learned and the reader sums.

Multi-item lines (3.14.0) — hea120 6m x2 + ipe200 4m x3. The
grammar doesn't change, it repeats: each segment goes through the
ordinary parser, so order tolerance, per-item grades, per-item rates and
per-item targets work per item for free. Saving one makes an assembly;
logging drops one tape line per item.

The > palette (3.15.0) — navigate, run an action, or find a saved
entry or project by name. Ranking is prefix > word start > substring.

Arithmetic, cut-list paste, mass tolerance (3.16.0) — 6m-50mm,
x2+3; paste a cut list and each row becomes an item; a ±% mass band on
every result.

Two fix releases (3.16.1, 3.16.2) from auditing the features against
each other rather than one at a time — see below.

Decisions worth a reviewer's attention

+ does two jobs. It joins items and appears in arithmetic. The
collision is settled by shape, not by guesswork: a + glued between a
character and a digit is arithmetic, anything else separates items,
because a new item always opens with a profile alias. A + also means a
space in a query string, so share links escape it — there's a test
pinning that round trip specifically.

The EN part of the tolerance band is deliberately missing. The
mechanism ships; the standards table does not. EN mass tolerances differ
per product standard and some (EN 10029 plate) derive from a thickness
class rather than being a single percentage, so a flat per-family table
would be an oversimplification wearing a standard's name — the opposite
of the credibility the feature was for. The percentage is the user's own
figure and the UI says so. Sourced values drop into one table.

Section properties are not implemented at all. The review assumed
"the dataset rows already carry geometry"; they don't — the standard
profiles carry areaMm2 and perimeterMm only. Ix/Wx/iy for ~150 sizes
would have to be generated rather than read, and invented section
properties in a structural tool are worse than the second browser tab
they were meant to replace. docs/REVIEW_2026-08.md §4.8 records this.

Five bugs came from features contradicting each other, not from any
feature being wrong on its own — multi-item lines introduced an
item-vs-line distinction the surrounding code predated. Paste replaced
the query outright and lost typed work; the breakdown showed one item's
mass band (±4% of 268 kg) directly under the hero's line band (±4% of
507 kg); copy summary sent one item of a two-item quote; the Save
button's state disagreed with what Save did; usage learning saw only the
last item. All fixed in 3.16.1–3.16.2, each with a regression test.

Checks

lint, test:all (442 unit tests across the engine, parser, stores and
jsdom component tests), i18n:check (en + bs parity), build, and 44
Playwright e2e including axe scans. Every new pure module landed with
unit tests; new UI landed with e2e.

Not done

Section properties (§4.8), as above — a data-sourcing task, not a code
one. Everything else in the review is closed.

claude added 2 commits August 6, 2026 21:47
Full scan at v3.7.0 covering the command-bar quick-type experience,
the saved-entry cards, the design system, new feature directions, and
a handful of correctness/trust issues (share links dropping pricing,
frozen saved prices, unwired saved-entry APIs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Sprint 0 + Sprint A of docs/REVIEW_2026-08.md.

The saved card was a list row with two numbers glued on, while ~10
already-implemented functions in useSaved (rename, notes, tags,
duplicate, bulk delete, usage counts) had no UI at all. And its money
was a snapshot: change the rate in Settings and every card kept showing
the old total, silently, while the line it restored priced differently.

- A shared SavedCard used by the desktop grid and the mobile library:
  the real cross-section drawing (ProfileDrawing gained a `thumb`
  variant rather than a second geometry source), spec-first title with
  the name as an optional override, the mode-leading metric dominant,
  kg/m and kg/piece, hover actions, and an expander with the full
  breakdown, notes and tags.
- Saved entries re-price at the current rate every render, state the
  rate they used, and badge what they cost when saved if it moved.
  Opening one restores at today's rate (inputToQuery `omitPrice`) so the
  command line agrees with the card, and counts the use.
- Search, sort (newest / most used / recently used / name), tag filter,
  pin, rename/notes/tags, duplicate, bulk select, card/compact density.
- Save is a toggle driven by savedFingerprint (geometry, never price, so
  a re-priced entry can't duplicate itself); deletes are undoable and
  the save toast offers "name it".
- Share links carry the sender's rate, currency, waste and VAT, and say
  so when they change local pricing.
- One EmptyState atom across Saved, Projects, Compare and the sheets.

Covered by e2e/saved.spec.ts, two new axe scans, and unit tests for the
pure parts (share round-trip, filter/sort, fingerprint, omitPrice).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ferroscale Ready Ready Preview Aug 11, 2026 6:54am

claude added 2 commits August 7, 2026 04:43
Finishes the last Sprint 0 item. The vitest suites covered the engine,
parser and stores; the bar's actual behaviour — keyboard routing, chip
editing, suggestion insertion, ghost accept, history recall — existed
only behind e2e smoke tests, which is thin ground for the keyboard
rewrite Sprint B does next.

Mounts the real CommandShell in jsdom with real messages, stubbing only
what jsdom lacks (service worker, locale routing, clipboard, matchMedia,
randomUUID). Each render resets the module graph and the URL, since the
shell's stores are caching singletons and jsdom keeps the mirrored ?q=
between tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
…y layer

Sprint B of docs/REVIEW_2026-08.md.

The moment a query was complete, the suggestion row showed exactly one
chip ("Save calculation") — the highest-intent moment in the app spent
on a single button. And every keyboard affordance the bar had (Tab
completion, history arrows, chip navigation) was invisible, with Enter
meaning different things on different surfaces.

- A finished line now offers variations: twice the pieces, the other
  stock lengths, the neighbouring catalog sizes, another grade — each
  with the resulting total under it, from arithmetic on the parse we
  already have, so the new chips cost no extra parses. Picking one swaps
  that token in place (new `refine` kind + `replaceKind`).
- Key routing is one pure resolver (command-keys.ts) shared by both
  surfaces instead of two drifting copies: Enter always advances, ⌥1–9
  picks a chip, ⌘S saves, ⌘⏎ compares, ? opens a reference generated
  from the alias table.
- A hint strip names the keys that work right now — "↵ insert" while a
  suggestion is pending, "↵ log" once the line is complete.
- Suggestions carry their provenance (yours / presets / standard) and
  the desktop row shows the headers.
- Returning users start on their last line, not the demo query.
- Phone: recents strip above the keypad, hold-backspace deletes a whole
  token, haptics (with a Settings switch), chips wrap to a second row.
- Suggestions no longer re-parse the query the shell already parsed, and
  per-metre weights for candidate sizes are cached.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
claude added 2 commits August 7, 2026 06:09
First half of Sprint C (docs/REVIEW_2026-08.md §4.1, §4.2, §4.4).

Pricing was a single rate for every material. Stainless costs roughly
four times mild steel per kg, so anyone working across materials was
quoting one of them wrong unless they retyped `@` on every line.

- Price book: a rate per material grade, synced like every other
  collection. Resolution order is inline `@rate/unit` > the grade's book
  rate > the single default, so an empty book behaves exactly as before.
  Editable from Settings on every viewport.
- Margin: one setting adds a sell-price row to the breakdown wherever a
  breakdown is rendered, turning cost into an offer without touching the
  cost figures themselves.
- Session → project: the tape already summed; now one button turns it
  into a project with all its lines.

Two things surfaced on the way. `addCalculation` reports success through
a flag its state updater sets, which only reads back correctly for the
first call in a batch — a loop silently added nothing, so bulk work now
goes through `addCalculations` (one updater, fingerprint-deduped). And
the price book needed a module-level store rather than component state,
since the calculator and the settings surface both read it and an edit
in one has to show up in the other.

Covered by e2e/quoting.spec.ts and parser tests for the resolution order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Second half of Sprint C (docs/REVIEW_2026-08.md §4.3, §4.7).

- Assemblies: a saved entry can hold several parts, which the storage
  model always supported and nothing surfaced. Add the line in the bar to
  any entry and its card becomes a bill of materials — parts listed,
  weight and price summed, each part re-priced at today's rate like the
  whole. Per-metre and per-piece figures drop out for an assembly, since
  they describe one profile and not a set.
- Printable quote: any project prints (or saves as PDF) as a clean
  document — header, line items, lengths, weights, totals, margin, and a
  note that the weights are theoretical. It is a portalled node revealed
  by `@media print`, so there is no route, no dependency and no network;
  it works offline like the rest of the app. Isolation uses visibility
  rather than display so it survives the app's fixed-position shell, and
  the node stays mounted until `afterprint` because unmounting mid-print
  prints nothing.

Covered by e2e/quoting.spec.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
…tion set

Sprint D of docs/REVIEW_2026-08.md (§3.1, §3.2, §3.4, §3.5, part of §3.6).

The 640–1023 range was a fixed 560px card floating on a background with
no tape, no library and no breakdown — an iPad in portrait, or a
half-width laptop window, getting the least capable surface in the app.
Everything ≥640 now runs the workspace with a `compact` flag: one
column, breakdown stacked under the tape, wordmark and ⌘K dropped from
the header for room. Sheets become phone-only, which let ~200 lines of
unreachable medium-viewport code leave the shell, and moved the e2e
specs that exercise sheet semantics onto the phone viewport.

- One numeric rule: tabular figures everywhere, so a value never shifts
  sideways as it counts up. Compare's headline weight joins the rest in
  mono.
- Accent discipline: --accent was the primary action, the weight metric,
  the profile glyphs, the selected tab, the density control and the
  focus glow. It is now the first two. The raised surface and its shadow
  were already saying "selected" without help.
- A shared motion vocabulary (fsPop / fsRise / fsFade / fsDrop) so
  movement means the same thing everywhere: something arrived, a view
  changed, a warning appeared. All of it off under reduced motion.
- First slice of the inline-style migration: a DeskPanel atom replaces
  the border/surface/shadow block written out in every desktop view.
- Breakdown rows carry a stable `data-row` hook, which also un-flakes the
  price-book e2e that was matching rows by DOM position.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
`hea120 6m =500kg` answers "how many of these make 500 kg"; with a
quantity fixed instead (`hea120 x10 =500kg`) it solves the length. Money
targets (`=250eur`, `=250€`) work the same way against the grand total.

The solver probes the engine rather than restating the pricing rules, so
waste, VAT, price basis and the price book are all counted in for free.
Two probes on the length branch separate the per-metre slope from any
fixed part, which is what keeps a per-piece rate — flat with respect to
length — from being "solved" into a nonsense answer.

Pieces round up, because you buy whole bars, so the parse result carries
what the solution actually comes to and a purple badge on the equation
line states the target and the overshoot. The token joins the grammar
reference, travels in ?q= links, and appears in copied summaries.

Also fixes a pre-existing type error in external-stores.test.ts, where a
string literal narrowed the store's generic and rejected the later set().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Usage stats ranked the suggestion bar from what you typed, but only on
the device you typed it — so a phone and a laptop each started from
zero. They now travel with the rest of the encrypted snapshot.

The merge is a grow-only counter per device rather than one shared
total. Each device pushes usage:<deviceId> holding only what it learned
itself, skips its own record when it comes back on a pull, and the
suggestion source sums every device's tally at read time: counts add,
timestamps take the later of the two. A shared total would double-count
on every round trip — five uses here plus the five just pulled back
reading as ten — which is the whole reason for the split.

With no peers the merge is skipped outright, so a single-device user's
ordering is unchanged.

The shell now subscribes to a usage version store instead of holding a
counter of its own, so a pull that brings a peer's habits in refreshes
the suggestions without a reload.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
`hea120 6m x2 + ipe200 4m x3` is now one line holding two calculations.
The grammar itself doesn't change — each `+`-joined segment goes through
the ordinary parser — so order tolerance, glue splitting, per-item
grades, per-item `@` rates and per-item `=` targets all work per item
for free, and a one-item line is exactly what it was. `+` is safe as the
separator because nothing else in the grammar uses it: sizes join with
`x`, rates with `@`, targets with `=`.

The hero shows the line total and the equation line becomes one row per
item, because a sum with no breakdown is a number you can't check.

Chips group per item with the separator drawn between them, and every
edit is scoped to its own item: removing a token from the first can't
reflow the second, and pulling a token back for editing can't teleport
it across a `+`. That index arithmetic lives in one tested module rather
than being written out twice in the phone and desktop views.

Saving a multi-item line makes one assembly with a part per item — the
saved model already held parts, this is what fills them. Save stays a
toggle for single lines only, since an assembly is a new object each
time and matching it against one of its own parts would un-save that
part. Logging drops one tape line per item, so the tape still adds up
and still becomes a project.

A share link carries the separator: `+` means a space in a query string,
so a literal one is escaped on the way out and restored on the way in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
The item model, the ranking and the shared action builder, with the
strings for both locales. Ranking is prefix > word start > substring so
typing "sav" puts Save above Go to saved above a saved entry that
merely contains the letters, and every word of the term has to match
somewhere so a second word narrows instead of widening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
wip: renders, navigates and runs; e2e and help entry still to come.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Typing `>` turns the command line into an action list: navigate to
saved, projects, compare or settings; save, compare, copy or share the
current line; open the reference or flip the theme. It also searches the
user's own saved entries and projects by name, so picking one loads it
straight into the bar — one input for calculate, navigate and recall.

Ranking is prefix > word start > substring, so `>sav` puts "Save this
line" above "Go to saved" above a saved entry that merely contains the
letters, and every word of the term has to match somewhere so a second
word narrows instead of widening.

A `>` line is a command, not a calculation: it isn't tokenized into
chips and the parser's "didn't understand that" never fires on it —
otherwise every palette keystroke would draw a warning underneath the
list. Palette mode also takes the arrows and Enter away from the
calculator's key map while it's open, since the list is the whole
interface at that point.

Actions that need a finished calculation are greyed rather than hidden,
so the line explains itself instead of quietly having fewer options.

Both surfaces build their list from one shared builder, differing only
in what "navigate" means: workspace views on desktop, Library tabs on
phone. The palette is keyboard-driven by nature and the phone keypad has
no `>` key, so on phone it is reachable only via a shared link — the
phone's own navigation already covers the same ground in one tap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
A cut list arrives as "the six-metre stock less the fifty-millimetre
joint", not as round numbers, and doing that subtraction in your head
before typing is the friction the bar exists to remove. Only + and -,
evaluated left to right: this is shorthand for a measurement, not an
expression language.

Terms without a unit inherit the first term's rather than the global
default, so "6m-50-50" is three metre terms and can't silently mix
scales. The answer is reported in the first term's unit, because that is
the one the user was thinking in.

The + here collides with the item separator, and the collision is
settled by shape: a + glued between a character and a digit is
arithmetic, anything else separates items. A new item always opens with
a profile alias, so "digit on the right" is enough to tell them apart.

An expression that doesn't come to a usable amount (50mm-6m, x2-2) is
reported rather than ignored — a silently dropped token would price the
line as if the cut had never been typed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
A cut list lives in a spreadsheet or an email as one part per row, and
that is one item per row here now that a line can hold several. Pasting
one produces the line the user would otherwise have typed instead of a
single unparseable blob. Tabs, semicolons and commas collapse to spaces
because the grammar is order-tolerant and reads the row either way, and
a paste far longer than anyone would work with is capped rather than
building a query nobody can read or edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Letter-spacing was an inline number on a dozen elements across five
desktop views, so "loosen the small caps a touch" was a
find-and-replace rather than an edit. Three named steps in globals.css
cover every repeated use, and the 10px tracked micro-label — written out
by hand in four views — is SectionLabel everywhere now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Rolled steel is sold by theoretical mass and delivered within a
tolerance, so a buyer working to a budget — or to a crane's limit —
wants the worst case as well as the nominal. A ±% in Settings produces
the band in the breakdown on every viewport, and under the hero when
weight is the headline metric.

The percentage is the user's own figure and the app says so. The EN part
of this idea is deliberately not implemented: mass tolerances differ per
product standard, and some (EN 10029 plate) derive from a thickness
class rather than being a single percentage, so a flat per-family table
would be an oversimplification wearing a standard's name — the opposite
of the credibility the feature was meant to buy. Sourced figures drop
into one table when someone has them.

Section properties from the same review section are not implemented
either, and the review is updated to say why: the premise that "the
dataset rows already carry geometry" is wrong — the standard profiles
carry area and perimeter only, so Ix/Wx/iy would have to be generated
rather than read, and inventing section properties for a structural tool
is worse than the second browser tab it was meant to replace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Pasting a cut list replaced the query outright, so a line the user had
already typed vanished with no undo. The rows are appended as further
items now: "I meant to add these" is by far the likelier intent, and a
destructive edit needs a better reason than convenience.

The breakdown describes one calculation — kg/m and per-piece weight
don't sum — but on a multi-item line it sat unlabelled under a hero
holding the line's total. The mass tolerance band made the mismatch
legible: ±4% of 268 kg in the breakdown, directly below ±4% of 507 kg
under the hero, both reading as the same claim about the same thing.
Both surfaces now title it "Breakdown · item 2 of 2" when the line holds
more than one, so each number is correctly scoped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Multi-item lines added a distinction the surrounding code predates: the
item under the caret versus the whole line. Three places still answered
for the item while presenting themselves as answering for the line.

Copy summary wrote only the active item, so copying a two-item quote
into an email sent one item's numbers. It now writes every item and the
line total — and withholds that total while any item is incomplete,
because a partial sum labelled "the total" is worse than none.

The Save button's filled state came from matching the active item
against the library, while pressing it creates a new assembly. A line
whose last part was saved therefore looked saved and then saved again.
State and behaviour now share the guard.

Usage learning recorded the active item only, so a two-part line taught
the suggestion ranking half of what was typed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
@nedimperva nedimperva changed the title docs: app review with design/UX/feature improvement proposals App review, and the work it proposed: v3.7.0 → v3.16.2 Aug 8, 2026
The mode switch moves into the hero's label row as two small KG/€ pills
instead of taking a full-width row of its own, and the 109px two-stat
card becomes a 34px strip — per piece, the other headline metric, and
"Breakdown ›" as the way into the result sheet. A Save / Compare / Share
row sits under it, which the phone previously only offered once you had
opened the sheet.

Net effect at 390×844: the hero block goes 270px → 221px and the
flexible gap grows 73px → 136px, which is the room the session ribbon
needs next. The screen still does not scroll.

The label above the number now names the metric rather than the mode,
since the highlighted pill already says which mode is on.

PreviewCard, ChipBadge and formatCommandAliasName went with the old
stat card — nothing else used them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
The fold puts a dashed ribbon under the hero — SESSION, the running
total, "Open ›" and a + that logs the current line. The phone could add
to the session before but never look at it, so the desktop rail's
running total had no counterpart here and the tape was write-only.

The library sheet gains SESSION as a fourth tab (the mockup shows three;
per the brief PROJECTS stays too). It lists the tape newest first, each
line loadable or removable, with the total and "Save as project" — the
same two actions the desktop rail has had since 3.10.0.

The recents strip it replaces was a partial stand-in for this: four
queries, no totals, no way to remove one. Those lines are now in the
session tab with everything else.

At 390×844 the ribbon costs 54px and the flexible gap absorbs it —
70px still spare, no scrolling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
The fold draws a `>` key in the keypad's bottom row, which closes a gap
I left open in 3.15.0: the palette shipped keyboard-driven and the phone
keypad has no way to type `>`, so phones had no route to it at all. Tap
it and the line becomes a command line.

The `+` beside Save / Compare / Share is not in the mockup, but without
it a phone can only view a multi-item line, never start one — and going
through the palette is a long way round for something the desktop does
in one press. It's disabled until the current line is a finished
calculation, since there's nothing to add an item to before that.

Three e2e now hold the fold's central property: at 390×844 the document
never scrolls, and the hero, ribbon, palette key and keypad are on
screen together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
The two loose stat tiles become the fold's four-cell grid — kg/m, per
piece, total weight, total cost. The values come from the same builder
the breakdown panel uses rather than being recomputed, so the row above
and the panel below cannot disagree about the same number.

The action row gains "+ another item", which is what the desktop fold
offers in place of hunting for the palette, and the top bar gains a `>`
commands hint. That hint is the only place in the app that says how the
palette opens — it shipped in 3.15.0 discoverable only by someone who
already knew.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Screenshotting the fold at 390×844 showed the bug: the strip wrapped to
two rows, and the second row was clipped by the query line sitting under
it. The layout has no vertical give to lend a strip whose height depends
on how many chips the current stage happens to produce.

The fold draws this as a single row that scrolls sideways, which is the
fix as well as the design — the strip is now a fixed height whatever the
stage offers. A test pins that, since the failure mode is silent: the
chips are still in the DOM and still tappable, they just can't be seen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Screenshotting a long line found what the tests hadn't: the chip box was
flex-wrap with no ceiling, so a three-item query grew to four rows and
pushed the keypad's bottom row — ↵, the unit key, the rate key — below
the fold. The box now stops at two rows and scrolls to follow the caret,
which sits at the end of the line; without that, typing walks off the
visible area even with the box capped.

The keypad also ended flush with the viewport edge, so on a device with
a home indicator the bottom row sat underneath it. It pads by
env(safe-area-inset-bottom) now, with a floor for browsers reporting
none.

And the hero had no flex-shrink-0, so on a short viewport it compressed
and clipped itself rather than letting the flexible gap give — which is
the only thing that should ever shrink.

With a general tightening on top (keys 40→36, hero 64→56, less padding
through the strip, actions and ribbon) the fold now fits a 667px screen
with room, not just the 844 it was drawn for. Three e2e assert the ↵ key
is fully inside the viewport at 844, 700 and 667.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Logging a line moved the whole screen down. The ribbon carried weight
and money side by side, and once both had digits there was no room left
for "Open ›" on one line — it wrapped, the row went 48px to 72px, and
everything below shifted with it.

It now shows the total in whichever unit the hero is showing, then the
line count, which is what the fold draws and is narrow enough that the
row holds one height empty or full. My first attempt truncated the money
figure instead, which was worse: a number cut mid-digit reads as wrong
rather than as abbreviated, and the full split is one tap away in the
session tab.

The e2e measures the ribbon empty and populated and asserts both height
and y-position, since the symptom was everything below it moving.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
Asked whether this was merge-ready, I screenshotted the surfaces I had
changed but never looked at. Two were broken.

The desktop glance row shared a flex row with the action cluster, which
crushed each of the four cells to 21px against the 91–114px its value
needed — every figure cut mid-number. The fold stacks these; they now
sit on their own full-width row with the actions beneath. My e2e for it
passed because getByText matches text that is in the DOM but invisible.

The library's fourth tab ran off a 390px screen with PROJECTS cut in
half. I first tuned the font down until English fitted, which was the
wrong fix: Bosnian's POREĐENJE is longer than COMPARE, so it would have
broken on the next locale. The pills are sized to their own labels now
and the row scrolls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
The rail's weight and money columns were a fixed 82px and 92px, so a
value that outgrew them wrapped its unit onto a second line — KM
1,396.41 split into KM above 1,396.41, with empty space next to it.
Reported on BAM, whose KM is two characters where € is one; that is why
it never showed up in testing, and why the fix is verified by switching
the app to BAM rather than by reasoning about it. The columns are a
minimum width with nowrap now: still aligned, free to grow, and the unit
cannot leave its number. The saved card had the same fixed 92px on both
its figures and is fixed with it.

The phone library tabs now label only the open one; the rest are their
icon and count. Four labelled tabs never fitted 390px — my previous
attempt let the row scroll, which just moved PROJECTS off screen. This
keeps all four visible, and since only one label is ever rendered the
row is the same width in every language rather than depending on how
long the words happen to be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvGsTcCt5QTiGFkJNYwPH1
@nedimperva
nedimperva merged commit c8a08af into master Aug 11, 2026
4 checks passed
@nedimperva
nedimperva deleted the claude/app-improvements-review-2j0x69 branch August 11, 2026 06:58
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.

2 participants