One row rhythm across the dashboard, and a transaction row that stops lying - #227
Merged
Conversation
Jakub, on device: the Transactions section does not respect the structure Assets and Markets have - different gaps around the separator, a different wall, the logo in a different place. Find a sweet spot between them and roll it out across the app. Measured first, ink to ink, at 390pt. Only one of the three was a decision: Transactions 4.00 / 4.00 symmetric, declared Markets 13.25 / 16.75 asymmetric, 100% Material's bare ListTile Assets 15.63 / 20.00 asymmetric, vertical:4 plus 27.62 of snap So "adopt the Assets structure" would have meant adopting a default nobody chose, and it is why two of the three were asymmetric while the declared one was exact. Jakub picked Assets' geometry and 12 for the rule. `space6` = 12 is the midpoint of the [4, 20] range, on the 4-pt grid, and was already shipping symmetrically at three other row lists, so the value is one this app had settled on rather than a new one. The ListTile had to go rather than be tuned: its contentPadding is charged INSIDE the tile snap and cannot subtract from it - Assets declared vertical:4 and still rendered 15.63/20.00. Hitting 12/12 through the tile would have meant driving five knobs to produce a number no reader could predict from source, which is the disease being cured. `gw_row_rhythm.dart` holds the four values; `gw_row_rhythm_test.dart` measures PAINTED INK rather than reading source, because the numbers this replaces were invisible in source. Also aligned dashboard_markets.dart's rule from a bare `Container` to the `Divider` form every other list uses - the same pixel drawn by two widgets, recorded as unresolved in sketch 019. The two ROW INSET cases in gw_section_title_rhythm_test.dart asserted `C > kMaxAbsorbableInset` BECAUSE the snap existed. They characterised a Material default this commit deletes, so they now assert the absorbed condition instead. R1=12 and R2=26 do not move; Markets improves from a rendered 26.75 to exactly 26.
…d at 70% Three of Jakub's calls on the transaction row, in one file because they interleave in it. TYPE SCALE. develop's 260806-hfe gated a phone density pass on `!useDesktopLayout(context)`, which reads the WINDOW, so on a phone it was true on every surface - the dashboard panel included, which was never its target. Title rendered 14 against titleMd's 16, subtitle 11 against bodySm, amount 13 against 16, the identity glyph 28 against 40. Jakub's call was to remove the type-scale half everywhere rather than thread density from the call site; the density half - padding, the dropped time column - stays. Note the recorded value was wrong where it was written down: bodySm is 14 and always has been. The 13px floor belongs to labelMd. Deleting the override and letting the token govern is right either way, and it renders one step above that floor rather than on it. AMOUNT. `_toneColor` gave `incoming` statusSuccess and `outgoing` textPrimary, so direction was carried by colour in one direction only, and stated twice on a credit and once on a debit. statusSuccess against textPrimary measures 1.86:1 - under a third of 1.4.11's 3:1 - while `+` against `-` is a shape at 19.29:1, and the badge already states direction, in colour, in both. The tint was a fourth statement of a fact stated three times. It is gone, and colour now means STATUS, which a sign cannot carry. VALUE LINE. It was gated on `valueLine != null`, and `_fiatLine` returns null whenever the price map has no entry - so a BTC row silently lost its second line. The row shrank only 0.857px, which is why it read as a non-defect, but `Row` centres its children, so the amount DROPPED 11.571px out of the column its neighbours share. The slot is now always occupied, reading `No price`. The value line's colour keys off the `Not charged` sentinel rather than a literal port of `isDead`, which is a local inside `txRowContent` and not a field. A literal port would have painted a Banxa order's honest fiat number red, because `orderRowContent` deliberately folds an unrecognised status onto `cancelled` while keeping the real value. Pinned by a test. LEAD. `Sent` / `Processing job` / `Card purchase` sat in textPrimary, the same ink and weight as the title 2px above it - 1.00:1. They arrived there in 0cd2889, which moved them out of a surfaceMenu chip. Now textPrimary70, the fallback the file already named by hand for this case.
… panel `endOfTransactionsLabel` was appended unconditionally in the list branch. The dashboard panel passes `limit: kDashboardTransactionsCap`, so it renders the five most recent rows and leaves the rest behind `View all` - and then told the reader there were no more. A wallet with forty transactions showed five and claimed that was all of them. The comment beside it asserted "neither presentation truncates, so no more is literally true". True of the page, never true of the panel. Now gated on `limit == null`, which is the page. The same label also carried the last of develop's phone type-scale shrink, printing at 11 under rows that print at 14. It takes bodySm at every width now. The padding around it stays gated on `compact`: that is density, not type scale, and is treated the same way as the rest of the row. Nothing pinned any of this before. Three tests do now, in the file that already had a host mounting the panel with `page: false`.
Three whole schemes for the transaction row's amount column and its direction signal, drawn at 1:1 on a 390x844 phone with the shipping Inter subset inlined so every measurement is the width Flutter lays out. A - neutral amount, fixed two-line slot. B - tinted in, neutral out, as a declared rule. C - value-first hierarchy. Jakub picked A, plus the independent LEAD control at L2 (white 70%). C was rejected on a fact rather than on taste: `fiatValue()` multiplies by `currentPrice`, so it applies today's price to a past transaction. C would have made the row's least reliable number its headline, on a screen that exists as a historical record. The brief's premise was wrong and Jakub's memory was right. He recalled a colour decision about the four lead strings; `git show 0cd2889` has them moving out of a surfaceMenu chip at labelMd 11 in textSecondary and into the subtitle lead at bodySm 14 w600 in textPrimary. The change did not separate them from the ticker, it collapsed them into it.
260807-ubg restore the transaction row type scale 260807-v6m dashboard panel bottom inset - PLANNED, NOT EXECUTED 260807-wbu the app-wide row rhythm rollout 260808-k2l sketch 186 scheme A plus lead L2 260807-v6m is deliberately unexecuted. Its measurements were taken before the rhythm rollout changed the rows underneath it, so its call-site values are stale and it has to be re-measured before it runs. It also carries SEPARATOR-RHYTHM-MEASURED.md, the survey the rollout was decided from.
braianxde
marked this pull request as ready for review
August 8, 2026 13:53
Repo rule, and 16 comment lines added by this branch broke it. Only lines this branch added were touched; pre-existing prose elsewhere in these files is left alone.
henriqueaklein
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three panels on the dashboard drew the same kind of row three different ways, and the transaction row told the reader things that were not true. Walked on a physical iPhone throughout.
What a user hits
The Transactions panel lied about how much history it had. It renders the five most recent rows and leaves the rest behind
View all, and then printed "No more transactions" underneath. A wallet with forty transactions showed five and claimed that was all of them. The terminus now belongs to the page, which does not truncate.A BTC row fell out of line with its neighbours. The second line was gated on a price lookup succeeding, and
_fiatLinereturns null whenever the price map has no entry. The row shrank by only 0.857px, which is why it read as a non-defect - butRowcentres its children, so the amount itself dropped 11.571px out of the column every other row shares. The slot is now always occupied, readingNo pricewhen there is no price.The amount's colour said the same thing twice, in one direction only. A credit was green, a debit was plain white.
statusSuccessagainsttextPrimarymeasures 1.86:1 - under a third of WCAG 1.4.11's 3:1 bar - while+against-is a shape at 19.29:1, and the badge already states direction, in colour, in both directions. The tint was a fourth statement of a fact stated three times. Colour on the amount now means status: pending, failed, not charged.The type scale had been shrunk on every phone surface by accident.
develop's density pass was gated on!useDesktopLayout(context), which reads the window, so on a phone it was true on the dashboard panel as well as the page it was written for. Title rendered 14 againsttitleMd's 16, subtitle 11, amount 13, the identity glyph 28 against 40. The type-scale half is gone; the density half - padding, the dropped time column, the 44pt filter-bar touch targets - stays.The row lead was the same ink as the title 2px above it (1.00:1). It arrived there in 0cd2889, which moved those four strings out of a
surfaceMenuchip. NowtextPrimary70.Transactions, Assets and Markets now draw one row. Measured before deciding, ink to ink, at 390pt:
Only Transactions was a decision. The other two inherited Material's
ListTilesnap - Markets declared nocontentPaddingat all - which is why the two undeclared ones were the asymmetric ones. All three are now wall 8, leading glyph 38, glyph to text 8, right wall 8, rule 12/1/12.Notes for the reviewer
space6= 12 was not invented for this. It is the midpoint of the measured [4, 20] range, on the 4-pt grid, and was already shipping symmetrically at three other row lists.The
ListTilehad to be replaced rather than tuned. ItscontentPaddingis charged inside the tile snap and cannot subtract from it - Assets declaredvertical: 4and still rendered 15.63/20.00. Reaching 12/12 through the tile would have meant driving five knobs to land on a number no reader could predict from source, which is the condition being removed.The value line's colour keys off the
Not chargedsentinel rather than a status test.orderRowContentdeliberately folds an unrecognised Banxa status ontocancelledwhile keeping the real fiat number, so a status test would have painted an honest number red. Pinned by a test.The two
ROW INSETcases ingw_section_title_rhythm_test.dartassertedC > kMaxAbsorbableInsetbecause the snap existed. They characterised a Material default this branch deletes, so they now assert the absorbed condition. R1 = 12 and R2 = 26 do not move - Markets improves from a rendered 26.75 to exactly 26.gw_row_rhythm_test.dartmeasures painted ink rather than reading source, because every number it replaces was invisible in source.Verification
flutter testflutter analyzedart format lib testcheck_brace_stylecheck_raw_colorscheck_onboarding_seed_safetycheck_no_new_key_logging --scan-treecheck_agent_rules_syncorigin/developtoocheck_agent_rules_syncis pre-existing:AGENTS.mdand its mirror have drifted on the "Not lazy about" paragraph. This branch touches neither file, and the gate was verified failing on a cleanorigin/developworktree. Not fixed here because it is unrelated to this change.Deliberately not here
The transaction detail drawer still gates its own value line. It inherits the amount tone correctly but does not get
No price. Found while applying scheme A; left out because the drawer is a different surface with a different width budget and deserves its own look.The dashboard panel's bottom padding is still asymmetric. The distance from the box top to its title is owned by
GWSectionTitle; the distance from the last row to the bottom edge is owned by nothing, so it is whatever the last widget happens to declare - a button's fixed height, a row's own padding, an unmeasuredListTilesnap. Planned as quick 260807-v6m and deliberately not executed: its measurements were taken before this branch changed the rows underneath it, so its call-site values are stale and have to be re-measured first.Three tightenings that fall out of the rhythm change, none of them chosen, all walked on device: the Assets total band to first row goes 20 to 12, the
/assetskicker to first row goes 28 to 20, and a zero-balance Assets row gives the token name about 27px less width thanListTileused to. The long-balance case gets more room than before; the split is now deterministic rather than varying with the trailing content's width.settings_screen.dartandorder_card.dartstill draw dividers that do not pin their colour, so they inheritdividerTheme, which resolves tosurfaceMenu- a fill colour, not a border colour. That is a colour bug, not a rhythm bug, and is out of scope here.Scheme C from sketch 186 was rejected on a fact, not on taste.
fiatValue()multiplies bycurrentPrice, so a value-first hierarchy would apply today's price to a past transaction and make the row's least reliable number its headline, on a screen that exists as a historical record.