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
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD
| 2026-07-17 | PR #713 / codex/chat-workflow-ideas-0916 | b52112df6aa36311d7420189064acd79dcf2c3f5 + reviewed follow-up diff | workflow toolkit review follow-up | Fixed all 14 actionable Codex and CodeRabbit threads: cross-platform path fixtures, installation-managed preflight guidance, complete Supabase-backed API database scoping, per-command approval boundaries, plugin-ignore narrowing, isolated CI-scope proof, remote-Git command guarding, repository-skill verification classification, `TypeError` diagnosis, strict CLI option values, machine-parseable JSON evidence output, and preservation of baseline database/clinical approval gates in the RAG lab. No unresolved actionable finding remains in the reviewed scope. | `npm run verify:cheap` passed with 273 files and 2,599 tests; focused toolkit Vitest 20/20; CI-scope self-test; plugin-ignore proof; `git diff --check`. Exact-head hosted CI remains required after the follow-up push. No Supabase, OpenAI, or other live product-provider command was run. |
| 2026-07-17 | PR #699 / codex/test-reliability-hardening | 6202835ab1cf3703af311b9afdf372f74c63e040 | branch-cleanup-superseded | Closed as fully superseded by merged PR #705 (`e5caaa46c`). Range-diff maps the original implementation commit to #705's first integration commit; #705 then adds seven focused reliability fixes, while #699's remaining commit is merge-only and contributes no unique relevant patch. The exact-SHA remote ref and the unregistered local predecessor ref (`b518c1de9`) were deleted after final rechecks. | Fresh GitHub PR/head/status inventory; exact `ls-remote` and local-ref checks; cherry-pick-aware log; range-diff against PR #705's merged head; merge-parent verification; exact leased remote deletion and exact-old-value local `update-ref` deletion. No Supabase/OpenAI/product-provider checks run. |
| 2026-07-17 | PR #716 / codex/pr-process-hardening-20260717 | 220de891f10f82df11eb2e8137367514c139a206 + reviewed implementation/follow-up diff | PR metadata, CI triage, review routing, Actions permissions, secret scanning, and branch-hygiene hardening | No remaining high-confidence P0-P2 defect in the reviewed process diff. Added a trusted base-SHA PR metadata/risk policy with draft and merge-queue handling; corrected CI triage to compare only the same workflow's latest completed `main` run; wired self-tests into local/hosted gates; documented the policy; created the missing durable Codex routing labels; removed four unused per-head routing labels; and applied read-only default Actions tokens, immutable Action pinning, no Actions-authored approvals, push protection, and automatic merged-branch deletion. Four policy defects were fixed before merge: API-only paths no longer trigger UI evidence, slash-bearing outcome titles are accepted, all seven governance items are required exactly, and placeholder risk/rollback text is rejected. | Offline `check:pr-policy`, `check:ci-triage`, `check:ci-scope`, action-pin check, scoped Prettier, and `git diff --check` passed. Initial hosted CI passed static, safety, coverage, build, images, Semgrep, Gitleaks, GitGuardian, and the aggregate gate; exact-head CI is required after the review fix. Broader local gates were deferred because other registered worktrees repeatedly held the heavyweight lock. GitHub provider inspection/settings changes were explicitly authorized. No Supabase/OpenAI/product-provider command ran. |
| 2026-07-17 | codex/mobile-search-phone-refresh-20260717 (supersedes PR #700) | 42a3e3ce65dc5a0e1dce386e0b91fccd23d13d6c + reviewed follow-up diff | phone universal-search command-panel recovery and merge-readiness review | Recovered the still-useful behavior from PR #700 onto current `main`, including its hydration fix and wide-touch regression coverage. Hosted Production UI then exposed one desktop focus race: capability state intentionally initializes false for hydration safety, but an input could receive focus before the post-hydration effect synchronized the real browser state. The follow-up recomputes the same guarded predicate synchronously on focus; it requires the placement breakpoint plus either a fine pointer or a zero-touch desktop fallback, so wide touch devices remain suppressed while desktop keeps the first command-panel interaction. No remaining high-confidence P0-P2 defect was found in the scoped diff. | Focused Vitest 7/7; `npm run ensure` verified the project at `http://localhost:3751`; hosted static, safety, coverage, build, advisory UI, Semgrep, Gitleaks, and GitGuardian passed; the first hosted Production UI run isolated the nine desktop regressions. The focused browser proof reproduced the desktop race while the wide-touch regression passed, and exact-head hosted Production UI remains required after the focus fix. `format:changed -- --check` and `git diff --check` passed before the final follow-up. No Supabase/OpenAI/product-provider command ran. |
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import { cn } from "@/components/ui-primitives";
import { appModeDefinition, type AppModeId } from "@/lib/app-modes";
import { appModeIcons } from "@/lib/app-mode-icons";
import {
commandDropdownCanDisplay,
commandDropdownMinimumWidthMediaQuery,
commandDropdownPointerMediaQuery,
differentialRedFlagTerms,
filteredSuggestions,
isFormCodeQuery,
searchCommandSurfaceConfig,
type CommandSurfacePlacement,
} from "@/lib/search-command-surface";
import type { UniversalSearchDomain } from "@/lib/universal-search";
import { universalSearchModeForDomain } from "@/lib/universal-search-mode-context";
Expand Down Expand Up @@ -146,8 +150,6 @@ function OptionShell({ active, children, hint }: { active: boolean; children: Re
);
}

export type CommandSurfacePlacement = "bottom-dock" | "inline";

function SmartRotatingHint({ examples, modeLabel }: { examples: string[]; modeLabel: string }) {
const [activeExampleIndex, setActiveExampleIndex] = useState(0);
const activeExample = examples[activeExampleIndex % examples.length];
Expand Down Expand Up @@ -220,7 +222,7 @@ function CommandDropdown({
// template, so without it the section headings inherit text-center.
"universal-command-dropdown absolute left-0 right-0 z-30 overflow-hidden rounded-2xl border border-[color:var(--border-strong)] bg-[color:var(--surface)] text-left shadow-[var(--shadow-elevated)]",
opensUpward ? "bottom-[calc(100%+0.5rem)] top-auto" : "top-[calc(100%+0.5rem)]",
"block max-sm:rounded-xl",
placement === "bottom-dock" ? "hidden sm:block" : "hidden lg:block",
)}
role="presentation"
>
Expand Down Expand Up @@ -380,11 +382,39 @@ export function UniversalSearchCommandSurface({
const [activeIndex, setActiveIndex] = useState(-1);
const trimmedQuery = query.trim();
const mode = appModeDefinition(modeId);
// The dropdown is a fine-pointer desktop enhancement. Width-only checks let
// wide, zoomed, or desktop-mode phones open it over the page.
const dropdownMinimumWidthQuery = commandDropdownMinimumWidthMediaQuery(placement);
const [dropdownDisplayable, setDropdownDisplayable] = useState(false);
useEffect(() => {
if (typeof window.matchMedia !== "function") return;
const minimumWidthMedia = window.matchMedia(dropdownMinimumWidthQuery);
const pointerMedia = window.matchMedia(commandDropdownPointerMediaQuery);
const sync = () => {
const displayable = commandDropdownCanDisplay({
minimumWidthMatches: minimumWidthMedia.matches,
pointerMatches: pointerMedia.matches,
maxTouchPoints: navigator.maxTouchPoints,
});
setDropdownDisplayable(displayable);
if (!displayable) {
onDropdownOpenChange(false);
setActiveIndex(-1);
}
};
sync();
minimumWidthMedia.addEventListener("change", sync);
pointerMedia.addEventListener("change", sync);
return () => {
minimumWidthMedia.removeEventListener("change", sync);
pointerMedia.removeEventListener("change", sync);
};
}, [dropdownMinimumWidthQuery, onDropdownOpenChange]);
// A true "everything" view: the active mode's own domain is included (no excludeDomain) so
// the palette surfaces every entity type, ordered by the server's intent-aware domainOrder.
const universal = useUniversalSearch({
query: trimmedQuery,
enabled: dropdownOpen && Boolean(config),
enabled: dropdownOpen && dropdownDisplayable && Boolean(config),
contextMode: modeId,
});
const savedRegistryFavourites = useSavedRegistryFavourites();
Expand Down Expand Up @@ -850,6 +880,14 @@ export function UniversalSearchCommandSurface({
const activeItemId = activeIndex >= 0 && activeIndex < flatItems.length ? flatItems[activeIndex].id : null;

function handleComposerKeyDown(event: ReactKeyboardEvent<HTMLInputElement>) {
if (!dropdownDisplayable) {
if (event.key === "Escape") {
onDropdownOpenChange(false);
setActiveIndex(-1);
}
onInputKeyDown?.(event);
return;
}
if (event.key === "ArrowDown") {
event.preventDefault();
onDropdownOpenChange(true);
Expand Down Expand Up @@ -946,7 +984,17 @@ export function UniversalSearchCommandSurface({
}
}}
onFocusCapture={() => {
onDropdownOpenChange(true);
// Focus can arrive before the post-hydration effect has synchronized
// the conservative false initial state. Re-evaluate synchronously so
// desktop input never loses its first command-panel interaction.
if (typeof window.matchMedia !== "function") return;
const displayable = commandDropdownCanDisplay({
minimumWidthMatches: window.matchMedia(dropdownMinimumWidthQuery).matches,
pointerMatches: window.matchMedia(commandDropdownPointerMediaQuery).matches,
maxTouchPoints: navigator.maxTouchPoints,
});
setDropdownDisplayable(displayable);
if (displayable) onDropdownOpenChange(true);
}}
onBlurCapture={(event) => {
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
Expand All @@ -956,7 +1004,7 @@ export function UniversalSearchCommandSurface({
}}
>
{children}
{dropdownOpen ? (
{dropdownOpen && dropdownDisplayable ? (
<CommandDropdown
modeId={modeId}
query={trimmedQuery}
Expand All @@ -978,7 +1026,7 @@ export function UniversalSearchCommandSurface({
examples={config.examples}
onPickExample={(example) => {
onQueryChange(example);
onDropdownOpenChange(true);
if (dropdownDisplayable) onDropdownOpenChange(true);
onFocusSearchInput?.();
}}
/>
Expand Down
28 changes: 28 additions & 0 deletions src/lib/search-command-surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ export type SearchCommandSurfaceConfig = {
crossModes: AppModeId[];
};

export type CommandSurfacePlacement = "bottom-dock" | "inline";

export function commandDropdownMinimumWidthMediaQuery(placement: CommandSurfacePlacement) {
const minimumWidth = placement === "bottom-dock" ? "640px" : "1024px";
return `(min-width: ${minimumWidth})`;
}

export const commandDropdownPointerMediaQuery = "(hover: hover) and (pointer: fine)";

/**
* The command panel is a desktop enhancement. Width alone is not enough to
* identify that environment: phones can report a wide viewport in landscape,
* display-zoom, or desktop-site modes. A fine pointer enables the panel on
* hybrid desktops; a zero-touch fallback keeps headless/remote desktops usable
* when the browser reports no pointer hardware at all.
*/
export function commandDropdownCanDisplay({
minimumWidthMatches,
pointerMatches,
maxTouchPoints,
}: {
minimumWidthMatches: boolean;
pointerMatches: boolean;
maxTouchPoints: number;
}) {
return minimumWidthMatches && (pointerMatches || maxTouchPoints === 0);
}

const searchCommandSurfaceByMode: Partial<Record<AppModeId, SearchCommandSurfaceConfig>> = {
documents: {
examples: ["clozapine ANC thresholds", "lithium monitoring table", "QT prolongation quote"],
Expand Down
22 changes: 22 additions & 0 deletions tests/search-command-surface.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest";

import {
commandDropdownCanDisplay,
commandDropdownMinimumWidthMediaQuery,
commandDropdownPointerMediaQuery,
differentialRedFlagTerms,
favouriteMatchesCommandScopes,
filteredSuggestions,
Expand Down Expand Up @@ -28,6 +31,25 @@ function serviceRecord(overrides: Partial<ServiceRecord> = {}): ServiceRecord {
}

describe("search command surface", () => {
it("requires a desktop-sized non-touch or fine-pointer environment for the command dropdown", () => {
expect(commandDropdownMinimumWidthMediaQuery("bottom-dock")).toBe("(min-width: 640px)");
expect(commandDropdownMinimumWidthMediaQuery("inline")).toBe("(min-width: 1024px)");
expect(commandDropdownPointerMediaQuery).toBe("(hover: hover) and (pointer: fine)");

expect(commandDropdownCanDisplay({ minimumWidthMatches: true, pointerMatches: true, maxTouchPoints: 5 })).toBe(
true,
);
expect(commandDropdownCanDisplay({ minimumWidthMatches: true, pointerMatches: false, maxTouchPoints: 0 })).toBe(
true,
);
expect(commandDropdownCanDisplay({ minimumWidthMatches: true, pointerMatches: false, maxTouchPoints: 5 })).toBe(
false,
);
expect(commandDropdownCanDisplay({ minimumWidthMatches: false, pointerMatches: true, maxTouchPoints: 0 })).toBe(
false,
);
});

it("returns mode-specific command surface config", () => {
const documents = searchCommandSurfaceConfig("documents");
expect(documents?.examples.length).toBeGreaterThan(0);
Expand Down
19 changes: 8 additions & 11 deletions tests/ui-tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,23 +693,20 @@ test.describe("Clinical KB tools launcher", () => {
});
}

test("phone bottom-dock search opens the bounded command results sheet", async ({ page }) => {
test("phone bottom-dock search keeps the command results sheet hidden", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 820 });
await gotoLauncher(page, "/services?q=13YARN&focus=1&run=1");
await expect(page.getByRole("button", { name: "Mode Services" })).toBeVisible();
const input = visibleGlobalSearchInput(page).first();
await expect(input).toBeVisible();

// Phone searches use the same accessible listbox as larger viewports, bounded
// above the bottom dock so results stay reachable without horizontal overflow.
// Phones keep the full search results in the page instead of opening a
// command sheet over the small viewport.
await input.click();
await input.press("ArrowDown");
await expect(page.locator(".universal-command-dropdown:visible")).toHaveCount(1);
await expect(page.getByRole("listbox", { name: "Services search suggestions" })).toBeVisible();
await expectNoPageHorizontalOverflow(page);

await page.evaluate(() => window.dispatchEvent(new Event("scroll")));
await expect(page.locator(".universal-command-dropdown:visible")).toHaveCount(0);
await expect(page.getByRole("listbox", { name: "Services search suggestions" })).toHaveCount(0);
await expectNoPageHorizontalOverflow(page);
});

test("phone mode homes keep the shared search in the hero, not the bottom dock", async ({ page }) => {
Expand All @@ -728,11 +725,11 @@ test.describe("Clinical KB tools launcher", () => {
expect(geometry.top).toBeGreaterThan(0);
expect(geometry.bottom).toBeLessThan(geometry.viewportHeight - 40);

// Hero composers expose the same bounded universal results sheet on phones.
// Phone hero composers must not cover the page with the universal sheet.
await heroInput.click();
await heroInput.press("ArrowDown");
await expect(page.locator(".universal-command-dropdown:visible")).toHaveCount(1);
await expect(page.getByRole("listbox")).toBeVisible();
await expect(page.locator(".universal-command-dropdown:visible")).toHaveCount(0);
await expect(page.getByRole("listbox")).toHaveCount(0);
await expectNoPageHorizontalOverflow(page);
}
});
Expand Down
27 changes: 19 additions & 8 deletions tests/ui-universal-search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,25 @@ test.describe("universal search typeahead", () => {
await expect(page.getByText("Saved").first()).toBeVisible();
});

test("shows cross-mode typeahead on a phone viewport", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await mockUniversalSearch(page);
const input = await openComposer(page);
await input.fill("acamprosate");

await expect(page.getByText(/Current mode · Documents · 1/)).toBeVisible();
await expect(page.getByText("Also in Medication · Medications · 1")).toBeVisible();
test("keeps cross-mode typeahead hidden on a landscape touch phone", async ({ browser, baseURL }) => {
const context = await browser.newContext({
...(baseURL ? { baseURL } : {}),
hasTouch: true,
viewport: { width: 844, height: 390 },
});
const page = await context.newPage();

try {
await mockUniversalSearch(page);
const input = await openComposer(page);
await input.fill("acamprosate");

await expect(page.locator(".universal-command-dropdown:visible")).toHaveCount(0);
await expect(page.getByText(/Current mode · Documents · 1/)).toHaveCount(0);
await expect(page.getByText("Also in Medication · Medications · 1")).toHaveCount(0);
} finally {
await context.close();
}
});

test("keeps compact cross-mode matches visible after submission", async ({ page }) => {
Expand Down