Skip to content

fix: member-machines UI round one — eleven annotated fixes - #107

Merged
pythonlearner1025 merged 11 commits into
mainfrom
fix/member-machines-ui
Aug 29, 2026
Merged

fix: member-machines UI round one — eleven annotated fixes#107
pythonlearner1025 merged 11 commits into
mainfrom
fix/member-machines-ui

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

Follow-up to #106, from the user's DOM annotations on the live canary. Eleven fixes, one commit each.

Shell

  • A2: the strip carries one Drive icon; the three panel toggles live only in the right rail.
  • A6: deterministic per-workspace tile gradients, contrast-checked (worst case 4.83:1 over all hues).
  • A7: the avatar opens Settings directly; the account menu is gone. The Discord link moved to the settings side nav (desktop; mobile placement needs a small layout decision, noted).
  • B4: Discord-style right-click menu on workspace tiles — Rename (PATCH name), Settings, Invite (Members tab, add-member picker focused). Non-admins see Settings only.

Rail

  • A3: the pinned action reads "New tab" and reuses the tab strip's menu — one shared NewTabMenu component, duplicate deleted.
  • B5: a "My machine" panel behind the pre-feat: member machines — the workspace is a server, every member gets a machine #106 box glyph: the member's own machine, §3-gated lifecycle controls in the first person, disabled verbs name the admins to ask. No request backend.
  • B6: the Members button wears the Drive share glyph.

Dialogs

Create

  • A4: repositories Refresh sits bottom-right under the list.
  • B2: per-member "Persistent volume" toggle (default on) next to the type select; the workspace-level Volume section is gone. Wire: one optional boolean on members[]/AddWorkspaceMember/ProvisionMemberMachine, absent = true; the flag feeds the existing provisionWorkspaceVolume call site — no new volume code.

Gates: typecheck 0; lint at baseline (102/0, 7 max-lines); CP 633, actor 119, webapp 358, all pass.

🤖 Generated with Claude Code

pythonlearner1025 and others added 11 commits August 29, 2026 09:09
The strip's Files / teenyapps / Connections buttons duplicated the right
icon strip (WorkspaceRailStrip), which owns those panels. The slot now
holds a single Drive icon that navigates to the Drive page — the route the
account menu's Drive entry used.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail's pinned action drew its own popover that looked like the tab
strip's "+" menu but listed less: no live ports, no preview links. The
menu now lives in one component, src/NewTabMenu.tsx, and both call sites
render it with their own anchor. SessionTypeIcon moves to
src/SessionTypeIcon.tsx so the shared menu needs no import back into
WebAppHeader; WebAppHeader re-exports both, so its importers are unchanged.
The pinned action reads "New tab". The "+" button behaves as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…igned

PR #95 put Refresh in the filter row. It now sits in a footer row under the
list, on the right edge, beside the selection count. Behaviour is
unchanged: the same click still bumps the refresh version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The strip's tiles were flat, so a two-letter code was the only thing
telling them apart. src/shell/workspace-tile.ts hashes the workspace id to
a hue, paints a 135° gradient to hue + 40°, and picks a near-white or
near-black ink from the gradient's average luminance. Ids whose average
lands in the band where neither ink clears 4.5:1 are darkened out of it;
measured worst case over all 360 hues is 4.83:1. The active ring and the
dashed create tile are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The strip's avatar drew a four-item popover. Drive is a strip icon since
A2, Settings was one click away, and the name was a label. Clicking the
avatar now navigates to settings. "Ask us on Discord" moves to the bottom
of the settings side navigation, under the section tabs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dialog #106 built kept the three tabs the plan asks for and lost the
look the pre-#106 one had. This puts the look back without touching what
any control does:

- the header says `Workspace details “name”` again;
- every panel opens with the micro-caps section heading the old
  `workspace-details-grid` sections wore;
- the member rows take the old access-list geometry (a 28px avatar, a
  48px row) instead of the taller #106 one;
- Clone and Delete leave the bottom of the Settings tab for the restored
  `workspace-details-footer`, which is where the old dialog kept Delete.

Members, Credentials and Settings are the same three tabs, and every
write behind them is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every member machine has taken its own volume since #106, and there was
no way to say a machine does not need one. The member row now carries the
answer.

Wire: `members[].persistentVolume` on the create request and the same
optional boolean on `AddWorkspaceMemberRequest` and
`ProvisionMemberMachineRequest`, in `wire.ts` and `packages/schema`
together. Absent means true, so only a refusal travels.

Control plane: the flag reaches `ProvisionMachineInput` and, at the ONE
existing call site in `provisionMachine`, skips
`provisionWorkspaceVolume` exactly as an explicit `volumeId` already
does. No new volume path was written.

Webapp: the toggle sits next to the machine-type select in every member
row, in the create dialog's draft rows and in the live members editor.
A row whose machine exists reports the disk it has, disabled — the disk
is created with the machine and this is not the route that moves one.
The workspace-level Volume section leaves the create dialog; `volumeId`
stays on the wire, where a recreate still reads it.

The test fake grew an optional `volumeLocation`, so a suite can turn on
the volume placement the fake otherwise declines to do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inside the workspace-details dialog the role listbox, the machine-type
listbox and the lifecycle menu all came out under their neighbours. The
popover was `position: absolute`, so `.workspace-details-body` — which
scrolls — was both the thing that clipped it and the thing it could not
paint over.

One rule in the shared stylesheet fixes all three: the popover is
`position: fixed` with the top stacking position, so no scroll container
between it and the page can clip it. `WebAppSelectMenu` writes `left`
and one of `top`/`bottom` from the trigger's viewport rect, clamped to
the viewport, flipping to whichever side has more room; it follows a
scroll in any container above it. The one per-instance override of the
popover's geometry (the mobile settings picker) is gone with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The strip's tiles answered a left click and nothing else. A right click
now opens the same context menu the tab strip serves: the chrome is
`.webapp-session-menu`, positioned at the pointer and clamped to the
viewport, closed by Escape or by a click outside.

Three items, graded by §3:

- Rename, which edits the name in a field the popover holds — the tile
  is two letters wide — and writes it with the PATCH the settings tab
  already uses;
- Settings, which opens the details dialog on its Settings tab;
- Invite, which opens it on Members with the add-member picker focused.
  There is no email invite here: adding somebody means adding an
  existing member of the organization.

A member sees Settings alone. The left click is untouched, and a right
click never selects the tile it opened on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail header said what the workspace is and never what the member's
own machine is. A third button opens that panel, wearing the box glyph
`files/DriveIcons.tsx` has drawn since before #106.

The dialog is the workspace-details chrome with no tab row: one view,
because there is one machine to read. It names the state, the type and
what the type is (vCPU, RAM, disk, price, all off the existing catalog
helpers), whether a persistent volume is attached, and the machine's own
error where it has one.

The lifecycle controls carry the §3 matrix in the first person. Stop and
start are a member's own business; recreate, destroy and the machine-type
change are workspace-admin work, and provisioning where there is no
machine row at all is the member-add route in disguise. A verb this
member may not run is shown DISABLED and names who to ask — "Ask a
workspace admin: Ada Owner" — because there is no request to file and
pretending one was filed would be worse than saying who can act.

A viewer holds no machine (§2.2) and is told so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail drew its own three-node share glyph while the Drive page drew a
person-with-a-plus for the same verb. The rail now imports the Drive
page's `ShareGlyph`, so one shape means "share" across the app, and the
strip's copy is deleted rather than left to drift — the rail was its only
caller. The button still opens Members and nothing else changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pythonlearner1025
pythonlearner1025 merged commit 9387b80 into main Aug 29, 2026
8 checks passed
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