Skip to content

Act by instance id; render Dashboard cards in place (#128) - #133

Merged
scttfrdmn merged 1 commit into
mainfrom
fix/128-act-by-id
Nov 11, 2026
Merged

scttfrdmn merged 1 commit into
mainfrom
fix/128-act-by-id

Conversation

@scttfrdmn

Copy link
Copy Markdown
Contributor

Fixes #128

1. Every action takes an instance id

SpawnClient.stop / start / hibernate / terminate / extend / signalComplete now take instanceId: string and reject anything that isn't i- followed by 8 or 17 hex digits. The error says why ("an instance's Name is not unique…"). Before this, the Dashboard passed i.name, and EC2Provider.get(name) returned the first DescribeInstances match. In the audit that was a terminated test, so terminate on the live card "succeeded" on the dead one.

Name lookups that remain are client.get(nameOrId) (read-only) and the in-browser terminal's Go-parity commands (stop test). Both now resolve exactly as Go's resolveInstance does (spawn/cmd/utils.go:186) via the new core/resolve.ts:

  • spawn-managed live instances only, i.e. Go's default state filter. Terminated and shutting-down instances never match.
  • Name matching is case-insensitive (EqualFold).
  • A single running match wins a tie (Go #313).
  • Anything still ambiguous throws AmbiguousNameError listing the candidate ids. The terminal prints it.

get(id) returns the instance in any state, so a terminated instance shows as terminated. It returns null for InvalidInstanceID.NotFound and for an instance that isn't spawn-managed (Go: "must be spawn-managed").

The terminal keeps name-or-id on purpose, because Go's CLI accepts a Name with exactly these rules. Only the library's action API is id-only.

2. Cards render in place, keyed by id

renderInstances replaced every card on each instances event, which fired about every 125 ms, so a 180 ms press lost its click. Now:

  • each card is created once per instance id (data-instance-id);
  • its info block is rewritten only when its HTML changes;
  • its buttons are rebuilt only when the offered set changes, and never while an action on that instance is running;
  • the busy state is kept per id across renders;
  • the orphan banner is rebuilt only when the orphan set changes.

3. signalComplete is mock-only

It throws on any non-simulated provider. On EC2, spored runs on-complete when the completion file appears. The Dashboard hides the button when backend.isReal.

Tests

  • Dashboard, the audit's account: a terminated test and a live test. Extend, stop, start, hibernate, start and terminate on the live card all hit the live id (provider spied), and the dead one is untouched.
  • Two live namesakes: each card acts on its own id.
  • mousedown, then step plus refresh (the TTL meter changes), then mouseup: it is the same button node, still connected, and the click opens the confirm.
  • A hanging stop keeps the card's buttons disabled across two renders. They re-enable when it settles and show start.
  • signalComplete is hidden and refused on a real-flagged provider.
  • All six mutators reject a Name.
  • EC2Provider.get covers: by id, terminated by id, unmanaged by id, NotFound versus other errors, a Name skipping a terminated namesake, case-insensitivity, a single running match beating stopped ones, and an ambiguous Name throwing with both ids.

Breaking for existing consumers

Yes, noted in the CHANGELOG: passing a Name to those six methods now rejects. Pass instance.instanceId. The in-repo demos already pass ids.

Relationship to the other PRs

SpawnClient's mutating methods (stop/start/hibernate/terminate/extend/
signalComplete) take an instance id and reject a Name. Name lookups that
remain (get, the terminal commands) resolve as Go's resolveInstance:
spawn-managed live instances only, case-insensitive, a single running
match wins, otherwise AmbiguousNameError with the candidate ids.

The Dashboard keeps one card per instance id and updates it in place:
text only when it changes, buttons only when the offered set changes and
never mid-action, so a slow press survives the monitor's renders.
signalComplete is mock-only and hidden on real backends.

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

Labels

None yet

Projects

None yet

1 participant