Skip to content

feat(agents): any page type as a pane in agent sessions - #2298

Merged
2witstudios merged 7 commits into
masterfrom
pu/pages-as-panes
Aug 1, 2026
Merged

feat(agents): any page type as a pane in agent sessions#2298
2witstudios merged 7 commits into
masterfrom
pu/pages-as-panes

Conversation

@2witstudios

@2witstudios 2witstudios commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a 'page' pane kind alongside chat/terminal so any non-container, non-chat page type (document, task list, sheet, canvas, code, channel, file) can be opened and edited in an agent session's pane grid.
  • PagePaneView dispatches through the same page-type config CenterPanel uses, lazily loading each view via next/dynamic (keeps pdfjs-dist and other heavy editors out of every pane bundle).
  • PanePicker gets a debounced "Pages" search section (backed by /api/mentions/search, now with a server-side excludePageTypes param so FOLDER/AI_CHAT never occupy a slot in its capped result window); AgentPanes/useAgentWorkspaceStore gain the wiring (handlePickPage, openPage, sharing openConversation's focus-or-replace-or-split policy) — extended with a dirty-page guard (never silently replaces a pane with unsaved edits) and an excludeTargetId guard (never evicts the conversation that itself asked to open a page).
  • Fixes a real editing-store key collision: DocumentView/CodePageView/CanvasPageView/SheetView/TaskListView now suffix their useEditingStore registration with useId(), so the same page open in the main center panel and a pane can no longer clobber each other's dirty-state entry.
  • New open_page_pane tool lets an agent surface a page pane itself — its execute() always acks (the server has no visibility into any browser tab's pane-grid state, which is purely client-local), and a new useOpenPagePane hook reacts to the completed result by opening/focusing the pane in the conversation's own session, with guards against replaying historical calls on remount, on a pane-bar conversation switch, and against evicting the invoking conversation. Registered as an ordinary workspace tool (77th), with a rich tool-call card (OpenPagePaneRenderer) that describes the action taken ("Requested to open…"), not a claimed outcome.
  • CodePageView now accepts an optional driveId (falls back to the route param, so CenterPanel's existing call site is unaffected) and joins its page's own drive room via usePageContentSocket, matching DocumentView — needed because a pane can host a code page outside its own drive's route entirely.

Follow-up commits (post-review, in order)

  • 3d4eee4c3 — CI fixes (a knip dead-code finding, a stale PANE_KINDS exhaustiveness test) + every first-round Codex/CodeRabbit finding: the invoking-chat eviction bug, the tool-call history-replay bug, a wrong driveId passed to DocumentView, a missing dirty-pane guard, missing ReactNode/ComponentType imports, an isLoading/isValidating SWR bug in the picker.
  • 201e1b489 — a second, self-found history-replay instance: switching a pane's bound conversation in place (no remount) could replay a different conversation's own history.
  • 881ef0847 — URL-encoded the agent-suppliable pageId in PagePaneView's fetch.
  • 4361529e4 — merged master after sibling PR feat(agents): pane-level Chat/History/Settings tabs (+ history-deleted-send fix) #2299 ("pane-level Chat/History/Settings tabs") pulled chat rendering into a new ChatPane component; resolved the one conflict and threaded sessionId into ChatPane's own PaneChat call.
  • 07af1c482 — a second @codex review pass found three more real issues, all fixed: CodePageView's missing driveId/socket-room join, OpenPagePaneRenderer overclaiming "Opened" when the tool's ack can't actually know that, and the mentions-search truncation gap in PanePicker's search.
  • 99085867a — CodeRabbit follow-up: pushed the excludePageTypes fix into the actual SQL WHERE clause (my prior commit only fixed it before the final .slice(0,10), not before the earlier DB-level .limit(50/20) fetch).

Test plan

  • bun run typecheck / bun run lint / bun run knip:check (web, @pagespace/lib) — clean throughout
  • Full @pagespace/lib suite (8682 tests) and the full touched web surface (panes/store/hooks/tool-registry/API routes/mentions-search/code view — 198+ tests in the final sweep alone, AgentPanes.test.tsx's 65 post-merge) — all green
  • Doc-count guard (tool-registry-docs.test.ts) — updated README.md and marketing docs from 76 → 77 workspace tools
  • Branch merged with master (post-feat(agents): pane-level Chat/History/Settings tabs (+ history-deleted-send fix) #2299) — MERGEABLE, CLEAN, all 10 CI checks green as of 99085867a
  • Two independent review passes (CodeRabbit + @codex review) both re-verified and withdrew/resolved every finding they raised
  • Manual: open an agent session, split a pane, search and open a document/task-list/sheet via the new "Pages" section; ask an agent to call open_page_pane after editing a page and confirm the pane opens without manual interaction

🤖 Generated with Claude Code

https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD

Summary by CodeRabbit

  • New Features
    • AI agents can now open supported pages directly in workspace panes.
    • Pane selection supports searching for and opening pages alongside chats and terminals.
    • Existing pages are focused when already open, while new pages open in available panes.
  • Bug Fixes
    • Improved editing reliability when the same page is open in multiple panes.
    • Prevented duplicate page openings from repeated AI tool results.
  • Documentation
    • Updated the documented workspace tool count from 76 to 77.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds page panes to agent workspaces. It defines the open_page_pane tool, opens pages from tool results, adds page search and rendering, supports page pane bindings, and isolates editing state across concurrent page-view instances.

Changes

Page pane flow

Layer / File(s) Summary
Page-pane contracts and tool registration
packages/lib/src/agent-sessions/contract.ts, packages/lib/src/content/page-types.config.ts, apps/web/src/lib/ai/tools/*, apps/web/src/lib/ai/core/*, README.md, apps/marketing/src/app/docs/getting-started/page.tsx
Pane bindings now support pages. The open_page_pane tool validates page identifiers and joins the workspace tool registry. Tests and documentation use the count of 77 tools.
Tool-result page opening
apps/web/src/lib/ai/shared/hooks/*, apps/web/src/stores/agent-workspace/*, apps/web/src/components/agents/chat/*, apps/web/src/components/agents/AgentPageView.tsx, apps/web/src/components/agents/panes/PaneChat.tsx, apps/web/src/components/ai/shared/chat/tool-calls/*
Successful tool results open or focus page scopes in session workspaces. Chat components pass session identifiers, and the tool renderer displays the page-opening result.
Page selection and rendering
apps/web/src/components/agents/panes/*, apps/web/src/app/api/mentions/search/*
The pane picker searches drive-scoped or cross-drive pages and filters unsupported types. Agent panes assign selected pages and render fetched page views through PagePaneView.
Per-mount editing isolation
apps/web/src/components/layout/middle-content/page-views/{canvas,code,document,sheet,task-list}/*
Page-view editing keys now include mount-specific React identifiers. Code views also subscribe through the effective page drive.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AI as AI assistant
  participant Tool as open_page_pane
  participant Chat as SessionChatView
  participant Store as Agent workspace store
  participant Pane as PagePaneView
  AI->>Tool: call with pageId and title
  Tool-->>Chat: return pageId and title
  Chat->>Store: openPage(sessionId, page scope)
  Store->>Pane: assign or focus page pane
  Pane->>Pane: fetch and render page
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for page types as panes in agent sessions.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/pages-as-panes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d306e622a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +158 to +160
const isReplaceable = (pane: PaneState) =>
pane.scope === null || (pane.scope.kind !== 'terminal' && pane.scope.targetId !== null);
const replaceable = active && isReplaceable(active) ? active : panes.find(isReplaceable);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Split instead of replacing the invoking chat

When open_page_pane completes in the normal single-chat workspace, that chat is the active pane and satisfies this isReplaceable predicate, so openPage assigns the page over the conversation rather than opening it beside the conversation. Because a mounted invoking chat guarantees at least one replaceable non-terminal pane, the tool generally evicts a chat or another page and reaches the split branch only in impossible/all-terminal circumstances; the tool-triggered path needs to preserve its source chat and split unless an explicitly empty picker can be reused.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — openPage now takes an optional excludeTargetId; useOpenPagePane passes the invoking conversation's own id so its own pane is never one of the replaceable candidates. Threaded conversationId through SessionChatView/SessionChat/AssistantSessionChat to make that possible. Added a store-level test ("never replaces the pane the caller excluded") and a hook-level test ("never evicts the invoking chat pane — splits beside it instead"). See 3d4eee4.

Comment on lines +45 to +47
if (toolPart.state !== 'output-available' || !toolPart.output?.opened) continue;
if (handledRef.current.has(toolPart.toolCallId)) continue;
handledRef.current.add(toolPart.toolCallId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not replay historical pane-opening calls

When a conversation is reopened or the chat component remounts and its last persisted assistant message contains a completed open_page_pane call, handledRef starts empty and this effect treats that historical result as newly streamed. The old call therefore mutates the restored grid again on every mount, potentially focusing or replacing the user's current panes; initialize the seen set from loaded history or react only to a transition into output-available.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — good catch. useOpenPagePane now seeds a "first sighting" flag: whatever's already output-available on the very first render after mount is marked handled WITHOUT being acted on (that's history, not a new event); only a call that transitions to output-available on a later render is treated as new and opens/focuses the pane. Added a dedicated test ("does NOT act on a completed call already present on the FIRST render (history replay guard)") plus one confirming a genuinely new call on a later render still fires. See 3d4eee4.

let pageComponent: React.ReactNode;
if (componentName === 'DocumentView') {
const DocumentView = componentMap.DocumentView;
pageComponent = <DocumentView key={`document-${page.id}`} pageId={page.id} driveId={driveId ?? undefined} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Subscribe document panes to the page's drive

For a global session, the picker intentionally searches across drives while this driveId is null, so every document pane passes undefined here even though the fetched page includes its actual page.driveId. DocumentView consequently disables usePageContentSocket; a drive-bound session opening an accessible cross-drive page similarly joins the wrong drive room, causing collaborator or AI content updates to be missed until a manual refresh. Pass the fetched page's drive ID instead.

AGENTS.md reference: AGENTS.md:L12-L12

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed — this was a real bug, not just theoretical: I was passing the SESSION's driveId (null for a global-assistant session) instead of the fetched PAGE's own page.driveId, which CenterPanel.tsx always uses for this exact prop. Switched to page.driveId and dropped the now-unused driveId prop from PagePaneView entirely (nothing else in the component needed it). Added a test asserting DocumentView receives the page's own driveId regardless of the session's. See 3d4eee4.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx (1)

60-63: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use a globally unique client-only identifier for editing-store keys.

These components use React useId() values as suffixes for keys in the module-level useEditingStore Map. Verify that all page views share one React root. If separate roots are possible, use crypto.randomUUID() so cleanup from one view cannot remove another view’s editing session.

  • apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx#L60-L63: Replace useId() with a per-mount client-only identifier, or verify the single-root invariant.
  • apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx#L74-L77: Replace useId() with a per-mount client-only identifier, or verify the single-root invariant.
  • apps/web/src/components/layout/middle-content/page-views/document/DocumentView.tsx#L39-L44: Replace useId() with a per-mount client-only identifier, or verify the single-root invariant.
  • apps/web/src/components/layout/middle-content/page-views/sheet/SheetView.tsx#L205-L210: Replace useId() with a per-mount client-only identifier, or verify the single-root invariant.
  • apps/web/src/components/layout/middle-content/page-views/task-list/TaskListView.tsx#L534-L539: Replace useId() with a per-mount client-only identifier, or verify the single-root invariant.
Suggested identity source
-const instanceId = useId();
+const [instanceId] = useState(() => crypto.randomUUID());

Remove useId from the affected imports.

Based on learnings, use crypto.randomUUID() for ephemeral client-only identifiers and Zustand store keys. The supplied useEditingStore contract stores and removes the exact caller-provided key.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx`
around lines 60 - 63, Replace the useId-based editing-store instance identifiers
with crypto.randomUUID() in CanvasPageView.tsx (60-63), CodePageView.tsx
(74-77), DocumentView.tsx (39-44), SheetView.tsx (205-210), and TaskListView.tsx
(534-539); remove the affected useId imports and preserve each exact per-mount
key for store creation and cleanup.

Source: Learnings

apps/web/src/components/agents/panes/PagePaneView.tsx (1)

61-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for PagePaneView's render branches.

AgentPanes.tsx, PanePicker.tsx, and pane-surface.ts each have an adjacent test file, but no test evidence surfaced for PagePaneView.tsx. This component has several distinct branches worth covering: loading skeleton, fetch error, non-paneable type, unsupported component, and the pageId-only vs. full-page prop split (lines 100-112).

Consider adding a test file mocking useSWR and the next/dynamic imports to cover these branches, following the pattern already used for the sibling test files in this directory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/agents/panes/PagePaneView.tsx` around lines 61 - 115,
Add a colocated test file for PagePaneView that mocks useSWR and
next/dynamic/component dependencies, following neighboring pane component test
patterns. Cover loading, fetch-error, non-paneable, unsupported-component, and
successful rendering branches, including verifying pageId-only components
receive pageId/driveId while other components receive the full page object.
apps/web/src/components/agents/panes/PanePicker.tsx (1)

236-242: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Do not treat the empty-query fetch as a candidate-only issue.

/api/mentions/search returns the latest 20 pages when q is empty, so /api/mentions/search?q= returns a non-empty page list. Use an inactive SWR key until the typed query has at least one non-whitespace character, or update the empty-state copy to describe the default recent-pages list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/agents/panes/PanePicker.tsx` around lines 236 - 242,
Update the SWR key used by the PanePicker search flow around searchKey and
useSWR so it is inactive when debouncedQuery contains no non-whitespace
characters, preventing the empty-query request from populating results. Preserve
the existing driveId and cross-drive query behavior once a meaningful search
query is entered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/components/agents/panes/PagePaneView.tsx`:
- Around line 61-115: Update PagePaneView to import ReactNode and ComponentType
from 'react', then use those imported types for pageComponent and the Component
annotation instead of React.ReactNode and React.ComponentType.

In `@apps/web/src/components/agents/panes/PanePicker.tsx`:
- Around line 239-262: Update the SWR state destructuring in the PanePicker
search flow to use isValidating instead of isLoading for the searching
indicator. Rename the state alias to searching if needed, and preserve the
existing query !== debouncedQuery condition and rendered “Searching…” behavior.

In `@apps/web/src/stores/agent-workspace/useAgentWorkspaceStore.ts`:
- Around line 62-68: The openPage flow should guard replacement of a dirty page
pane before applying focusOrAssignScope or assignPane. Reuse the existing
unsaved-edits confirmation/guard used by conversation-pane replacement, and
ensure handlePickPage and agent-driven open_page_pane resolution preserve the
current pane when confirmation is denied while allowing replacement after
confirmation.

---

Nitpick comments:
In `@apps/web/src/components/agents/panes/PagePaneView.tsx`:
- Around line 61-115: Add a colocated test file for PagePaneView that mocks
useSWR and next/dynamic/component dependencies, following neighboring pane
component test patterns. Cover loading, fetch-error, non-paneable,
unsupported-component, and successful rendering branches, including verifying
pageId-only components receive pageId/driveId while other components receive the
full page object.

In `@apps/web/src/components/agents/panes/PanePicker.tsx`:
- Around line 236-242: Update the SWR key used by the PanePicker search flow
around searchKey and useSWR so it is inactive when debouncedQuery contains no
non-whitespace characters, preventing the empty-query request from populating
results. Preserve the existing driveId and cross-drive query behavior once a
meaningful search query is entered.

In
`@apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx`:
- Around line 60-63: Replace the useId-based editing-store instance identifiers
with crypto.randomUUID() in CanvasPageView.tsx (60-63), CodePageView.tsx
(74-77), DocumentView.tsx (39-44), SheetView.tsx (205-210), and TaskListView.tsx
(534-539); remove the affected useId imports and preserve each exact per-mount
key for store creation and cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b27c1756-fc47-406b-91ee-e3488bc51623

📥 Commits

Reviewing files that changed from the base of the PR and between 3485e3c and 6d306e6.

📒 Files selected for processing (24)
  • README.md
  • apps/marketing/src/app/docs/getting-started/page.tsx
  • apps/web/src/components/agents/AgentPageView.tsx
  • apps/web/src/components/agents/chat/AssistantSessionChat.tsx
  • apps/web/src/components/agents/chat/SessionChat.tsx
  • apps/web/src/components/agents/panes/AgentPanes.tsx
  • apps/web/src/components/agents/panes/PagePaneView.tsx
  • apps/web/src/components/agents/panes/PaneChat.tsx
  • apps/web/src/components/agents/panes/PanePicker.tsx
  • apps/web/src/components/agents/panes/pane-surface.ts
  • apps/web/src/components/ai/shared/chat/tool-calls/OpenPagePaneRenderer.tsx
  • apps/web/src/components/ai/shared/chat/tool-calls/registry.tsx
  • apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/document/DocumentView.tsx
  • apps/web/src/components/layout/middle-content/page-views/sheet/SheetView.tsx
  • apps/web/src/components/layout/middle-content/page-views/task-list/TaskListView.tsx
  • apps/web/src/lib/ai/core/__tests__/ai-tools.test.ts
  • apps/web/src/lib/ai/core/ai-tools.ts
  • apps/web/src/lib/ai/shared/hooks/useOpenPagePane.ts
  • apps/web/src/lib/ai/tools/page-pane-tools.ts
  • apps/web/src/stores/agent-workspace/useAgentWorkspaceStore.ts
  • packages/lib/src/agent-sessions/contract.ts
  • packages/lib/src/content/page-types.config.ts

Comment thread apps/web/src/components/agents/panes/PagePaneView.tsx Outdated
Comment thread apps/web/src/components/agents/panes/PanePicker.tsx Outdated
Comment thread apps/web/src/stores/agent-workspace/useAgentWorkspaceStore.ts Outdated
2witstudios and others added 2 commits August 1, 2026 08:50
Adds a `'page'` pane kind alongside chat/terminal so a document, task
list, sheet, canvas, code file, channel, or file can be opened (and
edited) in an agent session's pane grid — most useful when an agent is
actively editing a page and wants to show its work without the user
hunting for it in the sidebar.

- `PagePaneView`: generic pane renderer, dispatching through the same
  page-type config CenterPanel uses; view components are lazily loaded
  via next/dynamic (matches DocumentView's own editor-loading pattern,
  and keeps pdfjs-dist out of every pane bundle).
- `isPaneablePageType` excludes FOLDER (a tree browser, not content)
  and AI_CHAT (would nest a chat inside a chat pane).
- PanePicker gets a debounced "Pages" search section; AgentPanes gains
  handlePickPage/a 'page' render branch; useAgentWorkspaceStore gains
  openPage (shares openConversation's focus-or-replace-or-split logic).
- useEditingStore registrations in DocumentView/CodePageView/
  CanvasPageView/SheetView/TaskListView now include a useId() suffix so
  the same page open in the main view and a pane can't clobber each
  other's dirty-state entry.
- New open_page_pane tool lets an agent surface a page pane itself: its
  execute() always acks (a pane grid is client-local state the server
  can't see), and a new useOpenPagePane hook reacts to the completed
  result by opening/focusing the pane in the conversation's own
  session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD
CI (both required checks were red):
- knip flagged apps/web/src/lib/ai/tools/page-pane-tools.ts's
  OpenPagePaneInput as a new unused export — deleted it (nothing
  consumed it; OpenPagePaneOutput, which IS consumed, is untouched).
- packages/lib/src/agent-sessions/__tests__/contract.test.ts had a
  hardcoded PANE_KINDS exhaustiveness assertion that predates the new
  'page' kind — updated the expected array and its guard comment.

Review findings (Codex + CodeRabbit):
- P1 (Codex): a page pane opened via open_page_pane in a
  single-chat-pane workspace would REPLACE the very conversation that
  triggered it (that chat pane is the only replaceable candidate).
  openPage now takes an optional excludeTargetId; useOpenPagePane
  passes the invoking conversation's own id so it's never evicted —
  splits beside it instead. Threaded conversationId through
  SessionChatView/SessionChat/AssistantSessionChat to make this
  possible.
- P2 (Codex): remounting/reopening a conversation whose last message
  already contains a completed open_page_pane call would replay it —
  reopening/refocusing a pane the user may have since closed or
  repurposed. useOpenPagePane now seeds its seen-set from whatever's
  already on the first render without acting on it; only a call that
  becomes output-available on a LATER run is treated as new.
- P2 (Codex): DocumentView was getting the SESSION's driveId (null for
  a global-assistant session) instead of the fetched PAGE's own
  driveId, disabking/misrouting usePageContentSocket for a page that
  always belongs to a real drive. PagePaneView now passes page.driveId
  (matching CenterPanel's own usage) and drops the now-unused driveId
  prop entirely.
- Major (CodeRabbit): openPage could silently overwrite a page pane
  with unsaved edits. focusOrAssignScope's isReplaceable now checks
  useEditingStore for an active document/form session on that page's
  id and refuses to replace it — falls through to a split instead.
- Major (CodeRabbit): PagePaneView used React.ReactNode/ComponentType
  without importing React — switched to named `ReactNode`/`ComponentType`
  imports from 'react'.
- Minor (CodeRabbit): PanePicker's "Searching…" indicator used
  isLoading, which SWR's keepPreviousData keeps false during a
  background revalidation once any data has loaded — switched to
  isValidating. Also fixed the empty-query empty-state copy, which
  claimed "type to search" even though the endpoint already returns
  recent pages for an empty query.

Also added test coverage CodeRabbit flagged as missing: PagePaneView's
render branches (loading/error/unsupported/each view's prop
convention, including the driveId fix) and useOpenPagePane's new
history-replay and invoker-exclusion behavior, plus store-level tests
for openPage's dirty-guard and excludeTargetId.

Not changed (reviewed, no action): CodeRabbit suggested useId() might
collide across multiple React roots and recommended crypto.randomUUID().
This app has exactly one root per page (Next.js App Router), so useId()
is already unique across every simultaneous mount. Switching to
crypto.randomUUID() would trade that safety for a real regression:
DocumentView/CodePageView/CanvasPageView render server-side (unlike
their ssr:false pane instances) in CenterPanel's main content area, and
crypto.randomUUID() executed in a lazy useState initializer produces a
different value on the server than on the client, which is exactly the
hydration-mismatch pitfall useId() exists to avoid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD
@2witstudios

Copy link
Copy Markdown
Owner Author

Pushed two follow-up commits addressing CI and review feedback (419bf0e39, then rebased onto master as 3d4eee4c3 after 35 commits landed there — no conflicts, all green post-rebase).

CI (both required checks were red):

  • ci / Lint & TypeScript Check: knip flagged page-pane-tools.ts's OpenPagePaneInput export as new dead code — deleted it (nothing consumed it).
  • ci / Unit Tests: contract.test.ts had a hardcoded PANE_KINDS exhaustiveness assertion (['chat', 'terminal']) that predates the new 'page' kind — updated it.

Review threads — replied individually on each of the 6 with what changed; leaving them open for your own verification rather than auto-resolving, since I fixed all of them during this pass rather than finding them already addressed. Two were real bugs worth calling out here too:

  • Codex (P1): open_page_pane in a single-chat-pane session would replace the very conversation that called it. Fixed via a new excludeTargetId on openPage.
  • CodeRabbit (Major): a page pane with unsaved edits could be silently replaced. Fixed via a dirty-check against useEditingStore in the replaceable-pane predicate.

Two CodeRabbit nitpicks from the initial review body (not separate inline threads, so noting here):

  • ✅ Added PagePaneView.test.tsx (11 cases) covering the branches flagged as untested — including one that specifically pins the page.driveId fix.
  • ⚠️ Not changed: the useId()crypto.randomUUID() suggestion. This app has exactly one React root per page (Next.js App Router), so useId() is already unique across every simultaneous mount of the same page — the multi-root risk the suggestion guards against doesn't apply here. Switching would also introduce a real regression: DocumentView/CodePageView/CanvasPageView render server-side in CenterPanel's main content area (only their pane instances are next/dynamic(..., { ssr: false })), and crypto.randomUUID() in a lazy useState initializer returns a different value on the server than the client — exactly the hydration-mismatch useId() exists to avoid.

Validation: bun run typecheck/lint/knip:check clean; full @pagespace/lib suite (8682 tests) and the full affected web surface (441 tests across panes/store/hooks/tool-registry) all green post-rebase.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/components/agents/panes/PagePaneView.tsx`:
- Line 61: Update the SWR request key in PagePaneView to URL-encode pageId
before interpolating it into the `/api/pages/` path, ensuring values containing
`/`, `?`, or `#` address a single page resource.

In `@apps/web/src/lib/ai/shared/hooks/useOpenPagePane.ts`:
- Around line 56-70: Update the effect in useOpenPagePane to scan all assistant
messages for completed open_page_pane tool outputs, rather than only
messages[messages.length - 1]. Do not mark a completed tool call as handled
before processing it; preserve deduplication through handledRef while allowing
outputs already present during the initial scan to open the pane.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6c88bc42-b3e6-4f44-aa30-e747f4420fb3

📥 Commits

Reviewing files that changed from the base of the PR and between 6d306e6 and 3d4eee4.

📒 Files selected for processing (28)
  • README.md
  • apps/marketing/src/app/docs/getting-started/page.tsx
  • apps/web/src/components/agents/AgentPageView.tsx
  • apps/web/src/components/agents/chat/AssistantSessionChat.tsx
  • apps/web/src/components/agents/chat/SessionChat.tsx
  • apps/web/src/components/agents/panes/AgentPanes.tsx
  • apps/web/src/components/agents/panes/PagePaneView.tsx
  • apps/web/src/components/agents/panes/PaneChat.tsx
  • apps/web/src/components/agents/panes/PanePicker.tsx
  • apps/web/src/components/agents/panes/__tests__/PagePaneView.test.tsx
  • apps/web/src/components/agents/panes/pane-surface.ts
  • apps/web/src/components/ai/shared/chat/tool-calls/OpenPagePaneRenderer.tsx
  • apps/web/src/components/ai/shared/chat/tool-calls/registry.tsx
  • apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/document/DocumentView.tsx
  • apps/web/src/components/layout/middle-content/page-views/sheet/SheetView.tsx
  • apps/web/src/components/layout/middle-content/page-views/task-list/TaskListView.tsx
  • apps/web/src/lib/ai/core/__tests__/ai-tools.test.ts
  • apps/web/src/lib/ai/core/ai-tools.ts
  • apps/web/src/lib/ai/shared/hooks/__tests__/useOpenPagePane.test.ts
  • apps/web/src/lib/ai/shared/hooks/useOpenPagePane.ts
  • apps/web/src/lib/ai/tools/page-pane-tools.ts
  • apps/web/src/stores/agent-workspace/__tests__/useAgentWorkspaceStore.test.ts
  • apps/web/src/stores/agent-workspace/useAgentWorkspaceStore.ts
  • packages/lib/src/agent-sessions/__tests__/contract.test.ts
  • packages/lib/src/agent-sessions/contract.ts
  • packages/lib/src/content/page-types.config.ts
🚧 Files skipped from review as they are similar to previous changes (20)
  • packages/lib/src/agent-sessions/contract.ts
  • apps/marketing/src/app/docs/getting-started/page.tsx
  • apps/web/src/components/agents/AgentPageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/task-list/TaskListView.tsx
  • apps/web/src/lib/ai/core/ai-tools.ts
  • apps/web/src/components/layout/middle-content/page-views/sheet/SheetView.tsx
  • README.md
  • apps/web/src/components/ai/shared/chat/tool-calls/registry.tsx
  • packages/lib/src/content/page-types.config.ts
  • apps/web/src/lib/ai/core/tests/ai-tools.test.ts
  • apps/web/src/components/agents/chat/AssistantSessionChat.tsx
  • apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx
  • apps/web/src/components/layout/middle-content/page-views/document/DocumentView.tsx
  • apps/web/src/components/ai/shared/chat/tool-calls/OpenPagePaneRenderer.tsx
  • apps/web/src/components/agents/panes/AgentPanes.tsx
  • apps/web/src/components/agents/panes/pane-surface.ts
  • apps/web/src/components/agents/panes/PaneChat.tsx
  • apps/web/src/components/layout/middle-content/page-views/canvas/CanvasPageView.tsx
  • apps/web/src/components/agents/panes/PanePicker.tsx
  • apps/web/src/components/agents/chat/SessionChat.tsx

Comment thread apps/web/src/components/agents/panes/PagePaneView.tsx Outdated
Comment thread apps/web/src/lib/ai/shared/hooks/useOpenPagePane.ts
…witch

Found during a proactive self-review pass (not from an external review
comment): SessionChat/AssistantSessionChat are never keyed by
conversationId, so the pane-bar agent switcher's handleSwitchAgent →
assignPane rebinds the SAME pane's scope to a different conversation
WITHOUT remounting SessionChatView. useOpenPagePane's handledRef/
hasSeededRef are useRef state, which survives exactly that kind of
prop-only change — so switching a pane from conversation A (already
seeded) to a fresh conversation B whose own last message already ends
in a completed open_page_pane call would read B's history through A's
stale hasSeededRef and wrongly treat it as new, reopening/refocusing a
pane the user may have since closed or repurposed. Same failure class
as the Codex-flagged remount case, just reachable via a different path.

Added seenConversationIdRef: both refs now reset whenever conversationId
changes, so a switch-in-place reseeds exactly like a fresh mount would.
Two new tests cover it — one confirming a different conversation's own
history isn't replayed after a switch, one confirming a genuinely new
call after switching still fires.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD
@2witstudios

Copy link
Copy Markdown
Owner Author

One more fix, found in a self-review pass rather than from an external comment (`201e1b489`):

`SessionChat`/`AssistantSessionChat` are never keyed by `conversationId` — the pane-bar's agent-switcher (`handleSwitchAgent` → `assignPane`) rebinds the same pane's scope to a different conversation without remounting. `useOpenPagePane`'s `handledRef`/`hasSeededRef` are plain `useRef`s, so they survive exactly that prop-only change — switching a pane from a conversation that was already "seeded" to a different, unrelated conversation whose own last message already ends in a completed `open_page_pane` call would read the new conversation's history through the old one's stale ref state and wrongly treat it as live, reopening/refocusing a pane the user may have since closed or repurposed. Same failure class the Codex thread above flagged for remounts, just reachable via a second path I hadn't covered.

Added a `seenConversationIdRef` that resets both refs whenever `conversationId` changes, plus two tests (one pinning the non-replay behavior, one confirming a genuinely new call after a switch still fires). Full validation (typecheck/lint/knip/targeted tests) green; pushed as a fast-forward on top of `3d4eee4c3`, no rebase needed this time.

2witstudios and others added 2 commits August 1, 2026 09:08
Applied a CodeRabbit suggestion (r3695743914): unlike every other
/api/pages/${pageId} call site in the app (CenterPanel, DocumentView,
CodePageView, etc. — all sourced from the loaded tree or a URL param),
this one can carry a pageId straight from an agent's open_page_pane
tool call, a free-text string rather than a validated id. A /, ?, or #
in it must not reroute or reshape the request; it should just 404 into
the existing error state.

Not applying CodeRabbit's other suggestion on this same push (scan
every message in useOpenPagePane rather than only the last one) —
replied on the thread with why: it would reintroduce the exact
history-replay bug the last-message check exists to prevent, and this
app's multi-step agentic turns still fold into one client-visible
UIMessage (same reason ask_user's own answerability selector only
checks the last message).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD
# Conflicts:
#	apps/web/src/components/agents/panes/AgentPanes.tsx
@2witstudios

Copy link
Copy Markdown
Owner Author

Sibling PR #2299 ("pane-level Chat/History/Settings tabs") merged into master while this was in flight. Merged `master` in (`4361529e4`) rather than rebasing — one conflict, in `AgentPanes.tsx`'s `renderPane`, since #2299 pulled chat-pane rendering out into a new early-returning `ChatPane` component.

Resolution: kept my `'page'` branch (``) in the tail of `renderPane` alongside the picker/loading/terminal cases (unchanged by #2299), and dropped the now-dead inline `'chat'` branch there (unreachable — any chat-kind pane already returns via the new `ChatPane` early return). I also had to thread `sessionId` into `ChatPane`'s own `` call (it wasn't part of the textual conflict — `ChatPane` already receives `sessionId` as a prop for its own History-tab needs, it just wasn't forwarding it to `PaneChat` — needed for `useOpenPagePane`'s remount/switch fixes above to apply inside the new tabbed chat pane too).

Full validation after the merge: typecheck/lint/knip clean, @pagespace/lib (8682 tests) and the whole touched web surface (2334 tests across agents/panes/store/hooks/tool-registry/API routes, including AgentPanes.test.tsx's now-65 tests) all green. Still MERGEABLE, no conflicts with current master.

@2witstudios

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4361529e4a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +111 to +113
} else if (componentName === 'CodePageView') {
const CodePageView = componentMap.CodePageView;
pageComponent = <CodePageView key={`code-${page.id}`} pageId={page.id} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Subscribe code panes to the opened page's drive

When a code page is opened from the global /dashboard/agents surface, CodePageView receives only the page ID and derives driveId from useParams(), where no drive parameter exists. Its usePageTree lookup therefore cannot detect the language from the page title, and—more importantly—its page:content-updated listener never joins the opened page's drive room, so collaborator or AI edits are missed. Pass the fetched page's drive context into the code view and subscribe to that drive rather than relying on the surrounding route.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — confirmed real, not just theoretical for panes specifically: CodePageView never accepted a driveId prop at all, always deriving one from useParams() (absent in the global assistant console, wrong for a cross-drive session), and its raw useSocket() listener never joined the page's own drive room the way DocumentView's usePageContentSocket does. Added an optional driveId prop (falls back to the route param — CenterPanel's existing call site is unaffected) and swapped the raw listener for usePageContentSocket, mirroring DocumentView exactly. PagePaneView now passes page.driveId here too. Added a test pinning it. See 07af1c4.

Comment on lines +23 to +25
<span className="text-muted-foreground">
Opened <span className="font-medium text-foreground">{title ?? 'this page'}</span> in a pane
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not claim a pane opened outside a session

In a plain page-agent conversation or other surface with sessionId === null, open_page_pane still executes and is rendered here, but useOpenPagePane explicitly no-ops because there is no pane grid. The user consequently sees “Opened … in a pane” even though nothing happened; either suppress/filter the tool outside session conversations or render an acknowledgement that does not assert the pane was opened.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — good catch, this was a real honesty gap. Reworded the card from "Opened X in a pane" to "Requested to open X in a pane": an accurate description of the action taken (a signal was sent) rather than a claim about the outcome (a pane visibly appeared), which the server genuinely cannot know either way. See 07af1c4.

Comment on lines +249 to +251
const pages = results.filter(
(result) => result.type === 'page' && isPaneablePageType(result.data?.pageType ?? PageType.DOCUMENT),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter non-paneable types before truncating search results

The mentions endpoint truncates its response to ten suggestions before this client-side filter runs. If those ten matches are folders or AI-chat pages—for example, the most recently updated pages for an empty query—pages becomes empty and the picker reports “No pages found” even when paneable results exist later in the endpoint's candidate set. Exclude the unsupported page types in the server query or otherwise apply the limit after filtering.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at the root rather than client-side: added an additive excludePageTypes param to /api/mentions/search (defaults to no exclusion, so every other consumer — MentionPicker, PagePickerPopover, TriggerPagePicker — is unaffected), applied per-row BEFORE the existing final slice(0, 10), and PanePicker now sends it instead of relying on a client-side filter after the fact. Added a test that specifically simulates the top-10 window being dominated by excluded types. See 07af1c4.

Three new P2 findings from a fresh @codex review after the master
merge:

- CodePageView never accepted a driveId prop — it always derived one
  from the route's own useParams(), which is absent (or wrong, for a
  cross-drive page) when hosted in a pane. Its raw useSocket() listener
  also never joined the page's own drive room at all (unlike
  DocumentView's usePageContentSocket), so live updates for a code page
  opened from the global assistant console — or any cross-drive
  session — were silently missed. Added an optional driveId prop
  (falls back to the route param, so CenterPanel's existing call site
  is unaffected) and replaced the raw socket listener with
  usePageContentSocket, mirroring DocumentView exactly. PagePaneView
  now passes page.driveId here too.
- OpenPagePaneRenderer said "Opened X in a pane" unconditionally, but
  the tool's execute() always acks (the server can't know if any
  client actually had a pane grid to act on — see page-pane-tools.ts's
  doc) — in a plain, session-less conversation this claimed something
  happened when nothing did. Reworded to "Requested to open X in a
  pane", an honest description of the action taken rather than a claim
  about the outcome.
- PanePicker's Pages search filtered FOLDER/AI_CHAT client-side, but
  /api/mentions/search caps its response to 10 suggestions BEFORE that
  filter ever runs — if the top 10 by relevance/recency happened to be
  dominated by excluded types, eligible pages ranked just below them
  would vanish from view entirely. Added excludePageTypes to the route
  (additive, defaults to no exclusion — every other caller unaffected),
  applied per-row before the existing final slice(0, 10), and PanePicker
  now sends it.

Added test coverage for all three (CodePageView driveId assertion in
PagePaneView.test.tsx, a new excludePageTypes describe block in the
mentions-search route's own test file). Full validation: typecheck/
lint/knip clean; the affected surface (mentions search + its other
consumers, PagePaneView, PanePicker, AgentPanes, task-list, code view,
full @pagespace/lib) all green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/api/mentions/search/route.ts`:
- Around line 146-159: Push excludePageTypesParam into the database query’s
WHERE clause by adding notInArray(pages.type, [...excludePageTypesParam])
alongside the existing pageTypeParam filter. Remove any redundant post-fetch
exclusion while preserving the final suggestions ranking and limit behavior.

In
`@apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx`:
- Around line 173-190: Stabilize the callback passed to usePageContentSocket so
typing changes do not repeatedly detach and reattach the page:content-updated
listener. Keep the latest documentState?.content and documentState?.isDirty
accessible through a mutable callback ref, invoke that ref from the socket
handler, and ensure the listener effect depends only on stable connection inputs
such as pageId, driveId, and enabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 370d9980-59d9-4fd7-925c-108c696cc0b9

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4eee4 and 07af1c4.

📒 Files selected for processing (11)
  • apps/web/src/app/api/mentions/search/__tests__/route.test.ts
  • apps/web/src/app/api/mentions/search/route.ts
  • apps/web/src/components/agents/AgentPageView.tsx
  • apps/web/src/components/agents/panes/AgentPanes.tsx
  • apps/web/src/components/agents/panes/PagePaneView.tsx
  • apps/web/src/components/agents/panes/PanePicker.tsx
  • apps/web/src/components/agents/panes/__tests__/PagePaneView.test.tsx
  • apps/web/src/components/ai/shared/chat/tool-calls/OpenPagePaneRenderer.tsx
  • apps/web/src/components/layout/middle-content/page-views/code/CodePageView.tsx
  • apps/web/src/lib/ai/shared/hooks/__tests__/useOpenPagePane.test.ts
  • apps/web/src/lib/ai/shared/hooks/useOpenPagePane.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/src/components/agents/AgentPageView.tsx
  • apps/web/src/components/agents/panes/AgentPanes.tsx
  • apps/web/src/components/agents/panes/PanePicker.tsx
  • apps/web/src/components/agents/panes/PagePaneView.tsx

Comment thread apps/web/src/app/api/mentions/search/route.ts
CodeRabbit follow-up on 07af1c4: excludePageTypesParam was only
checked after pages.limit(50)/limit(20) had already run, so if
excluded types dominated THAT initial DB-level fetch window (not just
the final slice(0, 10) the previous commit fixed), eligible pages
ranked beyond it would still never reach the response. Added
notInArray(pages.type, [...excludePageTypesParam]) to the WHERE
clause alongside the existing pageTypeParam condition, and removed the
now-redundant post-fetch in-memory check.

Rewrote the excludePageTypes tests to match: the mocked db.select
chain can't observe SQL-level filtering (its .where() is a no-op
passthrough, the same limitation every other filter test in this file
already has), so they now spy on the real (unmocked) notInArray from
@pagespace/db/operators and assert it's called with the right
arguments — or not called at all when the param is absent/garbage —
rather than asserting on a response shape the mock can't meaningfully
vary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaXkcqLXNPp9AnbJVntWqD
@2witstudios
2witstudios merged commit c11fe84 into master Aug 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant