diff --git a/tests/runner-e2e/chat-catalog-descriptors.json b/tests/runner-e2e/chat-catalog-descriptors.json
index 81bf0a8d5f2..b4a90ddc409 100644
--- a/tests/runner-e2e/chat-catalog-descriptors.json
+++ b/tests/runner-e2e/chat-catalog-descriptors.json
@@ -3642,7 +3642,7 @@
"chat-catalog-domains/internal-support.ts",
"chat-scenarios/navigation-preferences.yaml"
],
- "oracleVersion": "paperclip.chat-catalog.identity.v1",
+ "oracleVersion": "paperclip.chat-catalog.identity.quick-search.v2",
"expectedRunCount": 1,
"turnTimeoutMs": 120000,
"attemptTimeoutMs": 300000,
diff --git a/tests/runner-e2e/chat-catalog-domains/identity-navigation-preferences.ts b/tests/runner-e2e/chat-catalog-domains/identity-navigation-preferences.ts
index 4a544de6842..cb9282c4fa9 100644
--- a/tests/runner-e2e/chat-catalog-domains/identity-navigation-preferences.ts
+++ b/tests/runner-e2e/chat-catalog-domains/identity-navigation-preferences.ts
@@ -4,6 +4,19 @@ import { checks, entity, hasLink, project, task, verifyInternalResult } from "./
import { identityDefinition, identityObservation, waitForReply, probeAgentPermission } from "./identity-support.js";
import { createAuthenticatedPerson, enrollPrerequisitePerson } from "./identity-auth.js";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
+// The palette shortcut is a fixed product fact the person needs to act on, so it is graded as a literal key combination:
+// a modifier immediately followed by K, allowing the usual spellings (Cmd+K, ⌘K, Ctrl-K, `Ctrl` + `K`, ⌘K).
+const paletteShortcut = /(?:⌘|\b(?:cmd|command|ctrl|control|mod)\b)(?:[\s`*+\-–/]|<\/?kbd>)*k\b/i;
+async function openQuickSearch(page: ChatCatalogContext["input"]["page"]) {
+ const input = page.getByPlaceholder("Search tasks, agents, projects...");
+ // A freshly loaded page fires keydown before the app mounts the palette listener; wait for the shell and retry the shortcut.
+ await expect(page.getByRole("button", { name: "Global search" }).first()).toBeVisible({ timeout: 30_000 });
+ await expect(async () => {
+ if (!await input.isVisible()) await page.keyboard.press("ControlOrMeta+k");
+ await expect(input).toBeVisible({ timeout: 2_000 });
+ }).toPass({ timeout: 30_000 });
+ return input;
+}
const searchObservers = new WeakMap>>();
export function gradeNavigation(value: unknown) {
@@ -21,7 +34,7 @@ export function gradeNavigation(value: unknown) {
c.add("personal-preferences-not-agent-mutated", o.state.agentPreferenceReadStatus === 403 && o.state.preferencesBefore !== undefined
&& JSON.stringify(o.state.preferencesBefore) === JSON.stringify(o.state.preferencesAfterReply), "The actual agent cannot impersonate the person's preferences, and the response does not change saved order or browser favorites/recents.");
} else if (o.scenarioId === "navigation-preferences.quick-search") {
- c.add("documented-palette-entry", /(?:command|cmd|ctrl|control|⌘).*k|quick\s+search|command\s+palette/i.test(o.reply), "The answer identifies the actual quick-search entry point.");
+ c.add("documented-palette-entry", paletteShortcut.test(o.reply), "The answer gives the actual quick-search shortcut (Cmd/Ctrl+K) the person can press without leaving their page.");
c.add("browser-search-transition", o.state.paletteTaskVisible === true && o.state.searchTaskVisible === true
&& o.state.searchQuery === "Launch" && o.state.searchStatus === "backlog", "The human opens the palette, finds the fixture task, and carries its query and chosen status filter into full search.");
c.add("independent-search-audience", o.state.observerPublicVisible === true && o.state.observerPrivatePaletteHidden === true && o.state.observerPrivateSearchHidden === true,
@@ -71,12 +84,11 @@ export const navigationIdentityCases = [
return { ...state, privateTask, observerId: observer.id };
} catch (error) { await observer.dispose(); searchObservers.delete(ctx); throw error; }
}, gradeNavigation, {
- authentication: "human-session",
+ authentication: "human-session", oracleVersion: "paperclip.chat-catalog.identity.quick-search.v2",
sourceFiles: ["chat-catalog-domains/identity-navigation-preferences.ts", "chat-catalog-domains/identity-auth.ts", "chat-catalog-domains/identity-private-browser.ts", "chat-catalog-domains/identity-private-browser-worker.ts", "chat-catalog-domains/identity-wizard.ts", "chat-catalog-domains/identity-support.ts", "chat-catalog-domains/internal-support.ts", "chat-scenarios/navigation-preferences.yaml"],
async act(ctx, state) {
await waitForReply(ctx, chatCatalogOriginalPhaseDeadline(ctx)); const source = ctx.input.page.url();
- await ctx.input.page.keyboard.press("ControlOrMeta+k");
- await ctx.input.page.getByPlaceholder("Search tasks, agents, projects...").fill("Launch status:backlog");
+ await (await openQuickSearch(ctx.input.page)).fill("Launch status:backlog");
await expect(ctx.input.page.getByRole("dialog").getByText(entity(state, "task").title!, { exact: true })).toBeVisible();
state.paletteTaskVisible = true;
await ctx.input.page.getByTestId("command-search-all").click();
@@ -87,8 +99,7 @@ export const navigationIdentityCases = [
const page = await observer.context.newPage();
try {
await page.goto(`/${ctx.input.fixtures.company.issuePrefix}/projects/${entity(state, "project").id}`, { waitUntil: "domcontentloaded" });
- await page.keyboard.press("ControlOrMeta+k");
- await page.getByPlaceholder("Search tasks, agents, projects...").fill("Launch status:backlog");
+ await (await openQuickSearch(page)).fill("Launch status:backlog");
const palette = page.getByRole("dialog");
await expect(palette.getByText(entity(state, "task").title!, { exact: true })).toBeVisible();
await expect(palette.getByText(entity(state, "privateTask").title!, { exact: true })).toHaveCount(0); state.observerPrivatePaletteHidden = true;
diff --git a/tests/runner-e2e/chat-catalog-domains/identity.test.ts b/tests/runner-e2e/chat-catalog-domains/identity.test.ts
index 10fe4166583..54653ade43a 100644
--- a/tests/runner-e2e/chat-catalog-domains/identity.test.ts
+++ b/tests/runner-e2e/chat-catalog-domains/identity.test.ts
@@ -123,6 +123,17 @@ describe("identity catalog independent oracle calibration", () => {
const observation = valid("navigation-preferences.quick-search"); observation.state.observerPrivatePaletteHidden = false;
expect(gradeNavigation(observation).passed).toBe(false);
});
+ it("quick search accepts any spelling of the palette shortcut and rejects answers without it", () => {
+ const observation = valid("navigation-preferences.quick-search");
+ for (const reply of ["Press **⌘K** (or Ctrl+K on Windows) and type Launch.", "Hit `Ctrl` + `K` anywhere to open search.", "Use ⌘K, then pick Search all.",
+ "Open the command palette with Cmd/Ctrl-K and type \"Launch\".", "Command-K opens quick search from any page."]) {
+ observation.reply = reply; expect(gradeNavigation(observation).passed, reply).toBe(true);
+ }
+ for (const reply of ["Keep it in chat: just ask me to show Launch tasks and I'll list them here.", "Use the command palette to find Launch tasks.",
+ "Use the task list command to filter by the Launch project.", "Press Ctrl+Shift+F to search."]) {
+ observation.reply = reply; expect(gradeNavigation(observation).passed, reply).toBe(false);
+ }
+ });
it("requires native provider question identity and answer-bearing creation provenance", () => {
const o = valid("questions-and-approvals.necessary-question"); const original = o.state.original as Interaction;
o.runs = [o.runs[0]!]; original.payload.runtimeRequestId = "request-7"; original.idempotencyKey = "paperclip-runner-question:run:request-7";