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
43 changes: 22 additions & 21 deletions lib/public/css/overlays.css
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
.popover-copy:hover { color: var(--text); border-color: var(--text-dimmer); }
.popover-copy.copied { color: var(--success); border-color: var(--success); }

/* Skip permissions pill */
/* Quiet native toolbar control for session permission mode. */
.session-permission-control {
display: inline-flex;
align-items: center;
Expand All @@ -535,35 +535,40 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
flex: 0 0 auto;
gap: 1px;
padding: 2px;
border: 1px solid color-mix(in srgb, var(--border, #45484f) 88%, transparent);
border-radius: 10px;
background: color-mix(in srgb, var(--surface-raised, rgba(0,0,0,.18)) 86%, transparent);
box-shadow: inset 0 1px 0 rgba(255,255,255,.04), 0 2px 8px rgba(0,0,0,.12);
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
border-radius: 8px;
background: color-mix(in srgb, var(--bg-alt) 72%, transparent);
}
.session-permission-segmented button {
position: relative;
flex: 0 0 auto;
flex: 0 0 48px;
width: 48px;
height: 22px;
border: 0;
border-radius: 7px;
padding: 4px 9px;
color: var(--text-muted, inherit);
padding: 3px 4px;
color: var(--text-secondary);
background: transparent;
font: inherit;
font-weight: 500;
font-size: 11px;
line-height: 1;
white-space: nowrap;
cursor: pointer;
transition: color .15s ease, background .15s ease, box-shadow .15s ease;
}
.session-permission-segmented button:hover:not(:disabled) { color: var(--text, #fff); background: rgba(255,255,255,.07); }
.session-permission-segmented button:focus-visible { outline: 2px solid var(--accent, #6f8cff); outline-offset: 1px; z-index: 1; }
.session-permission-segmented button.active { background: var(--accent, #6f8cff); color: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.2); }
.session-permission-segmented button:disabled { cursor: not-allowed; opacity: .5; }
transition: color .15s ease, background .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.session-permission-segmented button:hover:not(:disabled) { color: var(--text); background: color-mix(in srgb, var(--bg) 76%, transparent); }
.session-permission-segmented button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; z-index: 1; }
.session-permission-segmented button.active { border: 1px solid var(--border); background: var(--input-bg); color: var(--text); font-weight: var(--font-weight-heading, 600); box-shadow: 0 1px 2px rgba(var(--shadow-rgb), .12); }
:root.light-theme .session-permission-segmented button.active { background: var(--bg); }
.session-permission-segmented button:disabled { cursor: not-allowed; }
.session-permission-control:not(.permission-pending) .session-permission-segmented button:disabled { color: var(--text-muted); opacity: .65; }
.session-permission-control.permission-pending .session-permission-segmented button:disabled { cursor: wait; }
.session-permission-segmented button.pending { color: #fff; }
.permission-spinner { display: inline-block; visibility: hidden; width: 10px; height: 10px; margin-left: 4px; vertical-align: -1px; border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; }
.session-permission-segmented button.pending { color: var(--text); background: color-mix(in srgb, var(--bg) 42%, transparent); }
.session-permission-segmented button.pending .permission-label { visibility: hidden; }
.permission-spinner { position: absolute; inset: 50% auto auto 50%; display: block; visibility: hidden; width: 10px; height: 10px; transform: translate(-50%, -50%); border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%; }
.session-permission-segmented button.pending .permission-spinner { visibility: visible; animation: permission-spin .7s linear infinite; }
@keyframes permission-spin { to { transform: rotate(360deg); } }
@keyframes permission-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
.session-permission-status {
flex: 0 1 auto;
min-width: 0;
Expand Down Expand Up @@ -592,12 +597,8 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
}
.permission-runtime-fallback .session-permission-status { color: var(--warning, #d49a55); }
.permission-globally-forced .session-permission-status { color: var(--warning, #d49a55); }
.permission-label-short { display: none; }
@media (max-width: 760px) {
.permission-label-long { display: none; }
.permission-label-short { display: inline; }
.session-permission-control { column-gap: 4px; }
.session-permission-segmented button { padding-inline: 7px; }
.session-permission-status {
max-width: 132px;
font-size: 9px;
Expand Down
28 changes: 16 additions & 12 deletions lib/public/css/pane.css
Original file line number Diff line number Diff line change
Expand Up @@ -365,31 +365,35 @@ body.pane-mode #input-area {
line-height: 1;
}

/* Header-scoped (two-class) selectors: pane.css loads BEFORE menus.css in
style.css, so the base .session-full-access rules would win a specificity
tie and keep the pill at its 22px title-bar size inside the 24px pane
header. Scope under .split-pane-header to outrank them. */
/* Header-scoped selectors keep the compact permission control clear of the
title and close affordance while preserving the shared header rhythm. */
.split-pane-header {
height: 28px;
min-height: 28px;
}

.split-pane-header .split-pane-full-access {
height: 22px;
height: 24px;
align-self: center;
margin-left: 0;
padding: 0;
gap: 4px;
font-size: 10px;
font-size: 11px;
letter-spacing: 0.02em;
flex-wrap: nowrap;
flex: 0 0 auto;
overflow: hidden;
}

.split-pane-header .session-permission-segmented button {
min-height: 18px;
padding: 3px 6px;
font-size: 10px;
min-height: 20px;
height: 20px;
font-size: 11px;
}

.split-pane-header .permission-label-long { display: none; }
.split-pane-header .permission-label-short { display: inline; }
.split-pane-header .session-permission-segmented {
height: 24px;
padding: 1px;
}

.split-pane-header .session-permission-status {
flex: 0 0 auto;
Expand Down
6 changes: 3 additions & 3 deletions lib/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,9 @@ <h1 class="wna-title" id="wna-title"></h1>
<button id="header-info-btn" type="button" title="Session info"><i data-lucide="info"></i></button>
<div id="header-full-access-btn" class="session-permission-control hidden" role="group" aria-label="Session permission mode">
<div class="session-permission-segmented">
<button type="button" data-permission-mode="default"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>
<button type="button" data-permission-mode="auto"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>
<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label-long permission-label">Skip permissions</span><span class="permission-label-short permission-label" aria-hidden="true">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>
<button type="button" data-permission-mode="default" aria-label="Ask permissions" title="Ask permissions"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>
<button type="button" data-permission-mode="auto" aria-label="Auto permissions" title="Auto permissions"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>
<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>
</div>
<span class="session-permission-status hidden" aria-live="polite"></span>
</div>
Expand Down
10 changes: 5 additions & 5 deletions lib/public/modules/permission-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export function createPermissionControl(className, ariaLabel) {
control.setAttribute("role", "group");
control.setAttribute("aria-label", ariaLabel || "Session permission mode");
control.innerHTML = '<div class="session-permission-segmented">' +
'<button type="button" data-permission-mode="default"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'<button type="button" data-permission-mode="auto"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label-long permission-label">Skip permissions</span><span class="permission-label-short permission-label" aria-hidden="true">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'<button type="button" data-permission-mode="default" aria-label="Ask permissions" title="Ask permissions"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'<button type="button" data-permission-mode="auto" aria-label="Auto permissions" title="Auto permissions"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
'</div><span class="session-permission-status" aria-live="polite"></span>';
control.querySelector(".session-permission-status").setAttribute("aria-atomic", "true");
return control;
Expand Down Expand Up @@ -101,6 +101,7 @@ export function renderPermissionControl(control, state) {
control.classList.toggle("permission-globally-forced", globallyForced);
control.classList.toggle("permission-runtime-fallback", state.permissionMode === "auto" && !!state.effectivePermissionMode && state.effectivePermissionMode !== "auto");
control.setAttribute("aria-busy", pending ? "true" : "false");
var buttonTitles = { default: "Ask permissions", auto: "Auto permissions", bypassPermissions: "Skip permissions" };
var buttons = control.querySelectorAll("[data-permission-mode]");
for (var i = 0; i < buttons.length; i++) {
var mode = buttons[i].dataset.permissionMode;
Expand All @@ -113,8 +114,7 @@ export function renderPermissionControl(control, state) {
buttons[i].classList.toggle("pending", requested);
buttons[i].setAttribute("aria-pressed", selected ? "true" : "false");
buttons[i].setAttribute("aria-busy", requested ? "true" : "false");
if (unsupported) buttons[i].title = "Auto permissions are unavailable for this Claude session.";
else buttons[i].removeAttribute("title");
buttons[i].title = unsupported ? "Auto permissions are unavailable for this Claude session." : buttonTitles[mode];
}
var status = control.querySelector(".session-permission-status");
if (status) {
Expand Down
31 changes: 26 additions & 5 deletions test/fixtures/permission-mode-browser.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
<!doctype html>
<html lang="en">
<html lang="en" class="dark-theme">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Production permission control browser fixture</title>
<link rel="stylesheet" href="../../lib/public/style.css">
<style>
:root { --bg: #151617; --surface-raised: #202224; --border: #3b3e42; --text: #f4f1e8; --text-muted: #aaa69d; --accent: #d66d4a; }
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; background: var(--bg); color: var(--text); font: 14px ui-monospace, monospace; }
body { margin: 0; min-height: 100vh; background: var(--bg); color: var(--text); font: 14px Pretendard, sans-serif; }
main { width: 100%; max-width: 860px; margin: 56px auto; padding: 0 24px; overflow: hidden; }
h1 { font: 500 24px Georgia, serif; }
.fixture-note, .fixture-copy span, #fixture-log { color: var(--text-muted); font-size: 12px; }
.fixture-controls { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0; }
.fixture-controls button, .fixture-controls select { border: 1px solid var(--border); background: var(--surface-raised); color: var(--text); border-radius: 6px; padding: 7px 10px; }
.fixture-controls button, .fixture-controls select { border: 1px solid var(--border); background: var(--bg-alt); color: var(--text); border-radius: 6px; padding: 7px 10px; }
.fixture-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 24px; min-width: 0; min-height: 44px; padding: 18px 0; border-top: 1px solid var(--border); }
.fixture-copy { flex: 1 1 220px; min-width: 0; }
.fixture-row > .session-permission-control { flex: 0 1 auto; }
.fixture-copy span { display: block; margin-top: 5px; }
.fixture-split-pane { flex: 0 0 358px; width: 358px; max-width: 100%; min-height: 0; border: 1px solid var(--border); }
.fixture-split-pane { --fixture-split-width: 356px; flex: 0 0 var(--fixture-split-width); width: var(--fixture-split-width); max-width: 100%; min-height: 0; border: 1px solid var(--border); }
.fixture-split-pane .split-pane-header { width: 100%; }
.fixture-split-pane .split-pane-vendor { background: var(--accent); }
#fixture-log { min-width: 0; min-height: 36px; padding-top: 14px; border-top: 1px solid var(--border); overflow-wrap: anywhere; white-space: pre-wrap; }
Expand All @@ -28,6 +27,7 @@
.fixture-row { align-items: flex-start; flex-direction: column; gap: 10px; }
.fixture-copy { flex: none; width: 100%; }
.fixture-row > .session-permission-control { align-self: stretch; }
.fixture-split-pane { flex: none; }
}
</style>
</head>
Expand All @@ -42,6 +42,14 @@ <h1>Production permission controls</h1>
<option value="reject">Reject request</option>
<option value="hold">Hold pending</option>
</select>
<select id="theme-mode" aria-label="Fixture theme">
<option value="dark">Dark theme</option>
<option value="light">Light theme</option>
</select>
<select id="split-width" aria-label="Split header width">
<option value="356">Split width 356px</option>
<option value="390">Split width 390px</option>
</select>
<button type="button" id="release-held">Release held response</button>
<button type="button" id="toggle-online">Go offline</button>
<button type="button" id="switch-session">Switch to session 8</button>
Expand Down Expand Up @@ -81,6 +89,9 @@ <h1>Production permission controls</h1>
var log = document.getElementById("fixture-log");
var onlineButton = document.getElementById("toggle-online");
var sessionButton = document.getElementById("switch-session");
var themeMode = document.getElementById("theme-mode");
var splitWidth = document.getElementById("split-width");
var splitPane = document.querySelector(".fixture-split-pane");

createStore({
currentSlug: "fixture-project",
Expand Down Expand Up @@ -166,6 +177,16 @@ <h1>Production permission controls</h1>
store.subscribe(renderProductionControls);
renderProductionControls();

themeMode.addEventListener("change", function () {
document.documentElement.classList.toggle("light-theme", this.value === "light");
document.documentElement.classList.toggle("dark-theme", this.value !== "light");
writeLog("Production theme tokens switched to " + this.value + ".");
});
splitWidth.addEventListener("change", function () {
splitPane.style.setProperty("--fixture-split-width", this.value + "px");
writeLog("Split Driver header width set to " + this.value + "px.");
});

document.getElementById("release-held").addEventListener("click", function () {
if (!heldRequest) { writeLog("No held production request."); return; }
var request = heldRequest;
Expand Down
Loading
Loading