Skip to content

ui: look up sandboxes outside the loaded list by identifier - #1244

Merged
ac-pplx merged 3 commits into
ac/admin-ui-sandbox-id-formsfrom
ac/admin-ui-sandbox-lookup
Nov 2, 2026
Merged

ac-pplx merged 3 commits into
ac/admin-ui-sandbox-id-formsfrom
ac/admin-ui-sandbox-lookup

Conversation

@ac-pplx

@ac-pplx ac-pplx commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Every sandbox can now be found from the sandbox list by its identifier, however large the fleet.

The list loads the newest 1000 sandboxes (GET /admin/api/v1/sandboxes?limit=1000), reloads them periodically while the page is visible, and filters them in the browser. The admin list has no cursor and no search, and it orders by created_at DESC. In a fleet with more than 1000 live sandboxes, the oldest ones (often the long-running sandboxes a support case is about) were therefore not found by any search, and nothing on the page said the list was cut off.

Changes:

  • A full identifier in the search box now names one sandbox and matches it whatever the other filters say. Accepted forms are the sbx_ ID, a bare or undashed UUID, and the pod name space-sbx-<hex>-g<N>. The placeholder now reads "ID, pod name or name".
  • When no loaded row matches, the page fetches that sandbox with GET /admin/api/v1/sandboxes/{id} and shows it with a notice. The table shows as loading only until the first answer for that ID; later refetches, including ones that answer 404, do not bring the spinner back. The row is fetched again after each list load, so it stays current after an action, a Refresh or a poll; it stays shown while that runs, and a 404 clears it. An unknown ID shows the API's "Sandbox not found".
  • The lifecycle policy dialog opens for a looked-up row as for any other.
  • When the list is capped, a notice says so and explains the direct lookup. Only sandbox rows count toward the cap: the admin list appends the rows nodes report without a database row (rowless pool slots, observed-local sandboxes) after its limit sandbox rows.
  • "Load newest" chooses 100, 1000 (the default) or the API maximum of 5000 rows. It is also available as ?limit=. A list larger than the default reloads less often than the default-size list (at most about every 15 seconds), and keeps its own refresh timestamp: console windows share one per view, and a default-size list open in another window would otherwise keep the larger list from ever reloading. An answer for a load size that has since changed is dropped.
  • The overview's sandbox count reads 1000+ instead of a capped number, counting sandbox rows only.

Partial text still filters the loaded rows by ID fragment or name.

Server-side search, filters and cursor paging need admin API work and are not part of this change. Until then, this keeps every sandbox reachable by ID.

Stack

Stacked on #1242, which adds parseSandboxRef. Merge #1242 first. This branch is restacked on #1242's review follow-up. #1248 is stacked on this PR.

Validation

  • npm ci, npm test (75 pass) and npm run build in ui/. The new unit cases in src/lib/sandbox-id.test.mjs cover exact lookup in every form, ID fragments, names, the limit clamp and the sandbox-row count.
  • A Playwright loop against a local Kind stack with synthetic data, comparing main with this branch (the Kind space-ui image). It found no new console errors, page errors or failed requests.
  • A real >1000-sandbox fleet was not seeded, because each sandbox is a Firecracker VM. The cap path is instead exercised with ?limit=2: only the two newest sandboxes load, and searching the pod name of an older one finds it by direct lookup.

Visual confirmation

With only the newest 2 sandboxes loaded, the pod name of an older sandbox finds it:

after: lookup outside the loaded rows

Video: after.

Before (main): no match After: found by direct lookup
before after

Severity and risk

Severity: high. Finding a sandbox by ID is the first step of most support cases, and it silently failed for any sandbox outside the newest 1000.

Risk: low.

  • Partial-text search behaves as before.
  • A full identifier now bypasses the template, status, node and age filters, on purpose.
  • While a full identifier that matches no loaded row is in the search box, the page makes one GET /admin/api/v1/sandboxes/{id} on each list reload, never two at once for the same ID. Nothing extra is requested otherwise.
  • Roll back by reverting the commit.

AI assistance: Claude Code wrote the code, the tests and this description, and ran the validation; a maintainer reviews it.

@github-actions github-actions Bot added the triage:pending Waiting for a maintainer to triage label Oct 31, 2026
@github-actions

Copy link
Copy Markdown

Thanks for opening this pull request! A SPACE maintainer will take a look.

CONTRIBUTING.md explains how we review outside contributions.

The sandbox list loads the newest 1000 rows from the admin API once and
filters them in the browser; the admin list has no cursor or search, so
an older sandbox in a larger fleet could not be found by any search,
and nothing said the list was cut off.

A full identifier in the search box (the public ID, a bare or undashed
UUID, or the sandbox pod name) now names one sandbox: it matches the row
whatever the other filters say, and when no loaded row matches, the page
fetches that sandbox directly and shows it with a notice. When the list
is capped the page says so. "Load newest" chooses 100, 1000 or the API
maximum of 5000 rows (also `?limit=` in the URL), and the overview's
count reads "1000+" instead of a capped number.

Server-side search, filters and paging still need admin API support;
this keeps every sandbox reachable until then.
A sandbox found by direct lookup is now refetched on every list load,
with at most one request per ID in flight. The row stays shown while it
refreshes, a 404 clears it, and an answer for an ID the search box no
longer names is dropped. The table shows as loading while the first
lookup is pending. A load-size change during a lookup used to cancel it
and leave an empty table with no message; the next list load now
refetches it. The lifecycle policy dialog opens for a looked-up row.

The admin list appends node-reported rows (rowless pool slots,
observed-local sandboxes) after its `limit` sandbox rows, so only
sandbox rows count toward "capped", on the list and on the overview.
A list answer for a load size that has since changed is dropped, and a
list larger than the default polls about every 15 seconds.
A looked-up ID with no row (a 404, or an error before any row was found)
brought the table's loading spinner back on every list reload, because
each refetch reset the pending state. The table now shows as loading
only until the first answer for that ID.

A list larger than the default used the refresh timestamp every
console window shares, so a default-size list open in another window
kept it "fresh" and it never reloaded, nor did its looked-up row. The
larger list now keeps its own refresh timestamp.
@ac-pplx
ac-pplx force-pushed the ac/admin-ui-sandbox-lookup branch from 9e65269 to 06814fc Compare October 31, 2026 20:16
@ac-pplx
ac-pplx marked this pull request as ready for review October 31, 2026 22:33
zibi-pplx
zibi-pplx previously approved these changes Nov 1, 2026
@zibi-pplx
zibi-pplx dismissed their stale review November 1, 2026 22:29

Withdrawing this approval for now. Any feedback above still stands.

@ac-pplx
ac-pplx added this pull request to stack #1283 November 2, 2026 04:30

@johnlouie-pplx johnlouie-pplx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 06814fc, with one nonblocking ask. The direct lookup uses the existing authorized endpoint, and all 75 UI tests passed locally.

Could we give the lookup effect in sandboxes/page.tsx a per-request token and only let the current request update the result or clear the in-flight marker? I reproduced A → B → A with the real component and mocked responses: the first A request can finish last and overwrite the newer result, including replacing a valid row with “not found.” A regression test that completes those requests out of order would cover it. #1248 fixes the separate list-spinner issue, but not this lookup race. Worth fixing proactively here, though I wouldn't hold the merge for it.

@ac-pplx
ac-pplx added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 3068a58 Nov 2, 2026
23 checks passed
@ac-pplx
ac-pplx deleted the ac/admin-ui-sandbox-lookup branch November 2, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:pending Waiting for a maintainer to triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants