Skip to content

feat(web): let operators forget remembered farms - #598

Merged
mforce merged 6 commits into
mainfrom
feat/remembered-farm-forget
Aug 25, 2026
Merged

mforce merged 6 commits into
mainfrom
feat/remembered-farm-forget

Conversation

@mforce

@mforce mforce commented Aug 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #587

Let an operator remove one remembered farm from the device-local login roster without clearing unrelated browser preferences, and give the login form's three controls stable HTML identifiers.

What ships

  • removeFarmCode(value): Promise<void> in farmCodeCache.ts — canonical, best-effort, serialized under the existing cluckwork.farmCodes.write Web Lock; same never-rejects contract as rememberFarmCode.
  • Login picker now renders for one or more remembered codes; each entry is a selection chip plus a distinct 44px Forget control gated behind the shared destructive useConfirm dialog. Confirming optimistically removes the code, clears the farm-code input only if it held that code, and queues focus to #farm-code (the trigger is gone by then).
  • Stable identifiers: farm code id="farm-code" / name="farmCode", email id="email" / name="email" (autocomplete username unchanged), password id="current-password" / name="password" (autocomplete current-password unchanged). No API or request-body change.
  • Copy in en/es/tl (auth confirmation, Help, in-app glossary) + product glossary. ADR accepted-disclosure revision deferred to Docs: multi-farm tenancy ADR + AGENTS.md / GLOSSARY sync #537 (see amendment on SPA: let an operator forget a remembered farm code #587).
  • Login.styles.test.ts — PostCSS guard (mirrors styles.dialog.test.ts, no new dependency) that fails if any matching Forget rule, at any media-query depth, declares a min-block-size/min-height/min-inline-size/min-width below 44px — the 44px floor holds in BOTH axes (a block-axis rule alone would leave the target as narrow as 36px) — or if the select chip is styled destructive.

#585 — deliberate won't-fix (see epic #530 amendment)

HTML supplies no standard manager storage-key contract for a tenant identifier; the username/password fields and autocomplete tokens are deliberately left unchanged. The stable identifiers above resolve the "anonymous to the browser" half of #585's amendment comment; #585 is closed as won't-fix with an amendment comment distinguishing the two.

Verification

  • npm run typecheck, npm test (1897), npm run test:coverage (auth 98.93/95.31/100/100 vs floor 98/90/100/100), npm run build, npm run verify:sw, npm run i18n:scan — all green.
  • Mutations (each red on the named test, restored green, no markers left): broken filter predicate → raw-storage removal tests; bypassed if (!accepted) → cancellation test (which waits for the dialog to close before asserting the roster, so the async continuation is ordered); removed current === code clear branch → prefilled-clearing test; removed 44px declaration → styles guard; each of the six id/name attributes removed one at a time → identifier test.

@mforce mforce mentioned this pull request Aug 24, 2026
27 of 28 tasks
mforce pushed a commit that referenced this pull request Aug 24, 2026
Review fixes for draft PR #598, per independent code review:

- Login: compare the farm-code field to the forgotten code CANONICALLY,
  not by raw string, so a case-mangled or padded form of the canonical
  code ("Farm-A", " farm-a ") clears when that code is forgotten.
  Regression tests cover the clear, and the other side (a different
  canonical code survives its own raw-form variant).
- farmCodeCache: removeFarmCode no longer writes "[]" when localStorage
  getItem throws but setItem would succeed. A private safe reader
  distinguishes a read FAILURE (removal is a no-op) from a readable
  malformed/non-array value (treated as an empty roster, mirroring
  readFarmCodes, and rewritten to the empty JSON array in the same call).
  Tests assert the raw stored text directly after the removal, not
  through a follow-up write that would normalise the same key.
- styles: the Forget glyph now uses the contrast-safe --error token at
  rest (2.76:1 for --danger over --surface-2 in the dark theme; --error
  clears 4.5:1 in every theme and palette), and the 44px touch target is
  enforced in BOTH axes (min-inline-size was 2.25rem = 36px). The
  selector-aware PostCSS guard fails if either axis lacks a >=44px
  floor; the at-rest and hover token pairs are pinned in styles.test.ts
  per brand and mode.
- i18n (tl): the two Help/glossary references to the English "Forget
  control" now use the translated kontrol na Kalimutan.
- docs: 00-lessons.md now records the mutation runs (M1-M5 plus the
  fix-round mutations, all red-then-green), the two review passes, and
  the fix-round false-green caught and corrected (assertion shared a
  normalising writer with its fixture).

Epic #530's T7a row and a new "Amendments to this body" section record
the deliberate #585 won't-fix, pointing at the amendment comment
(#issuecomment-5400679046), per the AGENTS issue-sync rule.

Verification: typecheck, 1897 unit tests, coverage (auth 98.93/95.31/
100/100 vs floor 98/90/100/100), build, verify:sw, i18n:scan — all
green. Mutations red as named: raw-string clear, read-failure no-op,
block-axis 30px, inline-axis shrunk/dropped, no markers left.
mforce pushed a commit that referenced this pull request Aug 24, 2026
- 00-lessons.md: the Suite counts row no longer carries a bogus
  per-commit test attribution (it miscounted the it.each expansions);
  it states the net 1881 -> 1897 (+16) between the two verification
  runs only.
- styles.css: drop the border-*-left-radius longhands the 4-value
  border-radius shorthand already overrides; use var(--on-danger)
  without the redundant #fff fallback.
- Login.tsx: the focus comment now says what actually happens — the
  confirm click's task finishes first (dialog close, trigger
  unmount, close-time restore no-op on the disconnected trigger) and
  only then does the queued frame focus the farm-code input.
  Behaviour unchanged.
mforce pushed a commit that referenced this pull request Aug 24, 2026
- 00-lessons.md: the Suite counts row no longer carries a bogus
  per-commit test attribution (it miscounted the it.each expansions);
  it states the net 1881 -> 1897 (+16) between the two verification
  runs only.
- styles.css: drop the border-*-left-radius longhands the 4-value
  border-radius shorthand already overrides; use var(--on-danger)
  without the redundant #fff fallback.
- Login.tsx: replace the focus comment with neutral wording that does
  not claim Dialog internals. Behaviour unchanged.
Review fixes for draft PR #598, per independent code review:

- Login: compare the farm-code field to the forgotten code CANONICALLY,
  not by raw string, so a case-mangled or padded form of the canonical
  code ("Farm-A", " farm-a ") clears when that code is forgotten.
  Regression tests cover the clear, and the other side (a different
  canonical code survives its own raw-form variant).
- farmCodeCache: removeFarmCode no longer writes "[]" when localStorage
  getItem throws but setItem would succeed. A private safe reader
  distinguishes a read FAILURE (removal is a no-op) from a readable
  malformed/non-array value (treated as an empty roster, mirroring
  readFarmCodes, and rewritten to the empty JSON array in the same call).
  Tests assert the raw stored text directly after the removal, not
  through a follow-up write that would normalise the same key.
- styles: the Forget glyph now uses the contrast-safe --error token at
  rest (2.76:1 for --danger over --surface-2 in the dark theme; --error
  clears 4.5:1 in every theme and palette), and the 44px touch target is
  enforced in BOTH axes (min-inline-size was 2.25rem = 36px). The
  selector-aware PostCSS guard fails if either axis lacks a >=44px
  floor; the at-rest and hover token pairs are pinned in styles.test.ts
  per brand and mode.
- i18n (tl): the two Help/glossary references to the English "Forget
  control" now use the translated kontrol na Kalimutan.
- docs: 00-lessons.md now records the mutation runs (M1-M5 plus the
  fix-round mutations, all red-then-green), the two review passes, and
  the fix-round false-green caught and corrected (assertion shared a
  normalising writer with its fixture).

Epic #530's T7a row and a new "Amendments to this body" section record
the deliberate #585 won't-fix, pointing at the amendment comment
(#issuecomment-5400679046), per the AGENTS issue-sync rule.

Verification: typecheck, 1897 unit tests, coverage (auth 98.93/95.31/
100/100 vs floor 98/90/100/100), build, verify:sw, i18n:scan — all
green. Mutations red as named: raw-string clear, read-failure no-op,
block-axis 30px, inline-axis shrunk/dropped, no markers left.
- 00-lessons.md: the Suite counts row no longer carries a bogus
  per-commit test attribution (it miscounted the it.each expansions);
  it states the net 1881 -> 1897 (+16) between the two verification
  runs only.
- styles.css: drop the border-*-left-radius longhands the 4-value
  border-radius shorthand already overrides; use var(--on-danger)
  without the redundant #fff fallback.
- Login.tsx: the focus comment now says what actually happens — the
  confirm click's task finishes first (dialog close, trigger
  unmount, close-time restore no-op on the disconnected trigger) and
  only then does the queued frame focus the farm-code input.
  Behaviour unchanged.
- 00-lessons.md: the Suite counts row no longer carries a bogus
  per-commit test attribution (it miscounted the it.each expansions);
  it states the net 1881 -> 1897 (+16) between the two verification
  runs only.
- styles.css: drop the border-*-left-radius longhands the 4-value
  border-radius shorthand already overrides; use var(--on-danger)
  without the redundant #fff fallback.
- Login.tsx: replace the focus comment with neutral wording that does
  not claim Dialog internals. Behaviour unchanged.
@mforce
mforce force-pushed the feat/remembered-farm-forget branch from 6132cfb to 7282f01 Compare August 24, 2026 22:15
@mforce
mforce marked this pull request as ready for review August 24, 2026 22:16

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7282f01ff5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/src/auth/farmCodeCache.ts Outdated
Comment on lines +183 to +185
.map((entry) => canonicalFarmCode(entry))
.filter((candidate): candidate is string => candidate !== null)
.slice(0, MAX_REMEMBERED)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Deduplicate the roster before applying its cap

When raw storage contains canonical duplicates, this caps entries before deduplicating them, unlike readFarmCodes(). For example, a hand-written roster containing ten variants of farm-a followed by farm-b is displayed as both farms, but forgetting farm-a rewrites the roster as [] and silently removes farm-b too. Build the same unique, capped roster as the read path before filtering the selected code.

Useful? React with 👍 / 👎.

Comment thread web/src/routes/Login.styles.test.ts Outdated
el.type = "button";
el.className = "auth-forget-farm";
entry.append(el);
main.append(entry);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Model the actual login DOM in the touch-target guard

The fixture appends the picker entry directly beneath .auth, while the real control is nested beneath form.card and the picker containers. Consequently, a future applicable override such as .auth .card .auth-forget-farm { min-inline-size: 30px; } is ignored by el.matches(...), leaving this guard green while the production touch target violates its asserted 44px floor. Construct the real ancestor chain before evaluating selectors.

AGENTS.md reference: AGENTS.md:L139-L139

Useful? React with 👍 / 👎.

…in in touch-target guard

#598 review P2s:

- removeFarmCode now builds the roster exactly like readFarmCodes
  (canonicalise, dedupe first-seen, cap at 10) before filtering, so
  forgetting a duplicated head can no longer erase codes the read path
  still shows.
- Login.styles.test.ts evaluates every selector against the true login
  ancestor chain (.auth > form.card > .auth-farm-picker > group >
  entry > button) via buildForgetControl, and adds proof that a
  .card-dependent selector is recognised so an override such as
  .auth .card .auth-forget-farm cannot evade the 44px floor. Guard
  at-rule refusal behavior is unchanged.
@mforce

mforce commented Aug 24, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Addressed both P2 findings in a749b90:

  • removeFarmCode now canonicalises, deduplicates, then caps before filtering; the duplicate-head regression test fails red on the old code and preserves farm-b after the fix.
  • the touch-target guard now constructs the real Login ancestor chain and has a mutation-proven card-selector regression test.

Please confirm the fixes.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a749b90051

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/src/routes/Login.styles.test.ts Outdated
function floorDecls(rule: Rule, el: Element) {
const matched = rule.selectors.some((selector) => {
try {
return el.matches(cleanSelector(selector));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match stateful selectors in the touch-target guard

The ancestor-chain fix is effective, but stateful selectors remain silently excluded because Element.matches() returns false for states that jsdom has not activated. As a fresh mutation check, adding .auth .card .auth-forget-farm:hover { min-inline-size: 30px; } to styles.css left all six Login.styles.test.ts tests green, so a hover, focus, or active override can still violate the claimed 44px floor without detection; normalize/model the relevant pseudo-classes before matching.

AGENTS.md reference: AGENTS.md:L139-L142

Useful? React with 👍 / 👎.

…n touch-target guard

jsdom's Element.matches reports :hover/:focus/:focus-visible/:focus-within/
:active as false on a static tree, so a floor spelled on such a selector was
silently skipped by the #587 size guard: a sub-44px min-size riding on
.auth .card .auth-forget-farm:hover would keep every other case green.

The guard is a static floor sweep, and a pointer arriving IS an interaction
state, so the shared matching helper now re-checks each selector with those
pseudo-classes stripped (whole :pseudo pieces only — a class merely
containing the word is untouched). Longest alternation names first, since
focus before focus-visible leaves a dangling "-visible" that matches
nothing. floorDecls, collectAxisFloors and the topology probe all go through
the one helper; existing selector and at-rule conservatism is unchanged.
Pinned by a focused regression with direct floorDecls assertions for the
:hover, :focus-visible and :hover:not(:disabled) probes.

Fixes P2 on #598.
@mforce

mforce commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Addressed the stateful-selector P2 in 086d6d7:

  • the touch-target guard now considers hover, focus, focus-visible, focus-within, and active selectors during its static floor sweep; longest names are normalized first.
  • regression coverage demonstrates the prior hover blind spot, plus mutations for raw matching and focus-visible ordering.

Please confirm this fix.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 086d6d77ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +47 to +49
const INTERACTION_PSEUDOS = /:(focus-visible|focus-within|hover|focus|active)(\([^)]*\))?/g;
const applicableSelector = (selector: string) =>
selector.replace(INTERACTION_PSEUDOS, "").trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle nested interaction pseudos in the guard

When a state is nested in a selector-list pseudo, such as .auth-forget-farm:is(:hover, :focus-visible) or :where(:hover, :active), this replacement leaves :is(, )/:where(, ), which does not match the fixture, while the raw selector remains false in jsdom. A future 30px stateful override written in either valid CSS form therefore remains invisible to the floor sweep. Fresh evidence beyond the prior finding is this nested-selector case, which the new direct probes do not cover; normalize these constructs into a selector that matches the applicable state or parse/expand their selector lists.

AGENTS.md reference: AGENTS.md:L139-L145

Useful? React with 👍 / 👎.

@mforce
mforce merged commit 577d94e into main Aug 25, 2026
10 checks passed
@mforce
mforce deleted the feat/remembered-farm-forget branch September 1, 2026 23:21
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>
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.

SPA: let an operator forget a remembered farm code

2 participants