Skip to content

refactor(front+server): 個人ページ(noteId === null)概念を根絶し Page.noteId を non-null 化 (#1020) - #1023

Merged
otomatty merged 4 commits into
developfrom
claude/github-issue-1020-egagp6
Jun 10, 2026
Merged

refactor(front+server): 個人ページ(noteId === null)概念を根絶し Page.noteId を non-null 化 (#1020)#1023
otomatty merged 4 commits into
developfrom
claude/github-issue-1020-egagp6

Conversation

@otomatty

@otomatty otomatty commented Jun 10, 2026

Copy link
Copy Markdown
Owner
  • server: GET /api/sync/pages の各ページ行に note_id を追加し、トップレベルに
    default_note_id を返す(クライアントのレガシー null 行移行用)
  • front(sync): pull 適用前に reassignNullNotePages で既存の noteId:null 行を
    デフォルトノートへ付け替え。push はデフォルトノート配下のみに限定
  • front(IndexedDB): PageMetadata.noteId を string に tighten。未移行の
    レガシー null 行は読み出しから除外し、同期時に自動移行
  • front(types): Page.noteId / PageSummary.noteId を string(non-null)へ再 tighten
    し、refactor(front): src/ の TypeScript strict 化(既存 tsc エラー解消 → CI typecheck 追加 → strict 有効化) #1011 で追加した消費側 null ガード・null 分岐を撤去
  • ゲストのローカルページ作成を廃止(refactor(collab): retire local Y.js mode and route all pages through Hocuspocus #889 以降 /notes/null/:pageId に遷移して
    実質機能していなかった)。createPageLocal / pageStore を削除し、FAB は
    未サインイン時に非表示
  • WikiLink ホバープレビューを pageStore からリポジトリ(IndexedDB)ベースの
    usePageByTitle / useGhostLinkReferenced に置き換え
  • 検索結果の「共有」バッジは noteId の有無ではなく結果ソースで判定
  • 呼び出し元のない importPersonalPageFromApi(note_id != null を拒否する
    ガード)を削除

[skip drizzle-check] サーバはルート実装のみの変更で、スキーマ変更なし

Closes #1020

https://claude.ai/code/session_01FH8KekbbgxZxsw5LLknKCQ

Summary by CodeRabbit

  • Refactor

    • Removed the legacy personal page model; all pages now belong to a note. Legacy personal pages are automatically remapped to your default note.
    • Updated page search to explicitly indicate shared vs. personal pages.
    • Simplified page edit permissions logic.
  • Chores

    • Removed uuid and @types/uuid dependencies.

…n-null 化 (#1020)

- server: GET /api/sync/pages の各ページ行に note_id を追加し、トップレベルに
  default_note_id を返す(クライアントのレガシー null 行移行用)
- front(sync): pull 適用前に reassignNullNotePages で既存の noteId:null 行を
  デフォルトノートへ付け替え。push はデフォルトノート配下のみに限定
- front(IndexedDB): PageMetadata.noteId を string に tighten。未移行の
  レガシー null 行は読み出しから除外し、同期時に自動移行
- front(types): Page.noteId / PageSummary.noteId を string(non-null)へ再 tighten
  し、#1011 で追加した消費側 null ガード・null 分岐を撤去
- ゲストのローカルページ作成を廃止(#889 以降 /notes/null/:pageId に遷移して
  実質機能していなかった)。createPageLocal / pageStore を削除し、FAB は
  未サインイン時に非表示
- WikiLink ホバープレビューを pageStore からリポジトリ(IndexedDB)ベースの
  usePageByTitle / useGhostLinkReferenced に置き換え
- 検索結果の「共有」バッジは noteId の有無ではなく結果ソースで判定
- 呼び出し元のない importPersonalPageFromApi(note_id != null を拒否する
  ガード)を削除

[skip drizzle-check] サーバはルート実装のみの変更で、スキーマ変更なし

Closes #1020

https://claude.ai/code/session_01FH8KekbbgxZxsw5LLknKCQ
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

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

@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 eradicates the legacy "personal page" concept (where noteId was null) by ensuring all pages belong to a note (Issue #1020). It retires the guest-local pageStore, updates the storage adapter and sync engine to migrate legacy rows to the default note, and restricts page creation to signed-in users. The review feedback highlights several key improvements: optimizing performance by avoiding redundant IndexedDB queries when page titles are empty in WikiLinkHoverCardLayer.tsx and AIChatWikiLink.tsx, caching the legacy migration in syncWithApi.ts using a localStorage flag to prevent scanning the database on every sync pull, and resolving a potential UI glitch in NotePageView.tsx by aligning isTitleEditable with canEdit.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/pages/NotePageView.tsx Outdated
? Boolean(access?.canEdit)
: canEdit && canEditTitle(userId ?? undefined, page);
const canEdit = canEditPage(access ?? undefined, page);
const isTitleEditable = Boolean(access?.canEdit);

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

If page is not yet loaded (i.e., null or undefined during loading states), isTitleEditable will evaluate to true if access?.canEdit is true. This can lead to UI rendering glitches or console errors when trying to edit a non-existent page title. Since canEdit already encapsulates both the access.canEdit check and the safety check for page existence, isTitleEditable should simply be assigned to canEdit.

Suggested change
const isTitleEditable = Boolean(access?.canEdit);
const isTitleEditable = canEdit;

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.

ee0b8e2 で修正しました。isTitleEditable = canEdit に揃え、canEdit(= canView + page 存在 + canEdit)を単一の判定源にしています。

Fixed in ee0b8e2isTitleEditable now aliases canEdit, which already encapsulates the canView / page existence checks.


Generated by Claude Code

// Issue #1020: adopt legacy `noteId: null` rows into the default note
// before applying the pull. Incremental pulls never resend old rows, so
// skipping this would leave legacy rows permanently unreadable.
await adapter.reassignNullNotePages(res.default_note_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.

medium

Calling reassignNullNotePages on every single sync pull triggers a full scan of the my_pages object store via an IndexedDB cursor. As the number of pages grows, this will become a significant performance bottleneck. Since this migration only needs to run once per user database, consider wrapping it in a one-time check using a localStorage flag keyed by the default_note_id.

  const migrationKey = `migration_null_note_pages_done_${res.default_note_id}`;
  if (!localStorage.getItem(migrationKey)) {
    await adapter.reassignNullNotePages(res.default_note_id);
    localStorage.setItem(migrationKey, "true");
  }

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.

問題意識は正しいですが、localStorage フラグは採用せず ee0b8e2 で代替実装にしました。

  • localStorage はユーザー(IndexedDB は zedi-storage-{userId} 単位)と分離されておらず、別ストレージにフラグを持つと「DB は消えたがフラグだけ残る」(resetDatabase 後など)といった分裂が起きます。
  • 代わりに IndexedDB の仕様(by_note index は noteId が null / 欠落の行を含まない)を利用し、store.count()index.count() の比較でレガシー行の有無を O(log n) で判定します。一致すればカーソル走査をスキップ。フラグ方式と違い、ロールアウト中の旧タブが null 行を書き込んでも次回同期で自己修復します。

Valid concern, but addressed with an alternative in ee0b8e2: instead of a localStorage flag (which lives outside the per-user IndexedDB and can desync after resetDatabase), reassignNullNotePages now compares store.count() vs the by_note index count — legacy rows are exactly the ones missing from the index — and skips the cursor scan when they match. Steady-state cost is two O(log n) counts per sync, and it self-heals if an old tab writes a null row during rollout.


Generated by Claude Code

// (IndexedDB)ベースのクエリでページ解決とゴースト参照判定を行う。
// The legacy guest store (`pageStore`) was retired by issue #1020; resolve
// the page and the ghost-reference state via the repository (IndexedDB).
const { data: resolvedPage } = usePageByTitle(target?.title ?? "");

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

When target is undefined (e.g., when the hover card is not active), target?.title evaluates to undefined, defaulting to "". This will trigger an unnecessary IndexedDB query for an empty title on every render. To optimize performance, consider passing an enabled option to usePageByTitle so the query is skipped when the title is empty.

Suggested change
const { data: resolvedPage } = usePageByTitle(target?.title ?? "");
const { data: resolvedPage } = usePageByTitle(target?.title ?? "", { enabled: !!target?.title });

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.

対応不要と判断しました。usePageByTitle は内部で enabled: isLoaded && title.trim().length > 0 をガードしており、空タイトルではクエリは発火しません(また、このフックは options 引数を受け取らないため提案のシグネチャはコンパイルできません)。useGhostLinkReferenced 側も同様に normalized.length > 0 でガード済みです。

No change needed: usePageByTitle already guards internally with enabled: isLoaded && title.trim().length > 0, so an empty title never triggers an IndexedDB query (and the hook takes no options parameter, so the suggested signature would not compile). useGhostLinkReferenced has the same internal guard.


Generated by Claude Code

// (IndexedDB)ベースのクエリでページ解決とゴースト参照判定を行う。
// The legacy guest store (`pageStore`) was retired by issue #1020; resolve
// the page and the ghost-reference state via the repository (IndexedDB).
const { data: resolvedPage } = usePageByTitle(normalizedTitle);

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

If normalizedTitle is empty, calling usePageByTitle will trigger an unnecessary IndexedDB query. To prevent redundant database access, consider passing an enabled option to skip the query when the title is empty.

Suggested change
const { data: resolvedPage } = usePageByTitle(normalizedTitle);
const { data: resolvedPage } = usePageByTitle(normalizedTitle, { enabled: !!normalizedTitle });

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.

対応不要と判断しました。usePageByTitle は内部で enabled: isLoaded && title.trim().length > 0 をガードしており、空タイトルでクエリは発火しません(options 引数も受け取らないため提案のシグネチャはコンパイルできません)。

No change needed: usePageByTitle already has the internal guard enabled: isLoaded && title.trim().length > 0, so an empty title never hits IndexedDB (and the hook takes no options parameter).


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@otomatty, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 6 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 54585901-18e8-402b-a150-c3b811884bb7

📥 Commits

Reviewing files that changed from the base of the PR and between 9432a97 and eb6cd52.

📒 Files selected for processing (22)
  • src/components/aiChat/AIChatWikiLink.tsx
  • src/components/aiChat/PromoteToWikiDialog.tsx
  • src/components/editor/TiptapEditor/WikiLinkHoverCardLayer.tsx
  • src/components/editor/TiptapEditor/useWikiLinkNavigation.ts
  • src/components/layout/FloatingActionButton.tsx
  • src/components/note/NotePagePublicView.tsx
  • src/components/page/LinkGroupRow.tsx
  • src/components/page/LinkSection.tsx
  • src/components/page/LinkedPagesSection.tsx
  • src/components/search/SearchResultCard.tsx
  • src/hooks/pages/useLinkedPages.ts
  • src/hooks/pages/usePageQueries.ts
  • src/hooks/search/useGlobalSearch.ts
  • src/hooks/wiki/useSyncWikiLinks.test.ts
  • src/hooks/wiki/useWikiLinkCandidates.test.ts
  • src/hooks/wiki/useWikiLinkCandidates.ts
  • src/lib/storageAdapter/IndexedDBStorageAdapter.test.ts
  • src/lib/storageAdapter/IndexedDBStorageAdapter.ts
  • src/lib/sync/syncWithApi.test.ts
  • src/pages/NotePageView.test.tsx
  • src/pages/NotePageView.tsx
  • src/pages/SearchResults.tsx
📝 Walkthrough

Walkthrough

This PR retires the legacy "personal page" concept (noteId===null) by ensuring all pages belong to exactly one note. The server now provides default_note_id and per-row note_id, the storage layer gains migration logic for legacy null-noteId rows, sync orchestrates the migration, and all component/hook interactions eliminate nullable noteId checks.

Changes

Personal Page Concept Retirement and noteId Non-Nullification

Layer / File(s) Summary
Core domain type tightening
src/types/page.ts, src/lib/storageAdapter/types.ts
Page.noteId, PageSummary.noteId, and PageMetadata.noteId change from string | null to string. All pages now belong to exactly one note; legacy "personal page" (null) model is retired.
Server sync response contract
src/lib/api/types.ts, server/api/src/routes/syncPages.ts, server/api/src/__tests__/routes/syncPages.test.ts
SyncPagesResponse gains required default_note_id field, and GET /api/sync/pages now returns note_id on each page row so clients can remap legacy null-noteId rows during pull.
Storage adapter migration and read filtering
src/lib/storageAdapter/StorageAdapter.ts, src/lib/storageAdapter/IndexedDBStorageAdapter.ts, src/lib/storageAdapter/IndexedDBStorageAdapter.test.ts
IndexedDBStorageAdapter gains reassignNullNotePages(noteId) public method to migrate legacy noteId:null rows in a readwrite cursor transaction, introduces hasNoteId type guard to exclude unmaterialized noteId during reads, and removes ?? null coercion from pageToStored and storedToPage.
Repository noteId handling and API-only creation
src/lib/pageRepository/StorageAdapterPageRepository.ts, src/lib/pageRepository/StorageAdapterPageRepository.test.ts, src/lib/pageRepository.test.ts
Removes guest/local page creation path; createPage always delegates to API. Removes public method importPersonalPageFromApi. Adjusts syncPageItemToMetadata, metadataToPage, metadataToPageSummary to no longer coerce noteId to null fallbacks. Test mocks include reassignNullNotePages stub and use DEFAULT_NOTE_ID for all expectations.
Sync engine default_note_id orchestration
src/lib/sync/syncWithApi.ts, src/lib/sync/syncWithApi.test.ts
normalizeSyncResponse requires and validates default_note_id, applyPull calls adapter.reassignNullNotePages() before pull, syncPageToMetadata uses defaultNoteId fallback for missing per-row note_id, and getPagesForPush constrains push scope to pages within the default note. All sync tests updated with DEFAULT_NOTE_ID constant and mock responses including default_note_id.
WikiLink resolution hook migration
src/hooks/usePageQueries.ts, src/components/aiChat/AIChatWikiLink.tsx, src/components/editor/TiptapEditor/WikiLinkHoverCardLayer.tsx, src/components/aiChat/PromoteToWikiDialog.tsx
Replace legacy usePageStore selectors with repository-backed usePageByTitle and new useGhostLinkReferenced hooks. pageKeys gains ghostReferenced(userId, linkText) query-key factory. useCheckGhostLinkReferenced removed and replaced with reactive useGhostLinkReferenced(linkText) hook. Components update to treat first non-null creation result as successful.
Component noteId handling and callback tightening
src/components/page/LinkGroupRow.tsx, src/components/page/LinkSection.tsx, src/components/page/LinkedPagesSection.tsx, src/components/layout/FloatingActionButton.tsx, src/pages/NotePageView.tsx, src/components/note/NotePagePublicView.tsx, src/pages/NotePageView.test.tsx
Component callbacks and internal state remove nullable noteId checks. Link callbacks tighten onPageClick to require non-null noteId. FloatingActionButton returns null for unsigned guests instead of rendering with hidden options. NotePageView removes userId-based owner override logic and passes noteId directly; simplifies edit/title-edit derivation. Tests updated to verify note-native read-only behavior and remove personal-page-scoped assertions.
Search result isShared semantics
src/components/search/SearchResultCard.tsx, src/hooks/useGlobalSearch.ts, src/pages/SearchResults.tsx
SearchResultCardPageItem tightens noteId to non-null string and adds explicit isShared: boolean. GlobalSearchPageResultItem.noteId changed from string | null to string. SearchResults annotates personal results as isShared: false, shared results as isShared: true. Dedup logic documentation emphasizes pageId-based matching against local IDB.
Test fixture noteId defaults
src/components/wikiLink/WikiLinkPreviewContent.test.tsx, src/lib/dateUtils.test.ts, src/lib/searchUtils.test.ts, src/hooks/useWikiLinkCandidates.test.ts, src/hooks/useSyncWikiLinks.test.ts
Test helpers and mocks updated to set noteId to "note-default" instead of null across all fixture data. Changes affect page/summary objects in mocked repository returns, sync payloads, and search test utilities. No test assertions or logic changed.
Deprecated code and dependency removal
src/stores/pageStore.ts, src/stores/pageStore.test.ts, package.json, knip.json
Removed usePageStore Zustand guest-mode store (271 lines, including CRUD, link/ghost-link helpers, and persist migration). Removed entire test suite (324 lines). Removed uuid and @types/uuid dependencies from package.json and knip.json.
Scope documentation updates
src/components/editor/TiptapEditor/useWikiLinkNavigation.ts, src/hooks/useWikiLinkCandidates.ts, src/lib/syncWikiLinks.ts, src/hooks/usePageQueries.ts
Update JSDoc and inline comments to clarify pageNoteId scope contract: null = local/default-note IndexedDB scope with /notes/:noteId/:id navigation, non-null string = explicit note scope with external candidate list and /notes/:pageNoteId/:id navigation. Added issue #1020 references. No functional logic changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • otomatty/zedi#722: Introduces/propagates pages.note_id semantics and sync-scoping changes including server syncPages.ts foundation that this PR extends with default_note_id.
  • otomatty/zedi#831: Prior "migrate personal pages into default note" work that this PR refines with the storage adapter reassignNullNotePages implementation.
  • otomatty/zedi#717: Core implementation of pageNoteId-scoped WikiLink candidate resolution that this PR builds on with updated hook-based resolution.

🐰 Null pages no more, the noteId stands tall,
Every page now owns a note, never left to call,
Default notes step in when legacy rows appear,
Personal pages vanish—non-null reigns here!
Migration done with grace, the schema's crystal clear.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.09% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main change: eliminating the personal page (noteId === null) concept and making Page.noteId non-null.
Linked Issues check ✅ Passed The PR comprehensively addresses all acceptance criteria from #1020: Page/PageSummary/PageMetadata.noteId are now non-null strings; null branches are removed from storage, IndexedDB, and sync layers; guest functionality is discontinued; existing null rows have migration paths via reassignNullNotePages.
Out of Scope Changes check ✅ Passed All changes directly support the #1020 objectives of eliminating the personal page concept and non-null-ifying noteId fields across server, sync, storage, and consumer code.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/github-issue-1020-egagp6

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

- NotePageView: isTitleEditable を canEdit に揃え、page 未ロード時の
  編集可能判定を排除
- IndexedDBStorageAdapter.reassignNullNotePages: store.count() と by_note
  index の count 比較によるレガシー行ゼロ時の高速パスを追加し、毎同期の
  全行カーソル走査を回避(localStorage フラグ案はユーザー間の漏れと
  ストレージ分裂があるため不採用)

https://claude.ai/code/session_01FH8KekbbgxZxsw5LLknKCQ

@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: 2

🧹 Nitpick comments (1)
src/lib/sync/syncWithApi.test.ts (1)

686-724: 💤 Low value

Consider making the call ordering assertion more explicit.

The test verifies that reassign happens first via expect(callOrder[0]).toBe("reassign"), but doesn't explicitly assert that upsert also occurred. While the test setup will trigger both calls, adding an explicit check would make the test's intent clearer and more robust against future refactoring.

📝 Optional improvement
  expect(adapter.reassignNullNotePages).toHaveBeenCalledWith(DEFAULT_NOTE_ID);
  expect(callOrder[0]).toBe("reassign");
+ expect(callOrder).toContain("upsert");
+ expect(callOrder.indexOf("reassign")).toBeLessThan(callOrder.indexOf("upsert"));
🤖 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/lib/sync/syncWithApi.test.ts` around lines 686 - 724, The test currently
only asserts that "reassign" occurred first via callOrder[0]; update the test
around syncWithApi(adapter, api, TEST_USER_ID) to also explicitly assert that
the adapter.upsertPage path was invoked and that callOrder shows the expected
sequence (e.g., callOrder[1] === "upsert" and/or
expect(adapter.upsertPage).toHaveBeenCalled()). Reference the existing symbols
adapter.reassignNullNotePages, adapter.upsertPage, callOrder, and syncWithApi to
locate and update the assertions so the test verifies both invocation and
ordering explicitly.
🤖 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 `@src/components/layout/FloatingActionButton.tsx`:
- Around line 81-87: Before returning null when not signed in, explicitly reset
the transient FAB/dialog state so it can't persist across sign-in transitions:
call the state setters to close menu/dialogs and clear seeded clip URL (e.g.
setIsMenuOpen(false), setIsImageDialogOpen(false), setInitialClipUrl(undefined
or ''), and ensure isWebClipperOpen is cleared via setIsWebClipperOpen(false))
before the early return that checks isSignedIn.

In `@src/pages/NotePageView.tsx`:
- Around line 862-863: The inline spec/comment claiming only the page owner can
rename is outdated; update the JSDoc/TSDoc near the title-related prop (the doc
referenced around Line 136) to reflect the new permission model where
isTitleEditable is derived from access?.canEdit (and canEdit =
canEditPage(access ?? undefined, page)), i.e. title edits are permitted whenever
access.canEdit is true. Locate references to isTitleEditable, canEditPage, and
the title/rename prop doc in NotePageView.tsx and replace the owner-only wording
with a concise statement that title renaming is allowed when access.canEdit is
true.

---

Nitpick comments:
In `@src/lib/sync/syncWithApi.test.ts`:
- Around line 686-724: The test currently only asserts that "reassign" occurred
first via callOrder[0]; update the test around syncWithApi(adapter, api,
TEST_USER_ID) to also explicitly assert that the adapter.upsertPage path was
invoked and that callOrder shows the expected sequence (e.g., callOrder[1] ===
"upsert" and/or expect(adapter.upsertPage).toHaveBeenCalled()). Reference the
existing symbols adapter.reassignNullNotePages, adapter.upsertPage, callOrder,
and syncWithApi to locate and update the assertions so the test verifies both
invocation and ordering explicitly.
🪄 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: a2e52e33-818c-464e-92f6-0d701fea9df8

📥 Commits

Reviewing files that changed from the base of the PR and between eb1fd6e and 9432a97.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • knip.json
  • package.json
  • server/api/src/__tests__/routes/syncPages.test.ts
  • server/api/src/routes/syncPages.ts
  • src/components/aiChat/AIChatWikiLink.tsx
  • src/components/aiChat/PromoteToWikiDialog.tsx
  • src/components/editor/TiptapEditor/WikiLinkHoverCardLayer.tsx
  • src/components/editor/TiptapEditor/useWikiLinkNavigation.ts
  • src/components/layout/FloatingActionButton.tsx
  • src/components/note/NotePagePublicView.tsx
  • src/components/page/LinkGroupRow.tsx
  • src/components/page/LinkSection.tsx
  • src/components/page/LinkedPagesSection.tsx
  • src/components/search/SearchResultCard.tsx
  • src/components/wikiLink/WikiLinkPreviewContent.test.tsx
  • src/hooks/useGlobalSearch.ts
  • src/hooks/useLinkedPages.ts
  • src/hooks/usePageQueries.ts
  • src/hooks/useSyncWikiLinks.test.ts
  • src/hooks/useWikiLinkCandidates.test.ts
  • src/hooks/useWikiLinkCandidates.ts
  • src/lib/api/types.ts
  • src/lib/dateUtils.test.ts
  • src/lib/pageRepository.test.ts
  • src/lib/pageRepository/StorageAdapterPageRepository.test.ts
  • src/lib/pageRepository/StorageAdapterPageRepository.ts
  • src/lib/searchUtils.test.ts
  • src/lib/storageAdapter/IndexedDBStorageAdapter.test.ts
  • src/lib/storageAdapter/IndexedDBStorageAdapter.ts
  • src/lib/storageAdapter/StorageAdapter.ts
  • src/lib/storageAdapter/types.ts
  • src/lib/sync/syncWithApi.test.ts
  • src/lib/sync/syncWithApi.ts
  • src/lib/syncWikiLinks.ts
  • src/pages/NotePageView.test.tsx
  • src/pages/NotePageView.tsx
  • src/pages/SearchResults.tsx
  • src/stores/pageStore.test.ts
  • src/stores/pageStore.ts
  • src/types/page.ts
💤 Files with no reviewable changes (5)
  • src/stores/pageStore.test.ts
  • src/stores/pageStore.ts
  • package.json
  • knip.json
  • src/pages/NotePageView.test.tsx

Comment thread src/components/layout/FloatingActionButton.tsx
Comment thread src/pages/NotePageView.tsx Outdated
- FloatingActionButton: 未サインイン遷移時に FAB のローカル UI 状態
  (メニュー / 画像ダイアログ)も畳む。initialClipUrl はサインイン往復
  (#826 handoff)を壊すため破棄しない
- NotePageView: isTitleEditable の prop ドキュメントを新権限モデル
  (access.canEdit ベース)に更新
- syncWithApi.test: reassign → upsert の呼び出し順アサーションを明示化

https://claude.ai/code/session_01FH8KekbbgxZxsw5LLknKCQ
@otomatty
otomatty merged commit f2f2579 into develop Jun 10, 2026
21 checks passed
@otomatty
otomatty deleted the claude/github-issue-1020-egagp6 branch June 10, 2026 08:17
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.

epic(front+server): 個人ページ(noteId === null)概念の根絶と Page.noteId の真の non-null 化

2 participants