Skip to content

SPA: let an operator forget a remembered farm code #587

Closed

Description

@mforce

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.theme and cluckwork.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

Activity

  1. added
    sliceThin vertical work item
    epic-1.6Phase 1.6 — Multi-farm tenancy
    on Aug 23, 2026
  2. mforce commented on Aug 24, 2026

    @mforce
    OwnerAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:frontendReact/Vite web clientepic-1.6Phase 1.6 — Multi-farm tenancysliceThin vertical work item

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions