refactor: replace store-based keyboard action requests with direct dispatcher and simplify agent attention clearing

This commit is contained in:
Mohamed Boudra
2026-03-06 14:49:06 +07:00
parent 885b575d14
commit 5b270444d5
25 changed files with 617 additions and 1355 deletions
@@ -0,0 +1,152 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
createKeyboardActionDispatcher,
type KeyboardActionDefinition,
} from "./keyboard-action-dispatcher";
describe("keyboard-action-dispatcher", () => {
let dispatcher: ReturnType<typeof createKeyboardActionDispatcher>;
beforeEach(() => {
dispatcher = createKeyboardActionDispatcher();
});
it("dispatches to the highest-priority active handler", () => {
const calls: string[] = [];
const action: KeyboardActionDefinition = {
id: "message-input.dictation-toggle",
scope: "message-input",
};
dispatcher.registerHandler({
handlerId: "low",
actions: [action.id],
enabled: true,
priority: 100,
isActive: () => true,
handle: () => {
calls.push("low");
return true;
},
});
dispatcher.registerHandler({
handlerId: "high",
actions: [action.id],
enabled: true,
priority: 200,
isActive: () => true,
handle: () => {
calls.push("high");
return true;
},
});
const handled = dispatcher.dispatch(action);
expect(handled).toBe(true);
expect(calls).toEqual(["high"]);
});
it("skips disabled and inactive handlers", () => {
const handle = vi.fn(() => true);
const action: KeyboardActionDefinition = {
id: "message-input.dictation-toggle",
scope: "message-input",
};
dispatcher.registerHandler({
handlerId: "disabled",
actions: [action.id],
enabled: false,
priority: 300,
isActive: () => true,
handle,
});
dispatcher.registerHandler({
handlerId: "inactive",
actions: [action.id],
enabled: true,
priority: 200,
isActive: () => false,
handle,
});
dispatcher.registerHandler({
handlerId: "active",
actions: [action.id],
enabled: true,
priority: 100,
isActive: () => true,
handle,
});
const handled = dispatcher.dispatch(action);
expect(handled).toBe(true);
expect(handle).toHaveBeenCalledTimes(1);
});
it("tries lower-priority handlers when a higher one does not consume the action", () => {
const calls: string[] = [];
const action: KeyboardActionDefinition = {
id: "message-input.focus",
scope: "message-input",
};
dispatcher.registerHandler({
handlerId: "first",
actions: [action.id],
enabled: true,
priority: 200,
isActive: () => true,
handle: () => {
calls.push("first");
return false;
},
});
dispatcher.registerHandler({
handlerId: "second",
actions: [action.id],
enabled: true,
priority: 100,
isActive: () => true,
handle: () => {
calls.push("second");
return true;
},
});
const handled = dispatcher.dispatch(action);
expect(handled).toBe(true);
expect(calls).toEqual(["first", "second"]);
});
it("does not dispatch after a handler is unregistered", () => {
const handle = vi.fn(() => true);
const action: KeyboardActionDefinition = {
id: "message-input.dictation-cancel",
scope: "message-input",
};
const unregister = dispatcher.registerHandler({
handlerId: "handler",
actions: [action.id],
enabled: true,
priority: 100,
isActive: () => true,
handle,
});
unregister();
const handled = dispatcher.dispatch(action);
expect(handled).toBe(false);
expect(handle).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,75 @@
export type KeyboardActionScope =
| "global"
| "message-input"
| "sidebar"
| "workspace";
export type KeyboardActionId =
| "message-input.focus"
| "message-input.dictation-toggle"
| "message-input.dictation-cancel"
| "message-input.voice-toggle"
| "message-input.voice-mute-toggle";
export type KeyboardActionDefinition = {
id: KeyboardActionId;
scope: KeyboardActionScope;
};
export type KeyboardActionHandler = {
handlerId: string;
actions: readonly KeyboardActionId[];
enabled: boolean;
priority: number;
isActive?: () => boolean;
handle: (action: KeyboardActionDefinition) => boolean;
};
type KeyboardActionRegistryEntry = KeyboardActionHandler & {
registeredAt: number;
};
export function createKeyboardActionDispatcher() {
let nextRegistrationOrder = 1;
const handlers = new Map<string, KeyboardActionRegistryEntry>();
return {
registerHandler(handler: KeyboardActionHandler) {
handlers.set(handler.handlerId, {
...handler,
registeredAt: nextRegistrationOrder++,
});
return () => {
const current = handlers.get(handler.handlerId);
if (!current) {
return;
}
handlers.delete(handler.handlerId);
};
},
dispatch(action: KeyboardActionDefinition): boolean {
const candidates = Array.from(handlers.values())
.filter((handler) => handler.actions.includes(action.id))
.filter((handler) => handler.enabled)
.filter((handler) => (handler.isActive ? handler.isActive() : true))
.sort((left, right) => {
if (left.priority !== right.priority) {
return right.priority - left.priority;
}
return right.registeredAt - left.registeredAt;
});
for (const handler of candidates) {
if (handler.handle(action)) {
return true;
}
}
return false;
},
};
}
export const keyboardActionDispatcher = createKeyboardActionDispatcher();
@@ -1,30 +1,8 @@
import { describe, expect, it } from "vitest";
import {
canToggleFileExplorerShortcut,
resolveSelectedOrRouteAgentKey,
} from "./keyboard-shortcut-routing";
import { canToggleFileExplorerShortcut } from "./keyboard-shortcut-routing";
describe("keyboard-shortcut-routing", () => {
describe("resolveSelectedOrRouteAgentKey", () => {
it("returns selected agent key when provided", () => {
const key = resolveSelectedOrRouteAgentKey({
selectedAgentId: "server-1:agent-1",
pathname: "/h/server-1/workspace/workspace-1?open=draft%3Adraft_123",
});
expect(key).toBe("server-1:agent-1");
});
it("maps workspace draft tabs to the draft tab id key", () => {
const key = resolveSelectedOrRouteAgentKey({
pathname: "/h/server-1/workspace/workspace-1?open=draft%3Adraft_123",
});
expect(key).toBe("server-1:draft_123");
});
});
describe("canToggleFileExplorerShortcut", () => {
const toggleFileExplorer = () => undefined;
@@ -1,37 +1,8 @@
import {
parseHostAgentRouteFromPathname,
parseHostWorkspaceOpenIntentFromPathname,
parseHostWorkspaceRouteFromPathname,
} from "@/utils/host-routes";
export function resolveSelectedOrRouteAgentKey(input: {
selectedAgentId?: string;
pathname: string;
}): string | null {
if (input.selectedAgentId) {
return input.selectedAgentId;
}
const workspaceRoute = parseHostWorkspaceRouteFromPathname(input.pathname);
const workspaceOpenIntent = parseHostWorkspaceOpenIntentFromPathname(input.pathname);
if (workspaceRoute && workspaceOpenIntent) {
if (workspaceOpenIntent.kind === "agent") {
const agentId = workspaceOpenIntent.agentId.trim();
return agentId ? `${workspaceRoute.serverId}:${agentId}` : null;
}
if (workspaceOpenIntent.kind === "draft") {
const draftId = workspaceOpenIntent.draftId.trim();
return draftId ? `${workspaceRoute.serverId}:${draftId}` : null;
}
}
const route = parseHostAgentRouteFromPathname(input.pathname);
if (!route) {
return null;
}
return `${route.serverId}:${route.agentId}`;
}
export function canToggleFileExplorerShortcut(input: {
selectedAgentId?: string;
pathname: string;