Skip to content

improvement(rich-md-editor): streaming, performance, minor bugfixes#5148

Merged
waleedlatif1 merged 18 commits into
stagingfrom
fix/file-editor-md
Jun 20, 2026
Merged

improvement(rich-md-editor): streaming, performance, minor bugfixes#5148
waleedlatif1 merged 18 commits into
stagingfrom
fix/file-editor-md

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Smooth streaming support, perf improvements, minor bugfixes

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 20, 2026 10:19pm

Request Review

@cursor

cursor Bot commented Jun 20, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches shared editor state reconciliation and workspace chrome hydration; incorrect baseline logic could show wrong content or unlock editing during agent writes, but changes are covered by new unit tests.

Overview
Improves agent file streaming and rich markdown editing, and fixes sidebar collapse painting at the wrong width on first paint.

Streaming & reconciliation: Text editor state gains hasBaseline so when a stream starts before the initial fetch (opening a file mid-agent-edit), pre-edit server content is adopted as the baseline instead of finalizing to stale text. Display uses paced useSmoothText while the reducer keeps true content for saves; isAgentEditing plus a mothership grace lock keeps editors read-only across agent edit gaps. Loading no longer unmounts the editor if the stream settles before fetch completes.

Rich markdown: Large streams throttle full re-parses (~40k+ chars). Bubble menu is Linear-style (fixed anchor, reveal on drag-release, portal to body); new link hover card and shared toolbar buttons. Mod+K is yielded from global commands via data-owned-shortcuts. Task-list checkboxes match design-system styling.

Sidebar: WorkspaceChrome is the single collapse source (data-collapsed + CSS --sidebar-width: 51px); Sidebar takes isCollapsed as a prop.

Reviewed by Cursor Bugbot for commit 871068a. Configure here.

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds smooth streaming support for the rich-markdown editor, performance improvements for large files, and several UX fixes including a new link-hover card, a drag-select–reveal bubble menu, and a sidebar collapse refactor that eliminates FOUC on SSR.

  • Streaming UX: useSmoothText gains a streaming-start snap to avoid replaying a pre-existing document word-by-word; text-editor-state adds hasBaseline to distinguish a real baseline from the initial placeholder, preventing premature finalization to stale content when streaming begins before the initial fetch.
  • Rich-editor improvements: New LinkHoverCard component with edit/copy/remove actions; bubble menu refactored with fixed-position anchoring and drag-select reveal; ToolbarButton extracted into a shared component; large-file stream throttle (>40 KB → 120 ms) prevents main-thread saturation.
  • Sidebar & chrome: Collapse derivation lifted to WorkspaceChrome, propagated as a prop; data-collapsed CSS variable set from the server-rendered attribute prevents the expand→collapse flash on first paint.

Confidence Score: 4/5

Safe to merge with two targeted fixes: the reconciling-path race in text-editor-state.ts, and the draftHref reset in the new link-hover card.

The streaming state machine correctly fixes the streaming-active path with the hasBaseline guard, but the parallel reconciling path still uses the bare fetchedContent !== savedContent check — leaving the same stale-content race for a fast stream that ends before the fetch. The new LinkHoverCard does not reset draftHref when activeLink changes while editing, which can silently apply the wrong URL to the wrong link. The remaining changes are well-reasoned and correct.

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-state.ts (reconciling path, line 151) and apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/link-hover-card.tsx (onOver handler).

Important Files Changed

Filename Overview
apps/sim/hooks/use-smooth-text.ts Adds a streaming-start snap that reveals large pre-existing documents in full when streaming begins; previous-tracker refs moved to committed useEffect, fixing the concurrent-mode discard issue.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-state.ts Adds hasBaseline field; fixes streaming-active hasFetchedAdvanced guard, but reconciling path (lines 150-151) still lacks the hasBaseline guard.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/link-hover-card.tsx New floating link-hover card; draftHref is not cleared when activeLink changes to a different element while editing, enabling silent wrong-link URL overwrite.
apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-editable-file-content.ts Integrates useSmoothText for display-only pacing, adds isAgentEditing lock, fixes isContentLoading guard.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx Adds useAgentFileEditLock with 1.5s grace window; timer cleanup is correct.
apps/sim/app/_styles/globals.css Adds --sidebar-width override for data-collapsed; modifies globals.css against project convention.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[streamingContent arrives] --> B{isNextStreaming?}
    B -- yes --> C{hasBaseline?}
    C -- no, fetch arrived --> D[adoptBaseline]
    C -- yes --> E{hasFetchedAdvanced?}
    E -- yes --> F[finalize: phase=ready]
    E -- no --> G[phase=streaming]
    D --> G
    B -- no --> H{phase=streaming/reconciling?}
    H -- yes --> I{fetchedContent defined?}
    I -- yes --> J{hasFetchedAdvanced? missing hasBaseline guard}
    J -- true --> K[finalize]
    J -- false --> L[phase=reconciling]
    I -- no --> L
    H -- no --> M[ready/uninitialized]
    style J fill:#ffcccc,stroke:#cc0000
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[streamingContent arrives] --> B{isNextStreaming?}
    B -- yes --> C{hasBaseline?}
    C -- no, fetch arrived --> D[adoptBaseline]
    C -- yes --> E{hasFetchedAdvanced?}
    E -- yes --> F[finalize: phase=ready]
    E -- no --> G[phase=streaming]
    D --> G
    B -- no --> H{phase=streaming/reconciling?}
    H -- yes --> I{fetchedContent defined?}
    I -- yes --> J{hasFetchedAdvanced? missing hasBaseline guard}
    J -- true --> K[finalize]
    J -- false --> L[phase=reconciling]
    I -- no --> L
    H -- no --> M[ready/uninitialized]
    style J fill:#ffcccc,stroke:#cc0000
Loading

Comments Outside Diff (1)

  1. apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-state.ts, line 150-157 (link)

    P1 hasBaseline guard missing in the post-stream reconcile path

    The streaming-active branch correctly adds state.hasBaseline to hasFetchedAdvanced, but the parallel reconciling path (reached when the stream ends before the initial fetch resolves) still uses the bare fetchedContent !== state.savedContent check. When a very short stream completes before the fetch resolves, the state enters reconciling with hasBaseline: false and savedContent: ''. The next fetch carries the file's pre-edit content, so fetchedContent !== '' evaluates true, hasFetchedAdvanced fires, and the editor immediately finalizes to the stale pre-edit content — the same race the streaming-active path was just fixed for.

    The new test suite exercises the scenario where the fetch arrives during streaming (so savedContent is already set to the baseline when the stream ends), but does not cover the scenario where the stream ends before the fetch — which is exactly the missing guard here.

Reviews (5): Last reviewed commit: "fix(rich-md-editor): lock the editor imm..." | Re-trigger Greptile

Comment thread apps/sim/hooks/use-smooth-text.ts Outdated
A collapsed rail painted at the expanded width then animated to 51px on
refresh: structure came from the cookie (server) while width came from
independent cookie reads (blocking script + store), so any disagreement left
the collapsed structure at the persisted expanded width until the store
corrected it.

Unify collapse into one derivation in WorkspaceChrome and drive the collapsed
width from a server-rendered data-collapsed attribute via CSS
(.sidebar-shell-outer[data-collapsed]) — the same cookie source as the
structure, so width can never diverge from it. This is shadcn's documented
pattern (data-attribute selectors over JS ternaries for collapsed dimensions).

Also removes the redundant migratedCollapsed reconciliation (the store already
seeds from the migrated cookie and hasHydrated flips in the same pre-paint
effect) and the now-unused per-Sidebar derivation; Sidebar takes isCollapsed
as a prop.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0834afe. Configure here.

The error UI renders Sidebar outside WorkspaceChrome, so it has no derived
collapse state; feed it the same source of truth via readCollapsedCookie() now
that isCollapsed is a required prop.
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

1 similar comment
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b064503. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 871068a. Configure here.

@waleedlatif1 waleedlatif1 merged commit 4bf7917 into staging Jun 20, 2026
4 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/file-editor-md branch June 20, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants