google / google/capsem

[triage:ui-02-gateway-client-duplication] Gateway client logic forked across capsem-tray and capsem-tui

Open
#152 0 comments 0 reactions 0 assignees View on GitHub
component: tray type:other
Dominant language
Rust
Stars
72
Forks
13
Avg merge
1d 2h
Merged PRs (30d)
5

Description

Imported from Capsem triage report `ui-02-gateway-client-duplication.md`.

- Severity: `medium`
- Category: `duplication`
- Area: `capsem-tray, capsem-tui`
- Location: `crates/capsem-tray/src/gateway.rs:1-201` and `crates/capsem-tui/src/gateway_provider.rs:135-525`
- Confidence: `verified`

## Summary
The tray and the TUI each independently reimplement the same loopback gateway
client: token discovery, `gateway.port`/`gateway.token` file reading, Bearer
auth, status fetch, and the identical VM-action endpoint set
(`/vms/{id}/stop`, `/vms/{id}/pause`, `/vms/{id}/resume`, `/vms/{id}/delete`,
`/vms/create`, `/purge`, `/profiles/list`). There is no shared gateway-client
crate (confirmed: capsem-core and capsem-proto expose none). This violates the
project's "one way to do things" / "capsem-core is the shared library"
invariant and means endpoint or auth changes must be made in (at least) two
Rust places plus the TS client.

## Evidence
Tray (`crates/capsem-tray/src/gateway.rs`):
- `discover()` reads `run_dir/gateway.port` + `run_dir/gateway.token` (lines 66-88).
- `stop_vm`/`delete_vm`/`suspend_vm`/`resume_vm` hit `/vms/{id}/stop|delete|pause|resume` (159-177).
- `provision_temp()` POSTs `/vms/create` (180-200).

TUI (`crates/capsem-tui/src/gateway_provider.rs`):
- `gateway_port()` reads `run_dir/gateway.port` (187-191); `fetch_token` GETs `/token` (135-148).
- `invoke_action()` hits the same `/vms/{id}/stop|pause|resume|delete`, `/vms/create`, `/purge` (324-441).
- `fetch_status` GETs `/status` and deserializes a `StatusResponse`/`VmSummary` pair (150-166, 532-572).

Both define their own `StatusResponse`/`VmSummary` serde structs with
overlapping fields. The endpoint paths and the port/token discovery rules are
duplicated verbatim.

Notably the two clients have already diverged in subtle ways:
- The tray's `parse_port_file` validates the port (rejects non-numeric/overflow,
`gateway.rs:35-40`), while the TUI silently swallows a bad port with
`.ok()?` and falls back to 19222 (`gateway_provider.rs:187-191`).
- The tray authenticates `/status` with a Bearer token; the TUI does too, but
fetches the token from a different endpoint shape. Divergence like this is
exactly what a shared client prevents.

## Impact
Endpoint, auth, or discovery changes must be applied in multiple crates and can
silently drift (as the port-validation difference shows). Higher maintenance
cost and a real risk of behavioral inconsistency between the tray and the TUI
talking to the same gateway.

## Suggested fix
Extract a single `capsem-gateway-client` (or a module in capsem-core, honoring
the thin-shell rule) that owns: port/token discovery, the authenticated
`reqwest::Client`, the status/profiles structs, and the VM-action methods. Have
both the tray's `GatewayClient` and the TUI's `GatewayProvider` delegate to it.

## Triage
Confirmed from the local reviewed report in `/Users/elie/git/capsem/tmp/bugs/ui-02-gateway-client-duplication.md`. Track implementation in the triage sprint; add regression coverage before fixing.

Contributor guide

Open the contributing guide

Research direction

Compare crates/capsem-tray/src/gateway.rs with crates/capsem-tui/src/gateway_provider.rs, starting with discovery, authentication, status types, and VM actions. Review capsem-core and capsem-proto for the shared-library boundary, then add regression coverage for the extracted client. Done means both existing clients delegate to one implementation with consistent port validation, token handling, status types, and endpoint behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, cli, desktop
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.