Skip to content

fix(sponsor-reports): make Purchase Details describe itself on every view - #1046

Merged
smarcet merged 16 commits into
masterfrom
fix/purchase-details-self-describing
Aug 21, 2026
Merged

fix(sponsor-reports): make Purchase Details describe itself on every view#1046
smarcet merged 16 commits into
masterfrom
fix/purchase-details-self-describing

Conversation

@caseylocker

@caseylocker caseylocker commented Aug 20, 2026

Copy link
Copy Markdown

ref: https://app.clickup.com/t/86bbh5vat

Frontend half of "Purchase Details: the report does not describe itself". Backend half is fntechgit/sponsor-reports-api#40.

DEPLOY GATE: merge and deploy sponsor-reports-api#40 first

This PR must not reach production ahead of the API. If it ships first:

  • Both freshness columns render blank on all three views, because the fields do not exist yet.
  • Worse, the Payment Method control becomes visible on the line views while the lines endpoint still ignores payment_method. base-api-utils drops undeclared filter fields silently on the AND path, so the filter reads as applied and does nothing. Before this PR the control was hidden, which was at least honest.

refunded_amount and is_canceled are already on origin/main, so the refund column works today regardless.

Deploying this

No config. The diff touches no .env, webpack config, package.json, yarn.lock, Dockerfile or workflow, and adds no new process.env reads. The three new i18n keys ship inside the bundle, so there is no separate translation step.

Merge target is master, not main.

Routine in-place redeploy inside the running container, per summit-admin-docker:

docker exec -it <show-admin-container> deployment.sh

Clones $BRANCH (master), runs yarn install and yarn build, swaps the release directory and prunes older deployments. Nothing else restarts.

Host note: the dev container is show-admin-dev-2 on the dev docker server, with the wrapper at ~/repos/summit-admin-docker. Production is not on the microservices droplet, so use whichever of the Production Docker hosts serves showadmin.fnvirtual.app.

Order still matters: sponsor-reports-api #40 has to be in production first, for the reasons under "DEPLOY GATE" above.

Optional before merge: commenting /deploy-preview on this PR builds it and publishes a Netlify preview at pr-1046--<site>.netlify.app. Only one PR can hold the alias at a time, because FNid does not allow wildcards in redirect URIs.

Verifying it landed: the prod index is served cf-cache-status: DYNAMIC, so it reflects the origin with no purge. Fetch / and confirm the main_<hash>.js filename changed; last-modified on the index dates the deploy. Bundle names are hashed, so no user hard-refresh is needed.

Smoke test on Purchase Details: the Show canceled checkbox appears on all three views, Synced and Source Updated populate, and the By Item drill-down shows Canceled for a soft-canceled line inside a Paid order.

What changed

Payment Method survives a grain switch. buildPurchaseLinesQuery destructured paymentMethod away and the control was hidden on the line views, so filtering to invoiced orders and clicking Line Items silently widened 22 items to 250. The lines endpoint now supports the filter, so it is carried at every grain. This also reaches the By Item rows and the lines CSV export, which share the same builder.

Line rows show their own state, not the parent order's. Cancellation is soft and per line: a canceled line leaves its order Paid, so dead rows read "Paid" in both the Line Items manifest and the By Item drill-down. Both now branch on the line's own is_canceled. Canceled lines still render, struck through; they are only excluded from the counts.

Freshness on every view. synced_at and source_updated_at appended to the Orders table, the Line Items manifest, and the By Item drill-down, in that order, all through the existing formatCheckoutTime. On line rows these are the line's own values, not the parent order's.

Refunds are visible on Orders. A refund never changes purchase status, so a fully refunded order read as a clean Paid on screen forever. The CSV already carried refunded_amount; the table now does too, right-aligned to match the other money column.

The chrome stops lying. Total Paid is labelled as gross (the figure is unchanged, deliberately: Total Refunded sits beside it). The Purchase Status control now states that canceled orders are excluded unless you select Canceled. The By Item chip no longer describes deliberately-filtered canceled orders as "purchased", and the per-sponsor line chip now says "live lines" since it counts only those.

Testing

Full suite green: 166 suites, 1520 tests. Every new assertion was verified by deliberately breaking the thing it guards and watching it fail, then reverting. That is how three weak assertions were caught and replaced: a freshness test that used one timestamp for two columns and so could not tell them apart, a header assertion that could not see a reordered column, and a copy guard that could not see the copy regress.

Known gaps, deliberately not addressed here

  • The status helper text needs a visual check. jsdom performs no layout, so no test can confirm its placement. The mechanism was verified by execution: the app theme forces MuiFormHelperText to position: absolute, and this instance overrides it back to static so it flows under the control.
  • Line Items and By Item summary tiles are order-grain while their rows are line-grain, so an order with no visible item lines counts toward Total Sales with no row beneath it. This is the deliberate trade-off from the API side (one filter, one sale count); the alternative made the same filter report different totals per view. Labelling those tiles as order-level is worth a follow-up.
  • Non-USD orders render a dollar sign. currencyAmountFromCents takes a currency argument that neither invoice_total (pre-existing) nor refunded_amount passes. Fixing only the new column would put two symbols on one row, so both want fixing together.
  • A pre-existing view-switch race: the entering view renders from its own stale redux slice while the fetch awaits an access token, so a CSV export clicked in that window can use stale filters. Affects every carried filter and predates this PR.

caseylocker and others added 12 commits August 20, 2026 09:57
buildPurchaseLinesQuery destructured paymentMethod away and the control was
hidden on the line views, so filtering to invoiced orders and clicking Line
Items silently widened 22 items to 250. The lines endpoint now supports the
filter, so it is carried through at every grain.

ref: https://app.clickup.com/t/86bbh5vat
getPurchaseDetailsByItemRows still claimed the lines-grain query drops
paymentMethod, but it calls buildPurchaseLinesQuery, which now carries it
through. Same false claim as the one already fixed at the query builder
itself, missed on the first sweep.

ref: https://app.clickup.com/t/86bbh5vat
A soft-canceled line leaves its parent order Paid, so the Status column read
Paid on a dead row and the per-sponsor chip counted it. Both now read the
line. Adds the two freshness columns.

ref: https://app.clickup.com/t/86bbh5vat
…ssertions

The timestamps test used the same epoch for both fields and asserted only
"at least one match", so it couldn't catch a swapped or missing cell. Now
uses two distinct values and pins them to the row's final two cells. The
live-count test asserted row count loosely (>2) without proving which rows
rendered; now pins the exact count and asserts the canceled row's identity.

ref: https://app.clickup.com/t/86bbh5vat
The drill-down rendered the parent order's status on a canceled line, and
the group chip described deliberately-filtered canceled orders as
'purchased'. Aggregates are unchanged (decision 8) - the wording is not.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>
…rity

Exact-equality on the chip catalog value closes the gap where a substring
guard could pass a reworded defect or unrelated copy. A new header/cell
cardinality check on the nested contributor table catches a header array
desync that a positional last-two-cells check alone would miss, in both
the by-sponsor (10 cols) and all-sponsors (11 cols) layouts.

ref: https://app.clickup.com/t/86bbh5vat
A refund never changes purchase status, so a fully refunded order read as a
clean Paid on screen; the CSV already carried the field.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>
…ader order

refunded_amount rendered left-aligned while invoice_total (the table's other
currency column) is right-aligned. Also strengthened the header-order test to
assert the complete 14-key sequence instead of only the appended tail, so a
reorder among the original 11 columns is caught too.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>
Total Paid is gross and sat unlabelled beside Total Refunded, whose dollar
is inside it. Canceled orders are excluded by default at both grains and
nothing in the UI said so.

ref: https://app.clickup.com/t/86bbh5vat
…drifting

CustomTheme's global MuiFormHelperText override makes helper text
position: absolute, anchored to the nearest positioned ancestor. The
new note is a sibling of MuiDropdown, not a child of its internal
FormControl, so it had no positioned ancestor and drifted to the
initial containing block instead of sitting under the dropdown.
Override back to static on this instance so it renders in normal flow
and reserves its own height.

ref: https://app.clickup.com/t/86bbh5vat
…est title

The lines_count chip still said "{count} lines" even though canceled
lines render (struck through) but aren't counted, so a group showing 2
rows could read "1 lines" — reworded to "{count} live lines" to match
the byitem chip's phrasing.

LinesManifestView had no columnheader-count-vs-cell-count assertion,
so a HEADERS/TableCell desync would misalign columns without failing
any test; added the same guard ByItemView already has for its
contributor table.

The ByItemView it.each drill-down test title formatted the mount
function as %i, rendering "NaN cells per row" in CI output; reworded
to use only the arguments actually referenced.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>
The chip is fed liveLineCount while canceled lines still render, so the
copy has to say which number it is. This module's i18n mock renders the
chip from the key and count only, so the existing count assertion is
value-independent: the English could regress to "{count} lines" and every
DOM test would still pass. Mirrors the By Item chip's catalog copy test.

ref: https://app.clickup.com/t/86bbh5vat
@caseylocker caseylocker self-assigned this Aug 20, 2026
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 44a3025d-503e-42b2-a707-31493201214b


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@caseylocker
caseylocker requested a lite review from Copilot August 20, 2026 18:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Updates the Sponsor Reports “Purchase Details” UI to be self-describing and consistent across Orders / Line Items / By Item grains, aligning displayed state and freshness at the correct (line vs order) level.

Changes:

  • Keeps the Payment Method filter visible and carried through across grain switches, including lines queries and exports.
  • Adds/updates UI chrome + copy so totals/filters communicate their semantics (gross paid, canceled-default exclusion, “live lines” wording).
  • Surfaces new columns (refund amount + freshness timestamps) and corrects line-level status rendering for canceled lines.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/pages/sponsors/sponsor-reports/purchase-details-report-page/index.js Adds status helper text and makes Payment Method filter visible across views
src/pages/sponsors/sponsor-reports/purchase-details-report-page/tests/index.test.js Adds assertions for Payment Method visibility + self-describing chrome
src/i18n/en.json Updates/introduces copy for gross paid label, canceled note, live lines wording, and new column headers
src/components/sponsors/reports/OrdersTable.js Adds refunded + freshness columns to the Orders table
src/components/sponsors/reports/LinesManifestView.js Counts “live lines”, renders canceled line state explicitly, adds freshness columns
src/components/sponsors/reports/ByItemView.js Carries and renders line-level cancellation + freshness in drill-down rows
src/components/sponsors/reports/tests/OrdersTable.test.js Adds tests for refunded + freshness columns and header order
src/components/sponsors/reports/tests/LinesManifestView.test.js Adds tests for line-level canceled status, freshness columns, and live-line counting
src/components/sponsors/reports/tests/ByItemView.test.js Adds tests for drill-down status/freshness and chip copy
src/actions/sponsor-reports-actions.js Stops dropping paymentMethod from line-grain query builder
src/actions/tests/sponsor-reports-query.test.js Updates query-builder expectations to include payment_method at line grain
src/actions/tests/sponsor-reports-actions.test.js Updates lines endpoint request test to expect payment_method filtering

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

…trol

The note explaining that canceled orders are excluded by default rendered
next to the Purchase Status dropdown but was not associated with it, so
screen readers never announced the two together. The note is a sibling
rather than a FormControl child, so MUI cannot wire this up itself; the
dropdown spreads unrecognised props onto the Select, so aria-describedby
reaches the rendered control.

ref: https://app.clickup.com/t/86bbh5vat
… icon

Helper text under the Purchase Status dropdown made that filter twice the
height of its siblings, and the center-aligned filter row rendered it out
of line with them. Switch to the repo's existing hover-info idiom, an
aria-hidden fa-info-circle carrying the copy in its title, so every filter
is the same height again.

That idiom is sighted-only, so the same copy is repeated in a
visually-hidden span the Select points at via aria-describedby, keeping
the note announced with its control.

Renames the key to filter_status_info, matching the label_key + _info
convention the rest of the app uses for hover info.

ref: https://app.clickup.com/t/86bbh5vat
@smarcet
smarcet requested a review from santipalenque August 21, 2026 01:20
line leaves its order Paid, so rendering purchase.status
printed "Paid" on a dead row — and the strikethrough that
was the only other signal does not survive CSV export. */}
{line.is_canceled ? (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@caseylocker This branch cannot render once sponsor-reports-api#40 is deployed: the only
line rows that reach it are ones whose parent order is already Canceled, where both
branches print the same label with the same tone.

include_cancelled is written in exactly one place — sponsor-reports-actions.js:226,
if (status === "Canceled") — so asking for canceled rows also emits status==Canceled,
which PurchaseLineDetailsFilter resolves to purchase__status
(purchase_line_details_filter.py:20). A soft-canceled line on a Paid order — the exact
row this change targets — is excluded by default, then excluded again by the status clause
the moment you try to un-hide it. statusTone("Canceled") returns default either way, so
in every reachable state this renders identically to the previous line.purchase?.status.

The API deliberately made this a separate axis, and it is already exercised there:
test_include_cancelled_true_restores_line_level_canceled_items
(apps/reports/tests/test_purchase_details_lines_endpoints.py:123) sends
?include_cancelled=true with no status filter and gets AV3 back — is_canceled: true
inside a Paid order. The fixture comment states it directly: "NO status selection could
ever remove it -- line-level cancellation is a separate axis". The frontend is the only
side coupling the two.

Suggested fix: give include_cancelled its own control rather than deriving it from the
status value — a "Show canceled" checkbox in FilterBar, carried as its own filter key and
emitted independently in buildReportQuery. That is the shape this repo already uses for
showArchived (.claude/rules/summit-admin-archive-pattern.md). The current coupling stays
correct at order grain; only the line grain needs the extra axis. The same fix makes the
By Item contributor branch at ByItemView.js:467 reachable, and lets the Purchase Status
note describe both axes truthfully on the line views.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c8bc53f.

You're right that the branch was unreachable. include_cancelled had exactly one write site, sponsor-reports-actions.js:226, and buildReportQuery is the single builder for the details, lines, By Item and both CSV paths, so the coupling closed the axis everywhere at once. Every row that could reach the pill already had purchase.status === "Canceled", and since StatusPill maps both spellings of canceled to tone default and status_canceled is literally "Canceled", the two arms rendered an identical chip. Same for ByItemView.js:467.

Took the separate control. One change to the shape you suggested: the status coupling stays, as an OR rather than a replacement.

if (status === "Canceled" || showCanceled) query.include_cancelled = "true";

Emitting include_cancelled only from the checkbox would regress the Orders view. purchase_details_report_view.py:44 excludes status='Canceled' by default, so a Canceled selection without the flag returns zero rows and the dropdown option goes dead. There's a test pinning that direction specifically, so a later cleanup can't quietly drop it.

The checkbox is on every grain, not the line views only. Filters are carried across a view switch, so a control that appears and vanishes would leave a live filter behind an invisible control, which is the same silent-filter class this report set out to fix.

Also updated the Purchase Status note per your last point: it's now grain-aware, since the line grains hide a second kind of row the order grain has no concept of.

One correction on the citation: .claude/rules/summit-admin-archive-pattern.md isn't in this repo, there's no .claude/ directory here. The doc lives in the fn-skills vault at patterns/show-admin/summit-admin-archive-pattern.md. The precedent itself is real in-repo, showArchived in form-template-actions.js, show-pages-actions.js and others, and the shape matched, so the checkbox follows it.

Full suite 1528 passing.

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@caseylocker please review

include_cancelled was written in exactly one place, derived from the status
value. Asking for canceled rows therefore also emitted status==Canceled, which
resolves to purchase__status at line grain, so a soft-canceled line inside a Paid
order was excluded by default and excluded again the moment you tried to un-hide
it. The line-level status pill added here was unreachable in consequence: every
row that could render it already had a Canceled parent, where both branches print
the same chip.

Adds a Show canceled checkbox carried as its own filter key. The status coupling
stays as an OR rather than being replaced -- the orders endpoint excludes
Canceled by default, so a Canceled selection without the flag would return no
rows and the dropdown option would go dead.

Shown on every grain rather than the line views only: filters are carried across
a view switch, and a hidden control holding a live filter is the silent-filter
class this report set out to fix.

The canceled-default note is now grain-aware, since the line grains hide a second
kind of row the order grain has no concept of.

Co-Authored-By: Claude <noreply@anthropic.com>
@caseylocker
caseylocker requested a review from smarcet August 21, 2026 13:10

@santipalenque santipalenque left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good, just a couple of nitpicks, in general lets keep the comments concise

{/* the line's own state — its parent order can be Paid
while this specific line is canceled */}
{c.isCanceled ? (
<StatusPill

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: can you change the status in the reducer ? so this render looks cleaner ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in d0b0ec6. One note on where: the reducer stores the API rows raw (sponsor-reports-purchase-details-by-item-reducer.js:64), so the resolution went into groupLinesByItem, which already builds the contributor. The cell is a single StatusPill again.

The canceled label drops its T.translate as a result. No visible change, and the statusMix chips in this same component already render raw status strings.

LinesManifestView keeps its branch. It renders straight off the API rows with no mapping layer, so matching this would mean adding one for symmetry alone.

const extraControls = (draft, update) => (
<>
<Box sx={{ width: 200 }}>
{/* The default excludes canceled orders at BOTH grains and nothing said so,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

do we need this comment just to explain an alignment ? seems unnecessary

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No we don't. Cut. Kept two lines for the part that is not visible in the code: the icon is aria-hidden, so deleting the "duplicate" hidden span silently breaks the screen-reader path.

{T.translate(statusNoteKey(view))}
</Box>
</Box>
{/* The second cancellation axis, which the status dropdown cannot reach:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

again, this could be a one-liner

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Trimmed to two lines, just why the axis has to be separate from the status filter.

…mments

Review nits from santipalenque.

groupLinesByItem now resolves a contributor's status to the line's own state, so
the drill-down cell is a single StatusPill again instead of a branch. The
canceled label loses its T.translate wrapper, which matches the statusMix chips
in the same component -- they already render raw status strings -- and the
catalog value is "Canceled" either way. The behavioural guarantee moves with it:
the mapper test now asserts the resolved status, and the view test only asserts
the cell renders what it is handed.

LinesManifestView keeps its branch: it renders straight off the API rows with no
mapping layer, so following this would mean inventing one for symmetry alone.

Comments trimmed to the non-obvious half. The status note keeps why the copy is
duplicated into a visually-hidden span (the icon is aria-hidden, so deleting the
"duplicate" silently breaks the screen-reader path a test pins) and drops the
alignment rationale. The checkbox comment keeps why the axis has to be separate.

Co-Authored-By: Claude <noreply@anthropic.com>

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@smarcet

smarcet commented Aug 21, 2026

Copy link
Copy Markdown

@caseylocker LGTM please review the changes requested by @santipalenque and we are good to merge many thanks

@caseylocker
caseylocker requested a review from smarcet August 21, 2026 15:09
@caseylocker

Copy link
Copy Markdown
Author

@caseylocker LGTM please review the changes requested by @santipalenque and we are good to merge many thanks

@smarcet this should be good now - as long as the changes for @santipalenque are ok. There was a jest ci failure on the last push but I re-ran it and it looks like it was a hiccup. All green.

@santipalenque santipalenque left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

lgtm

@smarcet
smarcet merged commit 215d1cf into master Aug 21, 2026
9 of 10 checks passed
smarcet pushed a commit that referenced this pull request Aug 21, 2026
…view (#1046)

* fix(sponsor-reports): keep Payment Method applied across grain switches

buildPurchaseLinesQuery destructured paymentMethod away and the control was
hidden on the line views, so filtering to invoiced orders and clicking Line
Items silently widened 22 items to 250. The lines endpoint now supports the
filter, so it is carried through at every grain.

ref: https://app.clickup.com/t/86bbh5vat

* docs(sponsor-reports): fix second stale paymentMethod-drop comment

getPurchaseDetailsByItemRows still claimed the lines-grain query drops
paymentMethod, but it calls buildPurchaseLinesQuery, which now carries it
through. Same false claim as the one already fixed at the query builder
itself, missed on the first sweep.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): show the line's own state in the manifest

A soft-canceled line leaves its parent order Paid, so the Status column read
Paid on a dead row and the per-sponsor chip counted it. Both now read the
line. Adds the two freshness columns.

ref: https://app.clickup.com/t/86bbh5vat

* test(sponsor-reports): strengthen manifest freshness and live-count assertions

The timestamps test used the same epoch for both fields and asserted only
"at least one match", so it couldn't catch a swapped or missing cell. Now
uses two distinct values and pins them to the row's final two cells. The
live-count test asserted row count loosely (>2) without proving which rows
rendered; now pins the exact count and asserts the canceled row's identity.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): By Item drill-down shows line state and freshness

The drill-down rendered the parent order's status on a canceled line, and
the group chip described deliberately-filtered canceled orders as
'purchased'. Aggregates are unchanged (decision 8) - the wording is not.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sponsor-reports): pin By Item chip copy and drill-down column parity

Exact-equality on the chip catalog value closes the gap where a substring
guard could pass a reworded defect or unrelated copy. A new header/cell
cardinality check on the nested contributor table catches a header array
desync that a positional last-two-cells check alone would miss, in both
the by-sponsor (10 cols) and all-sponsors (11 cols) layouts.

ref: https://app.clickup.com/t/86bbh5vat

* feat(sponsor-reports): surface refunded amount and freshness on Orders

A refund never changes purchase status, so a fully refunded order read as a
clean Paid on screen; the CSV already carried the field.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sponsor-reports): right-align refunded amount, pin full Orders header order

refunded_amount rendered left-aligned while invoice_total (the table's other
currency column) is right-aligned. Also strengthened the header-order test to
assert the complete 14-key sequence instead of only the appended tail, so a
reorder among the original 11 columns is caught too.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sponsor-reports): label the gross total and the canceled default

Total Paid is gross and sat unlabelled beside Total Refunded, whose dollar
is inside it. Canceled orders are excluded by default at both grains and
nothing in the UI said so.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): flow the status helper text in-page instead of drifting

CustomTheme's global MuiFormHelperText override makes helper text
position: absolute, anchored to the nearest positioned ancestor. The
new note is a sibling of MuiDropdown, not a child of its internal
FormControl, so it had no positioned ancestor and drifted to the
initial containing block instead of sitting under the dropdown.
Override back to static on this instance so it renders in normal flow
and reserves its own height.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): reword lines chip, add header/cell guard, fix test title

The lines_count chip still said "{count} lines" even though canceled
lines render (struck through) but aren't counted, so a group showing 2
rows could read "1 lines" — reworded to "{count} live lines" to match
the byitem chip's phrasing.

LinesManifestView had no columnheader-count-vs-cell-count assertion,
so a HEADERS/TableCell desync would misalign columns without failing
any test; added the same guard ByItemView already has for its
contributor table.

The ByItemView it.each drill-down test title formatted the mount
function as %i, rendering "NaN cells per row" in CI output; reworded
to use only the arguments actually referenced.

ref: https://app.clickup.com/t/86bbh5vat

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sponsor-reports): pin the lines_count copy in the catalog

The chip is fed liveLineCount while canceled lines still render, so the
copy has to say which number it is. This module's i18n mock renders the
chip from the key and count only, so the existing count assertion is
value-independent: the English could regress to "{count} lines" and every
DOM test would still pass. Mirrors the By Item chip's catalog copy test.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): announce the canceled-default note with its control

The note explaining that canceled orders are excluded by default rendered
next to the Purchase Status dropdown but was not associated with it, so
screen readers never announced the two together. The note is a sibling
rather than a FormControl child, so MUI cannot wire this up itself; the
dropdown spreads unrecognised props onto the Select, so aria-describedby
reaches the rendered control.

ref: https://app.clickup.com/t/86bbh5vat

* fix(sponsor-reports): carry the canceled-default note as a hover info icon

Helper text under the Purchase Status dropdown made that filter twice the
height of its siblings, and the center-aligned filter row rendered it out
of line with them. Switch to the repo's existing hover-info idiom, an
aria-hidden fa-info-circle carrying the copy in its title, so every filter
is the same height again.

That idiom is sighted-only, so the same copy is repeated in a
visually-hidden span the Select points at via aria-describedby, keeping
the note announced with its control.

Renames the key to filter_status_info, matching the label_key + _info
convention the rest of the app uses for hover info.

ref: https://app.clickup.com/t/86bbh5vat

* feat(sponsor-reports): give canceled rows their own filter axis

include_cancelled was written in exactly one place, derived from the status
value. Asking for canceled rows therefore also emitted status==Canceled, which
resolves to purchase__status at line grain, so a soft-canceled line inside a Paid
order was excluded by default and excluded again the moment you tried to un-hide
it. The line-level status pill added here was unreachable in consequence: every
row that could render it already had a Canceled parent, where both branches print
the same chip.

Adds a Show canceled checkbox carried as its own filter key. The status coupling
stays as an OR rather than being replaced -- the orders endpoint excludes
Canceled by default, so a Canceled selection without the flag would return no
rows and the dropdown option would go dead.

Shown on every grain rather than the line views only: filters are carried across
a view switch, and a hidden control holding a live filter is the silent-filter
class this report set out to fix.

The canceled-default note is now grain-aware, since the line grains hide a second
kind of row the order grain has no concept of.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(sponsor-reports): resolve line status in the mapper, trim comments

Review nits from santipalenque.

groupLinesByItem now resolves a contributor's status to the line's own state, so
the drill-down cell is a single StatusPill again instead of a branch. The
canceled label loses its T.translate wrapper, which matches the statusMix chips
in the same component -- they already render raw status strings -- and the
catalog value is "Canceled" either way. The behavioural guarantee moves with it:
the mapper test now asserts the resolved status, and the view test only asserts
the cell renders what it is handed.

LinesManifestView keeps its branch: it renders straight off the API rows with no
mapping layer, so following this would mean inventing one for symmetry alone.

Comments trimmed to the non-obvious half. The status note keeps why the copy is
duplicated into a visually-hidden span (the icon is aria-hidden, so deleting the
"duplicate" silently breaks the screen-reader path a test pins) and drops the
alignment rationale. The checkbox comment keeps why the axis has to be separate.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

4 participants