fix: text compose papercuts #270

Merged
talksik merged 2 commits from fix-text-compose-papercuts into main 2026-06-11 19:08:34 +00:00
talksik commented 2026-06-11 18:30:40 +00:00 (Migrated from github.com)

Closes #267

Summary by CodeRabbit

  • New Features

    • Fallback link-preview UI so links show actions even without metadata
    • Clickable external-link action added to link thumbnails
    • Image-block support in the markdown composer (uploads disabled) and improved editor autofocus behavior
    • Composer now auto-switches to card mode for long text, attachments, or detected markdown; adaptive immersive text sizing
  • Bug Fixes

    • Improved clipboard paste handling for file-only pastes
  • Style

    • Enhanced markdown editor and message-card styling, scrollbars, and layout variables
Closes #267 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Fallback link-preview UI so links show actions even without metadata * Clickable external-link action added to link thumbnails * Image-block support in the markdown composer (uploads disabled) and improved editor autofocus behavior * Composer now auto-switches to card mode for long text, attachments, or detected markdown; adaptive immersive text sizing * **Bug Fixes** * Improved clipboard paste handling for file-only pastes * **Style** * Enhanced markdown editor and message-card styling, scrollbars, and layout variables <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-11 18:30:53 +00:00 (Migrated from github.com)

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors desktop compose: adds URL/markdown helpers, ensures link-preview fallbacks, enables Milkdown ImageBlock (uploads disabled), improves autofocus, replaces forceCardMode with session-latched carded logic, refines paste handling, and updates editor/card styling.

Changes

Compose Editor Improvements

Layer / File(s) Summary
Utility helpers for URL and markdown detection
js/desktop/src/lib/link-metadata.ts, js/desktop/src/lib/markdown.ts, js/desktop/package.json
New domainFromUrl extracts normalized hostnames; hasMarkdownFormatting detects markdown via regex. @milkdown/kit added (7.21.1).
Markdown editor and global styling
js/desktop/src/features/compose/markdown-editor.css, js/desktop/src/styles/globals.css
Sets ProseMirror caret color, heading typography overrides, hides Milkdown image uploader UI, suppresses read-only image controls, and adds .scrollbar-card styling plus layout CSS variables.
Markdown editor ImageBlock & focus behavior
js/desktop/src/features/compose/markdown-editor.tsx
Enables Milkdown ImageBlock (uploads rejected), constrains image block height, and changes autofocus to dispatch an editor action moving cursor to document end.
Text editor card-mode state refactoring
js/desktop/src/features/compose/text-editor.tsx
Replaces forceCardMode with carded state; auto-switches to card mode when length, attachments, or markdown formatting indicate; updates shortcut and adds entry animations.
Link preview fallback UI and integration
js/desktop/src/components/link-preview-card.tsx, js/desktop/src/features/compose/attachment-strip.tsx, js/desktop/src/features/particles/text-particle-view.tsx
Adds LinkPreviewCardFallback and uses it where metadata is missing so links remain visible and actionable (open/copy).
Paste event capture-phase handling
js/desktop/src/hooks/use-file-input.ts
Registers paste listener in capture phase; intercepts only file-only pastes and calls stopPropagation() after preventing default before forwarding files.
Immersive text sizing helper
js/desktop/src/lib/immersive-text.ts
Adds getImmersiveTextStyle(length) to centralize size/weight choices for immersive text display.

🎯 3 (Moderate) | ⏱️ ~25 minutes

"🐰
I hop through links when metadata's gone,
I pad the editor until the caret's dawn.
Paste gently, focus to the doc's last line,
Carded modes and fallbacks light the sign.
Compose is steady — nibble, copy, open, shine."

🚥 Pre-merge checks | 3 | 2

Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check Inconclusive The title 'fix: text compose papercuts' is vague and generic, using the non-descriptive term 'papercuts' without clearly conveying what specific issues are being addressed in the compose functionality. Consider a more specific title that mentions key fixes, such as 'fix: text editor padding, cursor placement, and link fallback rendering' or similar to better convey the scope of changes.
Passed checks (3 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check Passed The pull request successfully addresses all seven coding requirements from issue #267: padding fixes (CSS variables and styles), cursor placement at document end, heading size reduction, width consistency via shared styling variables, improved paste handling via file-only detection, and link metadata fallback rendering.
Out of Scope Changes check Passed All changes are directly related to fixing text compose issues specified in issue #267. Supporting additions like the Milkdown library dependency and ImageBlock configuration are necessary to implement the required fixes and are not out of scope.

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

Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-text-compose-papercuts

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

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/flowy-live/llink/pull/270?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough Refactors desktop compose: adds URL/markdown helpers, ensures link-preview fallbacks, enables Milkdown ImageBlock (uploads disabled), improves autofocus, replaces forceCardMode with session-latched carded logic, refines paste handling, and updates editor/card styling. ## Changes **Compose Editor Improvements** |Layer / File(s)|Summary| |---|---| |**Utility helpers for URL and markdown detection** <br> `js/desktop/src/lib/link-metadata.ts`, `js/desktop/src/lib/markdown.ts`, `js/desktop/package.json`|New `domainFromUrl` extracts normalized hostnames; `hasMarkdownFormatting` detects markdown via regex. `@milkdown/kit` added (7.21.1).| |**Markdown editor and global styling** <br> `js/desktop/src/features/compose/markdown-editor.css`, `js/desktop/src/styles/globals.css`|Sets ProseMirror caret color, heading typography overrides, hides Milkdown image uploader UI, suppresses read-only image controls, and adds `.scrollbar-card` styling plus layout CSS variables.| |**Markdown editor ImageBlock & focus behavior** <br> `js/desktop/src/features/compose/markdown-editor.tsx`|Enables Milkdown `ImageBlock` (uploads rejected), constrains image block height, and changes autofocus to dispatch an editor action moving cursor to document end.| |**Text editor card-mode state refactoring** <br> `js/desktop/src/features/compose/text-editor.tsx`|Replaces `forceCardMode` with `carded` state; auto-switches to card mode when length, attachments, or markdown formatting indicate; updates shortcut and adds entry animations.| |**Link preview fallback UI and integration** <br> `js/desktop/src/components/link-preview-card.tsx`, `js/desktop/src/features/compose/attachment-strip.tsx`, `js/desktop/src/features/particles/text-particle-view.tsx`|Adds `LinkPreviewCardFallback` and uses it where metadata is missing so links remain visible and actionable (open/copy).| |**Paste event capture-phase handling** <br> `js/desktop/src/hooks/use-file-input.ts`|Registers paste listener in capture phase; intercepts only file-only pastes and calls stopPropagation() after preventing default before forwarding files.| |**Immersive text sizing helper** <br> `js/desktop/src/lib/immersive-text.ts`|Adds `getImmersiveTextStyle(length)` to centralize size/weight choices for immersive text display.| 🎯 3 (Moderate) | ⏱️ ~25 minutes > "🐰 > I hop through links when metadata's gone, > I pad the editor until the caret's dawn. > Paste gently, focus to the doc's last line, > Carded modes and fallbacks light the sign. > Compose is steady — nibble, copy, open, shine." </details> <!-- walkthrough_end --> <!-- pre_merge_checks_walkthrough_start --> <details> <summary>🚥 Pre-merge checks | ✅ 3 | ❌ 2</summary> ### ❌ Failed checks (1 warning, 1 inconclusive) | Check name | Status | Explanation | Resolution | | :----------------: | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Docstring Coverage | ⚠️ Warning | Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | | Title check | ❓ Inconclusive | The title 'fix: text compose papercuts' is vague and generic, using the non-descriptive term 'papercuts' without clearly conveying what specific issues are being addressed in the compose functionality. | Consider a more specific title that mentions key fixes, such as 'fix: text editor padding, cursor placement, and link fallback rendering' or similar to better convey the scope of changes. | <details> <summary>✅ Passed checks (3 passed)</summary> | Check name | Status | Explanation | | :------------------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. | | Linked Issues check | ✅ Passed | The pull request successfully addresses all seven coding requirements from issue `#267`: padding fixes (CSS variables and styles), cursor placement at document end, heading size reduction, width consistency via shared styling variables, improved paste handling via file-only detection, and link metadata fallback rendering. | | Out of Scope Changes check | ✅ Passed | All changes are directly related to fixing text compose issues specified in issue `#267`. Supporting additions like the Milkdown library dependency and ImageBlock configuration are necessary to implement the required fixes and are not out of scope. | </details> <sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub> </details> <!-- pre_merge_checks_walkthrough_end --> <!-- finishing_touch_checkbox_start --> <details> <summary>✨ Finishing Touches</summary> <details> <summary>📝 Generate docstrings</summary> - [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR - [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch </details> <details> <summary>🧪 Generate unit tests (beta)</summary> - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `fix-text-compose-papercuts` </details> </details> <!-- finishing_touch_checkbox_end --> <!-- tips_start --> --- <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end --> <!-- internal state start --> <!-- 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 --> <!-- internal state end -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-06-11 18:36:58 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
js/desktop/src/features/compose/markdown-editor.tsx (1)

87-107: ⚠️ Potential issue | 🟡 Minor | Quick win

Add error handling for editor creation.

The crepe.create() promise has no .catch() handler. If editor initialization fails (e.g., due to invalid markdown, missing dependencies, or runtime errors), the rejection will be unhandled and could crash the component or leave it in an inconsistent state.

🛡️ Proposed fix to add error boundary
     crepe.create().then(() => {
       if (cancelled) {
         crepe.destroy();
         return;
       }
       created = crepe;
       if (autoFocus && !readOnly) {
         // Place the caret at the end of the document — the editor often mounts
         // mid-typing (immersive → card flip), where start-of-doc would strand
         // the user. Selection-only transactions don't echo markdownUpdated.
         crepe.editor.action((ctx) => {
           const view = ctx.get(editorViewCtx);
           view.dispatch(
             view.state.tr
               .setSelection(Selection.atEnd(view.state.doc))
               .scrollIntoView(),
           );
           view.focus();
         });
       }
-    });
+    }).catch((error) => {
+      console.error('Failed to create markdown editor:', error);
+      // Optionally: notify user or fallback to textarea
+    });
🤖 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 `@js/desktop/src/features/compose/markdown-editor.tsx` around lines 87 - 107,
Attach a .catch to the crepe.create() promise to handle initialization failures:
in the catch handler call crepe.destroy(), clear/leave unset the created
variable, perform any component cleanup (e.g., set cancelled or call the
existing cleanup path), log the error with a descriptive message (or call a
provided onError callback if available) so the rejection is not unhandled, and
avoid running the success-path logic (autoFocus/readOnly/crepe.editor.action)
when creation fails.
🧹 Nitpick comments (2)
js/desktop/src/lib/markdown.ts (1)

1-5: 💤 Low value

Consider detecting markdown links and images.

The regex currently detects headings, lists, code, bold, italic, and blockquotes, but doesn't detect markdown links [text](url) or images ![alt](url). These are common markdown patterns that might warrant switching to card mode.

Since this is a heuristic for UI mode switching, the current implementation is acceptable, but adding link/image detection would make it more comprehensive.

📝 Suggested enhancement
 export function hasMarkdownFormatting(content: string): boolean {
-  return /^#{1,6} |^\s*[-*+] |^\s*\d+\. |^```|`[^`]+`|\*\*|__|\*\S[^*]*\*|\b_\S[^_]*_\b|^>/m.test(
+  return /^#{1,6} |^\s*[-*+] |^\s*\d+\. |^```|`[^`]+`|\*\*|__|\*\S[^*]*\*|\b_\S[^_]*_\b|^>|!?\[.+?\]\(.+?\)/m.test(
     content,
   );
 }

The pattern !?\[.+?\]\(.+?\) matches both [text](url) and ![alt](url).

🤖 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 `@js/desktop/src/lib/markdown.ts` around lines 1 - 5, The regex in
hasMarkdownFormatting currently misses markdown links and images; update the
pattern returned by hasMarkdownFormatting to also match link/image syntax (e.g.
include a branch like !?\[.+?\]\(.+?\) in the alternation) so both [text](url)
and ![alt](url) trigger true; ensure the new subpattern is added to the existing
alternation in the returned RegExp (preserving the /m flag and other checks) so
the function continues to act as a comprehensive heuristic for switching UI
modes.
js/desktop/package.json (1)

67-67: Quick win

Verify version pinning consistency across Milkdown packages.

The newly added @milkdown/kit is pinned to exact version 7.21.1, while the existing @milkdown/crepe on line 66 uses a caret range ^7.21.1.

For packages from the same monorepo, it's typically recommended to use consistent version ranges to ensure compatibility. Was the exact pinning intentional, or should both packages use the same versioning strategy?

🔧 Suggested fix for consistency
-    "`@milkdown/kit`": "7.21.1",
+    "`@milkdown/kit`": "^7.21.1",
🤖 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 `@js/desktop/package.json` at line 67, The package versioning is inconsistent:
`@milkdown/kit` is pinned to 7.21.1 while `@milkdown/crepe` uses ^7.21.1; update the
version spec so both Milkdown packages use the same range (e.g., change
"`@milkdown/kit`" to "^7.21.1" to match "`@milkdown/crepe`") and then
reinstall/update the lockfile (npm/yarn/pnpm) to ensure the dependency tree is
consistent.
🤖 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 `@js/desktop/src/features/compose/text-editor.tsx`:
- Around line 67-72: The state update setCarded(true) must not run during
render; move the conditional that checks shouldCard (computed from textContent,
IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) into a useEffect
that watches the dependencies (textContent, hasEnrichments, carded or
shouldCard) and calls setCarded(true) when shouldCard becomes true and carded is
false; keep the existing immersive = !carded logic in render but ensure all
state mutations happen inside that useEffect to avoid React render-phase
updates.

---

Outside diff comments:
In `@js/desktop/src/features/compose/markdown-editor.tsx`:
- Around line 87-107: Attach a .catch to the crepe.create() promise to handle
initialization failures: in the catch handler call crepe.destroy(), clear/leave
unset the created variable, perform any component cleanup (e.g., set cancelled
or call the existing cleanup path), log the error with a descriptive message (or
call a provided onError callback if available) so the rejection is not
unhandled, and avoid running the success-path logic
(autoFocus/readOnly/crepe.editor.action) when creation fails.

---

Nitpick comments:
In `@js/desktop/package.json`:
- Line 67: The package versioning is inconsistent: `@milkdown/kit` is pinned to
7.21.1 while `@milkdown/crepe` uses ^7.21.1; update the version spec so both
Milkdown packages use the same range (e.g., change "`@milkdown/kit`" to "^7.21.1"
to match "`@milkdown/crepe`") and then reinstall/update the lockfile
(npm/yarn/pnpm) to ensure the dependency tree is consistent.

In `@js/desktop/src/lib/markdown.ts`:
- Around line 1-5: The regex in hasMarkdownFormatting currently misses markdown
links and images; update the pattern returned by hasMarkdownFormatting to also
match link/image syntax (e.g. include a branch like !?\[.+?\]\(.+?\) in the
alternation) so both [text](url) and ![alt](url) trigger true; ensure the new
subpattern is added to the existing alternation in the returned RegExp
(preserving the /m flag and other checks) so the function continues to act as a
comprehensive heuristic for switching UI modes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c025be3d-4a0c-46a6-8ada-baf969e9803b

📥 Commits

Reviewing files that changed from the base of the PR and between 66d53497c5 and 5b199434bf.

📒 Files selected for processing (10)
  • js/desktop/package.json
  • js/desktop/src/components/link-preview-card.tsx
  • js/desktop/src/features/compose/attachment-strip.tsx
  • js/desktop/src/features/compose/markdown-editor.css
  • js/desktop/src/features/compose/markdown-editor.tsx
  • js/desktop/src/features/compose/text-editor.tsx
  • js/desktop/src/features/particles/text-particle-view.tsx
  • js/desktop/src/hooks/use-file-input.ts
  • js/desktop/src/lib/link-metadata.ts
  • js/desktop/src/lib/markdown.ts
**Actionable comments posted: 1** > [!CAUTION] > Some comments are outside the diff and can’t be posted inline due to platform limitations. > > > > <details> > <summary>⚠️ Outside diff range comments (1)</summary><blockquote> > > <details> > <summary>js/desktop/src/features/compose/markdown-editor.tsx (1)</summary><blockquote> > > `87-107`: _⚠️ Potential issue_ | _🟡 Minor_ | _⚡ Quick win_ > > **Add error handling for editor creation.** > > The `crepe.create()` promise has no `.catch()` handler. If editor initialization fails (e.g., due to invalid markdown, missing dependencies, or runtime errors), the rejection will be unhandled and could crash the component or leave it in an inconsistent state. > > > > > > <details> > <summary>🛡️ Proposed fix to add error boundary</summary> > > ```diff > crepe.create().then(() => { > if (cancelled) { > crepe.destroy(); > return; > } > created = crepe; > if (autoFocus && !readOnly) { > // Place the caret at the end of the document — the editor often mounts > // mid-typing (immersive → card flip), where start-of-doc would strand > // the user. Selection-only transactions don't echo markdownUpdated. > crepe.editor.action((ctx) => { > const view = ctx.get(editorViewCtx); > view.dispatch( > view.state.tr > .setSelection(Selection.atEnd(view.state.doc)) > .scrollIntoView(), > ); > view.focus(); > }); > } > - }); > + }).catch((error) => { > + console.error('Failed to create markdown editor:', error); > + // Optionally: notify user or fallback to textarea > + }); > ``` > </details> > > <details> > <summary>🤖 Prompt for AI Agents</summary> > > ``` > Verify each finding against current code. Fix only still-valid issues, skip the > rest with a brief reason, keep changes minimal, and validate. > > In `@js/desktop/src/features/compose/markdown-editor.tsx` around lines 87 - 107, > Attach a .catch to the crepe.create() promise to handle initialization failures: > in the catch handler call crepe.destroy(), clear/leave unset the created > variable, perform any component cleanup (e.g., set cancelled or call the > existing cleanup path), log the error with a descriptive message (or call a > provided onError callback if available) so the rejection is not unhandled, and > avoid running the success-path logic (autoFocus/readOnly/crepe.editor.action) > when creation fails. > ``` > > </details> > > <!-- cr-comment:v1:4ce607e06ed47caa1379e078 --> > > </blockquote></details> > > </blockquote></details> <details> <summary>🧹 Nitpick comments (2)</summary><blockquote> <details> <summary>js/desktop/src/lib/markdown.ts (1)</summary><blockquote> `1-5`: _💤 Low value_ **Consider detecting markdown links and images.** The regex currently detects headings, lists, code, bold, italic, and blockquotes, but doesn't detect markdown links `[text](url)` or images `![alt](url)`. These are common markdown patterns that might warrant switching to card mode. Since this is a heuristic for UI mode switching, the current implementation is acceptable, but adding link/image detection would make it more comprehensive. <details> <summary>📝 Suggested enhancement</summary> ```diff export function hasMarkdownFormatting(content: string): boolean { - return /^#{1,6} |^\s*[-*+] |^\s*\d+\. |^```|`[^`]+`|\*\*|__|\*\S[^*]*\*|\b_\S[^_]*_\b|^>/m.test( + return /^#{1,6} |^\s*[-*+] |^\s*\d+\. |^```|`[^`]+`|\*\*|__|\*\S[^*]*\*|\b_\S[^_]*_\b|^>|!?\[.+?\]\(.+?\)/m.test( content, ); } ``` The pattern `!?\[.+?\]\(.+?\)` matches both `[text](url)` and `![alt](url)`. </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/lib/markdown.ts` around lines 1 - 5, The regex in hasMarkdownFormatting currently misses markdown links and images; update the pattern returned by hasMarkdownFormatting to also match link/image syntax (e.g. include a branch like !?\[.+?\]\(.+?\) in the alternation) so both [text](url) and ![alt](url) trigger true; ensure the new subpattern is added to the existing alternation in the returned RegExp (preserving the /m flag and other checks) so the function continues to act as a comprehensive heuristic for switching UI modes. ``` </details> <!-- cr-comment:v1:cb3cbbd7841ae4664d3e3934 --> </blockquote></details> <details> <summary>js/desktop/package.json (1)</summary><blockquote> `67-67`: _⚡ Quick win_ **Verify version pinning consistency across Milkdown packages.** The newly added `@milkdown/kit` is pinned to exact version `7.21.1`, while the existing `@milkdown/crepe` on line 66 uses a caret range `^7.21.1`. For packages from the same monorepo, it's typically recommended to use consistent version ranges to ensure compatibility. Was the exact pinning intentional, or should both packages use the same versioning strategy? <details> <summary>🔧 Suggested fix for consistency</summary> ```diff - "`@milkdown/kit`": "7.21.1", + "`@milkdown/kit`": "^7.21.1", ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/package.json` at line 67, The package versioning is inconsistent: `@milkdown/kit` is pinned to 7.21.1 while `@milkdown/crepe` uses ^7.21.1; update the version spec so both Milkdown packages use the same range (e.g., change "`@milkdown/kit`" to "^7.21.1" to match "`@milkdown/crepe`") and then reinstall/update the lockfile (npm/yarn/pnpm) to ensure the dependency tree is consistent. ``` </details> <!-- cr-comment:v1:27315621e4465e526145454b --> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` 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 `@js/desktop/src/features/compose/text-editor.tsx`: - Around line 67-72: The state update setCarded(true) must not run during render; move the conditional that checks shouldCard (computed from textContent, IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) into a useEffect that watches the dependencies (textContent, hasEnrichments, carded or shouldCard) and calls setCarded(true) when shouldCard becomes true and carded is false; keep the existing immersive = !carded logic in render but ensure all state mutations happen inside that useEffect to avoid React render-phase updates. --- Outside diff comments: In `@js/desktop/src/features/compose/markdown-editor.tsx`: - Around line 87-107: Attach a .catch to the crepe.create() promise to handle initialization failures: in the catch handler call crepe.destroy(), clear/leave unset the created variable, perform any component cleanup (e.g., set cancelled or call the existing cleanup path), log the error with a descriptive message (or call a provided onError callback if available) so the rejection is not unhandled, and avoid running the success-path logic (autoFocus/readOnly/crepe.editor.action) when creation fails. --- Nitpick comments: In `@js/desktop/package.json`: - Line 67: The package versioning is inconsistent: `@milkdown/kit` is pinned to 7.21.1 while `@milkdown/crepe` uses ^7.21.1; update the version spec so both Milkdown packages use the same range (e.g., change "`@milkdown/kit`" to "^7.21.1" to match "`@milkdown/crepe`") and then reinstall/update the lockfile (npm/yarn/pnpm) to ensure the dependency tree is consistent. In `@js/desktop/src/lib/markdown.ts`: - Around line 1-5: The regex in hasMarkdownFormatting currently misses markdown links and images; update the pattern returned by hasMarkdownFormatting to also match link/image syntax (e.g. include a branch like !?\[.+?\]\(.+?\) in the alternation) so both [text](url) and ![alt](url) trigger true; ensure the new subpattern is added to the existing alternation in the returned RegExp (preserving the /m flag and other checks) so the function continues to act as a comprehensive heuristic for switching UI modes. ``` </details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `c025be3d-4a0c-46a6-8ada-baf969e9803b` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 66d53497c5c0c67cb162e1f61a66ee449875d973 and 5b199434bfc0d26413e27ad1837cc14e659ca054. </details> <details> <summary>📒 Files selected for processing (10)</summary> * `js/desktop/package.json` * `js/desktop/src/components/link-preview-card.tsx` * `js/desktop/src/features/compose/attachment-strip.tsx` * `js/desktop/src/features/compose/markdown-editor.css` * `js/desktop/src/features/compose/markdown-editor.tsx` * `js/desktop/src/features/compose/text-editor.tsx` * `js/desktop/src/features/particles/text-particle-view.tsx` * `js/desktop/src/hooks/use-file-input.ts` * `js/desktop/src/lib/link-metadata.ts` * `js/desktop/src/lib/markdown.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
coderabbitai[bot] (Migrated from github.com) commented 2026-06-11 18:36:57 +00:00

⚠️ Potential issue | 🔴 Critical | Quick win

Fix setState during render — causes React warnings and potential bugs.

The pattern if (shouldCard && !carded) setCarded(true); calls a state setter directly during the render phase, which violates React's rules and will trigger warnings like "Cannot update a component from inside the function body of a different component."

This must be moved into a useEffect to update state safely after render.

🔒 Required fix
  const hasEnrichments = attachmentCount > 0 || linkPreviews.length > 0;
  const shouldCard =
    textContent.length >= IMMERSIVE_CHAR_LIMIT ||
    hasEnrichments ||
    hasMarkdownFormatting(textContent);
-  if (shouldCard && !carded) setCarded(true);
  const immersive = !carded;
+
+  useEffect(() => {
+    if (shouldCard && !carded) {
+      setCarded(true);
+    }
+  }, [shouldCard, carded]);
🤖 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 `@js/desktop/src/features/compose/text-editor.tsx` around lines 67 - 72, The
state update setCarded(true) must not run during render; move the conditional
that checks shouldCard (computed from textContent, IMMERSIVE_CHAR_LIMIT,
hasEnrichments, hasMarkdownFormatting) into a useEffect that watches the
dependencies (textContent, hasEnrichments, carded or shouldCard) and calls
setCarded(true) when shouldCard becomes true and carded is false; keep the
existing immersive = !carded logic in render but ensure all state mutations
happen inside that useEffect to avoid React render-phase updates.
_⚠️ Potential issue_ | _🔴 Critical_ | _⚡ Quick win_ **Fix setState during render — causes React warnings and potential bugs.** The pattern `if (shouldCard && !carded) setCarded(true);` calls a state setter directly during the render phase, which violates React's rules and will trigger warnings like "Cannot update a component from inside the function body of a different component." This must be moved into a `useEffect` to update state safely after render. <details> <summary>🔒 Required fix</summary> ```diff const hasEnrichments = attachmentCount > 0 || linkPreviews.length > 0; const shouldCard = textContent.length >= IMMERSIVE_CHAR_LIMIT || hasEnrichments || hasMarkdownFormatting(textContent); - if (shouldCard && !carded) setCarded(true); const immersive = !carded; + + useEffect(() => { + if (shouldCard && !carded) { + setCarded(true); + } + }, [shouldCard, carded]); ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/features/compose/text-editor.tsx` around lines 67 - 72, The state update setCarded(true) must not run during render; move the conditional that checks shouldCard (computed from textContent, IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) into a useEffect that watches the dependencies (textContent, hasEnrichments, carded or shouldCard) and calls setCarded(true) when shouldCard becomes true and carded is false; keep the existing immersive = !carded logic in render but ensure all state mutations happen inside that useEffect to avoid React render-phase updates. ``` </details> <!-- fingerprinting:phantom:poseidon:puma --> <!-- cr-comment:v1:4af5402e746e945c6f904bbe --> <!-- This is an auto-generated comment by CodeRabbit -->
coderabbitai[bot] (Migrated from github.com) reviewed 2026-06-11 19:08:06 +00:00
coderabbitai[bot] (Migrated from github.com) left a comment
♻️ Duplicate comments (1)
js/desktop/src/features/compose/text-editor.tsx (1)

62-67: ⚠️ Potential issue | 🔴 Critical | Quick win

setState during render still present — must use useEffect.

The pattern if (shouldCard) setCarded(true); calls a state setter directly during the render phase, which violates React's core rule: "Never call set functions during rendering." While the !carded guard prevents infinite loops, this still triggers warnings and can cause issues with React 19's concurrent rendering and batching.

This is the same issue flagged in the previous review. Move the state update into a useEffect as originally recommended:

🔒 Required fix
  const hasEnrichments = attachmentCount > 0 || linkPreviews.length > 0;
  const shouldCard =
-   !carded &&
-   (textContent.length >= IMMERSIVE_CHAR_LIMIT ||
-     hasEnrichments ||
-     hasMarkdownFormatting(textContent));
- if (shouldCard) setCarded(true);
+   textContent.length >= IMMERSIVE_CHAR_LIMIT ||
+   hasEnrichments ||
+   hasMarkdownFormatting(textContent);
  const immersive = !carded;
+
+ useEffect(() => {
+   if (shouldCard && !carded) {
+     setCarded(true);
+   }
+ }, [shouldCard, carded]);
🤖 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 `@js/desktop/src/features/compose/text-editor.tsx` around lines 62 - 67, The
check that computes shouldCard (using textContent, IMMERSIVE_CHAR_LIMIT,
hasEnrichments, hasMarkdownFormatting) must not call setCarded during render;
move the side-effect into a useEffect that watches the same dependencies plus
carded and calls setCarded(true) when shouldCard is true (and optionally
setCarded(false) when it becomes false, if desired). Replace the inline if
(shouldCard) setCarded(true) with a useEffect that computes shouldCard inside it
and invokes setCarded accordingly to avoid calling the state setter during
rendering.
🤖 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.

Duplicate comments:
In `@js/desktop/src/features/compose/text-editor.tsx`:
- Around line 62-67: The check that computes shouldCard (using textContent,
IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) must not call
setCarded during render; move the side-effect into a useEffect that watches the
same dependencies plus carded and calls setCarded(true) when shouldCard is true
(and optionally setCarded(false) when it becomes false, if desired). Replace the
inline if (shouldCard) setCarded(true) with a useEffect that computes shouldCard
inside it and invokes setCarded accordingly to avoid calling the state setter
during rendering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 705610ec-394f-4e6b-922b-c8eb76b6c858

📥 Commits

Reviewing files that changed from the base of the PR and between 5b199434bf and e821cd02ef.

📒 Files selected for processing (9)
  • js/desktop/src/components/link-preview-card.tsx
  • js/desktop/src/features/compose/attachment-strip.tsx
  • js/desktop/src/features/compose/markdown-editor.css
  • js/desktop/src/features/compose/markdown-editor.tsx
  • js/desktop/src/features/compose/text-editor.tsx
  • js/desktop/src/features/particles/text-particle-view.tsx
  • js/desktop/src/hooks/use-file-input.ts
  • js/desktop/src/lib/immersive-text.ts
  • js/desktop/src/styles/globals.css
🚧 Files skipped from review as they are similar to previous changes (4)
  • js/desktop/src/hooks/use-file-input.ts
  • js/desktop/src/features/compose/attachment-strip.tsx
  • js/desktop/src/features/compose/markdown-editor.tsx
  • js/desktop/src/features/particles/text-particle-view.tsx
<details> <summary>♻️ Duplicate comments (1)</summary><blockquote> <details> <summary>js/desktop/src/features/compose/text-editor.tsx (1)</summary><blockquote> `62-67`: _⚠️ Potential issue_ | _🔴 Critical_ | _⚡ Quick win_ **setState during render still present — must use useEffect.** The pattern `if (shouldCard) setCarded(true);` calls a state setter directly during the render phase, which violates React's core rule: "Never call set functions during rendering." While the `!carded` guard prevents infinite loops, this still triggers warnings and can cause issues with React 19's concurrent rendering and batching. This is the same issue flagged in the previous review. Move the state update into a `useEffect` as originally recommended: <details> <summary>🔒 Required fix</summary> ```diff const hasEnrichments = attachmentCount > 0 || linkPreviews.length > 0; const shouldCard = - !carded && - (textContent.length >= IMMERSIVE_CHAR_LIMIT || - hasEnrichments || - hasMarkdownFormatting(textContent)); - if (shouldCard) setCarded(true); + textContent.length >= IMMERSIVE_CHAR_LIMIT || + hasEnrichments || + hasMarkdownFormatting(textContent); const immersive = !carded; + + useEffect(() => { + if (shouldCard && !carded) { + setCarded(true); + } + }, [shouldCard, carded]); ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@js/desktop/src/features/compose/text-editor.tsx` around lines 62 - 67, The check that computes shouldCard (using textContent, IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) must not call setCarded during render; move the side-effect into a useEffect that watches the same dependencies plus carded and calls setCarded(true) when shouldCard is true (and optionally setCarded(false) when it becomes false, if desired). Replace the inline if (shouldCard) setCarded(true) with a useEffect that computes shouldCard inside it and invokes setCarded accordingly to avoid calling the state setter during rendering. ``` </details> <!-- cr-comment:v1:b7fab31ce4e5972ede95003e --> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. Duplicate comments: In `@js/desktop/src/features/compose/text-editor.tsx`: - Around line 62-67: The check that computes shouldCard (using textContent, IMMERSIVE_CHAR_LIMIT, hasEnrichments, hasMarkdownFormatting) must not call setCarded during render; move the side-effect into a useEffect that watches the same dependencies plus carded and calls setCarded(true) when shouldCard is true (and optionally setCarded(false) when it becomes false, if desired). Replace the inline if (shouldCard) setCarded(true) with a useEffect that computes shouldCard inside it and invokes setCarded accordingly to avoid calling the state setter during rendering. ``` </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `705610ec-394f-4e6b-922b-c8eb76b6c858` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 5b199434bfc0d26413e27ad1837cc14e659ca054 and e821cd02ef994f38079134f28b80d2324c0ac6d7. </details> <details> <summary>📒 Files selected for processing (9)</summary> * `js/desktop/src/components/link-preview-card.tsx` * `js/desktop/src/features/compose/attachment-strip.tsx` * `js/desktop/src/features/compose/markdown-editor.css` * `js/desktop/src/features/compose/markdown-editor.tsx` * `js/desktop/src/features/compose/text-editor.tsx` * `js/desktop/src/features/particles/text-particle-view.tsx` * `js/desktop/src/hooks/use-file-input.ts` * `js/desktop/src/lib/immersive-text.ts` * `js/desktop/src/styles/globals.css` </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary> * js/desktop/src/hooks/use-file-input.ts * js/desktop/src/features/compose/attachment-strip.tsx * js/desktop/src/features/compose/markdown-editor.tsx * js/desktop/src/features/particles/text-particle-view.tsx </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
Sign in to join this conversation.