Skip to content

Fix: feedStore stale endpoint_type via cross-store subscription #35

@jpeggdev

Description

@jpeggdev

Problem

feedStore.toSummary() calls useEndpointStore.getState().endpoints — a point-in-time snapshot with no subscription. If endpoints are subsequently created, updated, deleted, or reloaded on project switch, the endpoint_type on every already-stored RequestSummary goes stale. Components rendering feed rows show incorrect type badges until the feed is also reloaded.

  • The stores are loaded in parallel on project switch (AppShell.tsx calls all data loaders in Promise.all)
  • No listener exists to sync feedStore when endpoints change
  • Store tests mock each store independently, completely missing the integration bug

Proposed Interface

Zero caller changes. The fix adds ~15 lines to feedStore.ts:

  1. Add endpoint_id: string | null to RequestSummary type (preserve it through toSummary() instead of discarding after lookup)
  2. Add a _reEnrich() internal action that re-maps endpoint_type from current endpointStore.endpoints
  3. Add a module-level Zustand subscription:
useEndpointStore.subscribe(
  (s) => s.endpoints,
  () => useFeedStore.getState()._reEnrich(),
  { equalityFn: shallow }
)

_reEnrich implementation:

_reEnrich: () => {
  const endpoints = useEndpointStore.getState().endpoints
  set((s) => {
    const requests = s.requests.map((req) => {
      const type = req.endpoint_id
        ? (endpoints.find((e) => e.id === req.endpoint_id)?.endpoint_type ?? null)
        : null
      return type !== req.endpoint_type ? { ...req, endpoint_type: type } : req
    })
    return { requests, filtered: applyFilters(requests, get().filters) }
  })
}

Components continue reading useFeedStore(s => s.filtered) unchanged.

Dependency Strategy

  • Category: In-process (both stores are in-memory Zustand)
  • feedStore already imports useEndpointStore (line 3) — no new dependency introduced
  • Subscription is one-directional: endpointStore -> feedStore
  • endpointStore does not know feedStore exists
  • Identity guard preserves object identity for unchanged rows, keeping memo() effective

Testing Strategy

  • New boundary tests: Seed requests with endpoint_id, call useEndpointStore.setState({ endpoints: [...] }), assert filtered rows have updated endpoint_type. Test the race condition: load endpoints after feed, verify enrichment fires.
  • Old tests to delete: None — existing feedStore tests don't test toSummary() enrichment
  • Test environment: Standard Vitest with Zustand stores

Implementation Recommendations

  • The module should own the join between request endpoint_id and endpoint endpoint_type
  • It should hide the subscription wiring, the re-enrichment trigger, and the identity-preserving update
  • It should expose the same filtered / requests interface callers already use
  • The endpoint_id field addition to RequestSummary is the only type change
  • The request-logged event path (addRequest) still calls toSummary() at event time, which is correct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions