Add metadata-only PUT endpoint and read-only GET endpoint for pages#888
Conversation
Y.js `local` モード廃止の Phase 1 として、以下を新設する。 - `PUT /api/pages/:id`: タイトル等のメタデータだけを更新する純粋な REST 経路。Y.Doc は Hocuspocus が一手に担うので、本ルートでバイト列は受けない。 既存の `applyPagesMetadataUpdate` / `tryPropagateTitleRename` / `emitPageUpdatedIfChanged` を再利用するため、PUT /:id/content と同じ ゲーティング(SSE emit、タイトル伝播)が維持される。 - `GET /api/pages/:id/public-content`: ゲスト / viewer 向けの読み取り専用 本文 API。`page_contents.content_text` だけを返し、Y.Doc バイト列は 含めない。`getNoteRole` で role を解決して private / restricted を 弾く。未ログインの guest は短期 `public, max-age=60` でエッジキャッシュ 可能、ログイン済みは `private, must-revalidate`。 これらは local モードを最終的に削除する後続 Phase の前提となる代替経路。 本 Phase では既存 `GET/PUT /content` には触れず、後続 PR で削除する。 Phase 1 — adds metadata + read-only content routes ahead of retiring the `local` collaboration mode. Editors will continue through Hocuspocus while read-only consumers (guests on public/unlisted notes, viewer-role members, MCP) move onto `public-content`. Existing `GET/PUT /:id/content` is intentionally untouched here; removal happens in a later PR. Test plan: - `cd server/api && bun run test` — 1278 tests pass - `bun run test:run` — full multi-package suite passes - `bun run lint` — 0 errors (warnings unchanged) - `bun run format:check` — clean - `cd server/api && bun run typecheck` — clean
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR introduces two new pages REST endpoints: ChangesPage Metadata and Public Content Endpoints
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces two new endpoints to the pages API: a metadata update route (PUT /api/pages/:id) and a read-only content route (GET /api/pages/:id/public-content). These changes support the transition away from the legacy local collaboration mode by separating metadata management from Y.Doc synchronization. The review feedback identifies a critical bug in the metadata update endpoint where a no-op request (sending current values) results in a response containing null values instead of the current resource state. Additionally, there is a recommendation to refine the TypeScript types to ensure updated_at is non-nullable, maintaining consistency with other API responses.
| return c.json({ | ||
| id: pageId, | ||
| title: updatedRow?.title ?? null, | ||
| content_preview: updatedRow?.contentPreview ?? null, | ||
| updated_at: updatedRow?.updatedAt?.toISOString() ?? null, | ||
| }); |
There was a problem hiding this comment.
applyPagesMetadataUpdate は、変更が検出されない場合(クライアントが現在と同じ値を送信した場合など)に updatedRow: null を返します。その結果、この実装では title、content_preview、updated_at が null として返されてしまいます。PUT エンドポイントは、実際に更新が行われたかどうかにかかわらず、リソースの現在の状態を返すべきです。
共有ヘルパーである applyPagesMetadataUpdate を修正して常に現在の行を返すようにするか、このルート内で updatedRow が null の場合に現在のデータを取得するように修正することを検討してください。
| body: JSON.stringify({ title: "Same Title", content_preview: "Same Preview" }), | ||
| }); | ||
|
|
||
| expect(res.status).toBe(200); |
There was a problem hiding this comment.
このテストケースでは、更新がスキップされた場合でもレスポンスボディに現在のページメタデータが正しく含まれていることを確認するようにしてください。現状の実装(no-op 時に null を返すバグ)を検出するために重要です。
| expect(res.status).toBe(200); | |
| expect(res.status).toBe(200); | |
| const body = (await res.json()) as Record<string, unknown>; | |
| expect(body).toMatchObject({ | |
| id: PAGE_ID, | |
| title: "Same Title", | |
| content_preview: "Same Preview", | |
| }); |
| id: string; | ||
| title: string | null; | ||
| content_preview: string | null; | ||
| updated_at: string | null; |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b528f9182
ℹ️ 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".
| title: updatedRow?.title ?? null, | ||
| content_preview: updatedRow?.contentPreview ?? null, | ||
| updated_at: updatedRow?.updatedAt?.toISOString() ?? null, |
There was a problem hiding this comment.
Return current metadata for no-op PUT updates
When PUT /api/pages/:id receives values that already match the stored metadata, applyPagesMetadataUpdate returns updatedRow: null and this response path emits title, content_preview, and updated_at as null. In that common round-trip scenario, clients that trust the response can overwrite valid local metadata with nulls even though no data changed server-side; the endpoint should return the current persisted values (or avoid nullable success fields) for no-op updates.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
server/api/src/__tests__/routes/pages.test.ts (1)
770-785: ⚡ Quick winAssert response payload in the no-op metadata test, not just UPDATE count.
This test currently verifies only DB-chain behavior. Add body assertions so a null-field response regression is caught.
Proposed test tightening
it("skips the UPDATE when title matches current value (PR `#867` invariant)", async () => { @@ expect(res.status).toBe(200); + const body = (await res.json()) as Record<string, unknown>; + expect(body).toMatchObject({ + id: PAGE_ID, + title: "Same Title", + content_preview: "Same Preview", + }); const updateChains = chains.filter((c) => c.startMethod === "update"); expect(updateChains.length).toBe(0); });🤖 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/__tests__/routes/pages.test.ts` around lines 770 - 785, The test "skips the UPDATE when title matches current value (PR `#867` invariant)" only asserts DB chain behavior; update it to also assert the response payload so a null-field regression is caught: after receiving res from app.request(`/api/pages/${PAGE_ID}`, ...) call await res.json() and assert the returned body contains the expected metadata fields (e.g., title === "Same Title" and content_preview === "Same Preview" or non-null metadata object) and that status is 200; reference createPagesAppWithChains, pageAccessPrefix, authHeaders, PAGE_ID and chains to locate the test and add these JSON body assertions alongside the existing updateChains length check.
🤖 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/routes/pages.ts`:
- Around line 911-924: Validate that incoming metadata fields are strings before
calling applyPagesMetadataUpdate: check that if body.title is present it is of
type string (otherwise throw new HTTPException(400, { message: "title must be a
string" })) and likewise for body.content_preview; perform these checks
immediately after parsing body and before assertPageEditAccess /
applyPagesMetadataUpdate so malformed JSON like { "title": 123 } returns a 400
instead of causing body.title.trim() inside applyPagesMetadataUpdate to throw.
- Around line 924-936: The handler currently returns null
title/content_preview/updated_at when applyPagesMetadataUpdate returns
updatedRow === null, which can clobber client state; fix by when updatedRow is
null, query the current page record (using db and pageId) to read the existing
title, contentPreview and updatedAt and use those values in the JSON response
instead of null; keep the existing flow for renamed/emitPageUpdatedIfChanged
(renamed, metadataChanged) unchanged and only use the fallback read when
updatedRow is null so responses always contain the current page fields.
---
Nitpick comments:
In `@server/api/src/__tests__/routes/pages.test.ts`:
- Around line 770-785: The test "skips the UPDATE when title matches current
value (PR `#867` invariant)" only asserts DB chain behavior; update it to also
assert the response payload so a null-field regression is caught: after
receiving res from app.request(`/api/pages/${PAGE_ID}`, ...) call await
res.json() and assert the returned body contains the expected metadata fields
(e.g., title === "Same Title" and content_preview === "Same Preview" or non-null
metadata object) and that status is 200; reference createPagesAppWithChains,
pageAccessPrefix, authHeaders, PAGE_ID and chains to locate the test and add
these JSON body assertions alongside the existing updateChains length check.
🪄 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: ebe0938c-e191-4dcc-8318-f431da7f02c1
📒 Files selected for processing (4)
server/api/src/__tests__/routes/pages.test.tsserver/api/src/routes/pages.tssrc/lib/api/apiClient.tssrc/lib/api/types.ts
PR #888 のレビューフィードバックに対応。 - gemini-code-assist / chatgpt-codex / coderabbitai 全員が指摘した 「no-op PUT で title/content_preview/updated_at が null で返り、 クライアントの正規キャッシュを破壊しうる」問題を修正。 `applyPagesMetadataUpdate` が UPDATE を skip して `updatedRow=null` を 返す場合に限り、現在の行を追加 SELECT してその値を返す。 - coderabbitai 指摘の型バリデーション欠落を修正。`body.title` / `body.content_preview` が非文字列の場合に、ヘルパー内の `.trim()` で 500 になる前にルート境界で 400 を返す。 - gemini-code-assist 指摘に従い `UpdatePageMetadataResponse.updated_at` を non-nullable に修正 (`pages.updated_at` はスキーマ上 notNull)。 - テストを拡張: no-op 経路がレスポンスボディに現在値を含めて返すこと、 および title/content_preview の非文字列入力で 400 を返すことを検証。 Test plan: - `cd server/api && bun run test` — 1280 tests pass (+2 new) - `bun run lint` / `bun run format:check` — clean - `cd server/api && bun run typecheck` — clean - root frontend typecheck — clean Refs: PR #888 review threads (HIGH/Major priority)
… Phase 2) (#893) * refactor(note): introduce NotePagePublicView for read-only guests (#889 Phase 2) Y.js `local` モード廃止の Phase 2。`PageEditorContent` を `src/components/note/` 配下に物理移動し、ゲスト / viewer 向けの読み取り専用 UI として新規 `NotePagePublicView` を追加する。 - `src/components/editor/PageEditor/` から `src/components/note/` へ `PageEditorContent.tsx` / `PageTitleBlock.tsx` / `PageTitleBlock.test.tsx` を `git mv` で移動。Phase 3 で `/pages/:id` ルートを削除する前提に向けて、 note 配下で完結する描画コンポーネントを集約する。`PageEditorContent` の 内部 import は絶対パスに書き換え、`WikiGeneratorStatus` は canonical な `@/hooks/useWikiGenerator` 由来に統一 (Phase 3 で旧 `types.ts` ごと削除)。 - 新規 `NotePagePublicView` は Phase 1 で追加した `GET /api/pages/:id/public-content` から `content_text` を取得し、 `convertMarkdownToTiptapContent` で Tiptap doc に変換した上で `PageEditorContent` を `isReadOnly` で再利用して描画する。Y.Doc / WebSocket は一切張らない。403 / 404 / その他エラーに応じた文言と再試行 UI を提供。 - `NotePageView` の `!canEdit` 分岐 (guest + viewer) を `NotePagePublicView` への委譲に置換。`useCollaboration` API は変更せず、既存の `canEdit && isSignedIn` ゲートでこれまで通り WebSocket は張られない。 - 既存テスト (`NotePageView.test.tsx`) を新しい branch 構造に合わせて更新。 `keeps note-native pages read-only` テストはタイトル編集 UI が `NotePagePublicView` 内に存在しないことを構造的に保証する形に書き換えた。 新規 `NotePagePublicView.test.tsx` で loading / success (Tiptap 変換) / title fallback / null content / 403 / 404 / 再試行を網羅。 Phase 2 — relocates `PageEditorContent` into `src/components/note/` and introduces `NotePagePublicView`, a Y.Doc-free read-only renderer that covers both `guest` (anonymous) and `viewer`-role members. The `NotePageView` `!canEdit` branch now delegates to it, paving the way for Phase 3's `/pages/:id` route removal and Phase 4's deletion of the legacy `GET/PUT /api/pages/:id/content` endpoints. `useCollaboration`'s API is untouched. Test plan: - `bunx vitest run src/components/editor src/components/note src/pages` — 79 files / 623 tests pass - `bun run lint` — 0 errors - `bun run format:check` — clean - `bunx tsc --noEmit --ignoreDeprecations 6.0 -p tsconfig.app.json` — no new errors in touched files (pre-existing unrelated errors elsewhere) Refs: Issue #889, PR #888 (Phase 1) https://claude.ai/code/session_016u6uKXnWVPobXkGjKnmLeR * fix(note): wire read-only Markdown export to public-content body (#893) Codex P1 (PR #893 review) で指摘された不整合への修正。`NotePageReadOnly` の `useMarkdownExport` は `page.content` を読んでいたが、`apiPageToPage` (`src/hooks/useNoteQueries.ts:295-297`) はメタデータ取得時に `content: ""` を強制セットしているため、画面に本文が見えていても export / copy で生成 される Markdown が常に空になる回帰だった。 - `usePagePublicContent` を新規切り出し (`src/hooks/usePagePublicContent.ts`)。 `["page-public-content", pageId]` のクエリキーを `NotePagePublicView` (本文描画) と `NotePageReadOnly` (Markdown export / copy のソース) で共有し、 TanStack Query 上で 1 リクエストに dedup させる。 - `NotePagePublicView` を新フック経由に切替。挙動は不変。 - `NotePageReadOnly` で `usePagePublicContent` を呼び、`content_text` を `useMarkdownExport` の本文引数に渡す。タイトルも response 側を優先。 - 公開コンテンツ未到着の間は `export-markdown` / `copy-markdown` メニュー 項目を `disabled` にし、ロード前に空 Markdown を吐く動線を塞ぐ。 - 既存テスト (`NotePageView.test.tsx`) を新フックの mock に対応。 `useMarkdownExport` モックを引数キャプチャ式に書き換え、Codex 指摘の 回帰テスト 2 件 (export ソースが `content_text` を使うこと、ロード中は メニュー項目が disabled になること) を追加。 Test plan: - bunx vitest run src/hooks src/components/note src/pages — 59 ファイル / 554 テスト全通過 - bunx eslint src/hooks/usePagePublicContent.ts src/components/note/NotePagePublicView.tsx src/pages/NotePageView.{tsx,test.tsx} — 0 errors - bun run format:check — clean Refs: PR #893 review (Codex P1) https://claude.ai/code/session_016u6uKXnWVPobXkGjKnmLeR * docs(note): refresh NotePageReadOnly contract comment for public-content source CodeRabbit nitpick (PR #893): `NotePageReadOnly` の docstring が古く、export / copy のソースが `page.title` / `page.content` だと記述していたが、実際の実装は 直前のコミット (`83aee43`) で `usePagePublicContent` 経由 (`publicContent.title` / `content_text`) に切り替わっている。実装と仕様コメントの食い違いを解消する。 コメント本体に以下を追記: - 本文表示と Markdown export / copy の両ソースが `usePagePublicContent` 由来 - `page.content` を読まない理由 (`apiPageToPage` が `""` に落とすため、export が 常に空になる Codex P1 回帰になっていた) - `NotePagePublicView` と同じフックを使うので TanStack Query が 1 リクエストに dedup する 挙動変更はゼロ。docstring 文言のみの更新。 Test plan: - bun run format:check — clean - bunx eslint src/pages/NotePageView.tsx — 0 errors (pre-existing size 警告のみ) Refs: PR #893 review (CodeRabbit nitpick) https://claude.ai/code/session_016u6uKXnWVPobXkGjKnmLeR --------- Co-authored-by: Claude <noreply@anthropic.com>
#889 Phase 3) (#894) * refactor(#892): retire local Y.js mode and /pages/:id route (#889 Phase 3) Issue #889 段階的リファクタの Phase 3。Phase 1 (#888) でメタデータ専用 `PUT /api/pages/:id` + 読み取り専用 GET ルートを、Phase 2 (#893) で `NotePagePublicView` を整備した上で、残った大本命のクリーンアップとして 以下を完全に廃止する。 - `CollaborationManager` の `local` モード(IndexedDB 同期と並行に `GET/PUT /api/pages/:id/content` を debounce で叩いて Y.Doc を REST 保存する経路)を撤去。全ページは所属ノートを持つので、Hocuspocus WebSocket 同期に統一する。 - `/pages/:id` および `/page/:id` ルートを撤去。ノートネイティブ経路 `/notes/:noteId/:pageId` に統合し、`useCreatePage` の戻り値が常に `noteId` を持つことを前提に 16 箇所の `navigate(...)` を書き換えた。 - `useCollaboration` API から `mode` / `flushSave` / `setPageTitle` を撤去 し、`PageEditor/` 配下の重複コンポーネント・フック 22 ファイルを削除。 - Web Clipper / AI チャット / PromoteToWiki / WikiLink dialog などの 作成フローは `navigate("/notes/:noteId/:pageId", { state: { initialContent } })` 経由で `NotePageView` に seed を渡し、Hocuspocus `synced` 後に Y.Doc に 反映する形式に切り替えた。 - サーバ側は `POST /api/pages` レスポンスに `note_id` を追加。PDF 派生 ページ・ハイライト一覧・グローバル検索の各レスポンスに `note_id` を 同梱して、クライアントが `/notes/:noteId/:pageId` を組み立てられるように した。`GET/PUT /api/pages/:id/content` 本体や `snapshotService.ts` の 削除は Phase 4 に温存(移行期セーフネット)。 - Phase 3 で `/pages/:id` 専用の `e2e/page-editor.spec.ts` と `e2e/wikilink-create-dialog.spec.ts` を削除し、`e2e/auth-mock.ts` の `createNewPage` helper を `/notes/:noteId/:pageId` URL を待つように更新。 Issue #889 phase 3 — retires the legacy `local` Y.js REST path and the top-level `/pages/:id` route. Every page now syncs through Hocuspocus and navigates under its owning note. `useCollaboration` loses `mode` / `flushSave` / `setPageTitle`; create flows pass an `initialContent` seed via React Router state and the editor applies it after the initial sync. The server's `POST /api/pages` response, the derive-page handler, the highlight list, and the global search rows now all carry the derived page's `note_id` so the client never has to ask twice. Phase 4 will delete the now-orphaned `/api/pages/:id/content` endpoints. Test plan: - `bun run lint` — 0 errors (621 pre-existing warnings) - `bun run format:check` — clean - `bunx vitest run` (main) — 229 files / 2327 tests pass - `cd server/api && bunx vitest run` — 95 files / 1280 tests pass Refs: Issue #892, Phase 1 (#888), Phase 2 (#893) https://claude.ai/code/session_01CVtupQrUS23UEerQJgPgLH * fix(#892): finish /pages/:id sweep + guard noteId on note-scoped nav PR #894 のレビューフィードバックを反映。Codex P1 二件と CodeRabbit Major 複数件への対応。 - 取り残されていた `/pages/:id` リンク発火を全て撤去(Codex P1): - `AIChatWikiLink` の `<Link to="/pages/...">` を `page.noteId` 付きに。 - `Onboarding` 完了後の `welcome_page_id` 遷移は新規 `welcome_page_note_id` と組で `/notes/:noteId/:pageId` へ。サーバ側 `WelcomePageCreationResult` に `noteId` を追加し、`POST /api/onboarding/complete` / `GET /api/onboarding/status` も `welcome_page_note_id` を返す。 - `IndexPage` の `__index__` ページリンクは `/api/activity/index` / `/api/activity/index/rebuild` が新たに返す `noteId` を使う。 `PersistIndexResult` / `rebuildIndexForOwner` も `noteId` を伝搬。 - `NotePageView` の `pendingInitialContent` を location.key 変更で再水和 できるよう同期 derived-state 化(Codex P1: NotePageView マウント中の intra-route navigation で seed が落ちていた)。`useEffect + setState` は `you-might-not-need-an-effect` ルールに引っかかるため避け、ref 経由の 最終適用キー記録は新 ルールの "Cannot access refs during render" を 踏むため、適用後の navigate クリアが `locationStateInitialContent` を undefined にする副作用に依存して二重適用を防ぐ。 - `noteId` 欠落時に `/notes/undefined/...` を組み立てるのを防ぐガードを AI チャット作成系・PromoteToWikiDialog に追加(CodeRabbit Major)。 - `useCreateNewPage` / `useFloatingActionButtonHandlers` の link-failed パスを「ページは作成済み」前提でデフォルトノート配下にフォールバック 遷移し、`common.attachPageToNoteFailed` (新規 i18n key) で正確な失敗 内容を伝える(CodeRabbit Minor)。 - `NotePageView` の冗長な `?? undefined` を削除(CodeRabbit nitpick)。 - Knip 失敗 (`src/hooks/useTitleValidation.ts` が unused) を解消。Phase 3 で `PageEditor/usePageEditorState.ts` が唯一の consumer だったので ファイル自体も削除した。 PR #894 review responses (Codex P1 + CodeRabbit Major): - Migrate the remaining `/pages/:id` emitters Codex caught: `AIChatWikiLink`, `Onboarding` welcome-page landing, and the `IndexPage` `__index__` link. Server-side adds `welcome_page_note_id` to the onboarding endpoints and `noteId` to the activity-index GET / rebuild responses + the `PersistIndexResult` shape. - Rehydrate `pendingInitialContent` on intra-mount `/notes/:noteId/:pageId` navigations (Codex P1). Uses the synchronous "derived state from location.key" pattern to satisfy the new `you-might-not-need-an-effect` lint rule without reading refs during render. - Guard `noteId` (alongside `id`) before building `/notes/:noteId/:pageId` in `runAIChatAction`, `useAIChatActions`, and `PromoteToWikiDialog` so partial backend responses never produce `/notes/undefined/...`. - `useCreateNewPage` / `useFloatingActionButtonHandlers` now fall back to the page's default-note URL when `addPageToNoteMutation` fails (the page itself was created successfully) and surface a new `common.attachPageToNoteFailed` toast so the message matches the actual failure (CodeRabbit Minor). - Drop the unused `useTitleValidation` hook (only consumer was the Phase-3-deleted `usePageEditorState.ts`) — this was the Knip CI failure. Test plan: - `bun run lint` — 0 errors - `bun run format:check` — clean - `DATABASE_URL=postgres://... bunx knip` — clean, exit 0 - `bunx vitest run` (main) — 229 files / 2327 tests pass - `cd server/api && bunx vitest run` — 95 files / 1280 tests pass Refs: PR #894 (#894) https://claude.ai/code/session_01CVtupQrUS23UEerQJgPgLH * fix(#892): tighten Phase 3 review nits (stale seed, conflict-read, bilingual) PR #894 の追加レビュー(CodeRabbit)への対応。 - `NotePageView` で `location.key` が変化するたびに `pendingInitialContent` を現在の location.state から無条件に再水和する ように修正。state なしの隣ページ遷移で未消費 seed が残り、 `<NotePageEditorEditable key={page.id}>` 再マウント先に古い initialContent が渡る回帰を防ぐ。 - `welcomePageService.findExistingWelcomePage` の戻り値を `{ id, noteId }` に拡張し、conflict-read パスでも永続化済みの note_id を返すように。 並行 insert で勝者ノートと自前で resolve したデフォルトノートがずれた 場合に、不正な遷移先 URL を生成しないようにする。 - `apiClient.getOnboardingStatus` の `welcome_page_note_id` と `IndexPage` の `IndexRebuildResponse.noteId` / `IndexViewModel.noteId` のコメントをバイリンガル化(プロジェクト規約 `**/*.{ts,tsx,js,md}` は 日本語・英語の両方を要求)。 CodeRabbit follow-up review fixes: - `NotePageView`: unconditionally re-sync `pendingInitialContent` from the current location on every `location.key` change. Without this, a route change without `location.state.initialContent` retained the prior pending seed and could leak it into the next `key={page.id}` remount. - `welcomePageService`: return the persisted `note_id` from the conflict-read branch so a concurrent insert that landed in a different note than the local `ensureDefaultNote` resolution still produces a valid `/notes/:noteId/:pageId` URL. - `apiClient.ts` / `IndexPage.tsx`: extend the new shorthand TSDoc to be bilingual per the repo coding guideline. Test plan: - `bun run lint` — 0 errors - `bun run format:check` — clean - `bunx vitest run src/pages/NotePageView` — 29 tests pass - `cd server/api && bunx vitest run src/__tests__/routes/onboarding.test.ts` — 11 tests pass Refs: PR #894 (#894) https://claude.ai/code/session_01CVtupQrUS23UEerQJgPgLH --------- Co-authored-by: Claude <noreply@anthropic.com>
概要
localコラボレーションモード廃止に伴い、ページメタデータ(タイトル・プレビュー)の更新と読み取り専用アクセスのための新しい REST エンドポイントを追加します。PUT /api/pages/:id— メタデータのみ更新(Y.Doc は Hocuspocus 経由)GET /api/pages/:id/public-content— 読み取り専用の本文取得(ゲスト・viewer 用)変更点
PUT /api/pages/:idエンドポイント追加content_previewのメタデータ更新に特化applyPagesMetadataUpdate、tryPropagateTitleRename、emitPageUpdatedIfChangedを再利用PUT /:id/contentと同等GET /api/pages/:id/public-contentエンドポイント追加content_textと派生情報のみ返すauthOptionalで未ログインゲストも public/unlisted ノートにアクセス可能public, max-age=60, must-revalidateでエッジキャッシュ可能private, must-revalidateで個人スコープに限定型定義追加 (
src/lib/api/types.ts)UpdatePageMetadataBodyUpdatePageMetadataResponsePagePublicContentResponseAPI クライアント追加 (
src/lib/api/apiClient.ts)updatePageMetadata()getPagePublicContent()テスト追加 (
server/api/src/__tests__/routes/pages.test.ts)PUT /api/pages/:idの 5 つのテストケースGET /api/pages/:id/public-contentの 6 つのテストケース変更の種類
テスト方法
bun testで新規テストがすべてパスすることを確認PUT /api/pages/:id/contentテストが引き続きパスすることを確認bun run lintとbun run format:checkが通ることを確認チェックリスト
関連 Issue
localモード廃止に伴う REST API の整備https://claude.ai/code/session_019PjuoaMdQiFcVdR96i9ATc
Summary by CodeRabbit