Skip to content

Economy: collapsible, searchable balance ledger - #63

Merged
CaYatur merged 2 commits into
mainfrom
feat/economy-ledger-search
Jul 27, 2026
Merged

Economy: collapsible, searchable balance ledger#63
CaYatur merged 2 commits into
mainfrom
feat/economy-ledger-search

Conversation

@CaYatur

@CaYatur CaYatur commented Jul 27, 2026

Copy link
Copy Markdown
Owner

What

Closes #14. The balance ledger is now hidden by default and, when expanded, searchable and filterable — on both surfaces.

  • shared/economy.ts (new): pure filterLedger(entries, { text, kind }) and ledgerSummary(entries).
  • Desktop Store view: collapsed section with the totals in the header (12 entries · +600 granted · −40 removed · 25 spent), so it still tells you something while closed. Expanded it gains a search box, a kind filter (grants / removals / set / purchases) and a shown-of-total count.
  • Web panel: the same collapse + search + kind filter over the existing /store/admin/ledger endpoint.

Design notes

Search matches the actor, not just the player. That is the whole point of the ledger per #15"which admin changed this balance" — and a search that only covers player names cannot answer it. Text matches mcName, reason and by.

Purchases are spend, never removal. Both are negative deltas, so the naive summary counts a purchase twice. ledgerSummary branches on kind first. removed and spent are reported positive, because "−450 spent" next to a minus sign reads as a double negative.

Filter order is preserved. The ledger is stored newest-first and both UIs rely on that; the filter never re-sorts.

Verification

  • npm run typecheck + npm run build — clean.
  • MSMS_SMOKE_WEBexit 0, with new units: empty filter keeps all; player search is case-insensitive; actor search returns exactly that actor's entries; reason is searchable; kind filter; 'all' does not filter; text AND kind both apply (not either/or); order preserved; and a summary assertion that pins granted=600 / removed=40 / spent=25 — i.e. the purchase is not double-counted.
  • MSMS_SMOKEexit 0.
  • Panel JS parity, actually executed: the generated panel script was bundled and run in a VM against a stub DOM, then ledgerFiltered() was compared to filterLedger() across 7 filter combinations — identical in all 7 — and the rendered summary line matched the shared one character for character. toggleLedger() was driven both ways (/, display none/block).
  • en/tr keys added in lockstep.

Disclosed gaps

  • The panel's filter is a hand-kept duplicate of the shared function — the panel is plain browser JS inside a template literal and genuinely cannot import the module. The parity test above is what keeps them honest, but a future change to shared/economy.ts will not automatically propagate. Commented as such at both sites.
  • The desktop expanded ledger was not rendered in a real browser; the smoke asserts the pure filter and that the app mounts, not the visual layout.
  • Ledger data is still fetched eagerly on view load (collapse is presentation only). Fine at the current 1000-entry cap; worth revisiting if the cap grows.

Closes #14.

shared/economy.ts adds pure filterLedger + ledgerSummary. Text matches player,
reason AND the actor - 'what did this admin change' is the question the ledger
exists to answer, and it is useless if you can only search by player.

Desktop Store view: the ledger is now collapsed by default (it is a long audit
list, not something you need in front of you to run the store) with the totals
in the header so it still says something while closed. Expanded, it gets a
search box and a kind filter, plus a shown/total count.

Web panel: same collapse + search + kind filter. The panel is plain browser JS
in a template literal and cannot import the module, so its filter is a hand-kept
mirror - verified identical against the shared function across 7 filter
combinations, and the summary line matches character for character.

Purchases count as spend only, never as a removal, so the header totals cannot
double-count the same entry.
Copilot AI review requested due to automatic review settings July 27, 2026 20:44

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… summary

The filter survived a server change on both surfaces while the section
re-collapsed, so another server's ledger looked empty with nothing on screen
explaining why. Desktop resets with the id; the panel tracks which server the
loaded ledger belongs to, because loadManage() also runs after every adjustment
and resetting there would wipe an active filter mid-work.
@CaYatur

CaYatur commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

/code-review — self review

One real finding, one polish. Both fixed on-branch.

1. The ledger filter leaked across servers, and the collapse hid the evidence — both surfaces

StoreView's useEffect([id]) reloaded the data on a server switch but left ledgerQuery / ledgerKind / ledgerOpen untouched.

Concretely: filter the ledger for Steve, switch to another server, expand its ledger → it looks empty, or shows a suspiciously short list. The filter that caused it is still sitting in a box that was just re-collapsed, so nothing on screen explains why entries are missing. On a page whose job is answering "who changed this balance", silently showing an incomplete answer is worse than showing none.

This is exactly the class of bug that PR #62's review turned up in ModsView (stale state surviving a server switch), which is why I went looking for it here.

Desktop: filter and open-state reset with the server.

Panel: the same leak existed, but it could not be fixed the same way — loadManage() runs after every balance adjustment, so resetting there would wipe an active filter mid-work. The panel now tracks mledgerFor (which server the loaded ledger belongs to) and resets only on an actual change of server, leaving refreshes and adjustments alone.

2. Empty ledgers advertised their own emptiness twice

A server with no balance history rendered 0 entries · +0 granted · −0 removed · 0 spent in the header, directly above "No balance changes yet." The summary is now suppressed at count === 0 on both surfaces.

Checked and fine

  • filterLedger applies text and kind conjunctively — a smoke unit pins this, since an || here would silently widen results.
  • ledgerSummary branches on kind before sign, so a purchase counts as spend only. Both are negative deltas, so the naive version double-counts.
  • Panel filter re-verified against the shared function after the change: identical across all 7 combinations; toggleLedger drives / and display correctly.

Re-verified: typecheck + build clean, panel script parses, MSMS_SMOKE_WEB exit 0, MSMS_SMOKE exit 0.

@CaYatur
CaYatur merged commit fcdc707 into main Jul 27, 2026
1 check passed
@CaYatur
CaYatur deleted the feat/economy-ledger-search branch July 27, 2026 20:47
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.

Economy: searchable + collapsible balance ledger

2 participants