Skip to content

chore(notes): drop pages[] from note shell; add /page-titles + /pages/:id (#860 Phase 6)#868

Merged
otomatty merged 4 commits into
developfrom
claude/issue-860-continuation-tp7vF
May 14, 2026
Merged

chore(notes): drop pages[] from note shell; add /page-titles + /pages/:id (#860 Phase 6)#868
otomatty merged 4 commits into
developfrom
claude/issue-860-continuation-tp7vF

Conversation

@otomatty

@otomatty otomatty commented May 14, 2026

Copy link
Copy Markdown
Owner

Removes the pages[] slice from GET /api/notes/:noteId (note shell). Visible
page lists already use the Phase 1 cursor-paginated /pages endpoint, but
wiki-link / AI-chat scope / add-dialog dedup need the complete title set, so
this PR adds two replacement routes instead of the shell array:

  • GET /api/notes/:noteId/page-titles — minimal { id, title, is_deleted, updated_at } payload for full-set title consumers (~50 bytes/page; small
    even for 10k-page notes). authOptional + getNoteRole, with weak ETag
    mixing version + role + (MAX(updated_at), COUNT(*)) so renames / deletes
    still bust 304s.
  • GET /api/pages/:pageId — single-page metadata fetch needed by
    useNotePage after losing the shell pages[]. Same authOptional +
    getNoteRole model.

Note shell RESPONSE_VERSION bumped v2v3 so old If-None-Match
validators do not revive stale pages[] bodies via 304.

Frontend:

  • Replace useNotePages with the new useNoteTitleIndex ({ id, title, isDeleted, updatedAt }[]) and migrate every consumer
    (useWikiLinkNavigation, useWikiLinkStatusSync, useTagStatusSync,
    useWikiLinkCandidates, useAIChatActions).
  • Repoint useNotePage to the new /api/pages/:id route, with an explicit
    noteId mismatch guard so a stale URL after a cross-note move resolves to
    null instead of rendering a mismatched page.
  • Extend the SSE handler (useNotePageEvents) to mirror window-cache
    patches into the title-index cache: page.added prepends, page.updated
    move-to-head with title overwrite, page.deleted removes. ready and
    note.permission_changed invalidate the title-index too.

Tests: 9 new title-index endpoint tests, 4 new single-page metadata tests,
title-index SSE patch coverage, and updated note-shell tests that now lock
in the "no pages[]" contract and the v2 → v3 cache-bust path.

Summary by CodeRabbit

  • New Features

    • In-note full-text search with cursor-based pagination and “load more”
    • Persistent note-scoped search bar in the Note view
    • Single-page metadata lookup endpoint for fast page access
    • Lightweight page-titles index endpoint for title-only lists
  • Improvements

    • Duplicate-title warning in Add Page dialog (accessible hint)
    • Faster, smaller title lookups powering wiki-links, AI chat, and editor
    • More robust caching/ETag behavior for notes, searches, and title lists

Review Change Stack

claude added 2 commits May 14, 2026 03:05
…/:id (#860 Phase 6)

Removes the `pages[]` slice from `GET /api/notes/:noteId` (note shell). Visible
page lists already use the Phase 1 cursor-paginated `/pages` endpoint, but
wiki-link / AI-chat scope / add-dialog dedup need the *complete* title set, so
this PR adds two replacement routes instead of the shell array:

- `GET /api/notes/:noteId/page-titles` — minimal `{ id, title, is_deleted,
  updated_at }` payload for full-set title consumers (~50 bytes/page; small
  even for 10k-page notes). `authOptional` + `getNoteRole`, with weak ETag
  mixing version + role + `(MAX(updated_at), COUNT(*))` so renames / deletes
  still bust 304s.
- `GET /api/pages/:pageId` — single-page metadata fetch needed by
  `useNotePage` after losing the shell `pages[]`. Same `authOptional` +
  `getNoteRole` model.

Note shell `RESPONSE_VERSION` bumped `v2` → `v3` so old `If-None-Match`
validators do not revive stale `pages[]` bodies via 304.

Frontend:

- Replace `useNotePages` with the new `useNoteTitleIndex` (`{ id, title,
  isDeleted, updatedAt }[]`) and migrate every consumer
  (`useWikiLinkNavigation`, `useWikiLinkStatusSync`, `useTagStatusSync`,
  `useWikiLinkCandidates`, `useAIChatActions`).
- Repoint `useNotePage` to the new `/api/pages/:id` route, with an explicit
  `noteId` mismatch guard so a stale URL after a cross-note move resolves to
  `null` instead of rendering a mismatched page.
- Extend the SSE handler (`useNotePageEvents`) to mirror window-cache
  patches into the title-index cache: `page.added` prepends, `page.updated`
  move-to-head with title overwrite, `page.deleted` removes. `ready` and
  `note.permission_changed` invalidate the title-index too.

Tests: 9 new title-index endpoint tests, 4 new single-page metadata tests,
title-index SSE patch coverage, and updated note-shell tests that now lock
in the "no `pages[]`" contract and the `v2 → v3` cache-bust path.
Adds first-class note-scoped search to the note detail page and turns the
existing `/api/notes/:noteId/search` endpoint into a keyset-paginated route
that public / unlisted guests can also use, completing the last leftover
Phase 5 items of the issue #860 epic.

Backend (`/api/notes/:noteId/search`):

- Auth model switches from `authRequired` to `authOptional` +
  `getNoteRole`, mirroring `/pages` and `/page-titles`. Public / unlisted
  notes are searchable by guests; private notes still 403 without a
  resolved role.
- Adds `?cursor=` keyset pagination on `(updated_at, id)` using the same
  microsecond-precision ISO string the `/pages` window already uses
  (Phase 1) — pg `to_char(...)` round-trips through the cursor and casts
  back via `::timestamptz`, so consecutive `defaultNow()` rows never get
  dropped. Response shape is now `{ results, next_cursor }`; the route
  fetches `limit + 1` rows to detect tails without an extra count query.
- Cursors are opaque base64url JSON and validated end-to-end (UUID + ISO
  timestamp), so malformed values become deterministic 400s instead of
  pg `22P02` 500s.

Frontend:

- New `useInfiniteNoteSearch(noteId, q, opts)` hook (built on
  `useInfiniteQuery`) flattens windows into a `NoteSearchResultItem[]`
  while exposing `hasNextPage` / `fetchNextPage` / `isFetchingNextPage`.
  Query key namespaces the cache per `(noteId, q, pageSize, principal)`.
- New `<NoteSearchBar />` component rendered above `<PageGrid />` on the
  note detail page. Debounced input (250 ms default; configurable for
  tests), results render as a click-to-navigate list with optional
  `content_preview` snippet, and a "show more" button drives
  `fetchNextPage()` when `hasNextPage` is true. Empty queries render
  nothing so the regular grid layout stays in place. New i18n keys live
  under `notes.search.*` in both ja and en.
- `NoteAddPageDialog` finally retires its old all-pages dedup TODO: it
  now reads the Phase 6 `useNoteTitleIndex` and surfaces an inline
  warning ("a page with this title already exists") on exact-match
  collisions, without blocking the add action.

Tests: 6 new backend tests cover cursor emission, exhaustion semantics,
malformed/UUID-less cursor rejection, the keyset SQL predicate, and the
401 → 403 auth-model change. Existing limit-clamp tests adjusted for the
`limit + 1` fetch (20 → 21, 100 → 101). NoteView test mock extended with
the new hooks so the search bar and add-dialog title-index render
cleanly.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59fb0e08-21c4-45c6-bbeb-46981a0110d4

📥 Commits

Reviewing files that changed from the base of the PR and between b67c42b and 5d87155.

📒 Files selected for processing (6)
  • server/api/src/__tests__/routes/notes/page-titles.test.ts
  • server/api/src/__tests__/routes/notes/search.test.ts
  • server/api/src/routes/notes/search.ts
  • src/hooks/useNoteQueries.ts
  • src/lib/api/types.ts
  • src/pages/NoteView/NoteViewAddPageDialogContent.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/pages/NoteView/NoteViewAddPageDialogContent.tsx
  • server/api/src/tests/routes/notes/page-titles.test.ts
  • src/lib/api/types.ts
  • server/api/src/routes/notes/search.ts
  • src/hooks/useNoteQueries.ts
  • server/api/src/tests/routes/notes/search.test.ts

📝 Walkthrough

Walkthrough

Phase 6 splits page data from the note shell: removes pages[] from GET /api/notes/:id (ETag v2→v3), adds /page-titles and /pages/:id, implements keyset search with cursors, migrates frontend hooks/caches to a title-index, and adds a note-scoped search UI.

Changes

Phase 6 API and Query Restructuring

Layer / File(s) Summary
Note-detail response redesign (note shell and ETag v3)
server/api/src/routes/notes/crud.ts, server/api/src/routes/notes/types.ts, server/api/src/__tests__/routes/notes/crud.test.ts
GET /api/notes/:noteId no longer returns pages[]; ETag response-shape version bumped v2→v3; types/tests updated to validate absence of pages and to compute ETag from pages-signal aggregate.
New page-listing endpoints (page-titles, page-metadata, search pagination)
server/api/src/routes/notes/titleIndex.ts, server/api/src/routes/notes/search.ts, server/api/src/routes/pages.ts, server/api/src/__tests__/routes/notes/page-titles.test.ts, server/api/src/__tests__/routes/notes/search.test.ts, server/api/src/__tests__/routes/pages.test.ts, server/api/src/routes/notes/index.ts
Added GET /api/notes/:noteId/page-titles returning { items: [{ id, title, is_deleted, updated_at }] } with weak ETag and 304; added GET /api/pages/:id for single-page metadata with role checks; GET /api/notes/:noteId/search now uses authOptional, keyset cursor encoding, limit+1 fetch, updated_at_iso, and returns next_cursor.
API types and client integration
src/lib/api/types.ts, src/lib/api/apiClient.ts
Removed pages from GetNoteResponse; added NotePageTitleItem, NotePageTitleIndexResponse, NoteSearchResultItem, NoteSearchResponse; apiClient adds getPage, getNotePageTitles, and searchNotePages.
Frontend query hooks and note page fetching
src/hooks/useNoteQueries.ts
Added NotePageTitle type and useNoteTitleIndex(noteId, options?); added useInfiniteNoteSearch for cursor-based infinite pagination; updated useNotePage to call GET /api/pages/:id and validate note_id; expanded noteKeys with titleIndex and search.
Real-time cache invalidation and event patching
src/hooks/useNotePageEvents.ts, src/hooks/useNotePageEvents.test.ts
useNotePageEvents now patches the title-index cache on page.added/updated/deleted, invalidates title-index on note.permission_changed and SSE ready; tests added to validate title-index patching and invalidation.
Wiki-link and reference resolution using title index
src/hooks/useWikiLinkCandidates.ts, src/hooks/useWikiLinkCandidates.test.ts, src/components/editor/TiptapEditor/useWikiLinkNavigation.ts, src/components/editor/TiptapEditor/*, src/hooks/useAIChatActions.ts
Hooks and editor logic migrated from full page lists to useNoteTitleIndex using minimal { id, title, isDeleted, updatedAt } rows; tests/mocks updated accordingly.
Note view UI components (search bar, dialog, integration)
src/components/note/NoteSearchBar.tsx, src/pages/NoteView/*
Added NoteSearchBar (debounced input + infinite search), integrated it into NoteView; NoteAddPageDialog uses useNoteTitleIndex to compute duplicateTitleExists and passes it to content component with accessibility markup; tests updated to stub new hooks.
Internationalization and translations
src/i18n/locales/en/notes.json, src/i18n/locales/ja/notes.json
Added duplicateTitleWarning and a search nested object with placeholders, labels, loading/error text, and untitled label for note-scoped search UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • otomatty/zedi#721: Related to note-scoped search implementation and pagination behavior.
  • otomatty/zedi#861: Overlaps with note-detail ETag/response-shape changes to GET /api/notes/:noteId.
  • otomatty/zedi#867: Related to SSE-driven page window cache updates that this PR extends to title-index patching.

"A rabbit typed with nimble paws,
Split pages out to tidy laws,
Titles sing and cursors hum,
ETags bumped — caches come,
Hops of code, hooray applause! 🐇"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: removing pages[] from the note shell and adding two new endpoints (/page-titles and /pages/:id), with a reference to the related issue (#860 Phase 6).
Docstring Coverage ✅ Passed Docstring coverage is 82.86% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-860-continuation-tp7vF

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements Phase 5 and 6 of Issue #860 by removing the pages[] array from the note shell response and introducing a lightweight page-titles endpoint for full-set title lookups. It also adds keyset cursor pagination to note-scoped search, makes search accessible to guests on public notes, and introduces a NoteSearchBar component. The NoteAddPageDialog now utilizes the new title index for duplicate title warnings. Review feedback highlighted the need to improve accessibility in the search bar by replacing incorrect ARIA listbox roles with standard list semantics for navigation links.

Comment thread src/components/note/NoteSearchBar.tsx Outdated
Comment on lines +125 to +126
role="listbox"
aria-label={t("notes.search.resultsLabel")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using role="listbox" for a list of navigation links is semantically incorrect. A listbox is intended for selecting values (like in a form), whereas these are search results that navigate to different routes. This can confuse screen reader users who expect selection behavior. It is better to use standard list semantics for navigation results. Consider removing the role and moving the aria-label to the ul element.

Suggested change
role="listbox"
aria-label={t("notes.search.resultsLabel")}
aria-label={t("notes.search.resultsLabel")}

Comment thread src/components/note/NoteSearchBar.tsx Outdated
{!error && results.length > 0 && (
<ul className="divide-border divide-y">
{results.map((row, idx) => (
<li key={ids[idx]} role="option">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Interactive elements like Link should not be nested inside an option role. Removing role="option" ensures the list items are treated as standard list items, which is more appropriate for a list of navigation links and avoids violating ARIA patterns.

Suggested change
<li key={ids[idx]} role="option">
<li key={ids[idx]}>

)

Drops `role="listbox"` / `role="option"` from the note-scoped search bar's
result container. The dropdown is a list of navigation links, not a
select-like selection widget, so the ARIA roles were misleading screen
readers into expecting selection behavior. Moves `aria-label` onto the
real `<ul>` and leaves the outer `<div>` as a role-less styling container
(it still hosts the loading / empty / load-more affordances around the
list, so it cannot itself be the `<ul>`).

Addresses gemini-code-assist's medium-priority comments on PR #868
(NoteSearchBar.tsx:126 and :148).

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/api/src/routes/notes/search.ts (1)

176-215: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep content_text out of the public search payload.

This query now selects pc.content_text and returns it verbatim in every hit. On long pages, limit=20/100 turns note search into a full-body download path and can balloon each keystroke or pagination fetch by megabytes. If this field is only reserved for future snippet work, keep it server-side until there is an explicit opt-in consumer.

🤖 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 `@server/api/src/routes/notes/search.ts` around lines 176 - 215, The query is
returning pc.content_text and exposing it in the public results; remove that by
stopping selection of pc.content_text in the SQL, deleting the content_text
property from the SearchRow type, and ensuring the public payload from results
(the visible.map code) does not include content_text; specifically, remove
"pc.content_text" from the SELECT, remove content_text: string | null from the
SearchRow type, and verify the results mapping (currently destructuring
updated_at_iso out) will no longer carry content_text to the wire (so
nextCursor/encodeSearchCursor logic remains unchanged).
src/hooks/useNoteQueries.ts (1)

970-999: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Allow guests to fetch public note pages.

GET /api/pages/:pageId now follows authOptional + getNoteRole, but this hook still short-circuits on isSignedIn. Signed-out viewers on public/unlisted notes never issue the request, so useNotePage stays empty on routes the server now allows.

Suggested fix
 export function useNotePage(
   noteId: string,
   pageId: string,
   _source?: "local" | "remote",
   enabled: boolean = true,
 ) {
-  const { api, isLoaded, isSignedIn } = useNoteApi();
+  const { api, isLoaded } = useNoteApi();
 
   return useQuery({
     queryKey: noteKeys.page(noteId, pageId),
     queryFn: async (): Promise<Page | null> => {
       const p = (await api.getPage(pageId)) as ApiPageMetadataRow | null;
@@
-    enabled: enabled && isLoaded && isSignedIn && !!noteId && !!pageId,
+    enabled: enabled && isLoaded && !!noteId && !!pageId,
   });
 }
🤖 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 `@src/hooks/useNoteQueries.ts` around lines 970 - 999, The hook useNotePage
currently prevents unsigned users from fetching pages by including isSignedIn in
the enabled condition; remove the isSignedIn gate so guests can call GET
/api/pages/:pageId (which is now authOptional). Update the query's enabled flag
in useNotePage to be enabled && isLoaded && !!noteId && !!pageId (leave other
logic like the note_id check and the queryFn using api.getPage and apiPageToPage
unchanged) so unsigned viewers can fetch public/unlisted pages while preserving
the existing safety checks.
🤖 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 `@server/api/src/__tests__/routes/notes/page-titles.test.ts`:
- Around line 156-168: The test name is misleading: the it(...) description
currently says "returns 401 when private note is accessed unauthenticated" but
the assertion checks for 403; update the test title string in the it(...) call
to "returns 403 when private note is accessed unauthenticated" (or equivalent)
so the description matches the asserted status code; ensure you only change the
test description and not the assertion or test logic in this test case.

In `@src/components/note/NoteSearchBar.tsx`:
- Around line 124-149: The component NoteSearchBar.tsx is using ARIA listbox
semantics (div role="listbox" and li role="option") for a simple navigable list
of links; remove or change these roles to plain list semantics by eliminating
role="listbox" on the wrapping div and role="option" on the li (or alternatively
use role="list" on the container and role="listitem" on each li) so the DOM
reflects a normal list of links and not a widget requiring listbox keyboard
behavior; update any tests or accessibility checks that expect the old roles.

In `@src/lib/api/types.ts`:
- Around line 336-355: The NoteSearchResultItem interface and its documentation
still treat note_id as nullable despite the server now hard-filtering p.note_id
= :noteId; update the interface and comment so note_id is non-nullable (change
note_id: string | null -> note_id: string) and adjust the JSDoc to state this
endpoint returns only note-scoped hits (remove the legacy linked-personal
explanation); search for NoteSearchResultItem to update any consumer typings or
tests that expect nullable note_id and fix unreachable branches accordingly.

In `@src/pages/NoteView/NoteViewAddPageDialogContent.tsx`:
- Around line 81-83: The aria-invalid attribute is incorrectly used to mark an
advisory duplicate warning as an input error; in NoteViewAddPageDialogContent
remove the aria-invalid={duplicateTitleExists || undefined} prop (or change it
to only truthy when you actually block submit) so the input isn’t announced as
invalid while still keeping aria-describedby={duplicateTitleExists ?
`${newPageTitleFieldId}-dup` : undefined} and the duplicateTitleExists logic for
the warning text.

---

Outside diff comments:
In `@server/api/src/routes/notes/search.ts`:
- Around line 176-215: The query is returning pc.content_text and exposing it in
the public results; remove that by stopping selection of pc.content_text in the
SQL, deleting the content_text property from the SearchRow type, and ensuring
the public payload from results (the visible.map code) does not include
content_text; specifically, remove "pc.content_text" from the SELECT, remove
content_text: string | null from the SearchRow type, and verify the results
mapping (currently destructuring updated_at_iso out) will no longer carry
content_text to the wire (so nextCursor/encodeSearchCursor logic remains
unchanged).

In `@src/hooks/useNoteQueries.ts`:
- Around line 970-999: The hook useNotePage currently prevents unsigned users
from fetching pages by including isSignedIn in the enabled condition; remove the
isSignedIn gate so guests can call GET /api/pages/:pageId (which is now
authOptional). Update the query's enabled flag in useNotePage to be enabled &&
isLoaded && !!noteId && !!pageId (leave other logic like the note_id check and
the queryFn using api.getPage and apiPageToPage unchanged) so unsigned viewers
can fetch public/unlisted pages while preserving the existing safety checks.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 22c5f30e-4df1-4009-b8e7-239b5e82b6bb

📥 Commits

Reviewing files that changed from the base of the PR and between 27a33e7 and b4c60a8.

📒 Files selected for processing (31)
  • server/api/src/__tests__/routes/notes/crud.test.ts
  • server/api/src/__tests__/routes/notes/page-titles.test.ts
  • server/api/src/__tests__/routes/notes/search.test.ts
  • server/api/src/__tests__/routes/pages.test.ts
  • server/api/src/routes/notes/crud.ts
  • server/api/src/routes/notes/index.ts
  • server/api/src/routes/notes/search.ts
  • server/api/src/routes/notes/titleIndex.ts
  • server/api/src/routes/notes/types.ts
  • server/api/src/routes/pages.ts
  • src/components/editor/TiptapEditor/useTagStatusSync.test.tsx
  • src/components/editor/TiptapEditor/useTagStatusSync.ts
  • src/components/editor/TiptapEditor/useWikiLinkNavigation.test.ts
  • src/components/editor/TiptapEditor/useWikiLinkNavigation.ts
  • src/components/editor/TiptapEditor/useWikiLinkStatusSync.test.tsx
  • src/components/editor/TiptapEditor/useWikiLinkStatusSync.ts
  • src/components/note/NoteSearchBar.tsx
  • src/hooks/useAIChatActions.ts
  • src/hooks/useNotePageEvents.test.ts
  • src/hooks/useNotePageEvents.ts
  • src/hooks/useNoteQueries.ts
  • src/hooks/useWikiLinkCandidates.test.ts
  • src/hooks/useWikiLinkCandidates.ts
  • src/i18n/locales/en/notes.json
  • src/i18n/locales/ja/notes.json
  • src/lib/api/apiClient.ts
  • src/lib/api/types.ts
  • src/pages/NoteView/NoteAddPageDialog.tsx
  • src/pages/NoteView/NoteView.test.tsx
  • src/pages/NoteView/NoteViewAddPageDialogContent.tsx
  • src/pages/NoteView/index.tsx

Comment thread server/api/src/__tests__/routes/notes/page-titles.test.ts Outdated
Comment thread src/components/note/NoteSearchBar.tsx Outdated
Comment thread src/lib/api/types.ts
Comment thread src/pages/NoteView/NoteViewAddPageDialogContent.tsx Outdated
Five small fixes from CodeRabbit's review on PR #868:

- `/api/notes/:noteId/search` no longer SELECTs `pc.content_text`. The
  join stays for the ILIKE-on-body match, but the full body is dropped
  from the wire — at limit=100 with long pages this could otherwise
  balloon each search response into multiple megabytes. The matching
  field is also removed from `SearchRow` and `NoteSearchResultItem`.
- `NoteSearchResultItem.note_id` becomes non-nullable. The SQL hard-
  filters `p.note_id = :noteId`, so the legacy linked-personal (`null`)
  branch is unreachable here and the type was inviting unreachable
  consumer code. Stale mock rows that pretended to return `note_id:
  null` are dropped from the search tests, and a new assertion locks in
  the "no `content_text` on the wire" contract.
- `useNotePage` drops the `isSignedIn` gate. `GET /api/pages/:pageId`
  has been `authOptional` + `getNoteRole` since Phase 6, so guests
  should be able to fetch pages on public / unlisted notes — the hook
  was slamming a door the server explicitly opens.
- `NoteViewAddPageDialogContent`'s title input no longer sets
  `aria-invalid`. A duplicate title is an advisory warning that does
  not block submit, so announcing the field as invalid was a
  semantically wrong cue. `aria-describedby` and the warning text stay
  intact.
- Renames the misleading "returns 401 when private note is accessed
  unauthenticated" page-titles test to "returns 403" so the description
  matches the actual assertion (auth model is `authOptional` +
  `getNoteRole`, which 403s on no-role).
@otomatty otomatty merged commit cc57e30 into develop May 14, 2026
16 checks passed
@otomatty otomatty deleted the claude/issue-860-continuation-tp7vF branch May 14, 2026 04:03
@coderabbitai coderabbitai Bot mentioned this pull request May 18, 2026
11 tasks
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.

2 participants