Skip to content

Make the runs list update itself #136

Description

@artyomsv

Goal

/runs updates itself. No F5.

New: spire-ui/src/useLiveRuns.ts

Model it on spire-ui/src/useLiveReviews.ts. Copy the socket lifecycle rather than reinventing
it
— every branch in it was paid for by a live incident:

Piece Why it is there
wsDelivered ref The REST snapshot can land after the socket's. Applying it then overwrites fresher live state.
onclose asks fetchMe() before reconnecting An expired handshake is answered with a redirect the browser cannot follow, so the socket fails with no useful close code. The session cookie's default life is five minutes, which makes expiry ordinary, not rare. Reconnecting blindly hammered the identity provider several times a second, forever.
needsLogin(me) -> goToFullLogin() Asks for every URL prefix in one sequence instead of discovering each missing one a page load at a time.
isLeavingForAuth() guards the error banner Otherwise logging out paints an alarming red failure over the dashboard for the moment before the page goes away.
setTimeout(connect, 1500) The reconnect delay.

Frame handling, same as reviews:

  • Array -> snapshot; replace the list.
  • Object with a runId -> upsert by runId.
  • Anything else -> ignore. Validate minimally (typeof d.runId === 'string'); a row without an id
    produces undefined or duplicate React keys and an unmergeable row.

Sort by startedAt descending with runId as tiebreak — the same order
FactoryRunProjection.list() uses, so the client never re-orders the server's page.

Change: spire-ui/src/components/Runs.tsx

  • Delete the getRuns effect. Read from useLiveRuns().
  • The Kind and Status selects filter the live list client-side. Consequence, and the point of
    doing it this way: a run that changes into the active filter now appears on its own. A
    server-side filter cannot do that, because the socket would have to know each client's filter.
  • Keep getRuns in api.ts. Other callers use it and the endpoint stays public.
  • Rename the Started header to Queued (see the sibling task on agent_started_at).

Traps

  • A UI assertion like "some dash exists" is satisfied by whichever column happens to be empty.
    This has bitten this file three times — most recently when the new Proposed column supplied the
    dash the cost test was asserting on. Scope every assertion to a row or a cell:
    within(row).getAllByRole('cell').
  • Array.at(-1) is not in this project's TS lib target. Use cells[cells.length - 1].
  • Filter option labels collide with cell text. Succeeded is both a status pill and an option in the
    Status select, so screen.getByText('Succeeded') matches two nodes. Read from the row.
  • vi.spyOn re-wraps the same module function, so call history leaks between tests in a file.
    vitest.setup.ts restores all mocks — rely on it, do not hand-roll.
  • A status the union does not list falls into the success branch. refused once rendered as five
    green segments. runStatusLabel, runStatusPill and isRunUnfinished already default an unknown
    value to unknown — reuse them, never re-implement.

Acceptance

  • An upserted row replaces the matching row in place, keeping sort order.
  • An upsert for an unseen run id appends it.
  • An array frame replaces the whole list.
  • A pushed row that does not match the active filter stays hidden.
  • A run whose status changes into the active filter becomes visible with no refetch.
  • A socket close with an expired session calls goToFullLogin() and does not reconnect.
    Mutation-verify: reconnect unconditionally, this test must fail.
  • Logging out shows no error banner.
  • cd spire-ui && npm test green; npx tsc --noEmit silent.

Part of #147

Activity

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

    factoryThe software factory subsystem (docs/factory)javascriptPull requests that update javascript code

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions