fix(ui): align workstation web contract with gateway API shape#1461
Open
eddydao wants to merge 1 commit into
Open
fix(ui): align workstation web contract with gateway API shape#1461eddydao wants to merge 1 commit into
eddydao wants to merge 1 commit into
Conversation
The workstations page was written against snake_case field names the gateway never served. Go's json decoder drops unknown members silently, so create requests arrived with an empty WorkstationKey and were rejected with "workstationKey is required", while list rows read undefined and rendered a blank key, "backend.undefined" and "Invalid Date". Align the TypeScript contract on the camelCase shape that both the WS handler and store model already use: - Rename Workstation, CreateWorkstationParams and every field read on the workstations page to workstationKey/backendType/createdAt/updatedAt. - Nest update params under `updates`; a flattened body decoded to an empty map and was rejected with "no updates provided". - Replace the Identity File input, which mapped to a field SSHMetadata does not accept, with a private key or password credential path. - Add the image field required by DockerMetadata and map the container name onto host and the daemon endpoint onto socketPath, so Docker workstations can be created at all. - Extract the payload builder so the wire contract is unit-testable, and move validation strings into the en/vi/zh catalogs. Covered by tests asserting the create payload shape and list rendering against a verbatim gateway response body.
eddydao
force-pushed
the
fix/1258-workstation-contract-casing
branch
from
July 21, 2026 07:23
f133f8a to
5568421
Compare
clark-cant
approved these changes
Jul 21, 2026
clark-cant
left a comment
Contributor
There was a problem hiding this comment.
Review — github-maintain
Summary: Aligns the workstation web UI TypeScript contract with the actual gateway API shape (camelCase), fixes Docker create metadata, and replaces the dead Identity File input with proper privateKey/password auth. Closes #1258.
Risk level: Low — UI-only changes, no backend modifications, well-scoped with regression tests.
Mandatory gates
- Duplicate / prior implementation: clear — no prior PR addresses this contract mismatch
- Project standards: docs found — follows existing patterns (cli-credentials helper precedent for extracted payload builder, i18n catalog structure matches other pages)
- Strategic necessity: clear value — the workstation create flow was 100% broken for both SSH (snake_case silently dropped by Go decoder) and Docker (missing image field). This is a real user-facing bug.
Findings
Positive observations:
- Excellent PR description with root cause analysis and scope expansion disclosure (Docker create was equally broken — flagged explicitly)
buildWorkstationCreatePayload()extracted into a pure, testable helper — follows the existingcli-credentialspattern- 7 regression tests that actually verify the wire contract, including a test that proves the guards fail without the fix
- i18n parity across en/vi/zh with 89 keys verified
- Update endpoint fix (
{ id, updates: params }instead of flattened) addresses the second half of #1258 that was never diagnosed - No anti-slop patterns: no unnecessary abstractions, no defensive paranoia, no phantom coverage
Suggestion:
- The
privateKeyHinti18n string says "encrypted at rest" — ensure this is actually true in the backend storage layer before shipping. If not, soften to "stored securely".
Verdict: Approve — clean, well-tested fix for a completely broken workflow. The scope expansion beyond #1258 is well-justified and explicitly flagged.
Merge deferred: mergeStateStatus=UNSTABLE, no CI checks reported yet. Will merge once CI is green.
Reviewed by github-maintain at $(date -u +'%Y-%m-%dT%H:%M:%SZ')
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1258.
Problem
The workstations page was written against snake_case field names the gateway never served. Go's json decoder drops unknown members silently rather than erroring, so the mismatch surfaced as confusing validation failures instead of decode errors:
workstation_key/backend_type; the handler readsworkstationKey/backendType(internal/gateway/methods/workstations.go:125-127). Both landed as zero values →"workstationKey is required", regardless of what was typed.Workstationinterface declaredworkstation_key/backend_type/created_at, but the API serializes camelCase (internal/store/workstation_store.go:14-26) → blank key column,backend.undefined,Invalid Date.metadata.identity_file, whichstore.SSHMetadatadoes not accept;UnmarshalSSHMetadatarequiresprivateKeyorpassword.Fix
Aligned the TypeScript contract on the camelCase shape the WS handler, HTTP handler, and store model already share. No backend changes — per the maintainer's decision on the issue, adding snake_case aliases in Go would leave list rendering and future update flows exposed to a second parallel contract.
Workstation/CreateWorkstationParamsfields and every read on the page.buildWorkstationCreatePayload()so the wire contract is unit-testable without mounting React, following the existingcli-credentialshelper precedent.Two findings beyond the issue text
1. Docker create was equally broken — not mentioned anywhere in #1258. The dialog sent
{container, docker_host}, butstore.DockerMetadatarequiresimageplushostorsocketPath, and stores the container name inhost(surfaced back asmetadataSummary.containerName). Every Docker workstation creation failed with"image is required". Added the missingimagefield and mapped container →host, daemon endpoint →socketPath.Flagging this explicitly because it expands the issue's stated scope. Fixing only what #1258 describes would have closed it with Docker creation still 100% broken. Happy to split it into its own PR if you'd prefer the boundary kept tight.
2. Update was broken for a second, independent reason.
useWorkstations.updateWorkstationsent{id, ...params}flattened, but the handler reads a nestedupdatesmap (internal/gateway/methods/workstations.go:180-182) →"no updates provided". Item 2 of the issue's scope covers this, but the cause was never diagnosed, so a rename-only fix would have left update dead.Tests
New
workstation-contract.test.ts— 7 tests covering create payload shape, SSH credential mapping, Docker metadata, and list rendering against a verbatimSanitizedView()response body.Verified the guards actually fail without the fix: reintroducing the snake_case payload produces
expected [ 'backend_type', … ] to deeply equal [ 'backendType', … ].Full-suite baseline comparison — 6 failing files / 2 failing tests both before and after, an identical pre-existing set (jsdom
localStorageand network stubs inhttp-client,channels,cli-credentials). This change adds +7 passing and introduces no new failures.tsc --noEmit,eslint --max-warnings=0, andpnpm buildall clean.Surface parity
workstations.createhas exactly one caller in the repo (ui/web/src/pages/workstations/hooks/use-workstations.ts); no CLI or desktop surface exists.