Skip to content

Commit 644c9af

Browse files
authored
Agent search: fast recent list with lazy per-card snippet loading (#74)
* Agent search recent open state and footer hints * Agent search global Cmd+O shortcut * Agent search keyboard scroll follow * recent-agents: add limit param to getRecentAgents and GET /agents/recent - agents-db.ts: getRecentAgents(query?, limit?) applies LIMIT in SQL before returning rows - get-recent-agents.ts: parse ?limit= query param, validate positive integer, pass to agentsDB - Tests for both: failing first (TDD), then implementation * recent-agents: pass limit=50 from AgentSearchDialog; add limit param to fetchRecentAgents - agents-api.ts: fetchRecentAgents(workspaceId, query?, limit?) passes ?limit=N in URL - AgentSearchDialog.tsx: call fetchRecentAgents with RECENT_LIMIT=50; remove client-side slice - AgentSearchDialog.test.tsx: update assertions to match new call signature * recent-agents: strip message loading from list endpoint; add /messages/snippet - get-recent-agents.ts: returns DB rows only — no harness calls, no Promise.all - get-agent-snippet.ts: new endpoint GET /agents/:id/messages/snippet, returns lastMessageAt/lastUserMessage/lastAgentMessage for one agent - agents.ts: register snippet route before /:id/messages to avoid Hono match order issue - agents.recent.test.ts: remove message-context describe block, update response shape assertions - get-agent-snippet.test.ts: new TDD tests for snippet endpoint * Agent search: lazy-load recent snippets per visible card * Agent search: hide unloaded recent timestamps * WorkspaceLayout: separate palette and agent-search bindings Prevent the user-configured command palette shortcut from being overwritten when it collides with +o. Add a regression test that verifies both shortcuts stay active when they share the same key combo. * AgentTypeahead: lazy-load @@ snippet previews * Change keyboard shortcuts * Agent search: Right Shift again dismisses the peek * Revert "Agent search: Right Shift again dismisses the peek" This reverts commit 9e66c06. * AgentModal: Right Shift dismisses the peek (mirrors Escape) * Agent search: pointer-move intent gate prevents mouse hijacking keyboard selection * docs: document Corvu mock prop forwarding and render helper side effects * Agent search: suppress hover highlight during keyboard navigation * Agent search: restyle footer hints — label first, key second * Agent search: footer hints — bold action labels, mono shortcut keys * Agent search: reorder footer hints — navigate, open, peek
1 parent aa0e3c7 commit 644c9af

21 files changed

Lines changed: 1565 additions & 593 deletions

projects/birdhouse/docs/code-review/testing.md

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,60 @@ createEffect(() => {
3030

3131
---
3232

33-
## Coming Soon
34-
35-
This guide will cover:
36-
- Component testing patterns
37-
- Testing user interactions
38-
- Snapshot testing
39-
- Mock strategies
40-
41-
**For now, see:**
42-
- `src/components/ui/Button.test.tsx` - Example component test
43-
- `src/workspace-config/components/WorkspaceConfigDialog.test.tsx` - Resource error testing
44-
- Run: `bun run test` or `bun run test:watch`
33+
## Corvu Component Mocks Are Load-Bearing
34+
35+
When a Corvu primitive (Dialog, Popover, Drawer, etc.) is mocked in a test file, the mock's
36+
prop interface becomes part of the test infrastructure. If the real component adds a new event
37+
handler prop — `onKeyUp`, `onFocus`, `onPointerDown`, etc. — and the mock doesn't forward it,
38+
the handler silently disappears. The test won't fail with a clear error; it will just never fire.
39+
40+
**Symptom:** You add a new event handler to a Corvu component in production code. Tests that
41+
exercise that handler pass zero calls to the mock function, even though the handler works in
42+
the browser.
43+
44+
**Cause:** The mock `Dialog.Content` (or equivalent) only forwards the props it was originally
45+
written to accept. New props are ignored unless explicitly added.
46+
47+
**Fix:** Keep the mock's prop type in sync with what the component actually uses. When you add
48+
a handler to `Dialog.Content` in the component, add the same prop to the mock:
49+
50+
```tsx
51+
// In the test file's vi.mock("corvu/dialog", ...) block:
52+
Dialog.Content = (props: {
53+
children: JSX.Element;
54+
class?: string;
55+
onKeyDown?: (e: KeyboardEvent) => void;
56+
onKeyUp?: (e: KeyboardEvent) => void; // ← add when component uses it
57+
}) => (
58+
<div role="presentation" onKeyDown={props.onKeyDown} onKeyUp={props.onKeyUp}>
59+
{props.children}
60+
</div>
61+
);
62+
```
63+
64+
The mock in `AgentSearchDialog.test.tsx` is the canonical reference for this pattern.
65+
66+
---
67+
68+
## Test Helper Side Effects
69+
70+
Test files often define small helpers like `renderDialog()` that set up shared mutable state
71+
before rendering. If that helper resets state you've already configured, tests silently use the
72+
wrong setup.
73+
74+
**Symptom:** You set a shared variable (e.g. `mockModalStack`) before calling a render helper,
75+
but the test behaves as if your assignment never happened.
76+
77+
**Cause:** The render helper resets the variable as a side effect.
78+
79+
**Fix:** Document the side effects on the helper, and in tests that need non-default state,
80+
set the variable *after* calling the helper:
81+
82+
```ts
83+
// renderDialog resets mockModalStack as a side effect — set it after if needed
84+
renderDialog();
85+
mockModalStack = [{ type: "agent-search", id: "main" }, { type: "agent", id: "agent-1" }];
86+
await screen.findByLabelText("Search agent messages");
87+
```
88+
89+
See `AgentSearchDialog.test.tsx` for a working example of both patterns.

projects/birdhouse/frontend/src/components/AgentModal.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ const AgentModal: Component<AgentModalProps> = (props) => {
5050
"max-width": `calc(1792px - ${sizeReduction}px)`,
5151
"z-index": baseZIndex + 2,
5252
}}
53+
onKeyUp={(e: KeyboardEvent) => {
54+
if (e.code === "ShiftRight" && !e.metaKey && !e.ctrlKey && !e.altKey && props.isTop) {
55+
props.onClose();
56+
}
57+
}}
5358
>
5459
{/* Provide increased z-index context to children (dialogs, popovers) */}
5560
<ZIndexProvider baseZIndex={baseZIndex + 10}>

0 commit comments

Comments
 (0)