Skip to content

feat(client): replace the charges table with a per-type charge record list - #4231

Open
gilgardosh wants to merge 2 commits into
mainfrom
feat/charges-record-list
Open

feat(client): replace the charges table with a per-type charge record list#4231
gilgardosh wants to merge 2 commits into
mainfrom
feat/charges-record-list

Conversation

@gilgardosh

Copy link
Copy Markdown
Collaborator

Why

Charges are a union of 11 types with genuinely different attributes, so a shared column set never fitted them. Working from the attached per-type field spec: of 14 display attributes, VAT and business trip each apply to exactly one type, which left those columns blank on ten rows out of eleven. Counterparty applies to four, tax category to five.

Rows also measured 166px (measured, not estimated) — ListCapsule rendered tags and each metadata count as its own bordered box, while the select and actions cells stacked their controls vertically and set the row height regardless of content.

What changed

Each charge renders as a record of six regions at fixed horizontal positions — manage, identity, meaning, health, money, actions. Region placement is identical on every record, which is what preserves the vertical scanning a table gave you. Which fields appear inside a region comes from a declarative matrix in charge-fields.ts, so a record's shape is a pure function of its __typename.

Records measure ~85px, or 50px with the new density toggle — down from 166px.

TanStack Table stays as the headless engine: sorting, selection (keyed by charge id), expansion and the batch-actions menu are unchanged, and all 8 call sites keep the same props. columns.tsx becomes an accessor-only sort schema; the presentational cells and utils.ts are removed (its per-type rules had already drifted from the server's validate.helper.ts).

User-visible

  • Missing info is summarised once per record as a badge, counting only fields that charge type actually displays — instead of up to six unlabelled red dots scattered across cells.
  • Suggestions are offered, not alarmed. Suggested descriptions and tags get a one-click accept inline, replacing a solid yellow background that read as an error.
  • Sorting moved to a list toolbar bound to the server's sortBy, so it orders every matching charge rather than only the loaded page. Select-all, batch actions and CSV export moved there too, and the toolbar announces the selection count.
  • A document can be dropped anywhere on a record, not just onto the narrow More Info cell.
  • Density toggle, remembered per browser.

Bugs fixed

  • The Date column never sorted. Its accessor returned the whole date object, so TanStack's automatic sort fell back to comparing "[object Object]" with itself and returned the same answer for every pair — in both directions.
  • Lists silently truncated at 100 rows. The rendered row model sits at the end of a pipeline ending in pagination, so charges-ledger-validation (streams without a limit) and the unbounded VAT report sections could never show anything past row 100. Leaving pageSize unset would have been worse — TanStack defaults to 10.
  • Nothing inside a row was clickable once wrapped for drag-and-drop, because Mantine's dropzone disables pointer events on its content. Unnoticed while it only wrapped inert text.
  • The header row rendered one more cell than the body, adding a phantom column.
  • CSV export could include charges selected in a sibling table — the VAT report shares one selection map across three lists.
  • Batch "refresh selected" did nothing for selected charges that were not currently rendered.
  • Accessibility: validation state was colour-only with no accessible name; count chips and the missing-info badge now carry text, row checkboxes name their charge instead of 100 identical "Select row", and the expand control is no longer a nameless chevron.

Verification

  • 71 unit tests across 6 files, including matrix coverage for all 11 types against a fully-populated fixture — so a field that is hidden is proven hidden by the spec, not merely absent from the data.
  • Typecheck clean, lint clean in touched files, prettier clean, client build passes.
  • Driven in the running app with Playwright: record heights, region alignment across all 11 types, expansion semantics (aria-expanded/aria-controls, panel inside the same <li>), selection, and the sort menu firing exactly one refetch per change with the right variables and a page reset.

Three Storybook stories are the review surface:

  • Charges → Spec Matrix — the transcribed spec, in the source sheet's row order.
  • Charges → Charge Record — all 11 types with every field populated, plus missing-info, health, special-case, density and status sections.
  • Charges → Indicators — every indicator state.

Reviewer notes

  • Please check the Spec Matrix story against the source sheet. The matrix was transcribed from an image; a correction is a single digit in charge-fields.ts and the tests will say which invariant moved.
  • Two things could not be verified against the mock server, because its chargesByIDs fabricates ids rather than echoing the requested ones: expand-all's panel contents (the single batched request is confirmed; only its id lookup misses) and the business detail page, whose route loader fails for the same reason. Both want one pass against a real backend.
  • One design call departs from the original sketch: health comes before money, so the amount lands at the right edge. Ordered the other way, a right-aligned amount butted into left-aligned count chips and read as one run-on string.

🤖 Generated with Claude Code

@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 17, 2026 16:03 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 17, 2026 16:03 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@accounter/client 0.1.0-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/green-invoice-graphql 0.8.7-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/hashavshevet-mesh 0.2.13-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/israeli-vat-scraper 0.1.13-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/modern-poalim-scraper 0.11.0-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/payper-mesh 0.2.13-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/scraper-app 0.0.3-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/server 0.2.0-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/shaam-uniform-format-generator 0.2.7-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎
@accounter/shaam6111-generator 0.1.9-alpha-20260824115950-8e76ec21f4e292cf778d572d2164c4227d15ab7e npm ↗︎ unpkg ↗︎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the client charges list UI from a traditional table layout into a per-charge “record list” whose visible fields are driven by a declarative per-type spec matrix, while keeping TanStack Table as the headless engine (selection, expansion, sorting).

Changes:

  • Replace table row/cell rendering with a record-based list (ChargeRecord + regions) and a per-type field visibility matrix (charge-fields.ts).
  • Move list-level controls into a dedicated toolbar (selection count, batch actions, density toggle, CSV export, and server/client sort menu).
  • Add new indicators/suggestion affordances and fix several behavioral issues (pagination truncation, selection/export leakage, batch refresh semantics, drag/drop interactivity, accessibility labeling).

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/client/src/components/screens/charges/missing-info-charges.tsx Binds server-side sort into screen-owned filter and passes sort binding to ChargesTable.
packages/client/src/components/screens/charges/charge.tsx Hides list toolbar on single-charge screen.
packages/client/src/components/screens/charges/all-charges.tsx Adds server-side sort binding passed to ChargesTable.
packages/client/src/components/common/inputs/drag-file.tsx Restores pointer events inside Mantine Dropzone content so record content remains interactive.
packages/client/src/components/charges/utils.ts Removes legacy per-type “shouldHaveX” helpers (superseded by spec matrix).
packages/client/src/components/charges/use-charge-density.ts Adds persisted per-browser density toggle with same-tab synchronization.
packages/client/src/components/charges/columns.tsx Removes legacy presentational TanStack column definitions (table layout).
packages/client/src/components/charges/columns.ts Adds accessor-only column schema used solely for sorting capabilities.
packages/client/src/components/charges/charges-toolbar.tsx Adds list toolbar for selection/batch actions/sort/density/export.
packages/client/src/components/charges/charges-table.tsx Converts table rendering into <ul> record list, adds refetch registry, fixes pagination truncation, export selection isolation, and integrates toolbar.
packages/client/src/components/charges/charges-sort-menu.tsx Adds shared sort dropdown for server-scope and page-scope sorting.
packages/client/src/components/charges/charges-row.tsx Removes legacy <tr> row + expansion-row rendering and row-model mutation.
packages/client/src/components/charges/charges-filters.tsx Removes sort controls from filters form and preserves toolbar-driven sort on submit/clear.
packages/client/src/components/charges/charges-batch-actions-menu.tsx Switches refresh-selected behavior to refetch-registry callback.
packages/client/src/components/charges/charge-suggestion-field.tsx Adds unified suggestion UI/behavior for description and tags (one-click accept + apply-to-similar).
packages/client/src/components/charges/charge-record.tsx Implements memoized charge record list item, DnD wrapper, and inline expansion panel.
packages/client/src/components/charges/charge-record.stories.tsx Adds Storybook coverage for all types, density, missing-info, and edge cases.
packages/client/src/components/charges/charge-record-regions.tsx Implements the six fixed-position regions composing each record.
packages/client/src/components/charges/charge-indicators.tsx Adds indicator primitives (needs badge, dots, chips, VAT/amount/ledger state).
packages/client/src/components/charges/charge-indicators.stories.tsx Adds Storybook gallery for indicator states and suggestion fields.
packages/client/src/components/charges/charge-fields.ts Adds per-type field visibility matrix and helpers (isFieldVisible, relevantMissingInfo, etc.).
packages/client/src/components/charges/charge-fields.stories.tsx Adds Storybook “Spec Matrix” story for validating matrix transcription.
packages/client/src/components/charges/charge-dates.ts Extracts/renames date aggregation logic from old cell into reusable helpers.
packages/client/src/components/charges/cells/vat.tsx Removes legacy VAT cell implementation.
packages/client/src/components/charges/cells/type.tsx Removes legacy charge type cell implementation.
packages/client/src/components/charges/cells/tax-category.tsx Removes legacy tax category cell implementation.
packages/client/src/components/charges/cells/tags.tsx Removes legacy tags cell implementation.
packages/client/src/components/charges/cells/more-info.tsx Removes legacy “More Info” cell (counts + DnD drop target).
packages/client/src/components/charges/cells/index.ts Removes legacy cell exports barrel.
packages/client/src/components/charges/cells/description.tsx Removes legacy description cell implementation.
packages/client/src/components/charges/cells/counterparty.tsx Removes legacy counterparty cell implementation.
packages/client/src/components/charges/cells/business-trip.tsx Removes legacy business trip cell implementation.
packages/client/src/components/charges/cells/amount.tsx Removes legacy amount cell implementation.
packages/client/src/components/charges/tests/pagination.test.tsx Adds regression test for pagination truncation behavior.
packages/client/src/components/charges/tests/columns.test.tsx Adds regression tests for date/amount sorting correctness.
packages/client/src/components/charges/tests/charges-toolbar.test.tsx Adds tests for toolbar behavior (selection count, sort bindings, density toggle, etc.).
packages/client/src/components/charges/tests/charge-indicators.test.tsx Adds tests for indicator semantics and accessibility labeling.
packages/client/src/components/charges/tests/charge-fields.test.ts Adds tests for matrix invariants and missing-info filtering behavior.
packages/client/src/components/charges-ledger-validation.tsx Adds server-side sort binding passed to ChargesTable.
packages/client/src/components/business/charges-section.tsx Removes redundant border wrapper around ChargesTable (list now draws its own).
.changeset/charges-record-list.md Documents the UI refactor and enumerates user-visible changes/bug fixes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +393 to +397
// Intersected with this table's own charges: the VAT report shares one selection map across three
// tables, so an unfiltered selection would leak the other tables' charges into this export.
const selectedIds = Object.keys(rowSelection).filter(
id => rowSelection[id] && chargeIds.includes(id),
);
Comment on lines +75 to +80
<Checkbox
checked={isSelected}
onCheckedChange={value => onSelectedChange(!!value)}
// 100 identical "Select row" labels are useless to a screen reader; name the charge.
aria-label={`Select charge ${row.description ?? row.id}`}
/>
Comment on lines +31 to +39
it('gives every type a full-width row', () => {
// Rows are positional against CHARGE_FIELDS; a short row would make later fields read as hidden.
for (const type of ALL_TYPES) {
const decided = CHARGE_FIELDS.filter(
field => isFieldVisible(type, field) || !isFieldVisible(type, field),
);
expect(decided).toHaveLength(CHARGE_FIELDS.length);
}
});
Comment on lines +58 to +69
type Visibility = 0 | 1 | 2;

/**
* The spec matrix. One row per charge type, positional against {@link CHARGE_FIELDS}, mirroring the
* spec spreadsheet 1:1 so a cell can be diffed against it by eye.
*
* Note how little of this a table could express: `vat` and `businessTrip` each apply to exactly one
* of eleven types, which is why the record composes fields per type rather than sharing columns.
*/
// prettier-ignore
const MATRIX: Record<ChargeType, readonly Visibility[]> = {
// ty md dr am vat cp de tg tax bt tx doc me ldg
Comment on lines +74 to +78
/**
* Client-side sortable columns, labelled. Keyed by the explicit `id`s in `columns.tsx` — which is why
* those ids are declared rather than derived (tanstack would otherwise name them
* `counterparty_counterparty_name`).
*/
@gilgardosh
gilgardosh force-pushed the feat/charges-record-list branch from 98b144d to ac8b67a Compare August 23, 2026 10:47
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 23, 2026 10:47 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 23, 2026 10:47 — with GitHub Actions Inactive
@gilgardosh

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (now on top of 250e05f4a). Five conflicts, all in files this PR replaces or rewrites. Both conflicting PRs fix real bugs whose causes still exist in the new architecture, so each was carried forward rather than dropped.

#4239 — charge row refetch threading and modal close logic

Its fix Carried forward as
Stabilize the data prop, because callers build the array inline and re-deriving rows discards fresher per-row refetch data Kept verbatim — useStableValue(data) in ChargesTable. Auto-merged; the reason is unchanged.
network-only on the row refetch, since it always runs right after a mutation and a cached result would re-apply the pre-mutation charge Applied to ChargeRecord's refetch. My version was missing it — a genuine bug in this branch.
Re-read selected rows at call time in the batch menu, since a captured value goes stale across an awaited mutation Adapted: the refresh registry is keyed by charge id, so ids are re-read from table.getSelectedRowModel() at call time instead of closed over.
Report the change on the mutation, not via SimilarChargesByIdModal's onClose Applied to charge-suggestion-field.tsx, which had exactly the bug being fixed — it passed onClose={onChange}.
Capture the dialog's comparison criteria at action time, so the follow-up survives the row refresh onAccept now resolves to the criteria it applied; the affordance holds them in state. Reading them off props would have made them vanish the moment the refresh removed the suggestion.

SimilarChargesByIdModal itself and the close/issue/bank-deposit/unlink callbacks are untouched by this PR, so those fixes carry over unmodified.

#4209 — drop the charge row when deleting its last document empties it

ChargeExtendedInfo gained onChargeDeleted, so a mutation that empties a charge drops the row instead of refetching a deleted id and leaving a shadow charge. ChargeRecord now passes onChargeDeleted={removeCharge}.

Its regression test

charges-table-refetch.test.tsx came from #4239 and guards exactly the invariants above, so I kept it working rather than replacing it. It selected the accept control by ConfirmMiniButton's .text-green-500 class, which no longer exists; it now matches on the accessible name ([aria-label^="Accept"]) — stabler, and closer to what the affordance promises a user. Both of its cases pass unchanged otherwise.

73 tests pass in the charges suite, 3647 repo-wide, typecheck and build clean. The one repo-wide failure is the pre-existing packages/migrations RLS test, which needs database-create privileges.

gilgardosh and others added 2 commits August 24, 2026 14:12
… list

Charges are a union of 11 types with genuinely different attributes, so a
shared column set never fitted them: of 14 display attributes, VAT and
business trip each apply to exactly one type, leaving those columns blank
on ten rows out of eleven. Rows also measured 166px.

Each charge now renders as a record of six regions at fixed horizontal
positions. Region placement is identical on every record, preserving the
vertical scanning a table gave; which fields appear inside a region comes
from a declarative matrix in charge-fields.ts, so a record's shape is a
pure function of its __typename. Records measure ~85px, or 50px compact.

TanStack Table stays as the headless engine, so sorting, selection (keyed
by charge id) and expansion are unchanged and every call site keeps the
same props. columns.tsx becomes an accessor-only sort schema; the
presentational cells and utils.ts (whose per-type rules had drifted from
the server's) are removed.

Bug fixes found along the way:

- The Date column never sorted: its accessor returned the whole date
  object, so the automatic sort compared "[object Object]" with itself
  and returned the same answer for every pair, in both directions.
- Lists silently truncated at 100 rows, hiding everything past row 100 on
  the screens that fetch without a limit.
- Nothing inside a row was clickable when wrapped for drag-and-drop,
  because Mantine's dropzone disables pointer events on its content.
- The header row rendered one more cell than the body.
- CSV export could include charges selected in a sibling table.
- Batch refresh did nothing for selected but unrendered charges.
- Validation state was colour-only with no accessible name, and the
  expand control was an unlabelled chevron.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ports an imported design mockup onto the record list. The mockup turned out to be a refinement of the
design already in this PR rather than a replacement — same six regions, same order, same 1/2/4/2/2/1
spans, same suggestion and needs-badge vocabulary — so this takes its visual deltas and leaves the
working structure alone. Its per-type `show` flags were an independent second transcription of the
spec matrix and agreed with `charge-fields.ts` on all 143 cells, so the matrix is unchanged.

Per-type colour chip. `CHARGE_TYPE_COLOR` gives each of the eleven types its own hue, rendered by
`ChargeTypeBadge` as a tinted ringed icon chip beside the type name; colour is what lets you pick
every Salary charge out of a hundred rows without reading. Three hues were reassigned because they
collided with the status vocabulary on the same row — amber is "needs attention", emerald "accept /
positive", red "error / negative" — so Salary moved amber → purple, Monthly VAT emerald → sky, and
Dividend rose → pink. A test asserts that reservation. The hue class lists are complete literals, not
interpolated from the hue name, because Tailwind only sees literals in source.

Selection and drop feedback. Selected records gain a 2px left accent rail on top of their tint.
`DragFile` names its dropzone group so the record styles itself off Mantine's `data-accept` and lights
the whole row on drag; before, the only sign a drag had registered was the cursor. The drop ring uses
`primary` rather than the `ring` token because `--color-accent` and `--color-muted` hold the same
value, leaving the drop tint indistinguishable from the selected background, and a grey ring at 40%
did not separate them either. Verified that the record's checkbox, approval control, actions menu and
expand button all still hit-test as clickable through the dropzone.

One surface. Toolbar and list now share a single bordered card rather than the toolbar floating above a
separately bordered list. The expansion panel is indented under its record behind a rail. Confirmed
the card's `overflow-hidden` does not clip the panel's nested horizontal scroller: with a forced
4000px child the scroller reports scrollWidth 4000 against clientWidth 1200, scrolls, and the page
gains no horizontal scrollbar.

Smaller fixes from the same pass. The CSV export gains a visible label, having had no accessible name
at all — a tooltip is a description, not a name. Count chips gain hover hints. "Delete Charge" is
styled destructive, having been the only irreversible item in the menu at the same weight as "Copy
Charge Link" above it. The accountant-status control gains a section label, per-option dot and
current-value checkmark, an `aria-label` on its trigger, a note that Pending is a downgrade rather
than a step toward approval, and the `dark:` variants it had none of; the VAT and business-trip reports
share it and inherit all of that. Its options map keeps its existing shape deliberately — the filter
in `charges-filters.tsx` reads it with `Object.values()` and spreads the leftover fields onto a
`Button`, so a new key would land on the DOM as an unknown attribute.

Charges-surface neutrals move onto the colour tokens; status accents and type hues stay literal, since
no token expresses them. The "absent value" placeholder, previously defined identically in two files,
is now shared.

Not adopted from the mockup, because the existing code is better: `role="status"` on the needs badge
(a hundred rows would mean a hundred live regions announcing on every refetch), and a count chip whose
state never reaches its accessible name. Its compact row also dropped the approval control and the
date and reflowed the spans — approval is the primary triage action and the date the second-most-
scanned field, and stable spans mean toggling density does not reflow the columns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gilgardosh
gilgardosh force-pushed the feat/charges-record-list branch from ac8b67a to f9c5bd1 Compare August 24, 2026 11:58
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 24, 2026 11:58 — with GitHub Actions Inactive
@gilgardosh
gilgardosh temporarily deployed to accounter-fullstack August 24, 2026 11:58 — with GitHub Actions Inactive
@gilgardosh

Copy link
Copy Markdown
Collaborator Author

Design refinement adopted (2nd commit)

The imported mockup at charges/redesign/ turned out to be a refinement of the design already in this PR, not a replacement — identical six regions, identical order (Health before Money), identical 1/2/4/2/2/1 spans, same dashed-amber suggestions, same needs badge, same scope labels, same ~50px compact row. Same brief, drawn twice. So this commit takes its visual deltas and leaves the working structure, GraphQL wiring and refetch registry alone.

The matrix is now independently validated

The mockup's config file carried its own per-type show flags — a second, independent transcription of the spec. Diffed against charge-fields.ts: 143/143 cells identical across 11 types × 13 fields, including both special cases (am + amountPair2, cp + counterpartyBoth2) and amountValid on credit-card charges only. The "please eyeball this transcription" item from the original plan is closed with no change.

Adopted

Type colour chip Eleven hues, tinted ringed icon chip beside the name. Colour is what lets you find every Salary charge without reading.
Selection accent rail 2px left rail on top of the tint.
One card Toolbar + list share one bordered surface; the select-all checkbox looked disconnected from the rows it governs.
Panel indent rail The expansion panel reads as belonging to its record rather than as the next list item.
Drop-target state Whole row lights on drag. New work: the mockup declared dropHover but never wired it.
Approval control Section label, per-option dot, current-value check, aria-label, dark: variants, "downgraded" note on Pending.
Destructive Delete The only irreversible menu item, previously at the same weight as "Copy Charge Link" above it.
CSV label Was icon-only with no accessible name — a tooltip is a description, not a name.

Three judgement calls worth your eye

1. Three hues reassigned. Amber means "needs attention", emerald "accept / positive", red "error / negative" — and all three appear on the same row as the type chip. The ref palette put Salary on amber, Monthly VAT on emerald, Dividend on rose, which would have made a Salary chip sit inches from an amber needs badge and stopped amber meaning attention. Salary → purple, Monthly VAT → sky, Dividend → pink; the other eight are untouched. charge-indicators.test.tsx now asserts the reservation so a future edit can't quietly undo it. Bank Deposit's orange is adjacent to the amber accent — kept as designed, but it's the one hue I'd want a second opinion on.

2. The drop ring is primary, not ring. --color-accent and --color-muted hold the same value in the token set, so the mockup's bg-accent drop tint measured identical to the selected bg-muted/60, and a grey ring at 40% didn't separate them. A dark ring enclosing the whole record can't be confused with selection, which only ever marks the left edge. Found by measuring, not by reading.

3. Kept shipped icons. The ref pairs conversion: ArrowLeftRight with internal_transfer: ArrowRightLeft — near-identical glyphs, which defeats icon-scanning.

Not adopted — the existing code is better

  • role="status" on the needs badge: 100 rows would mean 100 live regions announcing on every refetch.
  • A count chip whose dot is aria-hidden and whose state never reaches the accessible name (its own comment claims otherwise).
  • Its compact row dropped the approval control and the date and reflowed spans to 1/3/5/2/1. Approval is the primary triage action and the date the second-most-scanned field; stable spans mean toggling density doesn't reflow columns.
  • No responsive breakpoints — its "Mobile" toggle just narrows the container to 420px while grid-cols-12 stays twelve columns. The shipped regions reflow at md:.
  • Its show flags and its CountChip/NeedsBadge would have forked the spec matrix and the indicator primitives into second copies.

I also skipped its loading skeleton: ChargeExtendedInfo already renders a loader, so it would have doubled up.

Verification

Typecheck 0 errors across the whole client, lint 0 errors, prettier clean, 3666 tests pass (the one failure is the pre-existing packages/migrations RLS test needing DB-create privileges), build passes.

Driven live against the mock server, GraphQL confirmed hitting localhost:4000:

  • Region alignment holds — left edges identical across records in both densities (comfortable 127/246/485/962/1201/1440, compact 127/230/435/846/1051/1256). No horizontal page overflow.
  • Row heights unchanged by the added rail — 70px comfortable, 54px compact.
  • All 11 hues emit CSS in the bundle; the Bank Deposit chip computes to oklch(0.98 0.016 73.684) = orange-50.
  • Selection2px rail at primary/40 with bg-muted/60.
  • Drop-hover → Mantine sets data-accept="true", ring renders as 2px inset at primary/60, and the checkbox, approval control, actions menu and expand button all still hit-test as clickable through the dropzone (the Mantine pointer-events override survives).
  • The overflow-hidden risk is clear — forcing a 4000px child into the panel's scroller gives scrollWidth 4000 vs clientWidth 1200, scrollLeft takes effect, and the page gains no horizontal scrollbar.

One note on the screenshots: a missingInfoSuggestions does not exist on type OtherDocument toast appears under the mock server. That's an auto-mock schema artifact — this commit changes no GraphQL documents.

🤖 Generated with Claude Code

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