Skip to content

Goal: PDEV-610 — Stale-Data Banner Cross-User Signal

Lifecycle: Completed

A user with an items detail panel open does not see the “stale data” banner when a different user edits the same item. The banner only fires for the user who initiated the change, leaving concurrent observers free to act on stale state and hit If-Match conflicts on the next bulk action. This sub-project, part of the wider acceptance-test-failures effort, picks a cross-user staleness mechanism, wires it in, and adds the regression coverage that PDEV-596 §4.1 surfaced.

  • Linear — PDEV-610: Stale-data banner does not appear for the second user — cross-user staleness regression. Parent: PDEV-596 §4.1.
  • Related (refactor cascade that introduced the banner): PDEV-548, PDEV-549, PDEV-559, PDEV-597.
  • Related (adjacent staleness gap, different surface): PDEV-588 — tenant switch shows old item instance on low Wi-Fi.
RepositoryRolePlanned Changes
arda-frontend-appPrimary surface; owns banner, useFreshRead, ItemCardsContextAdd cross-user invalidation trigger (mechanism TBD: BroadcastChannel for same-browser, server push, or active polling) wired into useFreshRead / ItemCardsProvider. Add E2E or integration coverage.
operationsBackend; may need to publish item-change events if mechanism is server pushOnly if the chosen mechanism is server push (e.g., SSE/WebSocket fan-out, ETag-revision endpoint). Otherwise no backend change.
documentationProject artifactsThis goal, an investigation note, a short decision log entry for the mechanism choice, and acceptance criteria sign-off.

Investigation (/arda-frontend-app/src/app/items/ItemCardsContext.tsx) shows:

  • useFreshRead snapshots the rId set of cards at mount and only flips isStale=true when its own revalidation discovers rId changes. A second user’s mutation never triggers this hook in another tab/session.
  • ItemCardsProvider owns an in-memory Map<entityId, {cards, fetchedAt, inFlightPromise}> mounted at the root layout (post-PDEV-597). The cache TTL is wall-clock per browser; nothing in the codebase pushes invalidation events from one user to another.
  • No BroadcastChannel, SSE, WebSocket, polling, or server-push signal currently exists. The banner therefore works as designed for the local writer (their own refresh sees new rIds) but the cross-user case has no transport at all.

This makes the bug less a “regression” and more a previously-invisible gap that the refactor’s banner now exposes. The mechanism choice is the core decision of the sub-project.

  • Decide and document the cross-user staleness transport (BroadcastChannel for same-browser/session, polling on items detail panel, or server push). Record the decision in the inline Decision Log section of design.md.
  • Implement the chosen transport with minimal surface area; integrate it with useFreshRead / ItemCardsProvider so the existing banner renders without further plumbing.
  • Add automated coverage: E2E test for two concurrent sessions if feasible, otherwise a unit/integration test that simulates the cross-user event and asserts the banner appears.
  • Update PDEV-596 §4.1 acceptance evidence.
  • Fixing PDEV-588 (tenant switch staleness) — adjacent surface; the mechanism here may inform that fix but is not required to land it.
  • Bulk-action If-Match conflict UX beyond what the banner provides (separate ticket if needed).
  • Full real-time collaboration / live cursors / multi-user editing — out of project scope; the goal is notification, not co-editing.
  1. The transport must not add a hard dependency on a backend push channel if the same-browser case (BroadcastChannel) covers the majority of reported incidents — pick the smallest mechanism that meets the acceptance criteria.
  2. No change to the existing If-Match / rId revision model on the backend in this sub-project.
  3. Must not regress the local-writer banner behavior already shipped in v6.0.2.
  4. Frontend changes follow the react-best-practices and clean-components skills; backend changes (if any) follow kotlin-coding.
  1. With two browser sessions (or two tabs in the same browser, depending on transport choice) signed in to the same tenant and the same item open, an edit by session A causes session B to see the stale-data banner within an agreed-on latency budget (target: ≤ 5 s for polling, ≤ 1 s for BroadcastChannel / push).
  2. Automated regression coverage exists for the cross-user case and runs in CI.
  3. PDEV-610 acceptance criteria 1–3 satisfied; ticket moved to Done and PDEV-596 §4.1 re-verified.
  4. Decision log records the chosen mechanism with rationale and rejected alternatives.
  • PDEV-596 manual regression plan — source of the §4.1 failure.
  • /arda-frontend-app/src/app/items/ItemCardsContext.tsxuseFreshRead, ItemCardsProvider, the host for any invalidation hook.
  • /arda-frontend-app/src/components/common/StaleDataBanner.tsx — banner component (no change expected).
  • react-best-practices, clean-components, unit-tests-frontend skills.

Copyright: (c) Arda Systems 2025-2026, All rights reserved