Skip to content

feat(desktop): new-workspace screen experiment with sample prompts - #5907

Merged
saddlepaddle merged 4 commits into
mainfrom
sample-prompts-experiment
Jul 24, 2026
Merged

saddlepaddle merged 4 commits into
mainfrom
sample-prompts-experiment

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

Links

Summary

  • Test arm for the new-workspace-screen experiment: a page-based create surface for new users — Superset mark, "What should we build next?", three clickable sample prompts, and a decluttered composer — replacing the ~20-control modal. Control arm renders the existing modal, byte-identical.
  • Targets the worst step in the activation funnel: dashboard → first real workspace converts at 38% today (49% in March); ~54% of new users who reach the dashboard never create anything.

Why / Context

Sample prompts + reduced clutter at the exact moment new users stall ("what do I type?"). Primary metric: workspace_created (type != main) funnel from exposure; guardrails: agent_session_launch and 1–7d return retention, so a creation lift must be backed by real engagement.

How It Works

  • Arms: useNewWorkspaceScreenVariant gates on account age (NEW_WORKSPACE_SCREEN_EXPERIMENT_START), waits for PostHog flags to load before evaluating (avoids the undefined-flag race that would cross-contaminate brand-new users), and falls back to control-without-exposure after 2s offline. The new-workspace-screen-override flag short-circuits to the test UI with zero experiment exposure (team/dev access).
  • Test arm is a route (/new-workspace): store opens ("+", ⌘N, onboarding hand-off) redirect there; an empty dashboard renders the screen pinned as its zero state, replacing "No workspaces yet". TopBar hides under the same expanded-sidebar condition as the v2 workspace route (page brings its own drag strip).
  • Screen: purpose-built (NewWorkspaceScreen), reusing only non-visual plumbing (useSubmitWorkspace, draft store, pickers). Enter submits / Shift+Enter newlines (opt-in onEnterSubmit on MarkdownEditor), empty submit blocked, sample click fills + focuses the editor, suggestions animate out while typing.
  • Attachments: page-wide drop zone with animated "Drop to attach" overlay (dragover-heartbeat, self-healing), Codex-style cards — square image thumbnails with lightbox (download via new external.saveToDownloads), file cards with file-tree extension icons + reveal-in-Finder. Linked Linear/GitHub issue and PR parity with the modal (pills + link buttons + "based off PR #n").

Manual QA Checklist

Verified live over CDP against the dev stack (real mouse/keyboard input, screenshots captured):

  • Screen renders on /new-workspace with sidebar visible; no TopBar strip with expanded sidebar
  • Sample prompt click fills editor, hides suggestions, enables submit, focuses caret at end
  • Clear restores suggestions; Enter on empty does nothing
  • Typed prompt + Enter → workspace created, worktree provisioned, Claude launched with the prompt and responded (primary metric + guardrail path end-to-end)
  • 6× rapid nav bounce: exactly one screen instance, no exceptions
  • Draft text persists across navigation
  • Control modal unchanged (PromptGroup/ModalContent have zero diff)
  • Onboarding hand-off on a fresh account lands on /new-workspace with the new project pre-selected (needs a fresh-signup run)
  • Drag-drop overlay + image lightbox + reveal-in-Finder spot-check in packaged build

Testing

  • bun run typecheck
  • bun run lint
  • CDP-driven E2E in dev (above); one pre-existing console error observed (DockBadgeController setState-in-render from sidebar items — unrelated, tracked separately)

Design Decisions

  • Duplicate screen vs. layout-prop on PromptGroup: separate component so arms evolve independently and control stays pristine; only battle-tested plumbing is shared.
  • Code-gated eligibility vs. flag targeting: no signup-date person property exists, and identify/ingest races make flag-side date conditions unreliable for brand-new users; the code gate + surface-scoped evaluation keeps exposure exactly equal to the experiment population.
  • Test arm can't create promptless workspaces (control can): intentional treatment difference that biases against the test arm on the primary metric — a win is a conservative win.

Deployment / Rollout

  • Ships dark: experiment 387127 is a draft, so the flag serves nothing; only @superset.sh accounts see the screen (override flag, already live).
  • Before launching the experiment: bump NEW_WORKSPACE_SCREEN_EXPERIMENT_START to the release date, then launch in PostHog. New signups install the current build, so ramp is immediate.
  • Do not touch flag rollout/variants/prompts/cutoff mid-experiment.

Rollback

  • Kill the test UI instantly by setting the experiment flag to serve control 100% (or stopping the experiment); code paths degrade to the existing modal with no data impact.

https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5


Summary by cubic

Adds an experiment arm that replaces the dense “New workspace” modal with a full‑screen create page and sample prompts to help new users start faster. Control is unchanged.

  • New Features

    • New route: “/new-workspace”. Store opens redirect here in the test arm; the empty dashboard pins this screen only when isReady, there are zero workspaces, and default filters are active (prevents transient flashes and respects filtered views).
    • Composer: sample prompts insert full, actionable instructions with docs links (e.g., .superset/config.json setup). Enter submits; Shift+Enter adds a newline. Empty submit is blocked.
    • Attachments: page‑wide drag/drop overlay, compact attachment cards, image lightbox with “Save to Downloads” via external.saveToDownloads, and reveal-in-Finder.
    • Experiment gating: NEW_WORKSPACE_SCREEN with eligibility from NEW_WORKSPACE_SCREEN_EXPERIMENT_START. Evaluated only when the surface opens, after flags load, with a 2s offline fallback to control; NEW_WORKSPACE_SCREEN_OVERRIDE forces the screen without exposure.
    • Editor: MarkdownEditor now supports onEnterSubmit.
    • UI polish: hides TopBar on the new screen and adds a window drag strip.
  • Bug Fixes

    • Preselected project is applied once per mount to avoid snapping the picker back to the projectId URL param while editing.
    • Pinned empty state respects the default device filter (“This device”) so filtered‑to‑empty views keep the normal empty state.
    • Reveal-in-Finder avoids wrong-file opens by poisoning ambiguous same‑name attachments in the source path map.

Written for commit 1a34f55. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a full-screen authenticated new workspace experience at /new-workspace, including project preselection, prompt/configuration, and attachment cards.
    • Added sample prompts to quickly start the creation flow.
    • Added image preview with an in-app download to the system downloads folder, including safe filename handling and collision avoidance.
  • Improvements
    • Enabled Enter-to-submit in the new workspace markdown editor.
    • Rolled out the experience behind an experiment, with an optional override, and updated dashboard routing/empty-state behavior accordingly.

Test arm of PostHog experiment 387127 (flag new-workspace-screen): a
page-based create surface for new users — Superset mark, "What should we
build next?", three sample prompts, and a decluttered composer — replacing
the dense modal. Control arm is untouched.

- /new-workspace route; store opens redirect there in the test arm, and
  the empty dashboard renders the screen pinned as its zero state
- eligibility gated in code by NEW_WORKSPACE_SCREEN_EXPERIMENT_START;
  variant evaluated only after flags load, only on surface reach, so
  exposure matches the experiment population; new-workspace-screen-override
  flag forces the UI for the team with zero experiment exposure
- Enter submits (Shift+Enter newline), empty submit blocked, page-wide
  drag-drop with overlay, Codex-style attachment cards (image lightbox,
  reveal-in-Finder, saveToDownloads), linked issue/PR parity with the modal

Claude-Session: https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5
@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an experiment-gated full-screen new-workspace flow with project selection, prompt submission, attachments, image previews, Downloads persistence, and route-specific dashboard layout behavior.

Changes

New workspace flow

Layer / File(s) Summary
Variant selection and routing
packages/shared/src/constants.ts, apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/...
Feature flags and PostHog evaluation select the control or test experience; eligible flows route to /new-workspace, including optional project selection and the empty-dashboard screen.
Workspace creation screen
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/..., apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
Adds host, project, agent, model, effort, prompt, sample prompt, attachment, setup, and submission behavior, including plain-Enter submission support.
Attachment previews and Downloads
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/..., apps/desktop/src/lib/trpc/routers/external/index.ts
Adds attachment cards with image previews and a collision-safe Electron Downloads mutation for saving previewed images.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant NewWorkspaceScreen
  participant AttachmentCard
  participant ImagePreviewOverlay
  participant ExternalRouter
  participant DownloadsFolder
  User->>NewWorkspaceScreen: Add image attachment
  NewWorkspaceScreen->>AttachmentCard: Render attachment
  User->>AttachmentCard: Open image preview
  AttachmentCard->>ImagePreviewOverlay: Show image
  User->>ImagePreviewOverlay: Download image
  ImagePreviewOverlay->>ExternalRouter: saveToDownloads(filename, dataBase64)
  ExternalRouter->>DownloadsFolder: Write collision-safe file
  DownloadsFolder-->>ExternalRouter: Return saved path
  ExternalRouter-->>ImagePreviewOverlay: Return path
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, conventional, and accurately summarizes the main desktop experiment change.
Description check ✅ Passed The description includes the change summary, rationale, testing details, and checklist-style notes, covering the template requirements.
✨ 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 sample-prompts-experiment

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🤖 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 `@apps/desktop/src/lib/trpc/routers/external/index.ts`:
- Around line 162-168: Update the download save logic around the target filename
loop to use exclusive file creation with the writeFile flag "wx" instead of
existsSync probing. Retry with the next suffixed filename only when the write
fails with EEXIST, and preserve the existing base name, extension, directory,
and base64 content behavior.

In `@apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx`:
- Around line 364-372: Update the Enter submission condition in the custom
editor keydown handler to also require !event.isComposing before invoking
onEnterSubmitRef.current(), preserving submission for non-composition Enter
presses.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx`:
- Around line 42-45: Update the workspace rendering flow around
useNewWorkspaceScreenVariant and the variant === "test" branch so that when
counts.all === 0 and variant is unresolved (null), it returns a neutral loading
state or null instead of rendering V2WorkspacesList. Preserve the existing
NewWorkspaceEmptyScreen behavior once the variant resolves to "test".

In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx`:
- Around line 120-127: Update the attachment upload failure path associated with
AttachmentCard so host-side upload errors are logged to main.log in addition to
displaying the existing errorMessage tooltip/toast. Add the logging at the
server or main-process boundary where the upload failure is detected, preserving
the current UI behavior.
🪄 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: fe117ecb-85f4-439d-8318-0a1e99bdbe56

📥 Commits

Reviewing files that changed from the base of the PR and between f313df2 and 718cf3e.

📒 Files selected for processing (20)
  • apps/desktop/src/lib/trpc/routers/external/index.ts
  • apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/new-workspace/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/NewWorkspaceEmptyScreen/NewWorkspaceEmptyScreen.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/NewWorkspaceEmptyScreen/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/DashboardNewWorkspaceModal.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/components/ImagePreviewOverlay/ImagePreviewOverlay.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/components/ImagePreviewOverlay/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/SamplePrompts.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/constants.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useNewWorkspaceScreenVariant/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useNewWorkspaceScreenVariant/useNewWorkspaceScreenVariant.ts
  • packages/shared/src/constants.ts

Comment on lines +162 to +168
for (let i = 1; fs.existsSync(target); i++) {
target = nodePath.join(downloadsDir, `${name} (${i})${ext}`);
}
await fs.promises.writeFile(
target,
Buffer.from(input.dataBase64, "base64"),
);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate file and inspect relevant sections"
fd -a 'index.ts' . | sed 's#^\./##' | rg 'apps/desktop/src/lib/trpc/routers/external/index.ts' || true

if [ -f apps/desktop/src/lib/trpc/routers/external/index.ts ]; then
  wc -l apps/desktop/src/lib/trpc/routers/external/index.ts
  sed -n '120,185p' apps/desktop/src/lib/trpc/routers/external/index.ts | cat -n
  echo
  echo "Search for procedure definitions/imports"
  sed -n '1,120p' apps/desktop/src/lib/trpc/routers/external/index.ts | cat -n
fi

echo
echo "Search for callers/similar filename handling"
rg -n "input\.dataBase64|downloadsDir|base64|externalRouter|\.download|dataBase64" apps/desktop/src/lib/trpc apps/desktop/src -g '*.{ts,tsx}' || true

echo
echo "Node TOCTOU probe if node is available"
node - <<'JS'
const fs = require('fs');
const os = require('os');
const path = require('path');
const child = require('worker_threads').parentPort;
const shared = Buffer.alloc(4);
const { SharedArrayBuffer, Atomics } = require('worker_threads');
if (!child || !(shared instanceof SharedArrayBuffer)) {
  console.log('node worker/shared-memory probe unavailable in this execution context');
  process.exit(0);
}
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'node-tocouto-'));
const file = path.join(dir, 'same.txt');
(async () => {
  await Promise.all([
    new Promise(async (resolve) => {
      const w = child.constructor;
      const worker = new w(`
        const { Atomics, SharedArrayBuffer, workerData } = require('worker_threads');
        const shared = new SharedArrayBuffer(4);
        const sharedView = new Int32Array(shared);
        const file = workerData[0];
        Atomics.store(sharedView, 0, 0);
        Atomics.store(sharedView, 1, 1);
        require('fs').existsSync(file);
        Atomics.wait(sharedView, 0, 1);
        const first = require('fs').existsSync(file);
        try {
          return require('fs').openSync(file, 'wx', 0o644);
        } catch (error) {
          return error && error.code;
        }
      `, { workerData: [file] });
      worker.on('error', (err) => console.log('worker error', err.message));
      worker.on('exit', () => resolve(Atomics.load(new Int32Array(shared), 1)));
      Atomics.wait(new Int32Array(shared), 1, 1);
      Atomics.wait(new Int32Array(shared), 0, 0);
    }),
    new Promise((resolve) => {
      const w = child.constructor;
      const worker = new w(`
        const { Atomics, SharedArrayBuffer } = require('worker_threads');
        const shared = new Int32Array(workerData[0]);
        Atomics.store(shared, 0, 1);
        const ready = Atomics.wait(shared, 0, 0);
        return ready;
      `, { workerData: Int32Array.buffer });
      worker.on('error', (err) => console.log('worker error', err.message));
      worker.on('exit', () => resolve());
    }),
  ]);
  fs.writeFileSync(file, 'lost');
  await child.postMessage({ ok: 'done' });
})();
JS

Repository: superset-sh/superset

Length of output: 24656


🌐 Web query:

Node.js fs.writeFile exclusive flag EEXIST race condition

💡 Result:

Yes, using the wx flag with fs.writeFile (or the corresponding file system method) is the correct, recommended way to prevent race conditions when creating files [1][2]. The 'wx' flag stands for "write" and "exclusive" (x) [3]. When this flag is used, the underlying operating system performs the check for the file's existence and the file creation as a single, atomic operation [1]. This atomicity is critical because it eliminates the "time-of-check to time-of-use" (TOCTOU) race condition that occurs if you attempt to manually check for a file's existence using methods like fs.exists (deprecated) or fs.stat before writing [4][1][5]. In those manual approaches, another process could potentially create the file in the small window of time between your check and your write command, leading to an accidental overwrite [1]. When you use the wx flag: 1. The operation will fail if the file already exists, and the error returned will have the code EEXIST [1][5]. 2. If the file does not exist, it will be created and written to atomically [1]. Important considerations: - Handle the error: Since 'wx' causes an error when the file exists, your code must explicitly catch and handle the EEXIST error code to respond appropriately (e.g., by logging a message or choosing a different filename) [1][5]. - Not a general lock: While the wx flag ensures atomic creation, it does not act as a general file lock for subsequent read/write operations [3]. If you need to perform multiple operations on the same file without other processes interfering, you should look into more robust file locking mechanisms or architectural patterns [3]. - Don't use existence checks first: Official Node.js documentation explicitly warns against using fs.access or similar checks to test for file accessibility before calling file system methods, as this introduces the very race condition that the flags are designed to avoid [4][5].

Citations:


Make collision handling atomic.

The existsSync(target) probe and subsequent writeFile are separate operations, so concurrent saves resolving to the same filename can race and one can be lost. Use an exclusive write (wx) loop that retries on EEXIST instead of probing before each write.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 164-167: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.promises.writeFile(
target,
Buffer.from(input.dataBase64, "base64"),
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 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 `@apps/desktop/src/lib/trpc/routers/external/index.ts` around lines 162 - 168,
Update the download save logic around the target filename loop to use exclusive
file creation with the writeFile flag "wx" instead of existsSync probing. Retry
with the next suffixed filename only when the write fails with EEXIST, and
preserve the existing base name, extension, directory, and base64 content
behavior.

Source: Linters/SAST tools

Comment on lines +364 to +372
if (
onEnterSubmitRef.current &&
event.key === "Enter" &&
!event.shiftKey &&
!event.altKey
) {
onEnterSubmitRef.current();
return true;
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does ProseMirror handleKeyDown fire during IME composition isComposing keyCode 229

💡 Result:

ProseMirror is designed to ignore key events during IME composition to prevent them from interfering with the browser's native handling of composition sessions [1][2]. In its core implementation, ProseMirror's keydown handler utilizes an internal function—typically inOrNearComposition(view, event)—to check if a composition is currently active or recently ended [1][2]. When this check identifies that an IME composition is in progress, the handler returns early, effectively preventing the handleKeyDown prop from being invoked [1][2]. Regarding keyCode 229: 1. The value 229 is the "Composition" key code, a legacy identifier often used by browsers (notably older versions of Safari) to signal that a key event is being processed as part of an IME composition session [3][4][5]. 2. While modern browsers primarily use the isComposing flag on the event object to indicate composition, ProseMirror acknowledges the complexity of cross-browser IME behavior [2][5]. 3. ProseMirror's internal logic avoids triggering standard keydown processing when isComposing is true or when it detects that the event is associated with an active composition state [1][2]. If you are writing custom plugins or handlers, you should similarly guard your handleKeyDown logic by checking event.isComposing [5]. Additionally, some developers include a check for event.keyCode === 229 as a fallback to ensure compatibility with older or specific browser environments that may not reliably set the isComposing flag [4][5][6]. Top results: [1][2][5]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate MarkdownEditor file and related package files"
fd -a 'MarkdownEditor\.tsx|package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json' . | sed 's#^\./##' | head -80

echo
echo "MarkdownEditor outline"
ast-grep outline apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx --view expanded || true

echo
echo "Relevant MarkdownEditor sections"
wc -l apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
sed -n '200,390p' apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx

echo
echo "Search for tiptap/prosemirror imports and usages"
rg -n "tiptap|prosemirror|handleKeyDown|onEnterSubmit|isComposing|ProseMirror" apps/desktop/src/renderer/components/MarkdownEditor apps/desktop -g '*.{ts,tsx}' -g 'package.json' -g 'pnpm-lock.yaml' -g 'yarn.lock' -g 'package-lock.json' | head -200

Repository: superset-sh/superset

Length of output: 40431


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Lockfile/package manager"
git ls-files | rg '(^|/)package\.json$|(^|/)pnpm-lock\.yaml$|(^|/)yarn\.lock$|(^|/)package-lock\.json$' | head -20

echo
echo "Desktop package dependencies"
node - <<'JS'
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('apps/desktop/package.json','utf8'));
for (const section of ['dependencies','devDependencies']) {
  console.log(`\n[${section}]`);
  for (const [k,v] of Object.entries(p[section]||{}).filter(([k]) => k.includes('tiptap') || k.includes('prosemirror') || k.includes('electron'))) {
    console.log(`${k}: ${v}`);
  }
}
JS

echo
echo "Root lock snippets for tiptap/prosemirror/electron"
for f in pnpm-lock.yaml yarn.lock package-lock.json; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  rg -n "`@tiptap`|prosemirror|electron(-main|preload)?|electron(\@|:)|desktop app|electron|`@electron`" "$f" | head -120 || true
done

echo
echo "KeyboardHandler implementation"
sed -n '100,135p' apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx

Repository: superset-sh/superset

Length of output: 3265


🌐 Web query:

@tiptap/react 3 handleKeyDown isComposing composition events ProseMirror

💡 Result:

In Tiptap (built on ProseMirror), handling keyboard events during an IME composition session requires caution because browsers fire events in complex sequences that can cause input corruption if intercepted improperly [1][2]. To properly manage handleKeyDown in a Tiptap or ProseMirror context while respecting composition: 1. Use event.isComposing: The standard way to detect if a keyboard event is occurring during an IME composition session is to check the read-only isComposing property of the KeyboardEvent [3]. In your handleKeyDown plugin prop, you should generally ignore events where event.isComposing is true to avoid interfering with the native composition process [2]. 2. Understand ProseMirror's Role: ProseMirror's core view logic already includes internal guards—such as inOrNearComposition—to prevent the editor from attempting to handle key events that occur during or immediately around an IME composition session [1]. Interfering with this by trying to force-handle or block events can lead to duplicated characters, incorrect text placement, or broken composition flow [2]. 3. Implementation Example: When writing a custom extension that uses a ProseMirror plugin to intercept keys, you can implement the check as follows: import { Extension } from '@tiptap/core'; import { Plugin, PluginKey } from 'prosemirror-state'; export const MyKeyboardExtension = Extension.create({ name: 'my-keyboard-extension', addProseMirrorPlugins { return [ new Plugin({ key: new PluginKey('my-key-handler'), props: { handleKeyDown: (view, event) => { // Ignore events during composition to prevent input corruption if (event.isComposing) { return false; } // Your custom key handling logic if (event.key === 'Enter') { //... your logic... return true; } return false; }, }, }), ]; }, }); Key Considerations: - Do not attempt to start or end composition events programmatically; these are managed by the browser's IME [4][5]. - Be aware that on certain mobile browsers (particularly Android/Chrome), composition events can behave unpredictably [6][1]. The native ProseMirror input handlers are specifically designed to suppress certain keys (like Enter) during these states to maintain stability [1]. - If you find your logic is still being triggered during composition, ensure your check is placed at the very beginning of your handleKeyDown handler [2].

Citations:


🌐 Web query:

Tiptap 3 handleKeyDown composition events source code event.isComposing

💡 Result:

In Tiptap (which is built on ProseMirror), composition events and the isComposing state are handled primarily at the ProseMirror level rather than within Tiptap's core source code [1][2]. When you implement custom keyboard handling via Tiptap extensions—such as using the handleKeyDown prop—it is important to respect the browser's native composition session to avoid issues with IME (Input Method Editor) input, which is common for languages like Chinese, Japanese, and Korean [1][2]. The KeyboardEvent.isComposing property is a standard Web API that indicates whether a key event occurs during a composition session (i.e., after compositionstart and before compositionend) [3]. In ProseMirror's event handling architecture, this is used to prevent the editor from prematurely processing key inputs that are intended to be part of an ongoing composition [4][2]. If you are using handleKeyDown in a Tiptap extension, you can check the isComposing property on the event object to decide whether to bypass your custom logic [5][2]. A common pattern looks like this: handleKeyDown(view, event) { if (event.isComposing) { return false; // Let the browser/ProseMirror handle the event during composition } // Your custom logic here return true; } ProseMirror internally manages composition events (via compositionstart, compositionupdate, and compositionend) to ensure the document state remains consistent with the DOM [4][2]. Manually interfering with these events is generally discouraged, as the browser must handle them normally for composition to function correctly [2]. Issues involving composition in Tiptap are often inherited from ProseMirror's input handling, where special care is required to ensure that editor state updates do not interrupt or prematurely finalize a composition session [1][4].

Citations:


Check event.isComposing before submitting on Enter.

Even though Ti/Tiptap uses ProseMirror under the hood, this custom editorProps.handleKeyDown can still observe key events during IME composition. Guarding with !event.isComposing prevents committing an IME candidate from firing onEnterSubmit and submitting prematurely.

🐛 Proposed guard
 				if (
 					onEnterSubmitRef.current &&
 					event.key === "Enter" &&
+					!event.isComposing &&
 					!event.shiftKey &&
 					!event.altKey
 				) {
 					onEnterSubmitRef.current();
 					return true;
 				}
📝 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
if (
onEnterSubmitRef.current &&
event.key === "Enter" &&
!event.shiftKey &&
!event.altKey
) {
onEnterSubmitRef.current();
return true;
}
if (
onEnterSubmitRef.current &&
event.key === "Enter" &&
!event.isComposing &&
!event.shiftKey &&
!event.altKey
) {
onEnterSubmitRef.current();
return true;
}
🤖 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 `@apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx`
around lines 364 - 372, Update the Enter submission condition in the custom
editor keydown handler to also require !event.isComposing before invoking
onEnterSubmitRef.current(), preserving submission for non-composition Enter
presses.

Comment on lines +120 to +127
if (isError && errorMessage) {
return (
<Tooltip>
<TooltipTrigger asChild>{body}</TooltipTrigger>
<TooltipContent>{errorMessage}</TooltipContent>
</Tooltip>
);
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline "apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx" || true

echo "== relevant lines =="
cat -n "apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx" | sed -n '1,180p'

echo "== search for errorMessage / main.log / log error patterns =="
rg -n "errorMessage|main\.log|console\.log|log\\(" apps/desktop/src -g '*.ts' -g '*.tsx' | sed -n '1,220p'

Repository: superset-sh/superset

Length of output: 43696


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx"
echo "== git diff stat and specific diff if available =="
git diff -- "$file" || true
git diff -- "$file" | sed -n '1,220p' || true

echo "== attachment card references/usages =="
rg -n "AttachmentCard|file\.url|uploadError|error" apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal -g '*.ts' -g '*.tsx' | sed -n '1,260p'

Repository: superset-sh/superset

Length of output: 13628


🏁 Script executed:

#!/bin/bash
set -euo pipefail

store="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useUploadAttachments/store.ts"
hook="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useUploadAttachments/useUploadAttachments.ts"
submit="apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts"

echo "== store outline =="
ast-grep outline "$store" || true
echo "== store tail =="
cat -n "$store" | sed -n '1,140p'

echo "== hook lines =="
cat -n "$hook" | sed -n '1,120p'

echo "== submit error surface =="
cat -n "$submit" | sed -n '1,80p'
cat -n "$submit" | sed -n '110,180p'

echo "== exact main.log occurrences =="
rg -n "main\.log|mainLog|mainLog\(" apps/desktop/src apps/desktop -g '*.ts' -g '*.tsx' || true

Repository: superset-sh/superset

Length of output: 15332


Ensure attachment upload failures are written to main.log.

The upload path collects failures and shows them in a tooltip/toast, but main.log is not written anywhere for attachment upload failures. Add server-side or main-process logging when the host-side attachment upload fails.

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/AttachmentCard/AttachmentCard.tsx`
around lines 120 - 127, Update the attachment upload failure path associated
with AttachmentCard so host-side upload errors are logged to main.log in
addition to displaying the existing errorMessage tooltip/toast. Add the logging
at the server or main-process boundary where the upload failure is detected,
preserving the current UI behavior.

Source: Learnings

@github-actions

github-actions Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

…uctions

Short labels in the UI, real instructions inserted on click: the setup
prompt walks the agent through .superset/config.json (setup/teardown/run
scripts, per-workspace ports) with links to docs.superset.sh.

Claude-Session: https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/constants.ts`:
- Line 13: Update the prompt string in the Sample Prompts constants to instruct
copying only explicitly allowlisted files, such as .env, from
$SUPERSET_ROOT_PATH into new workspaces; remove the broad “untracked files”
wording while preserving the setup, teardown, port, and summary requirements.
🪄 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: 7021bd59-163a-4252-83c4-93b0951de145

📥 Commits

Reviewing files that changed from the base of the PR and between 718cf3e and 1dc05be.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/SamplePrompts.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/SamplePrompts.tsx

{
id: "set-up-project",
label: "Set up this project for Superset",
prompt: `Set up this repository to work well with Superset workspaces. Read https://docs.superset.sh/setup-teardown-scripts and create a .superset/config.json with: setup commands that install dependencies and copy untracked files (like .env) from "$SUPERSET_ROOT_PATH" into new workspaces, teardown commands that stop anything setup starts, and a run command that launches the dev server. If parallel workspaces would collide on dev-server ports, make the scripts pick a free port per workspace (see https://docs.superset.sh/ports). When you're done, summarize what you configured and how to use it.`,

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not copy arbitrary untracked files.

This wording could lead the agent to bulk-copy secrets, credentials, build outputs, or other host-only files into every workspace. Restrict the instruction to an explicit allowlist such as .env; the official setup docs demonstrate copying only that file. (docs.superset.sh)

🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/components/SamplePrompts/constants.ts`
at line 13, Update the prompt string in the Sample Prompts constants to instruct
copying only explicitly allowlisted files, such as .env, from
$SUPERSET_ROOT_PATH into new workspaces; remove the broad “untracked files”
wording while preserving the setup, teardown, port, and summary requirements.

Main (#5908) replaced counts with isReady in useAccessibleV2Workspaces —
textually clean merge but semantically broken (the pinned empty-state
check referenced the removed counts). Resolved by gating the pinned
create screen on isReady + zero rows + default filters, which also fixes
the transient-zero flash the old counts check was exposed to.

Claude-Session: https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5
- ref-guard preSelectedProjectId so the project picker isn't snapped
  back to the URL param on every change (mirrors the control modal)
- gate the pinned empty state on the default device filter so
  filtered-to-empty device views keep the normal empty state
- poison ambiguous same-name entries in the attachment path map so
  reveal-in-Finder never opens the wrong file

Claude-Session: https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5
@saddlepaddle
saddlepaddle merged commit 806762d into main Jul 24, 2026
18 checks passed
saddlepaddle added a commit that referenced this pull request Aug 17, 2026
…6557)

The suggestion block was positioned `absolute inset-x-6 bottom-full`, so it
took no layout space. The heading above it is the flex-1 spacer, which
therefore centered itself into space the suggestions then drew on top of —
the cards rendered straight through "What should we build next?".

Latent since the screen shipped in #5907; it only needed a tall enough
suggestion set to surface. The returning-tier explain-superset card wrapped
its title to two lines, which was enough.

Putting the block back in flow fixes it structurally: the flex-1 heading
absorbs the height, and because the composer block is bottom-anchored and
PromptInput sits below the suggestions, the composer itself does not move —
only the heading recenters. The root is already overflow-y-auto, so windows
too short for both scroll rather than collide.

Also shortens the explain-superset label to "Get more out of Superset" so the
card title fits one line.

This branch was previously deployed

1 inactive deployment
preview — 1a34f554 Deployed Jul 24, 2026 by saddlepaddle via Deploy API #6851
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.

1 participant