Skip to content

Destination-first Connect UI: Milestone 1 of SSH-anywhere complete - #337

Open
theogravity wants to merge 16 commits into
feat/ssh-anywhere-2from
feat/ssh-anywhere-3
Open

theogravity wants to merge 16 commits into
feat/ssh-anywhere-2from
feat/ssh-anywhere-3

Conversation

@theogravity

Copy link
Copy Markdown
Contributor

What this is

Tier 3 of docs/superpowers/specs/2026-10-07-ssh-anywhere-design.md (Plan: docs/superpowers/plans/2026-10-08-ssh-anywhere-3-destination-first.md). Stacked on #336. The human half of Milestone 1: the Connect page.

Surface

  • /connect: destination first (remembered + recent + the machine's config aliases + free text typed into a mirrored list row), connecting-machine disclosure fed by the preference and the gate, one POST launches and lands on the pane. Refusals render the server's cause: the blocked settings verbatim from the 422 outcome (ApiError now carries the parsed body, additive), the gate remedy naming the machine, held/offline/namings each their own. The Match-exec disclosure rides the page copy (it already rode the route descriptions).
  • Server: subshell views gain ssh: boolean and the per-viewer access downgrades edit -> view for non-owners of ssh panes, so every client's existing view-only posture becomes truthful with zero new client enforcement (REST/exec/WS/nudge already enforce owner-only since SSH launcher tier: discovery, resolve, rendered ssh-terminal panes, owner-only input #336).
  • The pane header labels SSH panes and tells a viewer why typing is off; remembered/recent ledger with forget/remember; default machine setter.

Deviations (adjudicated in-task, evidence in the plan's spec amendment)

Spec §11's "destination field is a SearchableSelect" contradicts its own "pick or type a host": the pinned Base UI combobox wipes mid-typing input and cannot commit unregistered values (measured 2026-09-29, re-probed at 1.7.0). The destination ships the working-directory field's input-plus-panel posture; the machine picker IS a SearchableSelect. Spec §11 now carries an amendment note.

Verification

bun run verify-types && bun run lint:check && bun run lint:prose && bun run test green (54/54 tasks, 0 fail); lint:design clean; e2e/tests/23-connect-flow.spec.ts passes for real on this host (4/4, two consecutive runs): gate-off disabled+reason, verbatim disclosure, browser-launched alias -> shell-evaluated echo back over the pane log, edit-sharee sees the view-only line and types nothing.

@theogravity
theogravity force-pushed the feat/ssh-anywhere-3 branch 6 times, most recently from 60cf668 to 5f82673 Compare October 8, 2026 09:24
theogravity added a commit that referenced this pull request Oct 8, 2026
Not an ssh change: the #318 backup test 'restricts download jobs to their owner
and cancels temporary output' polls a fixed ~1s for the async job-cleanup to
retire the record. CI's throttled slice ran it at ~1.8s and it flaked twice
across this stack (#336 rerun, #337 shard 1) while passing elsewhere - a
runner-speed-dependent budget. Swapped the fixed attempt-count for a wall
deadline that exits the instant 404 lands. Pre-existing flake, surfaced here,
same 'expose it, fix it' call as the web fetch-reset earlier this branch.
…iced message

The SSH launch route answers a refused resolve with a 422 {outcome} whose
settings list can outgrow the 200-char display slice every ApiError message
carries. parseErrorBody now hands the parsed JSON object to its callers, and
apiFetch passes it through as err.body; the API <status>: <message> string is
byte-identical, and legacy text bodies keep no body.
/connect opens with the destination leading: saved rows, recent rows, the
picked machine's config aliases, and a typed host the list mirrors as its
own row (input-plus-panel posture - Base UI's held combobox cannot keep
typed text across per-keystroke item swaps, the working-directory field
learned this first). The connecting machine follows as a disclosure line;
a gated-off machine stays visible and greyed with its reason, the default
pre-selects honestly, and no preference plus several enabled machines is a
required question with the gold star, never a silent pick. One POST opens
the pane; refusals render on the field they belong to, the 422's blocked
settings read verbatim from the parsed body (the message slice could cut
them). The two-sentence Match exec disclosure sits under the destination
before the button; Remember is the explicit PUT after success, the alias
token riding only config-list picks. The ledger below lists remembered and
recent rows with forget/remember acts, the recent copy reading once when a
destination is already saved.
…e now associated, the save hook's render claim matches reality, spec §11 records the combobox free-text contradiction
… no-op on an empty machine, the gate remedy rides the row
…ine's settings (matching the server's gateCause); the at-rest-gold picker carve-out is documented
CI's Test: web job timed out NetworkPluginCard's fail-then-succeed chain at
30.098s on this larger suite. The file is pre-existing (#318, last touched
before this stack); its own comments carry the whole measurement trail, and
the CI regime is the documented starved slice (serial, 0.4 CPU, width 1 once
stretched a 2.5s react-act chain past 25s). The chain runs 3.7s idle, so this
is the package's one sanctioned knob (par-test.sh, server testing-notes rule),
not an assertion change: every per-wait budget stays honest, and the knob
moves 20 -> 30 -> 45 exactly where it has before.
Focused: network-plugin-card 86 pass serially; full web suite green.
CI's Test: web job failed on backup-settings with unmocked fetches to
happy-dom's 127.0.0.1:80 - a victim file, not the cause. The per-file
pattern swaps globalThis.fetch and restores it inline with no afterEach;
a mid-test throw skips the restore, the next file that imports a
fetch-time binder (better-auth binds at module eval) captures the
leftover mock, and the poisoned global walks forward through the serial
worker until something reaches the real network. The file already re-parks
window.location after every test for the identical cross-file reason; this
completes that invariant for fetch so every file's import-time binders see
the delegator regardless of ordering. Root cause, not an ordering chase:
the full serial suite (2521) is green.
Focused: web suite serial 2521 pass; biome + verify-types + prose clean.
… correctly

Two comment-accuracy fixes from review. The 30.098s timeout belongs to the
fail-then-succeed test below the block, not the one the block annotates, and
only the 2026-10-08 event moved 30 to 45 (26.2s moved 20 to 30). In test-setup
the poisoned-global narrative said 'happy-dom's default origin' and 'one or
more workers'; the suite registers its window at WINDOW_URL (localhost -> :80,
where a relative fetch lands) and CI's serial regime is one process, so the
victim is several files down the same worker. Comments only; no behavior
change. Focused: the affected files pass serially.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant