Repository navigation
Description
Split out of #535 (slice T7) by owner decision at its design signoff, after the design review raised it.
Problem
#535 caches every farm code that has successfully signed in on a device and offers them as a picker on the login screen. Epic #530 decision 9 accepts the resulting disclosure: on a shared or compromised device the cached list is a durable roster of which farms that browser profile uses, and decision 6's distinct unknown-farm response then confirms which are live.
The disclosure is accepted. Its irrevocability is not — and was never argued for.
Under #535's implementation a code leaves the list only by being pushed past position 10 by ten subsequent logins on that device. So:
- a phone used once for a demo farm shows that farm's code on the login screen indefinitely;
- a device belonging to someone who has left a farm keeps advertising it;
- the only user-reachable remedy is clearing all site data for the origin, which also destroys
cluckwork.themeandcluckwork.lang— two settings that have nothing to do with the farm roster and that the app deliberately persists per device.
Scope
A "forget this farm" affordance on each picker entry. Roughly a two-line addition to the picker row that #535 ships (web/src/routes/Login.tsx), plus a removeFarmCode export in web/src/auth/farmCodeCache.ts, one i18n string in en, es and tl (the translate-now policy applies), and one CSS rule.
Design notes worth settling rather than rediscovering:
- Removing the last remaining code should leave the field empty and the picker gone, not an empty picker container.
- Removing a code while it is prefilled in the field: decide whether the field clears. Clearing it is probably right — the operator just said they do not use that farm — but it is a decision, not an obvious default.
- Keep it reachable on a phone. The picker entries are chips; a separate small control per chip needs a real touch target, and a swipe gesture is not discoverable.
- It must not be so easy to hit by accident that an operator loses a code they use daily and has to retype it.
Tests
Vitest, in the same PR (standing rule). farmCodeCache.ts lands in src/auth/**, locked at lines 100 / functions 100 / branches 90 (web/vite.config.ts:213), and that lock may only move up.
Cover: removing one of several; removing the only one; removing a code that is currently prefilled; removing when storage throws. Assert on the raw stored value — JSON.parse(localStorage.getItem("cluckwork.farmCodes")) — not through readFarmCodes(), which independently re-validates, dedupes and caps and will therefore mask a broken removal. (#535's review found exactly this masking on the write path; the same trap applies here.)
Acceptance
- An operator can remove a single remembered farm code from the login screen, without clearing unrelated device settings.
- The Docs: multi-farm tenancy ADR + AGENTS.md / GLOSSARY sync #537 ADR's accepted-disclosure entry for the device-local farm-code cache records that the roster is now revocable.
Activity
mforce commentedon Aug 24, 2026
Amendment: ADR acceptance item deferred to #537 (owner, 2026-08-24)
The acceptance item "The #537 ADR's accepted-disclosure entry for the device-local farm-code cache records that the roster is now revocable" is deferred to #537, the dedicated ADR/docs slice. This PR updates the immediately user-visible surfaces only: the product glossary, the Help page copy, and the in-app glossary (en/es/tl) all now state that each remembered farm is individually revocable without clearing unrelated device preferences. The ADR's accepted-disclosure revision ships when #537 lands.
fix(web): treat interaction-state selectors as applicable in the logi…
feat(web): let operators forget remembered farms (#598)
docs(732): correct the rename's prose and the callers it made stale
fix(web): restore the Forget glyph's --error contrast, keep .help-her…
fix(web): restore the Forget glyph's --error contrast, keep .help-her…