Skip to content

Refactor frontend into multi-package monorepo with EditorPort abstraction#589

Merged
hackerwins merged 21 commits intomainfrom
editor-port
Feb 26, 2026
Merged

Refactor frontend into multi-package monorepo with EditorPort abstraction#589
hackerwins merged 21 commits intomainfrom
editor-port

Conversation

@JOOHOJANG
Copy link
Copy Markdown
Contributor

@JOOHOJANG JOOHOJANG commented Feb 11, 2026

What this PR does / why we need it:

Refactors CodePair's frontend from a single monolithic package into a three-package pnpm workspace and moves backend/ into packages/ for a consistent monorepo layout.

The current frontend bundles all UI components, CodeMirror editor logic, and Yorkie collaboration into one package. This prevents adding a second editor (e.g., a yorkie.Tree-based rich text editor) because CodeMirror uses yorkie.Text
these are incompatible CRDTs that cannot share a sync layer. Each editor must own its full vertical slice.

New package structure:

  • @codepair/ui — Editor-agnostic shared types (EditorPort, EditorModeType, PresenceInfo) and pure UI components
  • @codepair/codemirror — Self-contained CodeMirror 6 vertical slice (editor, toolbar, preview, Yorkie sync), exposed via a single CMEditorSuite component
  • @codepair/frontend — App shell (routing, auth, workspace) that renders editors through the EditorPort interface
  • @codepair/backend — Moved from backend/ to packages/backend/ (no code changes)

ESLint no-restricted-imports rules enforce package boundaries at lint time. No separate build step is needed for library packages — Vite resolves workspace:* packages directly from source.

Which issue(s) this PR fixes:

Fixes

Special notes for your reviewer:

  • The EditorPort interface is the key abstraction — app shell features (scroll-to-user, file export) call methods on it without knowing which editor is running
  • CMEditorSuite accepts an intelligenceSlot prop to render AI features without a reverse dependency from the editor package to the app shell
  • Revision history requires Yorkie server >= 0.6.47 (the ListRevisions RPC was not available in older versions)
  • Architecture documentation is included at docs/editor-port-architecture.md with a step-by-step guide for adding a new editor package

Does this PR introduce a user-facing change?:

NONE


Additional documentation:

- [Architecture]: docs/editor-port-architecture.md

Checklist:

  • Added relevant tests or not required
  • Didn't break anything

Summary by CodeRabbit

Release Notes

  • Chores
    • Reorganized monorepo into a multi-package workspace structure for improved code organization and maintainability
    • Refactored editor architecture with abstracted interfaces to enhance modularity and future extensibility
    • Updated CI/CD pipelines and development configuration to align with new workspace layout

hackerwins and others added 7 commits February 11, 2026 12:48
…deMirror

Add a thin EditorPort abstraction (getSelection, replaceRange, scrollIntoView,
getContentWidth) so that intelligence, speech-to-text, and user-presence code
no longer import from "codemirror" directly. The CMEditorAdapter annotates
transactions with userEvent "input", letting yorkieSync handle Yorkie content
sync automatically and eliminating dual-write patterns in external consumers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move frontend/ to packages/frontend/ and backend/ to packages/backend/
so all application packages live under a single directory. Update
pnpm-workspace.yaml, CI workflows, Dockerfile, husky pre-commit hook,
and setup scripts to reflect the new paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract editor-agnostic types (EditorPort, EditorModeType, PresenceInfo) and
pure UI components (TooltipToggleButton, CodePairError, etc.) into a shared
package. Also fix docker-compose build context path after backend move.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract CodeMirror editor, toolbar, preview, Yorkie sync plugins, and
formatting utilities into a self-contained package. CMEditorSuite serves
as the single entry point the app shell renders, communicating back via
the EditorPort interface from @codepair/ui.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the multi-editor monorepo design: package structure, dependency
graph, EditorPort interface contract, data flow through the app shell,
and step-by-step guide for adding a new editor package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 11, 2026

Important

Review skipped

Too many files!

This PR contains 293 files, which is 143 over the limit of 150.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch editor-port

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

❤️ Share

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@JOOHOJANG JOOHOJANG marked this pull request as ready for review February 11, 2026 08:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (2)
packages/codemirror/src/plugins/intelligencePivot.ts (1)

25-25: ⚠️ Potential issue | 🟡 Minor

Pre-existing typo: relaitve should be relative.

While not part of this change, Line 25 has a typo in the CSS style.

🐛 Proposed fix
-			pair.create("style", `position: relaitve;`),
+			pair.create("style", `position: relative;`),
packages/codemirror/src/components/Editor.tsx (1)

56-96: ⚠️ Potential issue | 🟠 Major

Clear editorPort on teardown to prevent stale references.

When the editor unmounts, editorPort still points to a destroyed view. Downstream consumers rely on null-checks to avoid calling methods on it; reset it in the cleanup to propagate the null state through the context and store.

🛠️ Suggested fix
 		return () => {
 			view?.destroy();
+			setEditorPort(null);
 		};
🤖 Fix all issues with AI agents
In @.github/workflows/ci_backend.yaml:
- Around line 5-13: The workflow's path filter under the "paths:" key in
ci_backend.yaml currently only watches ".github/workflows/ci_backend.yaml" and
"packages/backend/**"; update both the push and pull_request path lists to also
include workspace-root dependency/config files (e.g., package.json,
package-lock.json, yarn.lock, pnpm-lock.yaml, pnpm-workspace.yaml, turbo.json,
and any other repo-level lock or workspace config) so changes to those files
trigger the backend CI; modify the two "paths:" arrays accordingly in the
existing file.

In @.github/workflows/ci_frontend.yaml:
- Around line 6-17: The workflow's path filters under the push and pull_request
triggers are missing root workspace and lockfile entries, so update the
triggers' paths arrays (the push and pull_request keys) to include the
repository root workspace config and lockfile (e.g., add "/" and your lockfile
name such as "pnpm-lock.yaml" or "package-lock.json") so changes to
workspace/lockfiles will also trigger the CI; modify the paths lists referenced
under push and pull_request in the YAML to contain those root/lockfile paths in
addition to the existing package globs.

In `@packages/codemirror/src/CMEditorSuite.tsx`:
- Around line 100-105: The current onMouseDown/onMouseUp pair in
CMEditorSuite.tsx can leave document.body.style.userSelect = "none" if the mouse
is released outside the element; to fix, when handling onMouseDown (the handler
referenced as onMouseDown) set document.body.style.userSelect = "none" and also
register a window-level one-time 'mouseup' listener (use { once: true } or
remove the listener inside the handler) that restores
document.body.style.userSelect = "auto"; ensure any existing listener is cleaned
up to avoid leaks and also restore userSelect in any component unmount cleanup
(e.g., via useEffect cleanup) so document.body.style.userSelect is never left
disabled.

In `@packages/codemirror/src/components/Preview.tsx`:
- Around line 21-29: Remove the duplicate addSoftLineBreak implementation in
Preview.tsx and instead import the shared function from its canonical export in
packages/frontend/src/features/document; delete the local function declaration,
add an import for addSoftLineBreak at the top of Preview.tsx, and ensure all
usages in the file call the imported addSoftLineBreak symbol (no other API
changes needed).

In `@packages/frontend/design/editor-port.md`:
- Around line 60-62: Update the documentation paths that reference the old
adapter location by replacing instances of
"features/editor/codemirror/CMEditorAdapter.ts" and the outdated file-structure
entries with the current monorepo package path
"packages/codemirror/src/CMEditorAdapter.ts"; update the file structure section
(the block currently showing lines with features/editor/...) to reflect the new
package layout so all references point to
packages/codemirror/src/CMEditorAdapter.ts and any sibling files are listed
under packages/codemirror/src.

In `@packages/frontend/src/features/editor/components/PreviewRevisionDialog.tsx`:
- Around line 31-32: In PreviewRevisionDialog.tsx the CSS imports use the
workspace alias while the codemirror package imports them with relative paths;
update the two import statements for editor.css and preview.css to use the same
relative path pattern used in packages/codemirror (e.g., change the imports that
currently reference "@codepair/codemirror/src/styles/..." to
"../../../codemirror/src/styles/...") so they align with Preview.tsx;
alternatively evaluate whether these styles should be consolidated into a single
shared import location to avoid duplication.

In `@packages/frontend/src/features/editor/shared/components/DocumentView.tsx`:
- Around line 55-58: The handler handleCodeKeyChange currently force-casts
CMCodeKeyType into the Redux CodeKeyType via "as unknown as", allowing invalid
values (like SUBLIME) to reach the CodeMirror component; replace this unsafe
cast with an explicit mapping function (e.g., mapCMToReduxKey or
mapReduxToCMKey) that converts between CMCodeKeyType and the store's CodeKeyType
(handle the SUBLIME -> DEFAULT mapping and preserve VIM), then call
dispatch(setCodeKeyType(mappedValue)) inside handleCodeKeyChange so only valid
CodeKeyType values are dispatched.

In
`@packages/frontend/src/features/intelligence/components/YorkieIntelligenceFooter.tsx`:
- Around line 24-27: The useMemo that computes width uses a non-null assertion
on editorStore.editorPort which can throw if editorPort is null; update
YorkieIntelligenceFooter to handle that by guarding editorStore.editorPort
before accessing getContentWidth (either early return from the component when
editorStore.editorPort is falsy, e.g. in YorkieIntelligenceFooter return null,
or compute width with a safe check/optional chaining and a sensible fallback
value) so that the width calculation and any calls to getContentWidth no longer
rely on the non-null assertion.
🧹 Nitpick comments (10)
.husky/pre-commit (2)

6-12: Harden changed-file iteration against whitespace in paths.

The current for FILE in $CHANGED_FILES will split on spaces/newlines. Using a NUL-delimited diff avoids false detection when filenames contain whitespace.

♻️ Suggested change
-CHANGED_FILES=$(git diff --cached --name-only)
-
-for FILE in $CHANGED_FILES; do
+while IFS= read -r -d '' FILE; do
   if [[ "$FILE" =~ ^packages/frontend/ ]]; then
     LINT_FRONTEND=true
   elif [[ "$FILE" =~ ^packages/backend/ ]]; then
     LINT_BACKEND=true
   fi
-done
+done < <(git diff --cached --name-only -z)

14-33: Guard cd to avoid running lint in the wrong directory.

If the package directory is missing or renamed, cd fails and npx lint-staged runs from the prior working directory. A guarded cd keeps the hook fail-fast and predictable.

♻️ Suggested change
-  cd packages/frontend
+  cd packages/frontend || { echo "packages/frontend not found"; exit 1; }
   npx lint-staged
   if [ $? -ne 0 ]; then
     echo "Frontend Linting & Formatting failed. Commit aborted."
     exit 1
   fi
   cd ../..
@@
-  cd packages/backend
+  cd packages/backend || { echo "packages/backend not found"; exit 1; }
   npx lint-staged
   if [ $? -ne 0 ]; then
     echo "Backend Linting & Formatting failed. Commit aborted."
     exit 1
   fi
   cd ../..
packages/codemirror/package.json (1)

15-18: Consider consistent version pinning for CodeMirror packages.

Some CodeMirror packages use exact versions (e.g., @codemirror/commands: 6.1.2, @codemirror/view: 6.23.1) while others use caret ranges (e.g., @codemirror/state: ^6.5.4). Inconsistent pinning can lead to subtle incompatibilities when peer dependencies resolve differently.

packages/codemirror/eslint.config.mjs (1)

18-24: Consider adding eslint-plugin-react for React component linting.

The codemirror package contains React components (e.g., CMEditorSuite, Editor), but the config only includes react: { version: "detect" } settings without the actual React plugin. The frontend config includes react-hooks and react-refresh plugins which would also benefit this package.

♻️ Optional: Add React linting plugins
 import typescriptPlugin from "@typescript-eslint/eslint-plugin";
 import typescriptParser from "@typescript-eslint/parser";
 import prettierPlugin from "eslint-plugin-prettier";
+import reactHooksPlugin from "eslint-plugin-react-hooks";

 export default [
 	{
 		// ...
 		plugins: {
 			"@typescript-eslint": typescriptPlugin,
 			prettier: prettierPlugin,
+			"react-hooks": reactHooksPlugin,
 		},
 		rules: {
 			...prettierPlugin.configs.recommended.rules,
 			...typescriptPlugin.configs.recommended.rules,
+			...reactHooksPlugin.configs.recommended.rules,
 			// ...
 		},
 	},
 ];
packages/frontend/src/features/editor/store/editorSlice.ts (1)

42-44: Clarify the use of Object.assign for setting editorPort.

The Object.assign(state, { editorPort: action.payload }) pattern is unconventional for Redux Toolkit reducers. With Immer (used by RTK), direct assignment like state.editorPort = action.payload typically works and is more readable. The Object.assign approach suggests there might be a specific reason—possibly to work around Immer's proxy handling of non-serializable objects like EditorPort.

If this is intentional to bypass Immer proxy issues, a brief inline comment would help future maintainers understand the design choice.

packages/codemirror/src/hooks/useFormatUtils.ts (1)

180-180: Consider adding a runtime guard before the type assertion.

The cast (editorPort as CMEditorAdapter).view assumes editorPort is always a CMEditorAdapter. While this hook lives in @codepair/codemirror (making CM-specific access acceptable), a defensive check would prevent runtime errors if the context ever provides a different implementation.

🛡️ Optional defensive check
-			applyFormat(format)((editorPort as CMEditorAdapter).view);
+			if (editorPort instanceof CMEditorAdapter) {
+				applyFormat(format)(editorPort.view);
+			}
packages/frontend/design/editor-port.md (1)

33-35: Consider adding language specifiers to file path code blocks.

Static analysis flagged code blocks without language specifiers. For file path listings, you could use ```text or ```plaintext to satisfy the linter while keeping them visually distinct from executable code.

Also applies to: 60-62, 98-113

packages/frontend/src/features/editor/port/EditorPort.ts (1)

1-11: Unify the EditorPort source to avoid type drift.

Since EditorPort is already exported from @codepair/ui, keeping another definition here risks divergence over time. Prefer a single source of truth and re-export it locally if needed.

♻️ Suggested change
-export interface EditorPort {
-	getSelection(): { from: number; to: number };
-	replaceRange(
-		from: number,
-		to: number,
-		insert: string,
-		selection?: { anchor: number; head?: number }
-	): void;
-	scrollIntoView(pos: number): void;
-	getContentWidth(): number;
-}
+export type { EditorPort } from "@codepair/ui";
packages/codemirror/src/CMEditorSuite.tsx (2)

15-15: Consider using a type-only import for clarity.

The inline import("@yorkie-js/sdk").Client syntax is valid but less readable than a standard type import. If there's no bundling or circular dependency concern, a top-level type import would be clearer.

+import type { Client } from "@yorkie-js/sdk";
 import { useCallback, useState, useRef } from "react";
 ...
 export interface CMEditorSuiteProps {
 	doc: CodePairDocType;
-	client: import("@yorkie-js/sdk").Client;
+	client: Client;

53-65: Memoize context value to prevent unnecessary re-renders.

The ctxValue object is recreated on every render, causing all CMEditorContext consumers to re-render even when none of the values have changed.

♻️ Proposed fix using useMemo
+import { useCallback, useState, useRef, useMemo } from "react";
-import { useCallback, useState, useRef } from "react";
-	const ctxValue: CMEditorContextValue = {
-		doc,
-		client,
-		editorPort,
-		setEditorPort,
-		themeMode,
-		codeKey,
-		setCodeKey: onCodeKeyChange,
-		fileUploadEnabled,
-		handleUploadImage,
-		intelligenceEnabled,
-		width,
-	};
+	const ctxValue: CMEditorContextValue = useMemo(
+		() => ({
+			doc,
+			client,
+			editorPort,
+			setEditorPort,
+			themeMode,
+			codeKey,
+			setCodeKey: onCodeKeyChange,
+			fileUploadEnabled,
+			handleUploadImage,
+			intelligenceEnabled,
+			width,
+		}),
+		[
+			doc,
+			client,
+			editorPort,
+			setEditorPort,
+			themeMode,
+			codeKey,
+			onCodeKeyChange,
+			fileUploadEnabled,
+			handleUploadImage,
+			intelligenceEnabled,
+			width,
+		]
+	);

Comment on lines 5 to +13
paths:
- ".github/workflows/ci_backend.yaml"
- "backend/**"
- "packages/backend/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_backend.yaml"
- "backend/**"
- "packages/backend/**"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Include workspace root dependency files in the CI path filters.

Backend CI can break when workspace-level files change (e.g., lockfile or workspace config), but those edits won’t currently trigger this workflow.

🛠️ Suggested update
@@
     paths:
       - ".github/workflows/ci_backend.yaml"
       - "packages/backend/**"
+      - "pnpm-lock.yaml"
+      - "pnpm-workspace.yaml"
@@
     paths:
       - ".github/workflows/ci_backend.yaml"
       - "packages/backend/**"
+      - "pnpm-lock.yaml"
+      - "pnpm-workspace.yaml"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
paths:
- ".github/workflows/ci_backend.yaml"
- "backend/**"
- "packages/backend/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_backend.yaml"
- "backend/**"
- "packages/backend/**"
paths:
- ".github/workflows/ci_backend.yaml"
- "packages/backend/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_backend.yaml"
- "packages/backend/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
🤖 Prompt for AI Agents
In @.github/workflows/ci_backend.yaml around lines 5 - 13, The workflow's path
filter under the "paths:" key in ci_backend.yaml currently only watches
".github/workflows/ci_backend.yaml" and "packages/backend/**"; update both the
push and pull_request path lists to also include workspace-root
dependency/config files (e.g., package.json, package-lock.json, yarn.lock,
pnpm-lock.yaml, pnpm-workspace.yaml, turbo.json, and any other repo-level lock
or workspace config) so changes to those files trigger the backend CI; modify
the two "paths:" arrays accordingly in the existing file.

Comment on lines 6 to +17
- ".github/workflows/ci_frontend.yaml"
- "frontend/**"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_frontend.yaml"
- "frontend/**"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Include root workspace/lockfile paths in filters to avoid skipped CI.

Changes to root workspace config or lockfile won’t trigger this workflow, so dependency updates could land unvalidated. Add the root workspace/lockfile paths to both push and pull_request.

✅ Suggested patch
     paths:
       - ".github/workflows/ci_frontend.yaml"
+      - "package.json"
+      - "pnpm-lock.yaml"
+      - "pnpm-workspace.yaml"
       - "packages/frontend/**"
       - "packages/ui/**"
       - "packages/codemirror/**"
@@
     paths:
       - ".github/workflows/ci_frontend.yaml"
+      - "package.json"
+      - "pnpm-lock.yaml"
+      - "pnpm-workspace.yaml"
       - "packages/frontend/**"
       - "packages/ui/**"
       - "packages/codemirror/**"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- ".github/workflows/ci_frontend.yaml"
- "frontend/**"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_frontend.yaml"
- "frontend/**"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
- ".github/workflows/ci_frontend.yaml"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
pull_request:
branches:
- main
paths:
- ".github/workflows/ci_frontend.yaml"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "packages/frontend/**"
- "packages/ui/**"
- "packages/codemirror/**"
🤖 Prompt for AI Agents
In @.github/workflows/ci_frontend.yaml around lines 6 - 17, The workflow's path
filters under the push and pull_request triggers are missing root workspace and
lockfile entries, so update the triggers' paths arrays (the push and
pull_request keys) to include the repository root workspace config and lockfile
(e.g., add "/" and your lockfile name such as "pnpm-lock.yaml" or
"package-lock.json") so changes to workspace/lockfiles will also trigger the CI;
modify the paths lists referenced under push and pull_request in the YAML to
contain those root/lockfile paths in addition to the existing package globs.

Comment on lines +100 to +105
onMouseDown={() => {
document.body.style.userSelect = "none";
}}
onMouseUp={() => {
document.body.style.userSelect = "auto";
}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

userSelect may remain disabled if mouse is released outside the splitter.

If the user starts dragging the splitter and releases the mouse outside the <Paper> element, onMouseUp won't fire, leaving document.body.style.userSelect = "none" indefinitely. This would prevent text selection until the page reloads.

🛡️ Proposed fix using window-level listener
 <Paper
 	id="splitter"
 	{...separatorProps}
 	onMouseDown={() => {
 		document.body.style.userSelect = "none";
+		const cleanup = () => {
+			document.body.style.userSelect = "auto";
+			window.removeEventListener("mouseup", cleanup);
+		};
+		window.addEventListener("mouseup", cleanup);
 	}}
-	onMouseUp={() => {
-		document.body.style.userSelect = "auto";
-	}}
 	sx={{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onMouseDown={() => {
document.body.style.userSelect = "none";
}}
onMouseUp={() => {
document.body.style.userSelect = "auto";
}}
onMouseDown={() => {
document.body.style.userSelect = "none";
const cleanup = () => {
document.body.style.userSelect = "auto";
window.removeEventListener("mouseup", cleanup);
};
window.addEventListener("mouseup", cleanup);
}}
sx={{
🤖 Prompt for AI Agents
In `@packages/codemirror/src/CMEditorSuite.tsx` around lines 100 - 105, The
current onMouseDown/onMouseUp pair in CMEditorSuite.tsx can leave
document.body.style.userSelect = "none" if the mouse is released outside the
element; to fix, when handling onMouseDown (the handler referenced as
onMouseDown) set document.body.style.userSelect = "none" and also register a
window-level one-time 'mouseup' listener (use { once: true } or remove the
listener inside the handler) that restores document.body.style.userSelect =
"auto"; ensure any existing listener is cleaned up to avoid leaks and also
restore userSelect in any component unmount cleanup (e.g., via useEffect
cleanup) so document.body.style.userSelect is never left disabled.

Comment on lines +21 to +29
function addSoftLineBreak(text: string) {
return text
.split("\n")
.map((line) => {
if (line.trim() === "") return "";
else return line + " ";
})
.join("\n");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "addSoftLineBreak" --type=ts --type=tsx -C2

Repository: yorkie-team/codepair

Length of output: 91


🏁 Script executed:

rg "addSoftLineBreak" -C2

Repository: yorkie-team/codepair

Length of output: 4577


Remove duplicate addSoftLineBreak utility; import from shared location.

This function is already exported from packages/frontend/src/features/document and used by other components (YorkieIntelligenceFeature.tsx, PreviewRevisionDialog.tsx). Remove the local definition in Preview.tsx and import from the canonical location to maintain a single source of truth.

🤖 Prompt for AI Agents
In `@packages/codemirror/src/components/Preview.tsx` around lines 21 - 29, Remove
the duplicate addSoftLineBreak implementation in Preview.tsx and instead import
the shared function from its canonical export in
packages/frontend/src/features/document; delete the local function declaration,
add an import for addSoftLineBreak at the top of Preview.tsx, and ensure all
usages in the file call the imported addSoftLineBreak symbol (no other API
changes needed).

Comment on lines +60 to +62
```
features/editor/codemirror/CMEditorAdapter.ts
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

File path in documentation appears outdated.

The adapter location is documented as features/editor/codemirror/CMEditorAdapter.ts, but based on the PR structure, the adapter is at packages/codemirror/src/CMEditorAdapter.ts. The file structure section (lines 98-113) also shows the old path structure.

Consider updating these paths to reflect the actual monorepo package layout.

🤖 Prompt for AI Agents
In `@packages/frontend/design/editor-port.md` around lines 60 - 62, Update the
documentation paths that reference the old adapter location by replacing
instances of "features/editor/codemirror/CMEditorAdapter.ts" and the outdated
file-structure entries with the current monorepo package path
"packages/codemirror/src/CMEditorAdapter.ts"; update the file structure section
(the block currently showing lines with features/editor/...) to reflect the new
package layout so all references point to
packages/codemirror/src/CMEditorAdapter.ts and any sibling files are listed
under packages/codemirror/src.

Comment on lines +31 to +32
import "@codepair/codemirror/src/styles/editor.css";
import "@codepair/codemirror/src/styles/preview.css";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if there are other consumers importing from `@codepair/codemirror/src/` paths
rg -n "@codepair/codemirror/src/" --type ts --type tsx --type css -g '!packages/codemirror/'

Repository: yorkie-team/codepair

Length of output: 91


🏁 Script executed:

# Also check for vite config files to understand workspace resolution
find . -maxdepth 3 -name "vite.config.*" -o -name "vitest.config.*" | head -20

Repository: yorkie-team/codepair

Length of output: 98


🏁 Script executed:

# Check if there's any documentation about this import pattern
find . -maxdepth 3 -name "*.md" | xargs grep -l "workspace" 2>/dev/null | head -10

Repository: yorkie-team/codepair

Length of output: 142


🏁 Script executed:

# Search for `@codepair/codemirror/src/` imports with correct rg syntax
rg "@codepair/codemirror/src/" --type ts --glob '!packages/codemirror/**'

Repository: yorkie-team/codepair

Length of output: 320


🏁 Script executed:

# Read the vite config
cat packages/frontend/vite.config.ts

Repository: yorkie-team/codepair

Length of output: 673


🏁 Script executed:

# Check CHANGELOG for context about this import pattern
grep -A 5 -B 5 "@codepair/codemirror\|workspace" CHANGELOG.md | head -40

Repository: yorkie-team/codepair

Length of output: 1927


🏁 Script executed:

# Check if there's a root vite config or workspace configuration
find . -maxdepth 2 -name "vite.config.*" -o -name "tsconfig.json" -o -name "jsconfig.json"

Repository: yorkie-team/codepair

Length of output: 87


🏁 Script executed:

# Search for other CSS/style imports to see the standard pattern
rg "import.*\.css" --type ts -g '!node_modules' | head -30

Repository: yorkie-team/codepair

Length of output: 772


🏁 Script executed:

# Check root package.json for workspace configuration
cat package.json | grep -A 20 "workspaces\|workspace"

Repository: yorkie-team/codepair

Length of output: 46


🏁 Script executed:

# Check if there's a monorepo root config
ls -la | grep -E "^-.*\.(json|config)" | head -20

Repository: yorkie-team/codepair

Length of output: 348


🏁 Script executed:

# Check codemirror package.json for exports configuration
cat packages/codemirror/package.json

Repository: yorkie-team/codepair

Length of output: 2077


🏁 Script executed:

# Read root package.json to confirm workspace setup
cat package.json

Repository: yorkie-team/codepair

Length of output: 1447


🏁 Script executed:

# Check if codemirror has a dist build or only src
ls -la packages/codemirror/ | head -20

Repository: yorkie-team/codepair

Length of output: 481


Align CSS imports with the package's own import pattern.

The same CSS files are imported inconsistently within the monorepo. In packages/codemirror/src/components/Preview.tsx, these styles are imported using relative paths (../styles/editor.css), but in this file they use a workspace alias (@codepair/codemirror/src/styles/editor.css).

Change these imports to match the relative path pattern used by the package itself:

import "../../../codemirror/src/styles/editor.css";
import "../../../codemirror/src/styles/preview.css";

Or better, consider if these styles should be imported from a single consolidated location to avoid duplication.

🤖 Prompt for AI Agents
In `@packages/frontend/src/features/editor/components/PreviewRevisionDialog.tsx`
around lines 31 - 32, In PreviewRevisionDialog.tsx the CSS imports use the
workspace alias while the codemirror package imports them with relative paths;
update the two import statements for editor.css and preview.css to use the same
relative path pattern used in packages/codemirror (e.g., change the imports that
currently reference "@codepair/codemirror/src/styles/..." to
"../../../codemirror/src/styles/...") so they align with Preview.tsx;
alternatively evaluate whether these styles should be consolidated into a single
shared import location to avoid duplication.

Comment on lines +55 to +58
const handleCodeKeyChange = useCallback(
(key: CMCodeKeyType) => {
dispatch(setCodeKeyType(key as unknown as CodeKeyType));
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate all CodeKeyType definitions
rg -nP "export\s+(enum|type)\s+CodeKeyType|enum\s+CodeKeyType|type\s+CodeKeyType" -S

Repository: yorkie-team/codepair

Length of output: 214


🏁 Script executed:

#!/bin/bash
# Read the first CodeKeyType definition
echo "=== packages/codemirror/src/types.ts ==="
head -20 packages/codemirror/src/types.ts

echo -e "\n=== packages/frontend/src/features/settings/store/configSlice.ts ==="
head -30 packages/frontend/src/features/settings/store/configSlice.ts

Repository: yorkie-team/codepair

Length of output: 1103


🏁 Script executed:

#!/bin/bash
# Read DocumentView.tsx around lines 55-58
sed -n '45,70p' packages/frontend/src/features/editor/shared/components/DocumentView.tsx

Repository: yorkie-team/codepair

Length of output: 562


🏁 Script executed:

#!/bin/bash
# Check imports at the top of DocumentView.tsx
head -40 packages/frontend/src/features/editor/shared/components/DocumentView.tsx | grep -E "import|from"

Repository: yorkie-team/codepair

Length of output: 872


🏁 Script executed:

#!/bin/bash
# Find setCodeKeyType definition
rg -nB2 -A5 "setCodeKeyType" packages/frontend/src/features/settings/

Repository: yorkie-team/codepair

Length of output: 2072


🏁 Script executed:

#!/bin/bash
# Search for other usages of the two CodeKeyType enums together
rg -n "CMCodeKeyType|CodeKeyType" packages/frontend/src/features/editor/shared/components/DocumentView.tsx

# Also check if there's any mapping or conversion logic elsewhere
rg -n "CMCodeKeyType.*CodeKeyType|CodeKeyType.*CMCodeKeyType" packages/frontend/src/

Repository: yorkie-team/codepair

Length of output: 504


🏁 Script executed:

#!/bin/bash
# Check if there's any handling in ModeSwitcher or where this is consumed
rg -n "handleCodeKeyChange|codeKey" packages/frontend/src/features/editor/shared/components/DocumentView.tsx | head -20

# Also check ModeSwitcher to see what values it passes
cat packages/frontend/src/features/editor/shared/components/ModeSwitcher.tsx | head -50

Repository: yorkie-team/codepair

Length of output: 1723


🏁 Script executed:

#!/bin/bash
# Check what values are actually passed and if there's a mismatch issue
rg -n "DEFAULT|SUBLIME|VIM" packages/codemirror/src/types.ts packages/frontend/src/features/settings/store/configSlice.ts

# Also check if there's any integration test or actual usage that shows the problem
rg -n "handleCodeKeyChange" packages/frontend/src/ -A 3 -B 3

Repository: yorkie-team/codepair

Length of output: 2147


Unify or explicitly map between the two CodeKeyType enums.

Two separate CodeKeyType enums exist with divergent values:

  • @codepair/codemirror: DEFAULT, VIM
  • Redux settings store: SUBLIME, VIM

The Redux store initializes with SUBLIME as the default, but the codemirror component doesn't support it—only DEFAULT and VIM. The as unknown as cast bypasses TypeScript's type safety, allowing the invalid SUBLIME value to reach the component at runtime. Either consolidate both enums to share the same values or add an explicit mapping function to convert between them safely.

🤖 Prompt for AI Agents
In `@packages/frontend/src/features/editor/shared/components/DocumentView.tsx`
around lines 55 - 58, The handler handleCodeKeyChange currently force-casts
CMCodeKeyType into the Redux CodeKeyType via "as unknown as", allowing invalid
values (like SUBLIME) to reach the CodeMirror component; replace this unsafe
cast with an explicit mapping function (e.g., mapCMToReduxKey or
mapReduxToCMKey) that converts between CMCodeKeyType and the store's CodeKeyType
(handle the SUBLIME -> DEFAULT mapping and preserve VIM), then call
dispatch(setCodeKeyType(mappedValue)) inside handleCodeKeyChange so only valid
CodeKeyType values are dispatched.

- Clear editorPort on Editor unmount to prevent stale references
- Fix CSS typo: relaitve → relative in intelligencePivot
- Fix splitter userSelect stuck when mouse released outside element
- Memoize CMEditorContext value to reduce unnecessary re-renders
- Add null safety for editorPort in YorkieIntelligenceFooter
- Update outdated file paths in editor-port design doc
- De-duplicate EditorPort: re-export from @codepair/ui instead of redefining
- Add pnpm-lock.yaml and pnpm-workspace.yaml to CI path filters

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
packages/codemirror/src/components/Preview.tsx (1)

107-136: ⚠️ Potential issue | 🟡 Minor

Cancel the throttled updater during cleanup to avoid trailing setState.

Line 132–135 cleans up subscriptions, but any pending throttled call can still fire after unmount/doc switch.

🧹 Suggested fix
		return () => {
			unsubscribe();
+			throttledUpdatePreviewContent.cancel();
			setContent("");
		};
Does lodash `throttle` return a function with `.cancel()` and `.flush()` that are safe to call in React effect cleanup?
🤖 Fix all issues with AI agents
In `@docs/editor-port-architecture.md`:
- Line 13: Several fenced code blocks in docs/editor-port-architecture.md lack
language identifiers (triggering markdownlint MD040); update each unlabeled
triple-backtick fence to include an appropriate language tag (e.g., change ```
to ```text for the package tree snippet containing "packages/  ui/  codemirror/ 
frontend/  backend/") and similarly add tags like typescript, tsx, yaml, or json
for the other unlabeled fences referenced around the other snippets (the ones at
the other reported locations). Locate the unlabeled fences by searching for the
shown snippet content (e.g., the package tree and other code examples) and add
the correct language identifier to each opening ``` so markdownlint no longer
warns.

In `@packages/backend/docker/docker-compose-full.yml`:
- Around line 35-40: Update the codepair-backend service's depends_on block to
include yorkie and yorkie-intelligence with the same health condition used for
mongo/minio/qdrant; specifically, in the docker-compose service definition for
codepair-backend add entries "yorkie: condition: service_healthy" and
"yorkie-intelligence: condition: service_healthy" so the backend waits for
YORKIE_API_ADDR and OLLAMA_HOST_URL targets to be healthy before startup.

In `@packages/codemirror/eslint.config.mjs`:
- Around line 7-8: Remove the ignores entry from the existing config object that
contains files: ["**/*.ts","**/*.tsx"] in eslint.config.mjs and add a separate
top-level config object whose sole property is ignores:
["dist","eslint.config.mjs"] so the ignore behavior is global; locate the
current config object (the one with files) and update it to no longer include
ignores, then create the new standalone config object with only ignores to
replicate .eslintignore semantics.

In `@packages/codemirror/src/components/EditorBottomBar.tsx`:
- Around line 1-20: The event type on handleOpen uses the missing React
namespace (React.MouseEvent); import the MouseEvent type directly from "react"
and update the handler signature to use that imported type instead of
React.MouseEvent so TypeScript compiles with jsx: "react-jsx" (locate handleOpen
in EditorBottomBar and the top imports to add import type { MouseEvent } from
"react").

In `@packages/codemirror/src/global.d.ts`:
- Line 6: Remove the bare module augmentation `declare module
"react-scroll-sync";` which masks the package's real types; delete that line so
TypeScript will pick up the package's own `./dist/index.d.ts` (or `@types`)
instead, then run a typecheck to verify imports using react-scroll-sync now have
proper typings.

In `@packages/frontend/design/editor-port.md`:
- Line 33: Update the three fenced code blocks that show file paths to include
the language specifier "text" so the linter and renderer handle them
consistently; specifically modify the blocks referencing
"packages/ui/src/types/EditorPort.ts",
"packages/codemirror/src/CMEditorAdapter.ts", and the tree block containing
"packages/ui/src/types/ └── EditorPort.ts" to use ```text instead of ``` to
ensure consistent rendering.
- Line 139: The doc suggests creating features/editor/prosemirror/, but to
follow the monorepo package pattern used by packages/codemirror/ you should
instead create a top-level sibling package packages/prosemirror/ (not nested
under frontend features); update any references in the spec and any proposed
import paths to point to packages/prosemirror/ and mirror the packaging/setup
used by packages/codemirror/ (package.json, build/config, exports) so the new
editor lives as its own package.
🧹 Nitpick comments (1)
packages/codemirror/src/plugins/intelligencePivot.ts (1)

5-7: Consider keeping shared DOM IDs in a single constants source to avoid drift.

If other modules rely on these IDs, duplicating the strings here risks divergence over time. Centralizing them (or re-exporting from a shared constants module) keeps consistency.

♻️ Suggested refactor
-import * as pair from "lib0/pair";
+import * as pair from "lib0/pair";
+import {
+  INTELLIGENCE_HEADER_ID,
+  INTELLIGENCE_FOOTER_ID,
+} from "../constants/intelligence";
 
-const INTELLIGENCE_HEADER_ID = "yorkie-intelligence-header";
-const INTELLIGENCE_FOOTER_ID = "yorkie-intelligence-footer";


## Package Structure

```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks (markdownlint MD040).

Several code fences lack a language tag; markdownlint warns on these. Add a language like text, typescript, tsx, yaml, or json as appropriate.

🧩 Suggested patch (apply similarly to other unlabeled fences)
-```
+```text
 packages/
   ui/              `@codepair/ui`         — shared types, interfaces, pure components
   codemirror/      `@codepair/codemirror`  — CodeMirror 6 + yorkie.Text sync
   frontend/        `@codepair/frontend`    — app shell (routing, auth, workspace, editor slot)
   backend/         `@codepair/backend`     — NestJS API server

</details>



Also applies to: 23-23, 93-93, 128-128, 190-190

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.20.0)</summary>

[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @docs/editor-port-architecture.md at line 13, Several fenced code blocks in
docs/editor-port-architecture.md lack language identifiers (triggering
markdownlint MD040); update each unlabeled triple-backtick fence to include an
appropriate language tag (e.g., change totext for the package tree
snippet containing "packages/ ui/ codemirror/ frontend/ backend/") and
similarly add tags like typescript, tsx, yaml, or json for the other unlabeled
fences referenced around the other snippets (the ones at the other reported
locations). Locate the unlabeled fences by searching for the shown snippet
content (e.g., the package tree and other code examples) and add the correct
language identifier to each opening ``` so markdownlint no longer warns.


</details>

<!-- fingerprinting:phantom:medusa:eagle -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +35 to +40
mongo:
condition: service_healthy
minio:
condition: service_healthy
qdrant:
condition: service_healthy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat packages/backend/docker/docker-compose-full.yml

Repository: yorkie-team/codepair

Length of output: 4990


🌐 Web query:

Docker Compose service_started service_healthy condition support version

💡 Result:

Docker Compose supports depends_on long syntax with condition: values including:

  • service_started (equivalent to short syntax; only ensures the dependency container is started)
  • service_healthy (waits until the dependency reports healthy, which requires a healthcheck:)
  • service_completed_successfully (waits until the dependency exits with code 0) (docs.docker.com)

What versions support this?

  • In current Docker Compose (the docker compose v2 plugin / Compose Specification), these condition: values are documented and supported. (docs.docker.com)
  • Related fields in the same depends_on long syntax have explicit minimum versions:
    • depends_on.<service>.restartintroduced in Docker Compose 2.17.0
    • depends_on.<service>.requiredintroduced in Docker Compose 2.20.0 (docs.docker.com)

Important limitation (Swarm)

  • depends_on (and therefore condition:) is ignored by docker stack deploy in Swarm mode, so you can’t rely on service_healthy ordering there. (github.com)

Citations:


Add yorkie and yorkie-intelligence to depends_on in the codepair-backend service.

codepair-backend uses YORKIE_API_ADDR=http://yorkie:8080 and OLLAMA_HOST_URL=http://yorkie-intelligence:11434 at startup. These services must be available before the backend container initializes. Add them to depends_on to prevent race conditions and startup failures.

Suggested adjustment
 depends_on:
   mongo:
     condition: service_healthy
   minio:
     condition: service_healthy
   qdrant:
     condition: service_healthy
+  yorkie:
+    condition: service_started
+  yorkie-intelligence:
+    condition: service_healthy
🤖 Prompt for AI Agents
In `@packages/backend/docker/docker-compose-full.yml` around lines 35 - 40, Update
the codepair-backend service's depends_on block to include yorkie and
yorkie-intelligence with the same health condition used for mongo/minio/qdrant;
specifically, in the docker-compose service definition for codepair-backend add
entries "yorkie: condition: service_healthy" and "yorkie-intelligence:
condition: service_healthy" so the backend waits for YORKIE_API_ADDR and
OLLAMA_HOST_URL targets to be healthy before startup.

Comment thread packages/codemirror/eslint.config.mjs Outdated
Comment on lines +7 to +8
files: ["**/*.ts", "**/*.tsx"],
ignores: ["dist", "eslint.config.mjs"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

ESLint flat config ignores property behavior documentation

💡 Result:

In ESLint flat config, the ignores behavior is documented mainly in these official pages:

  • Migration guide (eslintrc → flat config): explains that to replicate .eslintignore / ignorePatterns, you put ignores: [...] in a config object with no other keys, and notes the important glob difference that temp.js in flat config only matches next to the config file—use **/temp.js to match anywhere. It also notes dotfiles are not ignored by default in flat config (add "**/.*" if desired). [1]
  • “Ignore Files” (flat config) docs: explains ignoring via globalIgnores([...]) (for “completely ignore” / global ignore behavior), default ignore patterns (notably node_modules/ and .git/), how ignore patterns are resolved (e.g., .config/ vs **/.config/), and how ignored-file warnings work. [2]
  • ESLint blog (flat config intro): clarifies the two meanings:
    • ignores alongside files filters which files a config object applies to.
    • a config object with only ignores makes ESLint ignore those files entirely (similar to legacy ignorePatterns). [3]

Sources: [1] ESLint Migration Guide (Ignore Files section) [2] ESLint “Ignore Files” (flat config) documentation [3] ESLint blog: “New config system, Part 2: Introduction to flat config”


🏁 Script executed:

cat packages/codemirror/eslint.config.mjs

Repository: yorkie-team/codepair

Length of output: 864


Move ignores to a separate config object for global ignore behavior.

In ESLint flat config, ignores alongside other properties filters which files that config applies to. To globally ignore files (equivalent to .eslintignore), define a separate config object with only ignores:

♻️ Proposed fix
 export default [
+	{
+		ignores: ["dist/**", "eslint.config.mjs"],
+	},
 	{
 		files: ["**/*.ts", "**/*.tsx"],
-		ignores: ["dist", "eslint.config.mjs"],
 		languageOptions: {
🤖 Prompt for AI Agents
In `@packages/codemirror/eslint.config.mjs` around lines 7 - 8, Remove the ignores
entry from the existing config object that contains files:
["**/*.ts","**/*.tsx"] in eslint.config.mjs and add a separate top-level config
object whose sole property is ignores: ["dist","eslint.config.mjs"] so the
ignore behavior is global; locate the current config object (the one with files)
and update it to no longer include ignores, then create the new standalone
config object with only ignores to replicate .eslintignore semantics.

Comment on lines +1 to +20
import { Button, Menu, MenuItem, Paper } from "@mui/material";
import { useState } from "react";
import { useCMEditorContext } from "../CMEditorContext";
import { CodeKeyType } from "../types";

export const BOTTOM_BAR_HEIGHT = 25;

interface EditorBottomBarProps {
width: number | string;
}

function EditorBottomBar(props: EditorBottomBarProps) {
const { width } = props;
const { codeKey, setCodeKey } = useCMEditorContext();
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl);

const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the actual file content
cat -n packages/codemirror/src/components/EditorBottomBar.tsx | head -30

Repository: yorkie-team/codepair

Length of output: 1081


🏁 Script executed:

# Find and examine tsconfig
fd tsconfig.json

Repository: yorkie-team/codepair

Length of output: 208


🏁 Script executed:

# Check package.json for React version and JSX settings
cat packages/codemirror/package.json | grep -A 5 -B 5 '"react"' || cat package.json | grep -A 5 -B 5 '"react"'

Repository: yorkie-team/codepair

Length of output: 607


🏁 Script executed:

# Check the tsconfig for the codemirror package
cat packages/codemirror/tsconfig.json

Repository: yorkie-team/codepair

Length of output: 577


🏁 Script executed:

# Verify MouseEvent is available directly from react
rg "export.*MouseEvent" node_modules/@types/react/index.d.ts 2>/dev/null | head -5 || echo "Checking React type definitions..."

Repository: yorkie-team/codepair

Length of output: 46


🏁 Script executed:

# Check if other files in the repo use MouseEvent from react
rg "MouseEvent" packages/codemirror/src --type tsx --type ts -B 2 | head -30

Repository: yorkie-team/codepair

Length of output: 91


🏁 Script executed:

# Check the React types more carefully - look for exported event types
python3 << 'EOF'
import json
import subprocess

# Try to find how MouseEvent is typically used
result = subprocess.run(['rg', 'React\\.Mouse', 'packages/', '--type', 'ts', '--type', 'tsx'], 
                       capture_output=True, text=True)
print("Uses of React.MouseEvent:")
print(result.stdout[:1000] if result.stdout else "No results")

# Check for MouseEvent imports
result2 = subprocess.run(['rg', 'MouseEvent.*from.*react', 'packages/', '--type', 'ts', '--type', 'tsx'],
                        capture_output=True, text=True)
print("\nImports of MouseEvent from react:")
print(result2.stdout[:1000] if result2.stdout else "No results")
EOF

Repository: yorkie-team/codepair

Length of output: 146


🏁 Script executed:

# Check how other event handlers are typed in the codemirror package
rg "event:" packages/codemirror/src -A 1 --type ts | head -40

Repository: yorkie-team/codepair

Length of output: 754


🏁 Script executed:

# Look for other React event handler usages in the entire packages directory
rg "event.*HTMLElement|HTMLButtonElement" packages/ --type ts -B 1 -A 1 | head -50

Repository: yorkie-team/codepair

Length of output: 2200


🏁 Script executed:

# Check imports in files using MouseEvent vs React.MouseEvent
echo "=== useFormatUtils.ts (uses MouseEvent) ===" && \
head -10 packages/codemirror/src/hooks/useFormatUtils.ts && \
echo -e "\n=== DownloadMenu.tsx (uses MouseEvent) ===" && \
head -10 packages/frontend/src/components/common/DownloadMenu.tsx && \
echo -e "\n=== DocumentHeader.tsx (uses React.MouseEvent) ===" && \
head -15 packages/frontend/src/components/headers/DocumentHeader.tsx

Repository: yorkie-team/codepair

Length of output: 1528


🏁 Script executed:

# Check if React is imported in files that use React.MouseEvent
echo "=== EditorBottomBar.tsx (being reviewed) ===" && \
grep -n "^import" packages/codemirror/src/components/EditorBottomBar.tsx && \
echo -e "\n=== frontend EditorBottomBar.tsx ===" && \
grep -n "^import" packages/frontend/src/features/editor/shared/components/EditorBottomBar.tsx && \
echo -e "\n=== DocumentHeader.tsx ===" && \
grep -n "^import.*React" packages/frontend/src/components/headers/DocumentHeader.tsx

Repository: yorkie-team/codepair

Length of output: 628


🏁 Script executed:

# Check if DocumentHeader actually uses React.MouseEvent or if there's something else
grep -n "React.MouseEvent\|React.MouseEventHandler" packages/frontend/src/components/headers/DocumentHeader.tsx

Repository: yorkie-team/codepair

Length of output: 150


🏁 Script executed:

# Let's verify by checking what's actually exported from react for event types
python3 << 'EOF'
import subprocess

# Search for how React is used in the frontend package
result = subprocess.run([
    'rg', 'React\\.', 'packages/frontend/src/components/headers/DocumentHeader.tsx',
    '-n'
], capture_output=True, text=True)

print("React namespace usage in DocumentHeader.tsx:")
print(result.stdout if result.stdout else "No React.* usage found")
EOF

Repository: yorkie-team/codepair

Length of output: 293


🏁 Script executed:

# Check full imports of DocumentHeader to see if React is imported as namespace
head -30 packages/frontend/src/components/headers/DocumentHeader.tsx | grep -n "import"

Repository: yorkie-team/codepair

Length of output: 1257


Remove React namespace reference for MouseEvent type.

React.MouseEvent is used on line 18 but React namespace isn't imported. With jsx: "react-jsx", this will fail TypeScript compilation. Import the type directly instead, matching the pattern used elsewhere in the codebase.

🛠️ Proposed fix
-import { useState } from "react";
+import { useState, type MouseEvent } from "react";
@@
-const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
+const handleOpen = (event: MouseEvent<HTMLButtonElement>) => {
🤖 Prompt for AI Agents
In `@packages/codemirror/src/components/EditorBottomBar.tsx` around lines 1 - 20,
The event type on handleOpen uses the missing React namespace
(React.MouseEvent); import the MouseEvent type directly from "react" and update
the handler signature to use that imported type instead of React.MouseEvent so
TypeScript compiles with jsx: "react-jsx" (locate handleOpen in EditorBottomBar
and the top imports to add import type { MouseEvent } from "react").

declare module "markdown-it-task-checkbox";
declare module "markdown-it-link-attributes";
declare module "markdown-it-image-lazy-loading";
declare module "react-scroll-sync";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if `@types/react-scroll-sync` exists on npm
npm view `@types/react-scroll-sync` version 2>/dev/null && echo "=== `@types/react-scroll-sync` FOUND ===" || echo "=== `@types/react-scroll-sync` NOT FOUND ==="

# Check if react-scroll-sync includes its own types
echo ""
echo "=== Checking react-scroll-sync package ==="
npm view react-scroll-sync types typings --json 2>/dev/null | head -20

Repository: yorkie-team/codepair

Length of output: 172


Remove the bare module declaration — react-scroll-sync includes native type definitions.

The package provides its own type definitions at ./dist/index.d.ts and @types/react-scroll-sync is also available on npm. The bare declare module "react-scroll-sync"; declaration masks these actual types, treating all exports as any. Simply removing this line allows TypeScript to use the proper type information from the package itself.

🤖 Prompt for AI Agents
In `@packages/codemirror/src/global.d.ts` at line 6, Remove the bare module
augmentation `declare module "react-scroll-sync";` which masks the package's
real types; delete that line so TypeScript will pick up the package's own
`./dist/index.d.ts` (or `@types`) instead, then run a typecheck to verify imports
using react-scroll-sync now have proper typings.


## Interface

```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifiers to fenced code blocks.

The fenced code blocks at lines 33, 60, and 98 lack language specifiers. While these are file path indicators, adding text as the language improves rendering consistency and satisfies the linter.

📝 Proposed fix
-```
+```text
 packages/ui/src/types/EditorPort.ts

- +text
packages/codemirror/src/CMEditorAdapter.ts


-```
+```text
packages/ui/src/types/
└── EditorPort.ts                  # Editor-agnostic interface

Also applies to: 60-60, 98-98

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 33-33: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@packages/frontend/design/editor-port.md` at line 33, Update the three fenced
code blocks that show file paths to include the language specifier "text" so the
linter and renderer handle them consistently; specifically modify the blocks
referencing "packages/ui/src/types/EditorPort.ts",
"packages/codemirror/src/CMEditorAdapter.ts", and the tree block containing
"packages/ui/src/types/ └── EditorPort.ts" to use ```text instead of ``` to
ensure consistent rendering.

Comment thread packages/frontend/design/editor-port.md Outdated

To add a new editor (e.g., ProseMirror), you would:

1. Create `features/editor/prosemirror/` with its own components, hooks, and utils
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update hypothetical editor path to match monorepo pattern.

Line 139 suggests creating features/editor/prosemirror/ for a new editor, but this doesn't align with the monorepo structure. Based on how CodeMirror was extracted to packages/codemirror/, a new editor should be created as packages/prosemirror/ (a sibling package), not nested under frontend features.

📝 Proposed fix
 To add a new editor (e.g., ProseMirror), you would:
 
-1. Create `features/editor/prosemirror/` with its own components, hooks, and utils
+1. Create `packages/prosemirror/` as a new package with its own components, hooks, and utils
 2. Create `PMEditorAdapter.ts` implementing `EditorPort`
 3. Dispatch `setEditorPort(new PMEditorAdapter(...))` from the ProseMirror editor component
-4. Wire the new editor into `DocumentView` via `index.ts`
+4. Wire the new editor into the frontend app shell via appropriate imports
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
1. Create `features/editor/prosemirror/` with its own components, hooks, and utils
To add a new editor (e.g., ProseMirror), you would:
1. Create `packages/prosemirror/` as a new package with its own components, hooks, and utils
2. Create `PMEditorAdapter.ts` implementing `EditorPort`
3. Dispatch `setEditorPort(new PMEditorAdapter(...))` from the ProseMirror editor component
4. Wire the new editor into the frontend app shell via appropriate imports
🤖 Prompt for AI Agents
In `@packages/frontend/design/editor-port.md` at line 139, The doc suggests
creating features/editor/prosemirror/, but to follow the monorepo package
pattern used by packages/codemirror/ you should instead create a top-level
sibling package packages/prosemirror/ (not nested under frontend features);
update any references in the spec and any proposed import paths to point to
packages/prosemirror/ and mirror the packaging/setup used by
packages/codemirror/ (package.json, build/config, exports) so the new editor
lives as its own package.

- Fix React.MouseEvent type error by importing MouseEvent directly from react
- Add yorkie and yorkie-intelligence to codepair-backend depends_on
- Separate ESLint ignores into standalone config object for global behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
packages/frontend/design/architecture.md (1)

17-18: ⚠️ Potential issue | 🟡 Minor

Architecture doc still reflects pre‑monorepo editor layout.
Lines 17–69 and the “Feature-based Structure (After)” block (Lines 120–140) describe features/editor/codemirror and adding features/editor/prosemirror. The refactor moved editor implementations into sibling packages/* (e.g., packages/codemirror), so these examples should be updated to the new package-based layout.

Also applies to: 49-69, 120-140

🤖 Fix all issues with AI agents
In @.husky/pre-commit:
- Around line 6-12: The pre-commit loop only sets LINT_FRONTEND and LINT_BACKEND
based on CHANGED_FILES, missing packages/ui and packages/codemirror; update the
loop to also detect files under ^packages/ui/ and ^packages/codemirror/ and set
new flags (e.g., LINT_UI and LINT_CODEMIRROR) accordingly, and then add/ensure
the corresponding conditional blocks later in the script check LINT_UI and
LINT_CODEMIRROR (similar to LINT_FRONTEND/LINT_BACKEND) to invoke lint-staged
for those packages.

In `@packages/codemirror/src/components/Editor.tsx`:
- Around line 98-112: The useEffect that sets up the CodeMirror instance in
Editor.tsx includes applyFormat in its dependency array but applyFormat is not
referenced inside the effect, causing unnecessary re-creation of the editor;
remove applyFormat from the dependency array (keep element, client, doc,
codeKey, themeMode, fileUploadEnabled, handleUploadImage, intelligenceEnabled,
updateFormatBar, setKeymapConfig, setupVimKeybindings, setEditorPort) so the
effect only runs when truly relevant values change.

In `@packages/codemirror/src/plugins/intelligencePivot.ts`:
- Around line 5-7: INTELLIGENCE_HEADER_ID and INTELLIGENCE_FOOTER_ID are
duplicated across packages; centralize them to a single shared module and update
imports. Move the constants currently defined in intelligencePivot.ts into a
shared export (for example in `@codepair/ui` or export them from
`@codepair/codemirror` as a single source-of-truth), delete the local definitions
of INTELLIGENCE_HEADER_ID and INTELLIGENCE_FOOTER_ID, and update any consumers
to import the shared constants instead of redefining them.

In `@packages/frontend/design/editor-port.md`:
- Around line 128-133: Update the documentation paths so they point to the new
monorepo package locations: replace occurrences of
"features/editor/codemirror/**" with "packages/editor/codemirror/**" and replace
"features/intelligence/utils/intelligencePivot.ts" with
"packages/intelligence/utils/intelligencePivot.ts" (or the exact package name
housing the intelligence ViewPlugin) in editor-port.md so the import-boundary
guidance matches the current packages/* layout.

In
`@packages/frontend/src/features/intelligence/components/YorkieIntelligenceFooter.tsx`:
- Around line 24-27: The width calculation in YorkieIntelligenceFooter uses
useMemo to compute width = (editorStore.editorPort?.getContentWidth() ?? 0) - 12
which yields -12 when editorPort is null; fix by preventing negative widths or
avoiding render when editorPort is missing—either clamp the result to a minimum
of 0 (e.g., ensure width = Math.max(0,
(editorStore.editorPort?.getContentWidth() ?? 0) - 12)) or early-return from the
YorkieIntelligenceFooter render when editorStore.editorPort is falsy (e.g., if
(!editorStore.editorPort) return null); update the width constant and any
dependent layout usage accordingly.
🧹 Nitpick comments (4)
packages/codemirror/src/plugins/intelligencePivot.ts (1)

33-35: The compare method appears unused.

CodeMirror 6's WidgetType interface only uses eq() for equality comparisons. The compare method is not part of the standard API and will never be called by the framework. Consider removing it to reduce confusion.

🧹 Proposed cleanup
 	eq(widget: IntelligencePivotWidget) {
 		return widget.selectionRange === this.selectionRange;
 	}
-
-	compare(widget: IntelligencePivotWidget) {
-		return widget.selectionRange === this.selectionRange;
-	}
packages/codemirror/src/hooks/useFormatUtils.ts (1)

180-183: Type cast to CMEditorAdapter breaks the EditorPort abstraction.

Casting editorPort to CMEditorAdapter to access .view couples this hook to the CodeMirror implementation, defeating the purpose of the EditorPort abstraction. If a different editor is introduced, this code will fail.

Consider adding a format method to the EditorPort interface or keeping format logic within the CMEditorAdapter implementation:

♻️ Suggested approach

Option 1: Add an applyFormat method to EditorPort:

interface EditorPort {
  // existing methods...
  applyFormat(formatType: FormatType): void;
}

Option 2: If format tooling is CodeMirror-specific, consider moving this hook entirely into the @codepair/codemirror package where the coupling is acceptable.

packages/frontend/src/features/intelligence/components/YorkieIntelligenceFeature.tsx (1)

110-128: Normalize selection bounds before replaceRange.

If editorPort.getSelection() can return a reversed range (anchor > head), the current logic may insert at incorrect bounds. Consider normalizing from/to (or asserting the contract) before computing selectionFrom/selectionTo.

🔧 Suggested guard
- const selection = editorStore.editorPort.getSelection();
- let from = selection.from;
- const to = selection.to;
+ const { from: rawFrom, to: rawTo } = editorStore.editorPort.getSelection();
+ let from = Math.min(rawFrom, rawTo);
+ const to = Math.max(rawFrom, rawTo);
packages/codemirror/src/CMEditorSuite.tsx (1)

82-82: Consider making the fallback width configurable or documenting the choice.

The hardcoded fallback of 800 when width is not a number works, but this magic number could be documented or derived from a constant for clarity.

♻️ Optional: Extract to named constant
+const DEFAULT_WINDOW_WIDTH = 800;
+
 function CMEditorSuite({
     // ...
 }) {
     // ...
-    const windowWidth = typeof width === "number" ? width : 800;
+    const windowWidth = typeof width === "number" ? width : DEFAULT_WINDOW_WIDTH;

Comment thread .husky/pre-commit
Comment on lines 6 to 12
for FILE in $CHANGED_FILES; do
if [[ "$FILE" =~ ^frontend/ ]]; then
if [[ "$FILE" =~ ^packages/frontend/ ]]; then
LINT_FRONTEND=true
elif [[ "$FILE" =~ ^backend/ ]]; then
elif [[ "$FILE" =~ ^packages/backend/ ]]; then
LINT_BACKEND=true
fi
done
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add lint coverage for new packages (ui, codemirror).

Line 7 only sets lint flags for frontend/backend. Changes in packages/ui or packages/codemirror won’t trigger lint-staged, so those packages can bypass pre-commit checks.

✅ Proposed fix (add flags + blocks for new packages)
 LINT_FRONTEND=false
 LINT_BACKEND=false
+LINT_UI=false
+LINT_CODEMIRROR=false

 for FILE in $CHANGED_FILES; do
   if [[ "$FILE" =~ ^packages/frontend/ ]]; then
     LINT_FRONTEND=true
   elif [[ "$FILE" =~ ^packages/backend/ ]]; then
     LINT_BACKEND=true
+  elif [[ "$FILE" =~ ^packages/ui/ ]]; then
+    LINT_UI=true
+  elif [[ "$FILE" =~ ^packages/codemirror/ ]]; then
+    LINT_CODEMIRROR=true
   fi
 done

+# UI package
+if [ "$LINT_UI" = true ]; then
+  echo "Changes detected in ui. Linting & Formatting ui..."
+  cd packages/ui
+  npx lint-staged
+  if [ $? -ne 0 ]; then
+    echo "UI Linting & Formatting failed. Commit aborted."
+    exit 1
+  fi
+  cd ../..
+fi
+
+# CodeMirror package
+if [ "$LINT_CODEMIRROR" = true ]; then
+  echo "Changes detected in codemirror. Linting & Formatting codemirror..."
+  cd packages/codemirror
+  npx lint-staged
+  if [ $? -ne 0 ]; then
+    echo "CodeMirror Linting & Formatting failed. Commit aborted."
+    exit 1
+  fi
+  cd ../..
+fi
🤖 Prompt for AI Agents
In @.husky/pre-commit around lines 6 - 12, The pre-commit loop only sets
LINT_FRONTEND and LINT_BACKEND based on CHANGED_FILES, missing packages/ui and
packages/codemirror; update the loop to also detect files under ^packages/ui/
and ^packages/codemirror/ and set new flags (e.g., LINT_UI and LINT_CODEMIRROR)
accordingly, and then add/ensure the corresponding conditional blocks later in
the script check LINT_UI and LINT_CODEMIRROR (similar to
LINT_FRONTEND/LINT_BACKEND) to invoke lint-staged for those packages.

Comment on lines 98 to 112
}, [
element,
editorStore.client,
editorStore.doc,
configStore.codeKey,
client,
doc,
codeKey,
themeMode,
workspaceStore.data,
featureSettingStore.fileUpload?.enable,
dispatch,
createUploadUrl,
uploadFile,
fileUploadEnabled,
handleUploadImage,
intelligenceEnabled,
applyFormat,
updateFormatBar,
setKeymapConfig,
setupVimKeybindings,
setEditorPort,
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove applyFormat from the dependency array — it's not used in the effect.

applyFormat is included in the dependencies but is never used inside the effect body. This causes unnecessary editor recreation whenever the applyFormat reference changes, destroying and rebuilding the entire CodeMirror instance.

🔧 Proposed fix
 	}, [
 		element,
 		client,
 		doc,
 		codeKey,
 		themeMode,
 		fileUploadEnabled,
 		handleUploadImage,
 		intelligenceEnabled,
-		applyFormat,
 		updateFormatBar,
 		setKeymapConfig,
 		setupVimKeybindings,
 		setEditorPort,
 	]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}, [
element,
editorStore.client,
editorStore.doc,
configStore.codeKey,
client,
doc,
codeKey,
themeMode,
workspaceStore.data,
featureSettingStore.fileUpload?.enable,
dispatch,
createUploadUrl,
uploadFile,
fileUploadEnabled,
handleUploadImage,
intelligenceEnabled,
applyFormat,
updateFormatBar,
setKeymapConfig,
setupVimKeybindings,
setEditorPort,
]);
}, [
element,
client,
doc,
codeKey,
themeMode,
fileUploadEnabled,
handleUploadImage,
intelligenceEnabled,
updateFormatBar,
setKeymapConfig,
setupVimKeybindings,
setEditorPort,
]);
🤖 Prompt for AI Agents
In `@packages/codemirror/src/components/Editor.tsx` around lines 98 - 112, The
useEffect that sets up the CodeMirror instance in Editor.tsx includes
applyFormat in its dependency array but applyFormat is not referenced inside the
effect, causing unnecessary re-creation of the editor; remove applyFormat from
the dependency array (keep element, client, doc, codeKey, themeMode,
fileUploadEnabled, handleUploadImage, intelligenceEnabled, updateFormatBar,
setKeymapConfig, setupVimKeybindings, setEditorPort) so the effect only runs
when truly relevant values change.

Comment on lines +5 to +7

const INTELLIGENCE_HEADER_ID = "yorkie-intelligence-header";
const INTELLIGENCE_FOOTER_ID = "yorkie-intelligence-footer";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for usages of these intelligence pivot IDs across the codebase
# to verify if they're referenced elsewhere and need to be shared.

echo "=== Searching for INTELLIGENCE_HEADER_ID / INTELLIGENCE_FOOTER_ID ===" 
rg -n "INTELLIGENCE_HEADER_ID|INTELLIGENCE_FOOTER_ID"

echo ""
echo "=== Searching for the literal string values ==="
rg -n "yorkie-intelligence-header|yorkie-intelligence-footer"

Repository: yorkie-team/codepair

Length of output: 2408


Consolidate constants to a shared location to avoid duplication.

The INTELLIGENCE_HEADER_ID and INTELLIGENCE_FOOTER_ID constants are currently defined in two places: @codepair/frontend (exported) and @codepair/codemirror (locally). Since both packages reference the same DOM element identifiers and the frontend's intelligence features query these elements by ID, these constants should be centralized—either exported from @codepair/codemirror and imported by the frontend, or defined in @codepair/ui for cross-package consumption. Duplicate definitions risk divergence and maintenance issues.

🤖 Prompt for AI Agents
In `@packages/codemirror/src/plugins/intelligencePivot.ts` around lines 5 - 7,
INTELLIGENCE_HEADER_ID and INTELLIGENCE_FOOTER_ID are duplicated across
packages; centralize them to a single shared module and update imports. Move the
constants currently defined in intelligencePivot.ts into a shared export (for
example in `@codepair/ui` or export them from `@codepair/codemirror` as a single
source-of-truth), delete the local definitions of INTELLIGENCE_HEADER_ID and
INTELLIGENCE_FOOTER_ID, and update any consumers to import the shared constants
instead of redefining them.

Comment thread packages/frontend/design/editor-port.md Outdated
Comment on lines +128 to +133
After this change, `"codemirror"` and `"@codemirror"` imports only appear in:

- `features/editor/codemirror/**` — expected, this is the CM implementation
- `features/intelligence/utils/intelligencePivot.ts` — a CM ViewPlugin (moving it to `codemirror/` is a follow-up)

No other feature, shared component, or page imports from CodeMirror directly.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update import-boundary paths to monorepo package locations.
Line 130 still lists features/editor/codemirror/**, and Line 131 references features/intelligence/...; these should match the new packages/* layout to prevent stale guidance.

📝 Suggested update
- - `features/editor/codemirror/**`
- - `features/intelligence/utils/intelligencePivot.ts`
+ - `packages/codemirror/**`
+ - `packages/frontend/src/features/intelligence/utils/intelligencePivot.ts`
🤖 Prompt for AI Agents
In `@packages/frontend/design/editor-port.md` around lines 128 - 133, Update the
documentation paths so they point to the new monorepo package locations: replace
occurrences of "features/editor/codemirror/**" with
"packages/editor/codemirror/**" and replace
"features/intelligence/utils/intelligencePivot.ts" with
"packages/intelligence/utils/intelligencePivot.ts" (or the exact package name
housing the intelligence ViewPlugin) in editor-port.md so the import-boundary
guidance matches the current packages/* layout.

Comment on lines 24 to 27
const width = useMemo(
() => editorStore.cmView!.contentDOM.getBoundingClientRect().width - 12,
[editorStore.cmView]
() => (editorStore.editorPort?.getContentWidth() ?? 0) - 12,
[editorStore.editorPort]
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential negative width when editorPort is null.

The null safety improvement is good, but when editorPort is null, the width becomes 0 - 12 = -12, which could cause layout issues. Consider guarding the component render or using a minimum width:

🛡️ Proposed fix
 const width = useMemo(
-    () => (editorStore.editorPort?.getContentWidth() ?? 0) - 12,
+    () => Math.max((editorStore.editorPort?.getContentWidth() ?? 0) - 12, 0),
     [editorStore.editorPort]
 );

Alternatively, guard the render:

if (!editorStore.editorPort) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const width = useMemo(
() => editorStore.cmView!.contentDOM.getBoundingClientRect().width - 12,
[editorStore.cmView]
() => (editorStore.editorPort?.getContentWidth() ?? 0) - 12,
[editorStore.editorPort]
);
const width = useMemo(
() => Math.max((editorStore.editorPort?.getContentWidth() ?? 0) - 12, 0),
[editorStore.editorPort]
);
🤖 Prompt for AI Agents
In
`@packages/frontend/src/features/intelligence/components/YorkieIntelligenceFooter.tsx`
around lines 24 - 27, The width calculation in YorkieIntelligenceFooter uses
useMemo to compute width = (editorStore.editorPort?.getContentWidth() ?? 0) - 12
which yields -12 when editorPort is null; fix by preventing negative widths or
avoiding render when editorPort is missing—either clamp the result to a minimum
of 0 (e.g., ensure width = Math.max(0,
(editorStore.editorPort?.getContentWidth() ?? 0) - 12)) or early-return from the
YorkieIntelligenceFooter render when editorStore.editorPort is falsy (e.g., if
(!editorStore.editorPort) return null); update the width constant and any
dependent layout usage accordingly.

JOOHOJANG and others added 11 commits February 12, 2026 15:37
- Add pre-commit lint coverage for ui and codemirror packages
- Add lint-staged config to ui and codemirror package.json
- Remove unused applyFormat from Editor useEffect dependency array
- Guard against negative width in YorkieIntelligenceFooter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update root README.md package descriptions and paths to packages/
- Update frontend architecture.md to reflect CodeMirror extraction
- Update editor-port.md import boundaries and new editor guide
- Update backend README.md paths and relative links

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cture docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Define EditorSuiteProps<TDoc, TClient> in @codepair/ui as the shared
editor component contract, enabling editor packages to be swapped
without modifying the app shell. Unify CodeKeyType enum across
@codepair/ui, @codepair/codemirror, and frontend settings to eliminate
the `as unknown as` type hack in DocumentView.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename CMEditorSuite → EditorSuite and CMEditorAdapter → EditorAdapter
in the public API so that alternative editor packages can export the
same names naturally without CM-prefixed aliases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion, and export renaming

- docs/editor-port-architecture.md: Add EditorSuiteProps and CodeKeyType sections,
  update CMEditorSuite references to EditorSuite, fix "How to Add a New Editor" guide
- packages/frontend/README.md: Update directory structure to feature-based architecture,
  fix docs link path
- packages/frontend/design/redux-query.md: Add feature slice paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- YorkieCodeMirrorDocType → YorkieDocType
- YorkieCodeMirrorPresenceType → YorkiePresenceType
- Enables proxy package strategy for editor swapping without frontend changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The codemirror package pinned @yorkie-js/sdk@0.6.41 while the frontend
used 0.6.48, causing type incompatibilities at build time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds `EditorPosition = number | number[]` union type to EditorPort so
SmartEditor can use tree-index coordinates (number[]) while CodeMirror
continues to use character offsets (number).

- Export `EditorPosition` from @codepair/ui
- Update all EditorPort method signatures to use EditorPosition
- Add `getContent(): string` method to EditorPort interface
- Implement `getContent()` in CMEditorAdapter
- Fix arithmetic casts in codemirror/frontend (always number in CM context)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@hackerwins hackerwins merged commit 46318f0 into main Feb 26, 2026
7 checks passed
@hackerwins hackerwins deleted the editor-port branch February 26, 2026 11:40
@github-project-automation github-project-automation bot moved this to Done in CodePair Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants