Skip to content

Commit 44cf2ab

Browse files
committed
v0.6.0 - U2: add keyboard access fallback coverage
1 parent ca3ac8e commit 44cf2ab

6 files changed

Lines changed: 432 additions & 70 deletions

File tree

‎desktop/cortex-control-center/scripts/mock-cortex-server.mjs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,24 @@ function buildFixture() {
356356
},
357357
],
358358
},
359+
permissions: {
360+
grants: [
361+
{
362+
client: "codex",
363+
permission: "admin",
364+
scope: "*",
365+
granted_by: "expect-smoke",
366+
granted_at: isoMinutesAgo(30),
367+
},
368+
{
369+
client: "claude",
370+
permission: "read",
371+
scope: "cortex_recall",
372+
granted_by: "expect-smoke",
373+
granted_at: isoMinutesAgo(45),
374+
},
375+
],
376+
},
359377
feedAcks: new Map(),
360378
};
361379
}
@@ -435,6 +453,7 @@ export async function startMockCortexServer({ host = "127.0.0.1", port = 7437, t
435453
"/activity",
436454
"/savings",
437455
"/conflicts",
456+
"/permissions",
438457
"/dump",
439458
"/peek",
440459
"/recall",
@@ -513,6 +532,10 @@ export async function startMockCortexServer({ host = "127.0.0.1", port = 7437, t
513532
sendJson(response, 200, fixture.conflicts);
514533
return;
515534
}
535+
if (request.method === "GET" && url.pathname === "/permissions") {
536+
sendJson(response, 200, fixture.permissions);
537+
return;
538+
}
516539
if (request.method === "GET" && url.pathname === "/dump") {
517540
sendJson(response, 200, fixture.dump);
518541
return;
@@ -529,6 +552,33 @@ export async function startMockCortexServer({ host = "127.0.0.1", port = 7437, t
529552
sendJson(response, 200, { ok: true, action: "resolved" });
530553
return;
531554
}
555+
if (request.method === "POST" && url.pathname === "/permissions/grant") {
556+
const body = await readJsonBody(request);
557+
const grant = {
558+
client: String(body.client || "unknown").trim() || "unknown",
559+
permission: String(body.permission || "read").trim() || "read",
560+
scope: String(body.scope || "*").trim() || "*",
561+
granted_by: String(body.granted_by || "expect-smoke").trim() || "expect-smoke",
562+
granted_at: nowIso(),
563+
};
564+
fixture.permissions.grants = fixture.permissions.grants.filter(
565+
(entry) => !(entry.client === grant.client && entry.permission === grant.permission && entry.scope === grant.scope),
566+
);
567+
fixture.permissions.grants.push(grant);
568+
sendJson(response, 200, { ok: true, grant });
569+
return;
570+
}
571+
if (request.method === "POST" && url.pathname === "/permissions/revoke") {
572+
const body = await readJsonBody(request);
573+
const client = String(body.client || "").trim();
574+
const permission = String(body.permission || "").trim();
575+
const scope = String(body.scope || "*").trim() || "*";
576+
fixture.permissions.grants = fixture.permissions.grants.filter(
577+
(entry) => !(entry.client === client && entry.permission === permission && entry.scope === scope),
578+
);
579+
sendJson(response, 200, { ok: true, revoked: true });
580+
return;
581+
}
532582
if (request.method === "POST" && url.pathname === "/tasks/claim") {
533583
const body = await readJsonBody(request);
534584
const task = fixture.tasks.tasks.find((entry) => entry.taskId === body.taskId);

‎desktop/cortex-control-center/src/App.jsx‎

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
import { buildMonteCarloProjection } from "./analytics-projection.js";
3939
import { summarizeBootThroughput } from "./analytics-metrics.js";
4040
import { formatCompactNumber, formatSignedCompactNumber } from "./number-format.js";
41+
import { handleKeyboardActivation, shouldIgnoreGlobalShortcut } from "./keyboard-access.js";
4142
import {
4243
readControlCenterSettings,
4344
summarizeBudgetStatus,
@@ -3905,10 +3906,36 @@ export function App() {
39053906
};
39063907
}, [api, call, callMcpTool, cortexBase, readAuthToken, runRefreshAll, runRestartDaemonSequence, waitForDaemonReachable, writeDevVerificationReport]);
39073908

3909+
useEffect(() => {
3910+
if (!showConnectionDialog && !showEditorSetupWizard) {
3911+
return undefined;
3912+
}
3913+
3914+
function handleDialogKey(event) {
3915+
if (event.key !== "Escape") {
3916+
return;
3917+
}
3918+
3919+
if (showEditorSetupWizard && !isSettingUpEditors) {
3920+
event.preventDefault();
3921+
setShowEditorSetupWizard(false);
3922+
return;
3923+
}
3924+
3925+
if (showConnectionDialog) {
3926+
event.preventDefault();
3927+
dismissConnectionDialog();
3928+
}
3929+
}
3930+
3931+
window.addEventListener("keydown", handleDialogKey);
3932+
return () => window.removeEventListener("keydown", handleDialogKey);
3933+
}, [dismissConnectionDialog, isSettingUpEditors, showConnectionDialog, showEditorSetupWizard]);
3934+
39083935
// Keyboard nav
39093936
useEffect(() => {
39103937
function handleKey(e) {
3911-
if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT" || e.target.tagName === "TEXTAREA") return;
3938+
if (shouldIgnoreGlobalShortcut(e, showConnectionDialog || showEditorSetupWizard)) return;
39123939
const idx = panelIndex(panel);
39133940
if (e.key === "ArrowDown" || e.key === "j") {
39143941
e.preventDefault();
@@ -3926,7 +3953,7 @@ export function App() {
39263953
}
39273954
window.addEventListener("keydown", handleKey);
39283955
return () => window.removeEventListener("keydown", handleKey);
3929-
}, [changePanel, panel]);
3956+
}, [changePanel, panel, showConnectionDialog, showEditorSetupWizard]);
39303957

39313958
const effectiveSidebarCollapsed = sidebarCollapsed || isNarrowViewport;
39323959
const canStartDaemon = Boolean(invokeRef.current && !restartingDaemon && !daemonState.running);
@@ -4076,10 +4103,16 @@ export function App() {
40764103
<span className="topbar-stat"><span className="topbar-label">DEC</span> {stats.decisions}</span>
40774104
<span className="topbar-stat"><span className="topbar-label">EVT</span> {stats.events}</span>
40784105
<span className="topbar-stat"><span className="topbar-label">AGENTS</span> {normalizedSessions.length}</span>
4079-
<span className="topbar-stat topbar-connection" onClick={openConnectionDialog} title="Click to change connection">
4106+
<button
4107+
type="button"
4108+
className="topbar-stat topbar-connection"
4109+
onClick={openConnectionDialog}
4110+
title="Click to change connection"
4111+
aria-label={`Connection host ${hostLabel}. Open connection settings.`}
4112+
>
40804113
<span className="topbar-label">HOST</span>
40814114
{hostLabel}
4082-
</span>
4115+
</button>
40834116
<span className={`topbar-status ${daemonStatusBadge.className}`} title={daemonStatusBadge.title}>
40844117
{daemonStatusBadge.label}
40854118
</span>
@@ -4489,20 +4522,27 @@ export function App() {
44894522
<span className="sys-label">TASKS</span>
44904523
<span className="sys-value">{pendingTasks.length} PENDING</span>
44914524
</div>
4492-
<div
4525+
<button
4526+
type="button"
44934527
className={`sys-item sys-item-action ${isSettingUpEditors ? "sys-item-disabled" : ""}`}
4494-
onClick={isSettingUpEditors ? undefined : openEditorSetupWizard}
4495-
title="Preview and register Cortex MCP in supported clients"
4528+
onClick={openEditorSetupWizard}
4529+
title="Preview and register Cortex MCP in supported clients"
4530+
disabled={isSettingUpEditors}
44964531
>
44974532
<span className="sys-label">MCP</span>
44984533
<span className="sys-value">
44994534
{isSettingUpEditors ? "WORKING" : editorSetup ? `${editorSetupSummary.registered} EDITORS` : "SETUP"}
45004535
</span>
4501-
</div>
4502-
<div className="sys-item sys-item-action" onClick={() => changePanel("memory")} title="Open memory health and conflict resolution">
4536+
</button>
4537+
<button
4538+
type="button"
4539+
className="sys-item sys-item-action"
4540+
onClick={() => changePanel("memory")}
4541+
title="Open memory health and conflict resolution"
4542+
>
45034543
<span className="sys-label">RECALL</span>
45044544
<span className={`sys-value ${latestRecallHitRate >= 85 ? "sys-ok" : ""}`}>{latestRecallHitRate || 0}%</span>
4505-
</div>
4545+
</button>
45064546
</div>
45074547

45084548
{editorSetupSummary.results.length ? (
@@ -5115,7 +5155,17 @@ export function App() {
51155155
) : null}
51165156
<ul className="item-list">
51175157
{memoryResults.length ? memoryResults.map((match, index) => (
5118-
<li key={`${match.source}-${index}`} className="memory-item" onClick={() => !match.expanded && handleMemoryExpand(match.source)}>
5158+
<li
5159+
key={`${match.source}-${index}`}
5160+
className={`memory-item ${match.expanded ? "" : "memory-item-action"}`}
5161+
role={match.expanded ? undefined : "button"}
5162+
tabIndex={match.expanded ? undefined : 0}
5163+
aria-expanded={match.expanded ? undefined : false}
5164+
onClick={() => !match.expanded && handleMemoryExpand(match.source)}
5165+
onKeyDown={(event) =>
5166+
!match.expanded && handleKeyboardActivation(event, () => handleMemoryExpand(match.source))
5167+
}
5168+
>
51195169
<div className="memory-header">
51205170
<span className="memory-method">{match.method}</span>
51215171
<span className="memory-relevance">{(match.relevance * 100).toFixed(0)}%</span>
@@ -5124,7 +5174,7 @@ export function App() {
51245174
{match.expanded && match.excerpt ? (
51255175
<div className="memory-excerpt">{match.excerpt}</div>
51265176
) : null}
5127-
{!match.expanded ? <div className="memory-expand-hint">Click to expand</div> : null}
5177+
{!match.expanded ? <div className="memory-expand-hint">Press Enter or Space to expand</div> : null}
51285178
</li>
51295179
)) : memoryQuery ? <EmptyItem text="No matches -- try different keywords" /> : <EmptyItem text="Search to explore Cortex memories" />}
51305180
</ul>

0 commit comments

Comments
 (0)