Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ Compatibility is documented in release notes, not encoded in the version string.

### Changed

- **Log out** moved into the web terminal's display menu, alongside
**Settings** — the two now sit side by side under a line naming the signed-in
user. The separate user chip in the header is gone, leaving search and the
display menu there. Single-user terminals are unchanged apart from
**System Settings** being relabelled **Settings**.
- Pairing a virtual accelerator with the mock archiver is refused — at build,
at deploy, and at MCP server startup — because the VA moves channels for
modelled reasons while the mock archiver invents history at read time, and
Expand Down
254 changes: 104 additions & 150 deletions src/osprey/interfaces/web_terminal/static/css/terminal.css
Original file line number Diff line number Diff line change
Expand Up @@ -234,150 +234,6 @@
background: var(--bg-elevated);
}

/* ---- Identity chip (who you are + the way out) ----

One chip replaces the old user-name badge and the standalone logout door
button. The trigger states the fact (avatar initial + username); the popover
it opens holds the action (Log out) plus the deployment line, so an operator
on several terminals can confirm WHICH one they are about to leave before
they leave it. Popover grammar — capture-phase outside-click, Escape,
aria-expanded mirrored on the trigger — matches display-menu.js and
panel-add-menu.js, the shell's other two header popovers. */
.identity-menu {
position: relative;
display: flex;
flex-shrink: 0;
}

.identity-trigger {
display: flex;
align-items: center;
gap: var(--space-2);
height: 28px;
max-width: 200px;
padding: 0 8px 0 4px;
color: var(--text-primary);
background: var(--bg-panel);
border: 1px solid var(--border-default);
border-radius: var(--radius-full);
cursor: pointer;
transition: color var(--wt-transition-fast), border-color var(--wt-transition-fast),
background var(--wt-transition-fast);
}

.identity-trigger:hover,
.identity-trigger[aria-expanded="true"] {
border-color: var(--border-accent);
background: var(--bg-elevated);
}

/* The initial disc. Carries the accent so the chip reads as "you" at a glance
without needing an avatar image the framework has no way to source. */
.identity-avatar {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 20px;
height: 20px;
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
line-height: var(--leading-none);
color: var(--color-on-accent);
background: var(--color-accent);
border-radius: var(--radius-full);
}

.identity-name {
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: var(--weight-regular);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.identity-caret {
display: block;
flex-shrink: 0;
color: var(--text-muted);
}

/* The popover: drops below the chip, right-aligned to it — same geometry as
the display menu's card. */
.identity-card {
display: none;
position: absolute;
top: calc(100% + var(--space-2));
right: 0;
min-width: 200px;
flex-direction: column;
background: var(--bg-secondary);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-dropdown);
overflow: hidden;
z-index: var(--z-dropdown);
}

.identity-card.open {
display: flex;
}

.identity-card-identity {
display: flex;
flex-direction: column;
gap: 2px;
padding: var(--space-3);
border-bottom: 1px solid var(--border-default);
}

.identity-card-name {
font-family: var(--font-display);
font-size: var(--text-md);
font-weight: var(--weight-semibold);
color: var(--text-primary);
word-break: break-all;
}

.identity-card-sub {
font-family: var(--font-display);
font-size: var(--text-sm);
color: var(--text-secondary);
}

.identity-card-logout {
display: flex;
align-items: center;
gap: var(--space-2);
width: 100%;
padding: var(--space-3);
font-family: var(--font-display);
font-size: var(--text-sm);
text-align: left;
color: var(--text-secondary);
background: none;
border: none;
cursor: pointer;
transition: color var(--wt-transition-fast), background var(--wt-transition-fast);
}

.identity-card-logout:hover {
color: var(--color-error);
background: var(--bg-elevated);
}

.identity-card-logout[aria-busy="true"] {
cursor: progress;
opacity: 0.6;
}

.identity-card-logout-icon {
font-size: var(--text-md);
line-height: var(--leading-none);
}

/* ---- Display menu (the header sliders button) ----

Every display preference — Appearance (light/dark), View (Expert/Simple),
Expand Down Expand Up @@ -569,15 +425,96 @@ html[data-ui-mode="simple"] .mode-segment[data-mode="simple"],
background: var(--accent-tint-06);
}

/* System Settings: the card's last row, separated from the preference rows by
a hairline so it reads as an action rather than a fourth preference. Expert
only — the simple-mode gate lives with the other density deltas below. */
.display-menu-settings {
/* ---- Session footer (identity + the two actions) ----

The card's last block: who you are, then Settings and Log out side by side.
A hairline splits it from the preference rows so it reads as action rather
than a fourth preference. Both actions are present in BOTH ui modes — the
settings drawer's own warning gate, not the ui mode, is what guards expert
configuration. */
.display-menu-identity {
display: flex;
align-items: baseline;
gap: var(--space-2);
min-width: 0;
padding-top: var(--space-2);
border-top: 1px solid var(--border-default);
}

/* The initial disc. Carries the accent so the line reads as "you" at a glance
without needing an avatar image the framework has no way to source. */
.display-menu-identity-avatar {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
align-self: center;
width: 20px;
height: 20px;
font-family: var(--font-display);
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
line-height: var(--leading-none);
color: var(--color-on-accent);
background: var(--color-accent);
border-radius: var(--radius-full);
}

/* The name wins the row when both it and the deployment are too long for it:
`alice` is what identifies the session, the deployment only qualifies it. */
.display-menu-identity-name {
flex-shrink: 0;
max-width: 60%;
font-family: var(--font-display);
font-size: var(--text-base);
font-weight: var(--weight-semibold);
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.display-menu-identity-sub {
min-width: 0;
font-family: var(--font-display);
font-size: var(--text-sm);
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.display-menu-identity-sub::before {
content: '·';
margin-right: var(--space-1);
}

/* The hairline opens the footer, so it belongs to whichever element starts it:
the identity line on a multi-user terminal, the action row on a single-user
one (which renders no identity and no logout). */
.display-menu-actions {
display: flex;
gap: var(--space-2);
padding-top: var(--space-2);
border-top: 1px solid var(--border-default);
}

.display-menu-identity + .display-menu-actions {
padding-top: 0;
border-top: none;
}

/* `flex: 1` rather than a 2-column grid: with logout absent — a single-user
deployment, or one with no landing_url to return to — Settings fills the row
by itself instead of sitting in half of it. */
.display-menu-settings,
.display-menu-logout {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
gap: var(--space-2);
margin-top: calc(var(--space-1) * -1);
min-width: 0;
padding: 7px 10px;
font-family: var(--font-display);
font-size: var(--text-base);
Expand All @@ -586,6 +523,7 @@ html[data-ui-mode="simple"] .mode-segment[data-mode="simple"],
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
cursor: pointer;
white-space: nowrap;
transition: color var(--wt-transition-fast), border-color var(--wt-transition-fast),
background var(--wt-transition-fast);
}
Expand All @@ -597,7 +535,23 @@ html[data-ui-mode="simple"] .mode-segment[data-mode="simple"],
background: var(--bg-elevated);
}

.display-menu-settings-icon {
/* Logout hovers to the error colour, not the accent: it is the one control in
this card that ends the session rather than adjusting it. */
.display-menu-logout:hover {
color: var(--color-error);
border-color: var(--color-error);
background: var(--bg-elevated);
}

/* app.js locks the button (`disabled` + `aria-busy`) for the whole in-flight
logout and never resets it — every path out of that handler navigates away. */
.display-menu-logout[aria-busy="true"] {
cursor: progress;
opacity: 0.6;
}

.display-menu-settings-icon,
.display-menu-logout-icon {
font-size: var(--text-md);
line-height: var(--leading-none);
}
Expand Down
Loading
Loading