Repository navigation
feat(audit): show the sales-line audit payload as a readable Details column (#745) - #749
Merged
Merged
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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. Comment |
…t in the repo Owner directive: review binaries do not belong in the repo. The 1:1 before/after capture for the Details column now lives at a github.com/user-attachments URL referenced from the PR body, which depends on no ref and survives the branch being deleted (verified: still 200 after the uploading comment was removed). 735-picker-before-after.png is already on main and is left alone — that is history, not this slice's call.
mforce
added a commit
that referenced
this pull request
Sep 10, 2026
Both slices shipped without their design docs reaching the repo, which is out of step with the sibling slice on the same epic (#723/#724, already on main). These are the point-in-time records, not rewritten to match what finally shipped. The #722 design carries a superseded-by header instead, because two things moved after it was written: the payload gained productName and unit (#747, PR #748) after the audit artboard turned out to render product NAMES rather than ids, and nothing rendered the payload at all until #745 (PR #749). Rewriting the body would destroy the record of what was actually decided and when; the header points at what changed.
mforce
added a commit
that referenced
this pull request
Sep 10, 2026
Both slices shipped without their design docs reaching the repo. The sibling slice on the same epic — `docs/plans/723-724-discount-visibility/` — is already on `main`, so this brings #722 and #745 in line with it. ## What is here | File | What it records | |---|---| | `docs/plans/722-audit-price-payload/01-design.md` | the design as dispatched, including the line-identity decision and the `ExecuteInTransactionAsync` + interleaved-save shape | | `docs/plans/722-audit-price-payload/02-implementation-plan.md` | the increment ordering and mutation rows | | `docs/plans/745-audit-details-column/01-design.md` | read from the artboard, `docs/images/discount-mockups/AuditRow.png` | ## These are point-in-time records, not retrofitted The #722 design describes a payload carrying `productId` and **no** product name. That is what was designed, and it was the defect: the audit artboard renders product **names**, and resolving an id at read time yields *today's* name, so a renamed product would re-render history under a name the seller never saw. #747 (PR #748) fixed it by snapshotting the name and unit at write time. Rather than rewrite the body to match what shipped — which would destroy the record of what was actually decided and when — the file carries a **superseded-by header** naming both changes: the `productName`/`unit` addition, and the fact that nothing rendered the payload at all until #745 (PR #749). The full map lives in the amendment on #722. ## Verification `SchemaDocsTests` run with these files in the index — that is the point, since `PostgresImagePin_`/`RedisImagePin_IsOneIdenticalStringAcrossEveryTrackedFile` walk `git ls-files` and therefore begin applying to a document the moment it is staged. AGENTS.md records that exact trap costing a full implementer stop on #508. ``` Passed! - Failed: 0, Passed: 4, Skipped: 0, Total: 4 - Cluckwork.Api.IntegrationTests.dll ``` Docs only — no source, no tests, no schema. Co-authored-by: mforce <mforce@users.noreply.github.com>
mforce
added a commit
that referenced
this pull request
Sep 11, 2026
## TL;DR The audit **Details** cell printed each sales line's **list** price as though it were the price the line sold for. `AuditDetails` read `unitPriceMinorUnits`, used it only as a null guard, then discarded it whenever a list price existed — so every `SalesOrder.AddItem` row rendered `at list $<list>`, whatever it actually sold for. One branch in one component, plus tests. No API, payload, migration or i18n-key change. ## The defect, on fixture order `SO-1318252C` | Line | List | Sold | Sales screen | Audit **before** | Audit **after** | |---|---|---|---|---|---| | Sim Cracked Eggs | $0.18 | $0.15 | Below list, 16.7% | `at list $0.18` ❌ | `at $0.15 (list $0.18)` | | Sim Dirty Eggs | $0.22 | $0.30 | **Above list** | `at list $0.22` ❌ | `at $0.30 (list $0.22)` | | Sim Small Eggs | $0.30 | $0.30 | at list | `at list $0.30` ✅ | `at list $0.30` | | Sim Large Eggs | $0.45 | $0.20 | Below list, **55.6%** | `at list $0.45` ❌ | `at $0.20 (list $0.45)` | Three of four were wrong. The worst read as a line selling at $0.45 that sold at $0.20. Introduced by d26d389 (#749, for #745) — the commit that built the Details column. It shipped this way; it did not drift. It survived review and every test because `at list $0.45` is a plausible, correctly formatted number, and nothing on screen contradicted it until #721 began printing `Discount reason: Volume` directly beneath four rows all claiming to have sold at list. ## Rendering contract | Case | Renders | Changed? | |---|---|---| | no list price (`list === null`) | `at $0.15` | no | | sold at list (`unit === list`) | `at list $0.30` | no | | sold below **or above** list | `at $0.20 (list $0.45)` | **the fix** | An off-list line gets **no marker word in either direction**. #719's open question 4 deliberately treats a markup as not a discount, so the cell states both facts and lets the reader see the direction rather than editorialising. That reasoning is a comment in the code, not just here. **No new translation strings.** `detailsAtPrice`, `detailsAtList` and `detailsListParen` already existed in en, es and tl (3/3 each); `detailsListParen` is the same key the `UpdateItem` branch already renders in the same position, so per #688 each locale keeps its own existing wording for *list price* rather than a fresh translation. ## Reviewer entry points - **`web/src/routes/AuditPage.tsx`** — the whole behaviour change is the `SalesOrder.AddItem` ternary, ~6 lines. Everything else in the diff is the comment above it. - **`web/src/routes/AuditPage.test.tsx`** — the four new cases. ### One deletion to look at deliberately The previous test, *"renders an AddItem payload as the artboard's summary"*, asserted `/\$0\.45/` against a payload with `unitPriceMinorUnits: 40, listUnitPriceMinorUnits: 45`. **It pinned the defect**, not the intended behaviour — it passed precisely because the wrong number was on screen. It is replaced rather than weakened, by four tests asserting the cell's **literal full text** (`toBe`, on the cell's `textContent`, which is what pins the *order* of the two prices — a regex on one number is what let this ship). ## Mutation evidence Every new test was watched fail before being claimed. Three mutations, each reverted; `git grep -n -e MUTANT -e 'DEBUG-' -- src tests web` returns nothing. | Mutation | Tests red | Observed | |---|---|---| | **M1** restore the shipped defect (`money(list)` in the off-list branch) | below-list, above-list | `expected 'Large Eggs ×240 at list $0.45' to be 'Large Eggs ×240 at $0.20 (list $0.45)'` | | **M2** drop the `unit === list` collapse | at-list | `expected 'Large Eggs ×240 at $0.30 (list $0.30)' to be 'Large Eggs ×240 at list $0.30'` | | **M3** stop `list === null` short-circuiting | no-list-price | `expected 'Large Eggs ×240 at $0.15 (list $0.00)' to be 'Large Eggs ×240 at $0.15'` — M3 reproduces #720's zero-price failure mode exactly | All four tests are individually load-bearing: no mutation reddened a test that was not aimed at it. ## Gates ``` cd web && npm run typecheck # clean cd web && npx vitest run # 122 files, 2816 tests, all passing ``` Web-only — nothing outside `web/` changes, so no .NET build and no integration suite. ## Sibling check `grep -rn listUnitPriceMinorUnits web/src` finds one other renderer, `SalesPage.tsx`, which reads the sale and list prices separately and is the screen the issue confirms was already **correct**. No sibling defect; no coverage added there. ## Risk Low. Read-only rendering of a payload field already present on every row, in one component, behind four literal-text tests. No backfill concern: the change is presentational, so historical rows render correctly the moment this ships. The at-list and no-list-price paths are byte-identical to before and pinned as regressions. Closes #758 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Audit details now consistently display the sold unit price for added sales order items. - When a list price is available, matching prices are labeled “at list,” while differing prices show both the sold and list prices without classifying the difference as a discount or markup. - Items without a list price continue to display their existing details. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: mforce <mforce@users.noreply.github.com>
9 of 11 tasks
mforce
pushed a commit
that referenced
this pull request
Sep 12, 2026
🤖 I have created a release *beep* *boop* --- ## [0.1.0](v0.0.4...v0.1.0) (2026-09-12) ### ⚠ BREAKING CHANGES * log in by farm code, with per-account email identity ([#532](#532)) (#564) ### Features * **accounts:** add Account.Slug (farm code), suspend/reactivate, list-accounts verb ([#531](#531)) ([3fe9754](3fe9754)) * **accounts:** provision additional farms ([#581](#581)) ([006f298](006f298)) * add Aspire local development AppHost ([#567](#567)) ([2c9e6b9](2c9e6b9)) * add configurable worker sale allocation ([#619](#619)) ([0955095](0955095)) * add searchable entity pickers ([#642](#642)) ([60d2053](60d2053)) * **api:** provision-account takes an optional --timezone at creation ([#603](#603)) ([#694](#694)) ([a0aee39](a0aee39)) * **audit:** show the sales-line audit payload as a readable Details column ([#745](#745)) ([#749](#749)) ([d26d389](d26d389)) * **auth:** add ApplicationUser.StepUpLogoutEpoch column ([#338](#338)) ([#554](#554)) ([18306ee](18306ee)) * certify over-cap simulation fixture bands ([#633](#633)) ([a67b2e1](a67b2e1)), closes [#627](#627) * **cli:** rename-account verb to change a farm code ([#732](#732)) ([#733](#733)) ([4b70559](4b70559)) * **customers:** edit existing customer details ([#625](#625)) ([#626](#626)) ([062a55c](062a55c)) * **jobs:** single-runner leader gate for the durable job worker ([#271](#271)) ([#555](#555)) ([4148f9b](4148f9b)) * let owners change user email addresses ([#605](#605)) ([842347b](842347b)) * log in by farm code, with per-account email identity ([#532](#532)) ([#564](#564)) ([68adb62](68adb62)) * **ratelimit:** distributed IP-keyed auth limiters ([#544](#544)) ([#558](#558)) ([ec14972](ec14972)) * **ratelimit:** distributed per-account report concurrency cap with local-ceiling fallback ([#545](#545)) ([#559](#559)) ([1522e4e](1522e4e)) * **sales:** mark discounted lines, total the discount, and show it in the Orders list ([#723](#723), [#724](#724)) ([#741](#741)) ([1a07441](1a07441)) * **sales:** record list, old and new price in the order-line audit payload ([#722](#722)) ([#742](#742)) ([97c866f](97c866f)) * **sales:** refuse an over-ceiling confirm from a Sales user ([#727](#727)) ([#766](#766)) ([8c0792a](8c0792a)) * **sales:** show what each order still owes, and filter the list to unpaid ([#771](#771)) ([ca59d68](ca59d68)) * **sales:** snapshot the list price on the order line and show the discount ([#734](#734)) ([cffed5e](cffed5e)) * **sales:** snapshot the product name and unit in the order-line audit payload ([#747](#747)) ([#748](#748)) ([0481c06](0481c06)) * scope Worker reads to assigned flocks ([#388](#388)) ([#611](#611)) ([5884a9a](5884a9a)) * shared-state ports with Redis + in-process fallback ([#543](#543)) ([#552](#552)) ([f767fa9](f767fa9)) * suspend-account / reactivate-account operator verbs ([#534](#534)) ([#573](#573)) ([d0be26c](d0be26c)) * **tenancy:** write-side tenant guard + single-assignment TenantContext ([#546](#546)) ([#561](#561)) ([f371f1d](f371f1d)) * **web:** dashboard rework — capture-status tiles, 14-day trend, stock as a stacked bar ([#654](#654)) ([396ba23](396ba23)) * **web:** date-range filters on audit and expenses, and the stock lot filter gets its bounded toolbar ([#666](#666), [#667](#667), [#653](#653)) ([94b188f](94b188f)) * **web:** elevation hierarchy and sentence-case labels ([#651](#651), [#652](#652)) ([#661](#661)) ([28db4c7](28db4c7)) * **web:** Expenses and Audit keep a clear-filters control while rows are still showing ([#679](#679)) ([#697](#697)) ([b859982](b859982)) * **web:** expenses filters by a date range like its sibling screens ([#667](#667)) ([f13858f](f13858f)) * **web:** key the farm brand palette per farm ([#586](#586)) ([#600](#600)) ([7183a43](7183a43)) * **web:** let operators forget remembered farms ([#598](#598)) ([577d94e](577d94e)) * **web:** one-line provenance, bounded date filters, and empty states that invite action ([#653](#653), [#655](#655)) ([#668](#668)) ([80b53f4](80b53f4)) * **web:** prefill the farm code from ?farm= and remember it ([#535](#535)) ([#588](#588)) ([b7f5cc6](b7f5cc6)) * **web:** split authenticated routes into lazy chunks ([#620](#620)) ([5089271](5089271)) * **web:** the audit log filters by a date range, and says which window is empty ([#666](#666)) ([63027e0](63027e0)) * **web:** typeset numbers as numbers and refresh the Help glossary ([#650](#650), [#657](#657)) ([af4fe11](af4fe11)) ### Bug fixes * **api:** order same-instant audit events by a durable monotonic key ([#700](#700)) ([8fcf084](8fcf084)) * **api:** print the farm code from bootstrap-admin ([#589](#589)) ([#594](#594)) ([34032ac](34032ac)) * **audit:** show the price a line sold for, not its list price ([#759](#759)) ([e6b37d0](e6b37d0)) * **audit:** store catalog enums by name and guard the add-item transaction shape ([#751](#751)) ([23609ff](23609ff)) * **auth:** reject invalid account claims ([#622](#622)) ([8d6c7fe](8d6c7fe)) * **auth:** require step-up for durable user access ([#360](#360)) ([#607](#607)) ([f767dce](f767dce)) * **ci:** bound the npm audit calls and give the web job room to finish ([#686](#686)) ([153b7a8](153b7a8)) * **ci:** escalate the audit bound to SIGKILL, so it actually bounds ([#686](#686)) ([a0c8f4e](a0c8f4e)) * **ci:** fail closed on invalid vulnerability config ([#621](#621)) ([1690db8](1690db8)) * **ci:** lockfix covers the two AppHost lock files, derived from the sln ([efb05e6](efb05e6)) * **ci:** lockfix covers the two AppHost lock files, derived from the sln ([8986d77](8986d77)) * **ci:** remove invalid XML comment from nuget.lockfix.config ([#541](#541)) ([5f1bc0a](5f1bc0a)) * **ci:** the advisory vuln gate no longer blocks on an unusable report ([#686](#686)) ([aaf6934](aaf6934)) * **ci:** the advisory vuln gate no longer blocks on an unusable report ([#686](#686)) ([64f1f53](64f1f53)) * **i18n:** tl help text names the saleable flag and unit-system setting what their labels call them ([#688](#688)) ([#696](#696)) ([bfd24d7](bfd24d7)) * **infra:** AccountId must be a non-nullable Guid or both tenant write layers refuse ([#673](#673)) ([#695](#695)) ([2470c4e](2470c4e)) * require step-up for flock scope changes ([#609](#609)) ([4151f89](4151f89)) * **sales:** keep a line's discount markers agreeing while its price is edited ([#752](#752)) ([#753](#753)) ([c159b4b](c159b4b)) * **sales:** say which kind of missing list price a line has ([#774](#774)) ([489180e](489180e)) * scope legacy logout to selected farm ([#624](#624)) ([fae8d82](fae8d82)) * **seed:** drain the daily-entry lock sweep so deep simulation fixtures validate ([#644](#644)) ([730fa23](730fa23)), closes [#638](#638) * **tenancy:** AccountId is a concurrency token, so the database refuses a detached cross-tenant write ([#562](#562)) ([4d1dfa3](4d1dfa3)) * **tenancy:** AspNetUserRoles carries a tenant column, so a role write naming another farm's user is refused ([#670](#670)) ([fc0552a](fc0552a)) * **tests:** bump the image-pin allow-list counts for the AppHost LocalPorts tests ([#593](#593)) ([58d3056](58d3056)) * **tests:** the OTLP collector survives a lost port race and ignores traffic that is not an export ([#672](#672), [#676](#676)) ([#677](#677)) ([965c737](965c737)) * **web:** a scoped audit view filtered to nothing names both the record and the range ([#666](#666)) ([41bbfe1](41bbfe1)) * **web:** an abandoned dialog attempt's success no longer hijacks the replacement on Customers, Daily Entry, Flocks, Grades and Products ([#703](#703)) ([#705](#705)) ([85605db](85605db)) * **web:** an abandoned dialog attempt's success no longer hijacks the replacement on Inventory, Expenses, History and Stock ([#703](#703)) ([#706](#706)) ([60a4997](60a4997)) * **web:** an abandoned edit's success no longer hijacks the dialog that replaced it on Users ([#703](#703)) ([#710](#710)) ([778faab](778faab)) * **web:** an abandoned order attempt's success no longer hijacks the dialog that replaced it ([#702](#702)) ([522c699](522c699)) * **web:** capture screens open on the flock you last used, and assigning one no longer guesses ([#646](#646)) ([#699](#699)) ([7f8f317](7f8f317)) * **web:** constrain dialog session helpers to declared scopes ([#715](#715)) ([389e3c8](389e3c8)) * **web:** date validation gets one boundary table instead of one case per review round ([#666](#666)) ([215f830](215f830)) * **web:** keep a paged window and an item panel on the user's newest intent ([#645](#645)) ([d81bccf](d81bccf)) * **web:** keep Sales order panels closed after pending writes ([#711](#711)) ([f0f7492](f0f7492)) * **web:** keep Sales panels closed after pending Open reads ([#716](#716)) ([620411f](620411f)) * **web:** make login take the cross-tab cookie lock so a racing refresh cannot restore the wrong session ([#648](#648)) ([ff18beb](ff18beb)) * **web:** make the entity picker read as a search field and focus it on open ([#736](#736)) ([66ef667](66ef667)), closes [#735](#735) * **web:** page truncated customer and movement tables with usePagedList ([7cfe4d6](7cfe4d6)) * **web:** reconcile Sales line edits with refreshed orders ([#717](#717)) ([d7dd2c9](d7dd2c9)) * **web:** the audit date filter accepts low-numbered years, and its empty state covers every narrowing ([#666](#666)) ([af52d25](af52d25)) * **web:** the audit date filter rejects impossible dates, and its history guard actually guards ([#666](#666)) ([8d51846](8d51846)) * **web:** the expense range bounds are not capped at today, which the month-end default exceeds ([#667](#667)) ([7e01864](7e01864)) * **web:** the help text calls the expiry field what the field calls itself ([#666](#666)) ([2fd1f3c](2fd1f3c)) * **web:** the stock lot date range sits in the bounded toolbar ([#653](#653)) ([43dec5e](43dec5e)) ### Refactoring * **web:** extract SalesPage's dialog-write wrapper into a shared useDialogAction hook ([#703](#703)) ([#704](#704)) ([60ee9d9](60ee9d9)) ### Documentation * add k6 preparation steps to the dev-database fixture runbook ([#643](#643)) ([a4f1f09](a4f1f09)) * add runbook for loading the simulation fixture into a dev database ([#639](#639)) ([2d143b8](2d143b8)) * **agents:** a PR closes its issue from the body, not the title ([#744](#744)) ([39be13c](39be13c)) * **agents:** drop the commit and push gate, and require screenshots on UI changes ([#757](#757)) ([6225172](6225172)) * **agents:** find guards by grepping registry readers; amend issues a PR overtakes ([#580](#580)) ([fe3fde8](fe3fde8)) * **agents:** the Playwright specs have been in CI since 2026-08-08 ([#768](#768)) ([68ee612](68ee612)) * **aspire:** record the second local database and pin the AppHost dashboard ports ([#623](#623)) ([713b941](713b941)) * compress AGENTS.md to one paragraph per rule, and draw the two orders that matter ([#551](#551)) ([997ae8a](997ae8a)) * item 7 names each screen's actual initial filter value ([#666](#666)) ([70a53d8](70a53d8)) * multi-farm tenancy decision record and AGENTS/GLOSSARY sync ([#537](#537)) ([#601](#601)) ([2c34771](2c34771)) * name the scoped filtered-empty key and state the [#653](#653) relationship plainly ([#666](#666)) ([0e93dac](0e93dac)) * note that a PackageReference in Directory.Build.props is invisible to the dependency graph ([4845724](4845724)) * **plans:** commit the [#722](#722) and [#745](#745) design records ([#754](#754)) ([c942fcd](c942fcd)) * record [#579](#579) as won't-fix — suspension is immediate for use, not issuance ([#582](#582)) ([7a3be40](7a3be40)) * record the [#508](#508) audit ordering key and the tracked-file guard lesson ([#701](#701)) ([08964e9](08964e9)) * **runbooks:** add procedure to rename the default farm's code after upgrade ([#731](#731)) ([2f6e242](2f6e242)) * screenshots of the running SPA in the README ([#550](#550)) ([711488a](711488a)) * **sim:** commit the dashboard screenshot, capture the palette matrix, and record the [#651](https://github.com/mforce/cluckwork/issues/651)/[#652](https://github.com/mforce/cluckwork/issues/652) conventions ([#660](#660), [#662](#662), [#663](#663), [#664](#664)) ([#665](#665)) ([930ea30](930ea30)) * specify searchable entity picker ([#641](#641)) ([91d4300](91d4300)) * split the README into audience-scoped docs and adopt repo-template scaffolding ([#548](#548)) ([b3f3fcf](b3f3fcf)) * surface Aspire local development workflow ([#568](#568)) ([a343baa](a343baa)) * **web:** record the per-screen idempotency-key policies and runWrite's refresh contract ([#703](#703)) ([#707](#707)) ([8bee651](8bee651)) * **web:** the date-cap help text covers every stocked item, not only feed ([#666](#666), [#667](#667)) ([c8433c5](c8433c5)) * **web:** the help text claims only what is true of recording, and says nothing about filter caps ([#666](#666), [#667](#667)) ([e2f63d1](e2f63d1)) * **web:** the help text describes the date-range filters that shipped ([#666](#666), [#667](#667)) ([c3275b7](c3275b7)) * **web:** the help text stops describing a cap the filters no longer have ([#666](#666), [#667](#667)) ([49654cd](49654cd)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: cluckwork-lockfix[bot] <309265648+cluckwork-lockfix[bot]@users.noreply.github.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.
Closes #745.
The audit Details column renders SalesOrder.AddItem and SalesOrder.UpdateItem payloads as readable product and price summaries, with the old price struck through and the new price bold on updates. It falls back to the reason, then an em dash. English, Spanish and Tagalog labels are updated.
The renderer reads only the row payload and makes no network request. Money uses useFormat().money with the farm locale.
Two deliberate gaps against the artboard remain: the Entity column still renders
Sales order 10d2c04drather thanSO-10D2C04D, because the latter is the order's reference number and is not in the audit data.SalesOrder.Confirmrows still show an em dash because that handler writes no reason, and the discount badge needs #721.Validation from web/, run in the foreground:
npm ci; G1npm run typecheckpassed; G2npx vitest run src/routes/AuditPage.test.tsx src/i18n/catalogParity.test.tspassed (264 tests); G3npm run test:coveragepassed (121 files, 2730 tests; 91.08% statements, 87.36% branches, 86.33% functions, 94.04% lines); G4npm run buildpassed. Only the five allow-listed frontend files changed. No Docker or dotnet commands were run; a stack-based visual comparison was not captured.Before / after — captured at 1:1 from a stack rebuilt at this head
Both halves are the same API, the same database and the same four audit rows — only the frontend
differs.
main's Vite ran on :5174 from a worktree at0481c06, this branch's on :5173, both proxyingto one API started from this branch's worktree. Captured with
deviceScaleFactor: 1and no downscaling,per AGENTS.md #662: a downscaled capture is not a look.
What the capture shows, top to bottom in the AFTER half:
Order line addedJumbo Eggs (unpriced) ×8 at $16.50Order line changedLarge Eggs$0.40→$0.38(list $0.45)Order line addedLarge Eggs ×240 at list $0.45Sales order created—The BEFORE half is the same four rows under the old
Reasoncolumn: four em dashes, with the payloadreachable only as a raw JSON
titletooltip.