feat(ui): nest a contract's transactions, and make the alerts past the expansion limit reachable - #94
Merged
Merged
Conversation
Opening a contract produced rows styled exactly like the top-level ones, so its transactions read as siblings of the contract holding them rather than as its contents. The cause was a no-op. `indented` applied `pl-6` to the row's GUTTER cell, which is empty on a child row: padding needs content to push, so the indent had never moved anything since the day it was written. It now sits on the cell that holds the hash. A rule runs down the gutter, from the parent's chevron through its children. Drawn as a pseudo-element spanning each cell's full height, so consecutive children abut into ONE continuous line instead of a dashed column of segments, and positioned at the chevron's centre so the line visibly descends from the control that opened the group. On the parent it starts at half height, leaving the chevron rather than passing through it. Structure as the device, not decoration over it. Child dividers are inset, not removed. Removing them entirely was the first attempt and the render showed why it fails: the region gained no bottom edge and the rule ended in mid-air above the next group. A child now keeps its divider but starts it after the gutter, so rows still separate, the last one still closes the group, and the undrawn segment is exactly where the rule runs. A divider spanning the table's whole width is what says "top-level row", and now only rows that are top-level have one. The open contract is tinted, neutrally rather than in a severity colour, which would compete with the badge on the same line. While open, that row is both the heading of the rows beneath it and the control that closes them, and the tint says both: the region has a top, and the top is a control. The beyond-the-limit note moved inside the region it describes. Outside the rule, "134 older alerts are not listed here" read as a note from the table about the whole list. It also closes the region when it ends it, with the same inset edge the children use. Chosen by rendering, not by reasoning: headless Chrome against the real component markup and the real built stylesheet, iterating through no-dividers, inset dividers, and the region's closing edge. Every new utility was confirmed present in the bundle, `@property --tw-content` included: without its `initial-value: ""` the pseudo-element would not paint and the rule would be invisible with no error anywhere. Gates: 161 frontend tests (up 5), and the fixes verified falsifiable by restoring the original no-op indent, dropping the rule and dropping the tint, which fails exactly the three tests that target them. eslint, `tsc -b`, build and prettier clean. Backend untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e contract Expanding a contract fetches one page of GROUP_EXPANSION_LIMIT rows and has no pager of its own, so a contract with 137 alerts showed 50 and stated that 87 were not listed. The statement was honest and led nowhere: no view in the UI could reach them. Narrowing the attack-type or severity filters shifted which alerts fell inside the cap, but nothing could page past it, and for a single class holding more than the limit the older ones were simply unreachable. The table now takes a `contract` scope from the URL. While it is set, grouping stops (grouping by contract is pointless once a contract is chosen) and the table lists that contract's alerts flat, through the pager and the page-size control that already exist. The note that reports the surplus is the way in: it ends in "See all N for this contract". Almost none of this is new machinery. The `contract` filter already existed end to end, documented as tri-state on RiskAlertsParams and already used by the expansion itself; URL-held filter state already existed; the pager already existed. What was missing was a way in, a way out, and the mode switch. Details that are decisions rather than defaults: An empty value is treated as absent. `contract=''` is meaningful to the API, where it selects exactly the alerts naming NO contract, but that is the grouped view's own business: reaching it from the URL would produce a view that can be neither described nor cleared. The two queries are exclusive and each holds the other off through `enabled`, rather than fetching both and rendering one. Entering the scope drops the page number, which belonged to the grouped list, and closes the expansion that led there, which has no meaning once the whole table is that contract. The pinned critical row inherits the scope, for the same reason it already inherits the attack-type filter: a pin from outside the view contradicts the view. A chip beside the heading names the scope and is the only way out of it. Without it the table reads as a short, unexplained list; the URL explains it and nobody reads the URL. Scoped rows do not repeat the contract, since the chip states it once and restating one fact on every row is the noise grouping was built to remove. The truncation used for a contract address is now exported rather than duplicated, because the chip shortens the same addresses the group rows do. Gates: 168 frontend tests (up 7), each new behaviour verified falsifiable. One of those tests was inert when first written and was fixed: it asserted on the registry label, but a scoped row is given no label, so dropping the suppression would have repeated the truncated ADDRESS and the assertion would have passed with the bug present. It now asserts on the cell's whole content. eslint, `tsc -b`, build and prettier clean, and every new utility confirmed in the built CSS. Backend untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule marking an expanded contract's children began at `top-1/2` of the parent's gutter cell. The chevron is 1rem tall and vertically centred there, so that is the glyph's own middle: the line always drew through its lower half, crossing the arrow. Systematic, not a rendering accident, and visible as soon as anyone looked at a real render. It now starts 0.5rem lower, which is exactly half the icon, so it leaves the chevron and clears it. A test pins the offset, asserted on the class because jsdom computes no geometry for a pseudo-element: without it, reintroducing the overlap passed all 168 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Opening a contract group produced rows styled exactly like the top-level ones, and the alerts an expansion could not list had nowhere to be seen. UI only: no backend file is touched.
1. Nest an expanded contract's transactions under it (
40553ad)The cause was a no-op:
indentedappliedpl-6to the row's gutter cell, which is empty on a child row. Padding needs content to push, so the indent had never moved anything since the day it was written. It now sits on the cell holding the hash.2. Reach the alerts an expansion cannot list (
a490377)An expansion fetches one page of
GROUP_EXPANSION_LIMITrows and has no pager of its own, so a contract with 137 alerts showed 50 and stated that the rest were not listed. The statement was honest and led nowhere: no view in the UI could reach them. Narrowing the filters shifted which alerts fell inside the cap, but nothing could page past it.The table now takes a
contractscope from the URL. While it is set, grouping stops and the table lists that contract's alerts flat, through the pager and page-size control that already exist. The note that reports the surplus is the way in: it ends in "See all N for this contract".Almost none of this is new machinery: the
contractfilter already existed end to end, documented as tri-state and already used by the expansion itself; URL-held filter state and the pager already existed. What was missing was a way in, a way out, and the mode switch.Decisions rather than defaults:
contract=''is meaningful to the API, where it selects exactly the alerts naming NO contract, but that is the grouped view's own business: reaching it from the URL would produce a view that can be neither described nor cleared.enabled, rather than fetching both and rendering one.3. Start the rule below the chevron, not through it (
bc23427)The rule began at
top-1/2of the parent's gutter cell. The chevron is 1rem tall and vertically centred there, so that is the glyph's own middle: the line always drew through its lower half, crossing the arrow. Systematic, not a rendering accident. It now starts 0.5rem lower, exactly half the icon.Verification
main), and every new behaviour verified falsifiable by injecting the regression it guards: restoring the original no-op indent, dropping the rule, dropping the tint, keeping the page on scope entry, repeating the contract per row, and reintroducing the chevron overlap each fail exactly the tests that target them.@property --tw-contentincluded: without itsinitial-value: ""the pseudo-element would not paint and the rule would be invisible with no error anywhere.tsc -b,vite buildandprettier --checkclean. Backend untouched, so nothing here can affect detection behaviour.🤖 Generated with Claude Code