feat(editor): complete Wiki Compose P2 entry points and chat seed (#950)#961
Conversation
- Register PageActionHub wiki.compose action with compose navigation - Replace pendingChatPageGeneration with navigateToWikiCompose from AI chat - Pass chat seed through session metadata and graph chatSeed state - Wire wikiComposeHref into PageActionHub context from the page editor
📝 WalkthroughWalkthroughThis PR seeds Wiki Compose sessions with optional chat-context (outline, conversationText, optional schema/conversationId): adds backend types/state and LLM prompt wiring, a navigation helper that forwards seed via location.state, editor/PageActionHub wiring for a wiki.compose action, session hydration/projection APIs, and updated tests/i18n. ChangesWiki Compose Chat Seed Integration
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
Code Review
This pull request implements the 'Wiki Compose' feature, enabling a guided flow for co-authoring wiki pages with AI using context from chat sessions. It replaces the legacy 'pendingChatPageGeneration' logic with a structured 'chatSeed' passed through navigation state to a new split-screen Compose UI. Key changes include updates to the LangGraph state and nodes to handle chat context, the addition of a 'WikiComposeAction' to the Page Action Hub, and new navigation helpers. Feedback identifies a critical missing prop destructuring in 'PageEditorContent.tsx' that will cause a ReferenceError and an issue in 'useWikiComposeSession.ts' where internal state is not correctly hydrated when resuming existing sessions.
| wikiContentForCollab={wikiContentForCollab ?? undefined} | ||
| onWikiContentApplied={onWikiContentApplied} | ||
| pageNoteId={pageNoteId} | ||
| wikiComposeHref={wikiComposeHref} |
There was a problem hiding this comment.
| : undefined, | ||
| }); | ||
| sessionRef.current = session; | ||
| update({ session, status: session.status, error: null }); |
There was a problem hiding this comment.
When resuming an existing session via getSession, the hook's internal state (such as phase, briefQuestions, outlineProposal, etc.) is not hydrated from the session's persisted state. Since streamRun is skipped for non-pending sessions, the UI will remain in the initial state upon page refresh. You should extract the state from the session object and update the hook's state accordingly, similar to how reduceResumeOutput is used in the submission handlers.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e11568099
ℹ️ 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".
| if (!composeSeed || !location.state) return; | ||
| navigate(location.pathname + location.search + location.hash, { | ||
| replace: true, | ||
| state: null, | ||
| }); |
There was a problem hiding this comment.
Keep compose seed until run is durably initialized
Clearing location.state immediately drops the only seed source before we know the first POST /run actually persisted chatSeed into graph state. If the page is reloaded (or the initial run fails before consuming input), the URL now points to /compose/:sessionId with no state, and useWikiComposeSession will restart a pending/failed session without initialInput, so Brief loses the chat-derived outline/conversation context. This is user-visible in flaky-network or interrupted-first-run scenarios and makes chat→compose behavior non-deterministic.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/components/editor/PageActionHub/actions/WikiComposeAction.tsx (1)
15-16: ⚡ Quick winMake the exported component JSDoc bilingual for consistency.
Line 15 has an English-only JSDoc on an exported component; add a Japanese line to match the repository’s bilingual documentation convention.
Proposed fix
-/** Detail view for the wiki.compose hub action. */ +/** + * `wiki.compose` アクションの詳細ビュー。 + * Detail view for the wiki.compose hub action. + */As per coding guidelines, "Include both Japanese and English comments/documentation in code and documentation files to maintain project tone consistency".
🤖 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/components/editor/PageActionHub/actions/WikiComposeAction.tsx` around lines 15 - 16, The exported component WikiComposeAction currently has an English-only JSDoc; update its comment block above the declaration of WikiComposeAction to include a Japanese translation line matching the repository convention (add a brief Japanese sentence conveying "Detail view for the wiki.compose hub action"), keeping both English and Japanese sentences in the JSDoc for consistency with other exported components.src/components/editor/PageActionHub/registry.test.ts (1)
75-92: ⚡ Quick winAdd explicit read-only and signed-out cases for
wiki.composegates.This block currently verifies href/title gates, but not
isReadOnlyandisSignedIn. Adding both keeps tests aligned with the full gate contract and prevents silent regressions.✅ Suggested test additions
describe("wiki.compose availability gates", () => { const action = PAGE_ACTIONS.find((a) => a.id === "wiki.compose"); if (!action) throw new Error("missing wiki.compose"); @@ it("タイトルが空のときは不可 / blocked when title is empty", () => { expect( action.isAvailable(makeCtx({ pageTitle: "", wikiComposeHref: "/notes/n/p/compose" })), ).toBe(false); }); + + it("isReadOnly では不可 / blocked when read-only", () => { + expect( + action.isAvailable(makeCtx({ isReadOnly: true, wikiComposeHref: "/notes/n/p/compose" })), + ).toBe(false); + }); + + it("isSignedIn=false では不可 / blocked when signed out", () => { + expect( + action.isAvailable(makeCtx({ isSignedIn: false, wikiComposeHref: "/notes/n/p/compose" })), + ).toBe(false); + }); });🤖 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/components/editor/PageActionHub/registry.test.ts` around lines 75 - 92, Test coverage for the "wiki.compose" action is missing checks for read-only and signed-out gates; update the tests around PAGE_ACTIONS.find(a => a.id === "wiki.compose") and action.isAvailable by adding two cases using makeCtx: one where isReadOnly: true with a valid wikiComposeHref and non-empty pageTitle should assert false, and another where isSignedIn: false (or equivalent unsigned context) with valid href/title should assert false, ensuring the action.isAvailable behavior respects isReadOnly and isSignedIn gates.server/api/src/agents/graphs/wikiCompose/nodes/briefDialogue.ts (1)
77-77: ⚡ Quick winUse the shared
ComposeChatSeedtype instead of an inline shape.Typing
chatSeedwith the canonical interface avoids drift if fields evolve.Suggested diff
-import type { BriefQuestion } from "../types.js"; +import type { BriefQuestion, ComposeChatSeed } from "../types.js"; ... function buildUserPrompt( title: string, body: string, - chatSeed?: { outline: string; conversationText: string; userSchema?: string } | null, + chatSeed?: ComposeChatSeed | null, ): string {🤖 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/agents/graphs/wikiCompose/nodes/briefDialogue.ts` at line 77, Replace the inline chatSeed shape with the shared ComposeChatSeed type: update the function/type declaration that currently declares chatSeed?: { outline: string; conversationText: string; userSchema?: string } | null to use chatSeed?: ComposeChatSeed | null, add the import for ComposeChatSeed from its module, and ensure any usages inside briefDialogue (and exported types) are updated to the ComposeChatSeed fields where needed so the code compiles with the canonical type.
🤖 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/agents/graphs/wikiCompose/state.ts`:
- Around line 99-102: The channel comment for chatSeed is missing a Japanese
translation; update the documentation block for the chatSeed channel (the
comment above the Chat → Compose seed description) to include a Japanese
sentence that mirrors the existing English text so both languages are present
(e.g., add a Japanese line explaining that chatSeed is the Chat → Compose seed
set on the first POST /run input when the user arrives from AI Chat / Promote to
Wiki), keeping the same comment style and placement around the chatSeed/channel
documentation.
In `@server/api/src/agents/graphs/wikiCompose/types.ts`:
- Around line 17-20: The docblock for the new ComposeChatSeed type is
English-only; add a concise Japanese translation to the comment so it is
bilingual per project guidelines. Update the comment above ComposeChatSeed to
include the same content in Japanese followed by the existing English text (or
vice versa), covering that it is an optional chat context seeded when entering
Compose from AI Chat and that it is passed on the first graph `input` and stored
on the session row metadata. Ensure the Japanese text is natural and matches the
technical meaning of the English comment.
In `@src/hooks/useWikiComposeSession.ts`:
- Around line 109-112: Update the JSDoc comments for the interface properties
initialInput and composeSeed in useWikiComposeSession.ts to include both
Japanese and English descriptions; for example, add a Japanese sentence before
or after each English sentence so each property has a JA line and an EN line
(e.g., "初回実行時のオプションの入力(例: Brief のチャットシード)。 / Optional initial body for the first
run (e.g. chat seed for Brief)." and similarly for composeSeed referencing
ComposeNavigationSeed), keeping the existing wording but adding the Japanese
translation and preserving the property names initialInput and composeSeed.
In `@src/lib/wikiCompose/navigation.ts`:
- Around line 14-31: The exported interface NavigateToWikiComposeParams lacks
TSDoc and bilingual (JP/EN) documentation; add a JSDoc/TSDoc block above the
interface that documents the interface purpose and each field (navigate, noteId,
pageId, seed) in both English and Japanese, matching style of nearby exported
docs (e.g., reference COMPOSE_SEED_STATE_KEY and ComposeNavigationSeed types),
include brief descriptions for optional seed and NavigateFunction usage, and
keep punctuation and tags consistent with other exported comments.
In `@src/pages/WikiComposePage.tsx`:
- Line 50: Update the two new inline comments in WikiComposePage.tsx (the
comment near "Capture chat seed once; clearing `location.state` must not drop it
before run." at the first location and the comment around line 82) to include
both Japanese and English text per repo convention; replace each English-only
comment with a bilingual version (Japanese sentence followed by English sentence
or vice versa) while keeping the original meaning and referencing the same
context so the comments remain adjacent to the same code (no code changes
needed).
---
Nitpick comments:
In `@server/api/src/agents/graphs/wikiCompose/nodes/briefDialogue.ts`:
- Line 77: Replace the inline chatSeed shape with the shared ComposeChatSeed
type: update the function/type declaration that currently declares chatSeed?: {
outline: string; conversationText: string; userSchema?: string } | null to use
chatSeed?: ComposeChatSeed | null, add the import for ComposeChatSeed from its
module, and ensure any usages inside briefDialogue (and exported types) are
updated to the ComposeChatSeed fields where needed so the code compiles with the
canonical type.
In `@src/components/editor/PageActionHub/actions/WikiComposeAction.tsx`:
- Around line 15-16: The exported component WikiComposeAction currently has an
English-only JSDoc; update its comment block above the declaration of
WikiComposeAction to include a Japanese translation line matching the repository
convention (add a brief Japanese sentence conveying "Detail view for the
wiki.compose hub action"), keeping both English and Japanese sentences in the
JSDoc for consistency with other exported components.
In `@src/components/editor/PageActionHub/registry.test.ts`:
- Around line 75-92: Test coverage for the "wiki.compose" action is missing
checks for read-only and signed-out gates; update the tests around
PAGE_ACTIONS.find(a => a.id === "wiki.compose") and action.isAvailable by adding
two cases using makeCtx: one where isReadOnly: true with a valid wikiComposeHref
and non-empty pageTitle should assert false, and another where isSignedIn: false
(or equivalent unsigned context) with valid href/title should assert false,
ensuring the action.isAvailable behavior respects isReadOnly and isSignedIn
gates.
🪄 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: f2b882d3-9fc4-476b-b2bb-73deb9a657ac
📒 Files selected for processing (21)
server/api/src/agents/graphs/wikiCompose/nodes/briefDialogue.tsserver/api/src/agents/graphs/wikiCompose/state.tsserver/api/src/agents/graphs/wikiCompose/types.tssrc/components/ai-chat/PromoteToWikiDialog.tsxsrc/components/editor/PageActionHub/PageActionHub.test.tsxsrc/components/editor/PageActionHub/actions/WikiComposeAction.tsxsrc/components/editor/PageActionHub/registry.test.tssrc/components/editor/PageActionHub/registry.tssrc/components/editor/PageActionHub/types.tssrc/components/editor/TiptapEditor.tsxsrc/components/editor/TiptapEditor/types.tssrc/components/editor/TiptapEditor/useTiptapEditorController.tssrc/components/note/PageEditorContent.tsxsrc/hooks/runAIChatAction.test.tssrc/hooks/runAIChatAction.tssrc/hooks/useWikiComposeSession.tssrc/i18n/locales/en/editor.jsonsrc/i18n/locales/ja/editor.jsonsrc/lib/wikiCompose/navigation.test.tssrc/lib/wikiCompose/navigation.tssrc/pages/WikiComposePage.tsx
…950) - GET compose-sessions/:id returns checkpoint projection for interrupted rows - useWikiComposeSession merges projection on reload without POST /run - Clear location.state only after session leaves pending (Codex P2) - Add JA+EN TSDoc on new chatSeed/navigation types
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useWikiComposeSession.ts (1)
310-323:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMove or update the stale JSDoc block above
hydrateFromProjection.The first block documents
PATCH /resumeoutput, but it now sits abovehydrateFromProjection(GET projection merge). This makes docs misleading at the call site.🤖 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/useWikiComposeSession.ts` around lines 310 - 323, The JSDoc about `PATCH /resume` is stale and currently sits above `hydrateFromProjection`, which actually merges the `GET /compose-sessions/:id` projection; move the `PATCH /resume` block away from above `hydrateFromProjection` (either to the `resume` handler/function or a nearby `hydrateFromResume` helper) or update the comment to describe the GET projection behavior; specifically edit the comment blocks around the `hydrateFromProjection` function so its docstring accurately describes merging the `GET /compose-sessions/:id` checkpoint projection (and relocate the `PATCH /resume` description to the appropriate `resume`-related symbol).
🤖 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/composeSessionProjection.test.ts`:
- Around line 1-3: The file-level docblock at the top of
composeSessionProjection.test.ts is English-only; update that comment to include
a Japanese translation alongside the existing English text (JA + EN) so the
file-level comment follows repo tone guidelines—edit the top comment block that
currently reads "`composeSessionProjection` unit tests (`#950`)." to add an
equivalent Japanese sentence immediately before or after the English line,
keeping the same docblock format.
In `@server/api/src/routes/composeSessionProjection.ts`:
- Around line 16-17: Update the new JSDoc comments to be bilingual by adding
Japanese translations alongside the existing English for the added documentation
blocks; specifically, augment the JSDoc for ComposeSessionUiProjection and the
other recently added comment blocks (the ones near the other interface/type
comments referenced in the review) so each description has both English and
Japanese lines, preserving the original English text and adding clear Japanese
equivalents for titles, summaries, and any parameter/property descriptions.
- Around line 122-124: The code always overwrites projection.phase with
phaseFromSessionRow(state.phase, "interrupted") even when projection.phase was
set earlier from the "__interrupt__" source; change the assignment to only set
projection.phase from state.phase when projection.phase is not already set by
the interrupt handling (e.g., wrap the existing line with a guard like if
(typeof state.phase === "string" && projection.phase == null) { projection.phase
= phaseFromSessionRow(state.phase, "interrupted"); } so interrupt-derived
projection.phase is preserved.
In `@server/api/src/routes/composeSessions.ts`:
- Around line 214-233: The GET projection path can throw UnsupportedBackendError
from assertSupportedBackendP0(row.backend) and cause a 500; wrap the backend
validation in a try/catch around the call to assertSupportedBackendP0 when
building the context for loadComposeSessionProjection, and if
UnsupportedBackendError is caught set backend to null (or a safe sentinel) and
log a warning, then proceed to call loadComposeSessionProjection so
stale/unsupported session rows return a projection instead of failing; reference
assertSupportedBackendP0, UnsupportedBackendError, and
loadComposeSessionProjection in the change.
In `@src/hooks/useWikiComposeSession.ts`:
- Around line 513-533: The code constructs runInput from a casted metadataSeed
without validating types, so update the runInput construction in
useWikiComposeSession.ts to first verify that metadataSeed.outline and
metadataSeed.conversationText are strings (e.g., typeof === 'string') before
building chatSeed, and likewise ensure metadataSeed.userSchema and
metadataSeed.conversationId are strings or set them to undefined; only build
runInput when validated values exist to prevent non-string values from being
passed to the /run payload (refer to metadataSeed and runInput identifiers).
In `@src/lib/wikiCompose/composeService.ts`:
- Around line 78-85: The exported docs for GetComposeSessionResult and
getSession are currently English-only; update their JSDoc comments to include
Japanese translations alongside the English lines (e.g., add a Japanese line
above or below the existing English sentence) so both the interface
GetComposeSessionResult and the function getSession have bilingual (JA + EN)
documentation per the project guideline.
---
Outside diff comments:
In `@src/hooks/useWikiComposeSession.ts`:
- Around line 310-323: The JSDoc about `PATCH /resume` is stale and currently
sits above `hydrateFromProjection`, which actually merges the `GET
/compose-sessions/:id` projection; move the `PATCH /resume` block away from
above `hydrateFromProjection` (either to the `resume` handler/function or a
nearby `hydrateFromResume` helper) or update the comment to describe the GET
projection behavior; specifically edit the comment blocks around the
`hydrateFromProjection` function so its docstring accurately describes merging
the `GET /compose-sessions/:id` checkpoint projection (and relocate the `PATCH
/resume` description to the appropriate `resume`-related symbol).
🪄 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: 4563062c-c943-4bcc-b227-27352a403152
📒 Files selected for processing (11)
server/api/src/__tests__/routes/composeSessionProjection.test.tsserver/api/src/agents/graphs/wikiCompose/state.tsserver/api/src/agents/graphs/wikiCompose/types.tsserver/api/src/routes/composeSessionProjection.tsserver/api/src/routes/composeSessions.tssrc/hooks/useWikiComposeSession.test.tssrc/hooks/useWikiComposeSession.tssrc/lib/wikiCompose/composeService.tssrc/lib/wikiCompose/navigation.tssrc/lib/wikiCompose/types.tssrc/pages/WikiComposePage.tsx
…950) - Prefer interrupt-derived phase over row phase in projection - GET session: skip projection when backend is unsupported (no 500) - Validate metadata.composeSeed types before POST /run input - Complete JA+EN docs on new compose navigation/projection APIs
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/hooks/useWikiComposeSession.ts (1)
146-155: ⚡ Quick winMove the
activityIddocblock back to the correct function and keep it JA+EN.The
activityIddescription currently sits aboveparseComposeSeedFromMetadata, so the docs are misleading and the block is English-only.✏️ Suggested fix
-/** - * Returns a unique id for an activity row. Uses crypto.randomUUID when - * available (modern browsers); falls back to a coarse fallback for old - * environments and SSR. - */ /** * `session.metadata.composeSeed` を型検証して graph input 用 seed にする。 * Validate persisted `metadata.composeSeed` before sending `/run` input. */ function parseComposeSeedFromMetadata(metadata: Record<string, unknown> | null | undefined): @@ return out; } +/** + * 活動ログ行の一意 ID を返す。可能なら `crypto.randomUUID` を使う。 + * Returns a unique id for an activity row. Uses crypto.randomUUID when available. + */ function activityId(): string { if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID(); return `act-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; }As per coding guidelines:
**/*.{ts,tsx,js,jsx,md}should include both Japanese and English comments/documentation in code and documentation files.🤖 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/useWikiComposeSession.ts` around lines 146 - 155, The Javadoc for activityId was accidentally placed above parseComposeSeedFromMetadata and is English-only; move the activityId docblock so it immediately precedes the activityId function/constant and restore the bilingual (JA + EN) description per guidelines, leaving parseComposeSeedFromMetadata's own comment intact; search for the symbol activityId and update the comment block content and placement accordingly to include both Japanese and English descriptions.
🤖 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.
Nitpick comments:
In `@src/hooks/useWikiComposeSession.ts`:
- Around line 146-155: The Javadoc for activityId was accidentally placed above
parseComposeSeedFromMetadata and is English-only; move the activityId docblock
so it immediately precedes the activityId function/constant and restore the
bilingual (JA + EN) description per guidelines, leaving
parseComposeSeedFromMetadata's own comment intact; search for the symbol
activityId and update the comment block content and placement accordingly to
include both Japanese and English descriptions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6d6b7558-a491-48c5-9d5e-4c682dc3e842
📒 Files selected for processing (7)
server/api/src/__tests__/routes/composeSessionProjection.test.tsserver/api/src/routes/composeSessionProjection.tsserver/api/src/routes/composeSessions.tssrc/hooks/useWikiComposeSession.test.tssrc/hooks/useWikiComposeSession.tssrc/lib/wikiCompose/composeService.tssrc/lib/wikiCompose/navigation.ts
Summary
Completes the remaining issue #950 work on top of the merged P2 orchestrator/UI (#959, #960):
wiki.composeand opens the split-screen Compose flow from the hub whenwikiComposeHrefis configured.pendingChatPageGenerationnavigate state withnavigateToWikiCompose()(create-page, create-multiple-pages, Promote to Wiki).chatSeedsobrief_dialoguecan bias Brief questions.Test plan
vitest run—navigation,runAIChatAction,useWikiComposeSession,PageActionHub/registryserver/api—wikiComposeGraph.test.ts/composewith Brief phasee2e/wiki-compose.spec.tshappy path (unchanged)Notes
EditorPane); full Tiptap reuse is a follow-up.pendingChatPageGenerationhad no consumer onNotePageView; this PR removes the dead navigation contract.Closes #950
Summary by CodeRabbit
New Features
Tests
Localization