Skip to content

feat(desktop): configurable link-click behavior in v2#3600

Merged
saddlepaddle merged 6 commits into
mainfrom
settings-switch-links
Apr 22, 2026
Merged

feat(desktop): configurable link-click behavior in v2#3600
saddlepaddle merged 6 commits into
mainfrom
settings-switch-links

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 21, 2026

Summary

  • New Settings → Links page with two 3-tier mappers (File / URL). Each tier (Click, ⌘-click, ⌘⇧-click) binds to Do nothing, in-app pane (File viewer / In-app browser), or external (editor / system browser). Picking an action already bound to another tier demotes that tier to Do nothing — no silent swapping.
  • Preferences live in a new v2_user_preferences TanStack DB localStorageCollection keyed by organizationId. Consumers go through useV2UserPreferences, useTerminalLinkActions (3-tier), and useInlineLinkActions (2-tier).
  • v2 TerminalPane file + URL clicks dispatch through the tier map. Folder clicks stay hardcoded (⌘ reveals, ⌘⇧ external) — there's only one sensible primary for folders. MarkdownEditor (used by task detail + workspace chat input editing) now honors the URL pref via TipTap handleClickOn; previously URLs in task markdown were inert. FilePane toolbar gained an always-available "Open in editor" icon button.
  • Folder-reveal bug: threaded an isDirectory: true hint from TerminalPane through revealPathWorkspaceSidebarFilesTabuseFileTree.reveal so known-directory targets get expanded even when the terminal's statPath resolved path and listDirectory's child paths don't byte-match (case differences / symlink resolution).
  • Click-hint tooltip changed from the hardcoded "Hold ⌘ to open · ⌘⇧ for external" to "Not bound · configure in Settings → Links" so it reflects user-configurable reality.
  • Keyboard settings moved from the Personal group to Editor & Workflow.

Test plan

  • Settings → Links page: both mappers render; picking an action already bound to another tier demotes the other tier to Do nothing; toast "Changes saved" fires on each change; values persist across a window reload.
  • v2 terminal — file links:
    • Plain click with Click = Do nothing → hint tooltip.
    • ⌘-click with default mapping → opens in FilePane.
    • ⌘⇧-click with default mapping → opens in external editor (remote workspaces toast rather than firing).
    • Swap tiers in settings and reconfirm each click-type matches the new binding.
  • v2 terminal — folder links:
    • Plain click → hint.
    • ⌘-click → sidebar opens to Files tab, folder is expanded and scrolled into view. Confirm it works for nested folders and folders whose path differs from the tree's loaded entries (symlinks, case).
    • ⌘⇧-click → external editor.
  • v2 terminal — URL links (test the same 3-tier matrix): plain/⌘/⌘⇧ map to hint / in-app browser pane / system browser per default; flip in settings and reconfirm.
  • Task markdown — URL plain-click opens in system browser (previously inert); ⌘-click honors meta binding; if both tiers are Do nothing the link doesn't open.
  • FilePane toolbar — "Open in editor" button opens the current file in the external editor.
  • Chrome buttons (DashboardSidebar PR badge, Review tab CI check links) still open in the system browser — unchanged.
  • bun run --filter @superset/desktop typecheck and bun run lint:fix clean.

Summary by cubic

Adds a new Settings → Links page to configure what Click, ⌘-click, and ⌘⇧-click do for file paths and URLs across v2. Terminal, task markdown, and panes honor these settings; folders still use ⌘ to reveal and ⌘⇧ to open externally.

  • New Features

    • Links page with two 3-tier mappers (File, URL) mapping to Do nothing, in-app pane, or external; tiers pick independently.
    • Preferences stored in v2_user_preferences; hooks: useV2UserPreferences, useTerminalLinkActions (3-tier), useInlineLinkActions (2-tier).
    • Terminal: file/URL clicks route through the tier map; hover tooltip reads your live mapping and hides when unbound; plain unbound clicks show “Not bound · configure in Settings → Links”.
    • Markdown: URLs now clickable and follow the URL setting; without a pane context, “pane” and “external” both open the system browser. FilePane adds an always-available “Open in editor” button (with aria-label). Settings nav gains a Links section; Keyboard settings moved to Editor & Workflow.
  • Bug Fixes

    • Folder reveals reliably re-expand and re-scroll using a pendingReveal object; useFileTree.reveal(path, { isDirectory }) expands known directories even when path forms differ (case/symlink), and the effect cancels stale runs and waits for the worktree path.
    • openPane now activates panes replaced via reuse, so reused File/Browser panes get focus after link clicks.
    • Terminal URL provider forwards all activations; v1 consumer gates plain clicks so v1 behavior stays the same while v2 settings handle tiers.
    • Markdown: unbound URL tiers no longer swallow clicks (cursor placement works).

Written for commit dcb1a66. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Link clicks in editor, terminal, and markdown are now handled consistently and respect per-modifier actions; markdown links open via the new handler.
    • "Open in editor" button added to file header.
  • Settings

    • New Links settings page to map actions (pane/external/none) for file and URL clicks by modifier tier.
    • Settings navigation includes a Links section.
  • UX

    • Sidebar reveal supports queued/pending reveals for files/directories.
    • Link hover hint text simplified and made settings-driven.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds preference-driven link-action mappings (plain/meta/meta+shift), persisted v2UserPreferences, hooks to resolve inline/terminal link actions, settings UI to edit mappings, and wiring to control link clicks across MarkdownEditor, TerminalPane, sidebar reveal, and external editor/browser flows.

Changes

Cohort / File(s) Summary
Link Actions Core
apps/desktop/src/renderer/hooks/useV2UserPreferences/tiers.ts, apps/desktop/src/renderer/hooks/useV2UserPreferences/useLinkActions.ts, apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts, apps/desktop/src/renderer/hooks/useV2UserPreferences/index.ts
Add tier utilities (terminalTierFor, inlineTierFor, actionFor), useV2UserPreferences hook with persistence/upsert, and useTerminalLinkActions / useInlineLinkActions exposing getFileAction / getUrlAction.
Preferences Schema & Collection
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
Add v2UserPreferences Zod schema, types (V2UserPreferencesRow, LinkAction, LinkTierMap), defaults, constant ID, and register org-scoped v2UserPreferences collection.
Settings UI & Routing
apps/desktop/src/renderer/routes/_authenticated/settings/links/..., apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx, apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx, apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts, apps/desktop/src/renderer/stores/settings-state.ts
Add Links settings page and route (/settings/links), LinksSettings and LinkTierMapper components, search/index entries for links, and extend SettingsSection union with "links".
Markdown & Terminal Integration
apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/.../TerminalPane/TerminalPane.tsx, .../LinkHoverTooltip/LinkHoverTooltip.tsx
MarkdownEditor and TerminalPane now use inline/terminal action hooks to resolve link clicks; TerminalPane refactors click handling to dispatch `"pane"
Sidebar Reveal Flow
apps/desktop/src/renderer/hooks/host-service/useFileTree/useFileTree.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx, .../FilesTab/FilesTab.tsx, .../page.tsx
Extend reveal(path, options?: { isDirectory?: boolean }); add pendingReveal (path/isDirectory/nonce) threaded from page → WorkspaceSidebar → FilesTab to drive reveal and scrolling, honoring hint overrides.
File Pane UI
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/.../FilePaneHeaderExtras/FilePaneHeaderExtras.tsx
Always render header-extras container, add "Open in editor" external button (via useOpenInExternalEditor) and conditionally render FileViewToggle.
Exports / Barrels
apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/index.ts, apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinksSettings/index.ts, apps/desktop/src/renderer/hooks/useV2UserPreferences/index.ts
Add barrel re-exports for new settings components and preference hooks/types.
Pane Store Behavior
packages/panes/src/core/store/store.ts
Simplify replacePane: always set tab activePaneId to new pane id (remove conditional preservation).
Terminal URL handling & tests
apps/desktop/src/renderer/screens/main/.../Terminal/helpers.ts, .../url-link-provider.ts, .../url-link-provider.test.ts
Adjust URL activation to always forward activation to onOpen, prevent default only when modifier keys present, and update test expectations accordingly.
sequenceDiagram
    participant User
    participant Renderer as "Markdown/Terminal"
    participant LinkActions as "LinkActionsApi"
    participant Prefs as "PreferencesStore"
    participant App as "AppHandler"
    participant External as "ExternalAgent"

    User->>Renderer: click link (with modifiers)
    Renderer->>LinkActions: getFileAction(event) / getUrlAction(event)
    LinkActions->>Prefs: read fileLinks / urlLinks
    Prefs-->>LinkActions: return LinkTierMap
    LinkActions-->>Renderer: return LinkAction ("pane" / "external" / null)
    alt action == "external"
        Renderer->>App: request external open (editor/browser)
        App->>External: electronTrpcClient / system open
    else action == "pane"
        Renderer->>App: reveal/open in-app (may pass { isDirectory: true })
        App->>Renderer: reveal result / open pane
    else action == null
        Renderer->>User: show "Not bound · configure in Settings → Links" hint
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through code and set three tiers,

Clicks can open panes or fling to frontiers,
A nibble for settings, defaults snug and sound,
Reveals that scroll, and links that bound.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.34% 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
Title check ✅ Passed The title 'feat(desktop): configurable link-click behavior in v2' clearly summarizes the main change: adding configurable link-click behavior to the v2 workspace. It is specific, concise, and directly reflects the primary objective of the changeset.
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.
Description check ✅ Passed PR description is comprehensive and well-structured, covering changes, test plan, and objectives.

✏️ 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 settings-switch-links

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

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR adds a configurable link-click behavior system for v2 of the desktop app. It introduces a Settings → Links page where users can map three click tiers (plain, , ⌘⇧) to actions (Do nothing, in-app pane, external) independently for file paths and URLs. Preferences are stored in a new v2UserPreferences TanStack DB localStorageCollection keyed by organizationId and consumed through useV2UserPreferences, useTerminalLinkActions, and useInlineLinkActions. The PR also fixes a folder-reveal bug (path case/symlink mismatches), makes URLs in MarkdownEditor actionable for the first time, adds an "Open in editor" button to the FilePane toolbar, and moves the Keyboard settings entry into the Editor & Workflow group.

Key changes:

  • New LinkTierMap schema + Zod validation in dashboardSidebarLocal/schema.ts with sensible defaults (plain: null, meta: \"pane\", metaShift: \"external\").
  • LinkTierMapper component with demotion logic — picking an action already bound to another tier demotes that tier to Do nothing.
  • useFileTree.reveal extended with an isDirectory hint to handle case/symlink path mismatches when expanding terminal-clicked folders.
  • TerminalPane folder-click handling is intentionally hardcoded (not settings-controlled), which is clearly documented.
  • The handleClickOn TipTap handler in MarkdownEditor treats both \"pane\" and \"external\" actions identically (opens system browser), since no pane context is available — this is documented in a code comment and in the settings page description."

Confidence Score: 4/5

PR is safe to merge; one P2 logic nuance in MarkdownEditor and one minor callback memoization miss do not block the primary user path.

The overall architecture is clean and well-layered (schema → collection → hook → UI). The tier-demotion logic, the isDirectory reveal hint, and the settings plumbing are all correct. Two non-critical issues were found: (1) the handleClickOn handler unconditionally consumes link-click events even when the action is null, which prevents cursor placement on link text in the editing surface; (2) the onPendingRevealConsumed callback is not memoized, causing FilesTab's reveal effect to re-run on every WorkspaceContent render.

apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx — handleClickOn event-consumption logic; apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx — onPendingRevealConsumed memoization.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx Adds handleClickOn to intercept link clicks; unconditionally consumes the event for all anchor clicks, preventing cursor placement even when action is null.
apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts New hook; upsert logic for the singleton preferences row is correct; uses TanStack DB live query and insert/update correctly.
apps/desktop/src/renderer/hooks/useV2UserPreferences/useLinkActions.ts Clean 2-hook abstraction (terminal 3-tier / inline 2-tier); memoized callbacks key on stable preference object references.
apps/desktop/src/renderer/hooks/useV2UserPreferences/tiers.ts Tier-resolution helpers look correct; actionFor is exported but not used internally (harmless).
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx pendingRevealDirectory state threads the isDirectory hint correctly; onPendingRevealConsumed callback is not memoized causing needless FilesTab effect re-runs.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx pendingRevealDirectory propagation and reveal-with-isDirectory logic is sound; async onPendingRevealConsumed call after reveal resolves is safe.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx File and URL link dispatch correctly routes through tier map; folder clicks remain hardcoded as intended; getFileAction/getUrlAction added to deps array.
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts New schema definitions (LinkAction, LinkTier, LinkTierMap, V2UserPreferencesRow) are well-structured with Zod defaults and a singleton id literal.
apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx Demotion logic (demote conflicting tier to null rather than swap) is correct; Select/Label IDs are unique via idPrefix.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx Always-visible Open in editor button added correctly; existing FileViewToggle is now conditionally rendered instead of early-returning null.
apps/desktop/src/renderer/hooks/host-service/useFileTree/useFileTree.ts reveal signature extended with optional isDirectory hint; logic correctly falls back to entriesByPath lookup when the hint is not provided.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([User clicks link]) --> B{Surface?}
    B --> C[TerminalPane]
    B --> D[MarkdownEditor]
    C --> E{Link type?}
    E --> F[Folder]
    E --> G[File]
    E --> H[URL]
    F --> F1{Modifier?}
    F1 -->|none| F2[Show hint tooltip]
    F1 -->|⌘| F3[revealPath + sidebar expand]
    F1 -->|⌘⇧| F4[External editor]
    G --> G1[getFileAction via useTerminalLinkActions]
    G1 -->|null| G2[Show hint tooltip]
    G1 -->|pane| G3[Open in FilePane]
    G1 -->|external| G4[Open in external editor]
    H --> H1[getUrlAction via useTerminalLinkActions]
    H1 -->|null| H2[Show hint tooltip]
    H1 -->|pane| H3[Open in BrowserPane]
    H1 -->|external| H4[Open in system browser]
    D --> I[getUrlAction via useInlineLinkActions plain or meta tier only]
    I -->|null| I1[Do nothing - cursor placement blocked]
    I -->|pane or external| I2[Open in system browser no pane context]
    subgraph Settings
        J[Settings Links page] --> K[LinkTierMapper File links]
        J --> L[LinkTierMapper URL links]
        K --> M[(v2UserPreferences localStorage)]
        L --> M
    end
    M --> G1
    M --> H1
    M --> I
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
Line: 281-296

Comment:
**`return true` prevents cursor placement on links regardless of action**

`handleClickOn` always calls `event.preventDefault()` and returns `true` for any anchor element with an `href`. When `getUrlAction(event)` returns `null` ("do nothing"), the event is fully consumed — TipTap won't place the cursor at the clicked position and the browser won't follow the link. This means users can no longer single-click on link text to position their cursor there for editing.

In an editing surface (task detail, chat input), cursor-on-link is often needed to select or modify the link. With the current code, keyboard navigation or approaching from adjacent text becomes the only option.

A minimal fix is to skip `preventDefault` and return `false` (let TipTap handle normally) when `getUrlAction` is null:

```
handleClickOn: (_view, _pos, _node, _nodePos, event) => {
    const target = event.target as HTMLElement | null;
    const anchor = target?.closest?.("a") as HTMLAnchorElement | null;
    if (!anchor) return false;
    const href = anchor.getAttribute("href");
    if (!href) return false;
    const action = getUrlAction(event);
    if (action === null) return false; // let TipTap place the cursor
    event.preventDefault();
    electronTrpcClient.external.openUrl.mutate(href).catch((error) => {
        console.error("[MarkdownEditor] Failed to open URL:", href, error);
    });
    return true;
},
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
Line: 485

Comment:
**`onPendingRevealConsumed` not memoized — unnecessary effect re-runs**

The inline arrow function `() => setPendingRevealDirectory(null)` is recreated on every render of `WorkspaceContent`. Because `onPendingRevealConsumed` is in the dependency array of `FilesTab`'s reveal `useEffect`, that effect re-runs on every parent render. The guard `selectedFilePath !== prevSelectedRef.current` prevents a real reveal from firing twice, but the effect still executes and updates the ref on each render.

```suggestion
								onPendingRevealConsumed={onPendingRevealConsumed}
```

And in the component body alongside `revealPath`:
```
const onPendingRevealConsumed = useCallback(
    () => setPendingRevealDirectory(null),
    [],
);
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(desktop): configurable link-click b..." | Re-trigger Greptile

Comment on lines +281 to +296
handleClickOn: (_view, _pos, _node, _nodePos, event) => {
const target = event.target as HTMLElement | null;
const anchor = target?.closest?.("a") as HTMLAnchorElement | null;
if (!anchor) return false;
const href = anchor.getAttribute("href");
if (!href) return false;
event.preventDefault();
// No pane context available here, so "pane" and "external" both
// route to the system browser. Null means do nothing.
if (getUrlAction(event) !== null) {
electronTrpcClient.external.openUrl.mutate(href).catch((error) => {
console.error("[MarkdownEditor] Failed to open URL:", href, error);
});
}
return true;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 return true prevents cursor placement on links regardless of action

handleClickOn always calls event.preventDefault() and returns true for any anchor element with an href. When getUrlAction(event) returns null ("do nothing"), the event is fully consumed — TipTap won't place the cursor at the clicked position and the browser won't follow the link. This means users can no longer single-click on link text to position their cursor there for editing.

In an editing surface (task detail, chat input), cursor-on-link is often needed to select or modify the link. With the current code, keyboard navigation or approaching from adjacent text becomes the only option.

A minimal fix is to skip preventDefault and return false (let TipTap handle normally) when getUrlAction is null:

handleClickOn: (_view, _pos, _node, _nodePos, event) => {
    const target = event.target as HTMLElement | null;
    const anchor = target?.closest?.("a") as HTMLAnchorElement | null;
    if (!anchor) return false;
    const href = anchor.getAttribute("href");
    if (!href) return false;
    const action = getUrlAction(event);
    if (action === null) return false; // let TipTap place the cursor
    event.preventDefault();
    electronTrpcClient.external.openUrl.mutate(href).catch((error) => {
        console.error("[MarkdownEditor] Failed to open URL:", href, error);
    });
    return true;
},
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
Line: 281-296

Comment:
**`return true` prevents cursor placement on links regardless of action**

`handleClickOn` always calls `event.preventDefault()` and returns `true` for any anchor element with an `href`. When `getUrlAction(event)` returns `null` ("do nothing"), the event is fully consumed — TipTap won't place the cursor at the clicked position and the browser won't follow the link. This means users can no longer single-click on link text to position their cursor there for editing.

In an editing surface (task detail, chat input), cursor-on-link is often needed to select or modify the link. With the current code, keyboard navigation or approaching from adjacent text becomes the only option.

A minimal fix is to skip `preventDefault` and return `false` (let TipTap handle normally) when `getUrlAction` is null:

```
handleClickOn: (_view, _pos, _node, _nodePos, event) => {
    const target = event.target as HTMLElement | null;
    const anchor = target?.closest?.("a") as HTMLAnchorElement | null;
    if (!anchor) return false;
    const href = anchor.getAttribute("href");
    if (!href) return false;
    const action = getUrlAction(event);
    if (action === null) return false; // let TipTap place the cursor
    event.preventDefault();
    electronTrpcClient.external.openUrl.mutate(href).catch((error) => {
        console.error("[MarkdownEditor] Failed to open URL:", href, error);
    });
    return true;
},
```

How can I resolve this? If you propose a fix, please make it concise.

onSearch={handleQuickOpen}
selectedFilePath={selectedFilePath}
pendingRevealDirectory={pendingRevealDirectory}
onPendingRevealConsumed={() => setPendingRevealDirectory(null)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 onPendingRevealConsumed not memoized — unnecessary effect re-runs

The inline arrow function () => setPendingRevealDirectory(null) is recreated on every render of WorkspaceContent. Because onPendingRevealConsumed is in the dependency array of FilesTab's reveal useEffect, that effect re-runs on every parent render. The guard selectedFilePath !== prevSelectedRef.current prevents a real reveal from firing twice, but the effect still executes and updates the ref on each render.

Suggested change
onPendingRevealConsumed={() => setPendingRevealDirectory(null)}
onPendingRevealConsumed={onPendingRevealConsumed}

And in the component body alongside revealPath:

const onPendingRevealConsumed = useCallback(
    () => setPendingRevealDirectory(null),
    [],
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
Line: 485

Comment:
**`onPendingRevealConsumed` not memoized — unnecessary effect re-runs**

The inline arrow function `() => setPendingRevealDirectory(null)` is recreated on every render of `WorkspaceContent`. Because `onPendingRevealConsumed` is in the dependency array of `FilesTab`'s reveal `useEffect`, that effect re-runs on every parent render. The guard `selectedFilePath !== prevSelectedRef.current` prevents a real reveal from firing twice, but the effect still executes and updates the ref on each render.

```suggestion
								onPendingRevealConsumed={onPendingRevealConsumed}
```

And in the component body alongside `revealPath`:
```
const onPendingRevealConsumed = useCallback(
    () => setPendingRevealDirectory(null),
    [],
);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx (1)

18-24: ⚠️ Potential issue | 🟡 Minor

Make modifier-hover labels preference-aware.

With configurable link actions, Line 39 can still show hardcoded labels like “Open in pane” or “Open in external editor” even when the user bound that tier to another action or “Do nothing”. Please derive this label from the resolved file/URL action instead of shift alone.

Also applies to: 39-39

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx
around lines 18 - 24, The getLabel function is using hardcoded strings based
solely on shift and info.kind; change it to derive the modifier-hover label from
the resolved action for that link instead. Locate getLabel(LinkHoverInfo, shift)
and replace the branch logic to first compute the effective action (e.g., call
the existing resolver/util that maps a LinkHoverInfo + modifier to a File/URL
action or "do nothing"), then return a label based on that resolved action (for
example: resolvedAction === "openInExternalBrowser" -> "Open in external
browser", "openInPane" -> "Open in pane", "openInExternalEditor" -> "Open in
external editor", "revealInSidebar" -> "Reveal in sidebar", "doNothing" ->
appropriate label). Ensure you still consider info.isDirectory for reveal vs
open when the resolver returns a generic "open" action and include shift only as
an input to the resolver, not as the final label source.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx (1)

35-39: ⚠️ Potential issue | 🟡 Minor

Update UsePaneRegistryOptions.onRevealPath to accept the optional directory hint.

UsePaneRegistryOptions.onRevealPath at apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx:143 is typed as (path: string) => void, but TerminalPane at line 177 calls it with onRevealPath(link.resolvedPath, { isDirectory: true }). Update the interface to onRevealPath: (path: string, options?: { isDirectory?: boolean }) => void to align with the expected contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx
around lines 35 - 39, Update the UsePaneRegistryOptions type so onRevealPath
accepts an optional options object with the directory hint; change its signature
from (path: string) => void to onRevealPath: (path: string, options?: {
isDirectory?: boolean }) => void, so calls from TerminalPane (which invokes
onRevealPath(link.resolvedPath, { isDirectory: true })) type-check correctly;
locate and update the UsePaneRegistryOptions interface and any exported usages
in usePaneRegistry to match the new signature and adjust consumers where
necessary.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx (1)

40-49: Redundant guard in JSX.

shouldShowToggle already includes activeView !== null, so the extra && activeView on line 49 is redundant (though it does serve as a type narrower for TS). If the goal is narrowing, keeping it is fine; otherwise it can be simplified.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx
around lines 40 - 49, The JSX has a redundant runtime guard: shouldShowToggle
already includes activeView !== null, so in FilePaneHeaderExtras.tsx update the
conditional render expression in the return from "{shouldShowToggle &&
activeView && ( ... )}" to just "{shouldShowToggle && ( ... )}" to remove the
duplicate check; if you intended TypeScript narrowing instead, alternatively
ensure shouldShowToggle's type reflects non-null activeView so the extra "&&
activeView" isn't needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx`:
- Around line 281-296: The handler handleClickOn currently calls
event.preventDefault() and always returns true even when getUrlAction(event) ===
null, which swallows clicks and prevents TipTap's default selection (cursor
placement) inside links; fix by checking getUrlAction(event) first, and only
call event.preventDefault() and invoke
electronTrpcClient.external.openUrl.mutate(href) when getUrlAction(event) !==
null, otherwise return false (do not preventDefault) so the click falls through
to TipTap's default behavior; update the handler logic around getUrlAction,
anchor, href, and the return value accordingly.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx:
- Around line 57-70: The icon-only button inside FilePaneHeaderExtras (the
button wrapped by TooltipTrigger that calls handleOpenExternal) lacks an
accessible name; add an explicit aria-label (e.g., aria-label="Open in editor")
or aria-labelledby on that button element so screen readers announce it,
ensuring the label matches the TooltipContent text and preserves existing
onClick/props and classes; update the button in the FilePaneHeaderExtras
component where Tooltip and TooltipTrigger are used to include this attribute.
- Around line 43-45: The toolbar currently always renders the external-editor
button and calls openInExternalEditor via handleOpenExternal, which causes a
toast error for remote workspaces; update the FilePaneHeaderExtras component to
detect workspace locality (use the existing workspace/locality flag or hook used
elsewhere, e.g., isLocalWorkspace or workspace.host comparison) and
conditionally disable or hide the button when the workspace is remote, and if
you keep it visible add a tooltip explaining why it’s disabled; ensure
handleOpenExternal and the dependency array ([filePath, openInExternalEditor])
remain but never invoked for remote workspaces so the toast error is avoided.

In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`:
- Around line 222-226: Add a .superRefine to linkTierMapSchema that enforces
uniqueness of non-null link actions across the three keys (plain, meta,
metaShift) so duplicates can't be saved outside the UI; inside the superRefine
callback iterate the entries of the parsed object (referencing linkTierMapSchema
and the keys plain/meta/metaShift), collect non-null actions using a stable
identity (e.g., an id field if present or JSON.stringify as a fallback), detect
any duplicate identities, and call ctx.addIssue for each offending key (or add a
single issue on the root) to fail validation when the same action appears in
more than one tier—this mirrors the uniqueness enforced in LinkTierMapper.tsx
and prevents invalid localStorage/imported state.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx:
- Around line 18-24: The getLabel function is using hardcoded strings based
solely on shift and info.kind; change it to derive the modifier-hover label from
the resolved action for that link instead. Locate getLabel(LinkHoverInfo, shift)
and replace the branch logic to first compute the effective action (e.g., call
the existing resolver/util that maps a LinkHoverInfo + modifier to a File/URL
action or "do nothing"), then return a label based on that resolved action (for
example: resolvedAction === "openInExternalBrowser" -> "Open in external
browser", "openInPane" -> "Open in pane", "openInExternalEditor" -> "Open in
external editor", "revealInSidebar" -> "Reveal in sidebar", "doNothing" ->
appropriate label). Ensure you still consider info.isDirectory for reveal vs
open when the resolver returns a generic "open" action and include shift only as
an input to the resolver, not as the final label source.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx:
- Around line 35-39: Update the UsePaneRegistryOptions type so onRevealPath
accepts an optional options object with the directory hint; change its signature
from (path: string) => void to onRevealPath: (path: string, options?: {
isDirectory?: boolean }) => void, so calls from TerminalPane (which invokes
onRevealPath(link.resolvedPath, { isDirectory: true })) type-check correctly;
locate and update the UsePaneRegistryOptions interface and any exported usages
in usePaneRegistry to match the new signature and adjust consumers where
necessary.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx:
- Around line 40-49: The JSX has a redundant runtime guard: shouldShowToggle
already includes activeView !== null, so in FilePaneHeaderExtras.tsx update the
conditional render expression in the return from "{shouldShowToggle &&
activeView && ( ... )}" to just "{shouldShowToggle && ( ... )}" to remove the
duplicate check; if you intended TypeScript narrowing instead, alternatively
ensure shouldShowToggle's type reflects non-null activeView so the extra "&&
activeView" isn't needed.
🪄 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

Run ID: c016cb8a-aa4e-425f-b88f-145cd6fbd204

📥 Commits

Reviewing files that changed from the base of the PR and between daf0e16 and ad75065.

📒 Files selected for processing (23)
  • apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx
  • apps/desktop/src/renderer/hooks/host-service/useFileTree/useFileTree.ts
  • apps/desktop/src/renderer/hooks/useV2UserPreferences/index.ts
  • apps/desktop/src/renderer/hooks/useV2UserPreferences/tiers.ts
  • apps/desktop/src/renderer/hooks/useV2UserPreferences/useLinkActions.ts
  • apps/desktop/src/renderer/hooks/useV2UserPreferences/useV2UserPreferences.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/GeneralSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinksSettings/LinksSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinksSettings/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • apps/desktop/src/renderer/stores/settings-state.ts

Comment on lines +43 to +45
const handleOpenExternal = useCallback(() => {
openInExternalEditor(filePath);
}, [filePath, openInExternalEditor]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider disabling the button on remote workspaces.

useOpenInExternalEditor short-circuits with a toast.error("Can't open remote workspace paths in an external editor") when the workspace host machine doesn't match the local machine. The toolbar button is always rendered, so users on remote workspaces will get an error toast every click instead of a clearly unavailable affordance. Consider hiding/disabling the button (or surfacing the reason via the tooltip) when the workspace is non-local. If this is intentional — e.g. locality isn't reliably known at render time and the toast is the designed fallback — feel free to ignore.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/FilePane/components/FilePaneHeaderExtras/FilePaneHeaderExtras.tsx
around lines 43 - 45, The toolbar currently always renders the external-editor
button and calls openInExternalEditor via handleOpenExternal, which causes a
toast error for remote workspaces; update the FilePaneHeaderExtras component to
detect workspace locality (use the existing workspace/locality flag or hook used
elsewhere, e.g., isLocalWorkspace or workspace.host comparison) and
conditionally disable or hide the button when the workspace is remote, and if
you keep it visible add a tooltip explaining why it’s disabled; ensure
handleOpenExternal and the dependency array ([filePath, openInExternalEditor])
remain but never invoked for remote workspaces so the toast error is avoided.

Comment on lines +222 to +226
const linkTierMapSchema = z.object({
plain: linkActionSchema.nullable(),
meta: linkActionSchema.nullable(),
metaShift: linkActionSchema.nullable(),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Zod object-level validation is already available/used in the repo.
rg -n -C2 --type ts --type tsx '\.superRefine\s*\('
rg -n -C2 --type json '"zod"\s*:'

Repository: superset-sh/superset

Length of output: 4812


🏁 Script executed:

# Read the schema file to see the full context around lines 222-226
cat -n apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts | head -260 | tail -70

Repository: superset-sh/superset

Length of output: 3101


🏁 Script executed:

# Search for LinkAction and LinkTier type definitions
rg -n -A3 'type LinkAction|interface LinkAction|export.*LinkAction' apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/

Repository: superset-sh/superset

Length of output: 653


🏁 Script executed:

# Search for LinkTierMapper to understand the deduplication logic
rg -n -A10 'LinkTierMapper|demote.*duplicate' apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Check if .superRefine is used anywhere in the codebase (with corrected regex)
rg -n '\.superRefine' apps/desktop/

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Search for LinkTierMapper more broadly (not just in that directory)
rg -n 'LinkTierMapper' apps/desktop/

Repository: superset-sh/superset

Length of output: 1241


🏁 Script executed:

# Search for files that might import or use linkTierMapSchema
rg -n 'linkTierMapSchema|LinkTierMap' apps/desktop/src/renderer/ -t ts -t tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check for any existing .superRefine usage anywhere in apps/desktop
rg -n 'superRefine' apps/desktop/ -t ts -t tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Look for any validation related to duplicate actions or link tier deduplication
rg -n 'duplicate|demote|unique.*action' apps/desktop/src/renderer/ -t ts -t tsx -i

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Read the LinkTierMapper component to understand the deduplication logic
cat -n apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx | head -150

Repository: superset-sh/superset

Length of output: 3832


🏁 Script executed:

# Search for .superRefine usage without specifying file type
find apps/desktop -name "*.ts" -o -name "*.tsx" | xargs grep -l "superRefine" 2>/dev/null

Repository: superset-sh/superset

Length of output: 48


🏁 Script executed:

# Look for any validation logic or deduplication in the LinkTierMapper context
grep -r "duplicate\|demote" apps/desktop/src/renderer/routes/_authenticated/settings/links/ 2>/dev/null

Repository: superset-sh/superset

Length of output: 246


🏁 Script executed:

# Check what imports linkTierMapSchema uses it
grep -r "linkTierMapSchema\|fileLinks\|urlLinks" apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/ --include="*.ts" --include="*.tsx" 2>/dev/null

Repository: superset-sh/superset

Length of output: 1192


Add schema validation to prevent duplicate link actions across tiers.

The LinkTierMapper component enforces action uniqueness in the UI (lines 59–68 of LinkTierMapper.tsx), demoting any duplicate actions to null when a user interacts with the settings. However, the linkTierMapSchema has no validation, so data written directly to localStorage, imported, or passed via non-UI paths can bypass this constraint and persist duplicate actions.

Add a .superRefine check to the schema to own this invariant at the boundary:

Suggested validation
-const linkTierMapSchema = z.object({
+const linkTierMapSchema = z
+	.object({
 	plain: linkActionSchema.nullable(),
 	meta: linkActionSchema.nullable(),
 	metaShift: linkActionSchema.nullable(),
-});
+	})
+	.superRefine((map, ctx) => {
+		const seen = new Map<LinkAction, LinkTier>();
+		for (const tier of ["plain", "meta", "metaShift"] as const) {
+			const action = map[tier];
+			if (action === null) continue;
+			const previousTier = seen.get(action);
+			if (previousTier) {
+				ctx.addIssue({
+					code: z.ZodIssueCode.custom,
+					path: [tier],
+					message: `${action} is already assigned to ${previousTier}`,
+				});
+				continue;
+			}
+			seen.set(action, tier);
+		}
+	});
📝 Committable suggestion

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

Suggested change
const linkTierMapSchema = z.object({
plain: linkActionSchema.nullable(),
meta: linkActionSchema.nullable(),
metaShift: linkActionSchema.nullable(),
});
const linkTierMapSchema = z
.object({
plain: linkActionSchema.nullable(),
meta: linkActionSchema.nullable(),
metaShift: linkActionSchema.nullable(),
})
.superRefine((map, ctx) => {
const seen = new Map<LinkAction, LinkTier>();
for (const tier of ["plain", "meta", "metaShift"] as const) {
const action = map[tier];
if (action === null) continue;
const previousTier = seen.get(action);
if (previousTier) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [tier],
message: `${action} is already assigned to ${previousTier}`,
});
continue;
}
seen.set(action, tier);
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`
around lines 222 - 226, Add a .superRefine to linkTierMapSchema that enforces
uniqueness of non-null link actions across the three keys (plain, meta,
metaShift) so duplicates can't be saved outside the UI; inside the superRefine
callback iterate the entries of the parsed object (referencing linkTierMapSchema
and the keys plain/meta/metaShift), collect non-null actions using a stable
identity (e.g., an id field if present or JSON.stringify as a fallback), detect
any duplicate identities, and call ctx.addIssue for each offending key (or add a
single issue on the root) to fail validation when the same action appears in
more than one tier—this mirrors the uniqueness enforced in LinkTierMapper.tsx
and prevents invalid localStorage/imported state.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 23 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx:484">
P2: Directory reveal can be skipped when the Files tab mounts in the same update.</violation>
</file>

<file name="apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx">

<violation number="1" location="apps/desktop/src/renderer/components/MarkdownEditor/MarkdownEditor.tsx:287">
P1: When `getUrlAction(event)` returns `null` ("do nothing"), this handler still calls `event.preventDefault()` and returns `true`, which prevents TipTap/ProseMirror from placing the cursor at the click position. Users will be unable to click on link text to position their cursor for editing. Move `event.preventDefault()` after the null check and return `false` when the action is null to let ProseMirror handle selection normally.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Adds Settings → Links with two 3-tier mappers (File / URL) letting users
bind "Click", "⌘-click", and "⌘⇧-click" to one of: Do nothing, in-app
pane (File viewer / In-app browser), or external (editor / browser).

Picking an action already bound to another tier demotes that other tier
to "Do nothing" rather than swapping, so the three tiers stay distinct
without silently inheriting a meaning the user didn't choose.

Preferences live in a new `v2_user_preferences` localStorage collection
keyed by organizationId — a v2-native store, not the v1 `settings` table.
Three hooks expose the data: `useV2UserPreferences`, `useTerminalLinkActions`
(3-tier, terminal), and `useInlineLinkActions` (2-tier, chat / markdown).

Wired consumers:
- v2 TerminalPane — file + URL clicks dispatch through the tier map;
  folder clicks stay hardcoded (⌘ reveals, ⌘⇧ external) since there's
  only one sensible primary for folders.
- TaskMarkdownRenderer — URLs previously inert now dispatch; tasks have
  no pane context so "pane" and "external" both open the system browser.
- FilePaneHeaderExtras — new "Open in editor" toolbar button as an
  always-available escape hatch.

Keyboard settings moved to the Editor & Workflow group.

Folder reveal threads an `isDirectory: true` hint from TerminalPane
through page.tsx → WorkspaceSidebar → FilesTab → `useFileTree.reveal`
so a known-directory target gets expanded even when the terminal's
resolved path and listDirectory's child paths don't byte-match (case
differences, symlink resolution).

Click-hint tooltip updated from the hardcoded "Hold ⌘ to open · ⌘⇧ for
external" to "Not bound · configure in Settings → Links" so it reflects
user-configurable reality instead of defaults.
@saddlepaddle saddlepaddle force-pushed the settings-switch-links branch from ad75065 to bf3c52a Compare April 21, 2026 19:34
`openPane` calls `replacePane` whenever an existing unpinned pane of the
same kind is present in the active tab. `replacePane` was only updating
`activePaneId` if the *replaced* pane was already the active one —
otherwise it preserved the existing active pane. In practice that meant
user-initiated actions that reused a preview pane (⌘-click a terminal
file link when a FilePane was already open, for example) would swap the
file in but keep focus on the terminal.

URL clicks happen to escape this by taking the `splitPane` branch when
no browser pane exists yet — but the same bug would surface the second
time a different URL was clicked.

replacePane is internal to the panes store and only called by openPane,
whose contract is "the user asked for this, make it visible and
focused." Always activate the new pane. Existing tests still pass.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

`pendingRevealDirectory` was tracked as a single string, so a second
⌘-click on a collapsed folder — same path as the prior reveal — didn't
re-run the FilesTab reveal effect, leaving the folder collapsed. Switch
to a `{ path, isDirectory, nonce }` shape; the nonce bumps on every
reveal request, and the effect depends on the nonce so repeat reveals
always re-expand and re-scroll. Active file pane switches bump the
nonce too so file-pane changes still drive the tree into view.

Terminal hover tooltip read hardcoded labels from the old default
mapping (⌘ = pane, ⌘⇧ = external). Rebinding external to plain left
⌘⇧ hover still saying "Open in external editor". Now the tooltip reads
the user's live fileLinks / urlLinks mapping and derives the label
from whichever action is bound at the hovered tier. Unbound tiers
return null, so no tooltip renders. Folder hover stays hardcoded
(⌘ = Reveal in sidebar, ⌘⇧ = Open in editor) since folder behavior
itself isn't settings-driven.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:
- Around line 329-335: The async reveal in fileTree.reveal can complete
out-of-order and cause a stale scroll; tie the reveal/scroll to a pendingReveal
token and guard the scheduled scroll by verifying the token is still current
before calling querySelector/scrollIntoView. Specifically, when starting the
reveal (in the same block that calls fileTree.reveal), capture the current
pendingReveal identifier (e.g., pendingReveal or pendingRevealRef.current), and
in the .then and inside requestAnimationFrame check that pendingReveal still
equals that captured value (and that the component is mounted) before running
scrollContainerRef.current?.querySelector(...)? .scrollIntoView(...); also clear
or update pendingReveal on cleanup/unmount to prevent stale resolves from
acting.
🪄 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

Run ID: b284e7e0-2adf-42e4-8607-1943414286e4

📥 Commits

Reviewing files that changed from the base of the PR and between 41fdca7 and 213ea2a.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/components/LinkHoverTooltip/LinkHoverTooltip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/WorkspaceSidebar.tsx

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:329">
P2: This async reveal has no cancellation guard — if a new `pendingReveal` nonce fires while a previous `fileTree.reveal()` is still in flight, the stale promise callback will scroll the sidebar back to the old path. Return a cleanup function from the effect that sets a `cancelled` flag, and check it before the `requestAnimationFrame` scroll.</violation>

<violation number="2" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:336">
P1: The reveal effect depends on the whole `fileTree` object, which is recreated each render, so a single pending reveal can retrigger continuously. Depend on a stable function reference instead (e.g. `fileTree.reveal`) to avoid repeated reveal/scroll cycles.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- FilesTab reveal effect: drop fileTree/rootPath from deps. fileTree's
  identity changes every render, so the nonce-keyed effect was firing
  every render → setState → re-render → infinite loop. Closure captures
  the latest values via useFileTree's internal refs.
- UrlLinkProvider.handleActivation: forward every activation instead of
  gating on ⌘/ctrl internally. v2's settings layer maps "plain click" to
  an action, so the hardcoded modifier gate was eating clicks before v2
  could see them. v1 consumer (Terminal/helpers.ts) now enforces its
  own gate + preventDefault so v1 behavior is unchanged.
- LinkTierMapper: drop demote-on-conflict. Each tier picks independently;
  duplicates are fine (e.g. all three tiers bound to "external editor").
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1)

86-86: ⚠️ Potential issue | 🟠 Major

Forward the mouse event to the URL preference handler before legacy gating.

Line 194 drops plain-click URL activations, so a configured "plain" URL action can never fire. Also, Line 198 only passes uri, which prevents the v2 handler from distinguishing meta vs metaShift.

🐛 Proposed fix
-	onUrlClickRef?: { current: ((url: string) => void) | undefined };
+	onUrlClickRef?: { current: ((event: MouseEvent, url: string) => void) | undefined };
 	onUrlClick: (event, uri) => {
-		if (!event.metaKey && !event.ctrlKey) return;
-		event.preventDefault();
 		const handler = urlClickRef?.current;
 		if (handler) {
-			handler(uri);
+			event.preventDefault();
+			handler(event, uri);
 			return;
 		}
+		if (!event.metaKey && !event.ctrlKey) return;
+		event.preventDefault();
 		trpcClient.external.openUrl.mutate(uri).catch((error) => {

Also applies to: 193-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts`
at line 86, The URL click logic currently drops plain-click activations and only
forwards uri, so update the handlers to invoke onUrlClickRef.current (when
defined) with the full mouse event before executing the legacy gating logic and
fallback behavior; pass both the MouseEvent and the uri so the v2 handler can
distinguish modifiers (e.g., meta vs meta+shift). Apply this change for the
plain-click branch and the other URL-activation branch (both places referencing
onUrlClickRef) and ensure you short-circuit only if the v2 handler returns a
consumed result or indicates it handled the event.
♻️ Duplicate comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx (1)

327-338: ⚠️ Potential issue | 🟡 Minor

Stale reveal can overwrite a newer scroll.

The async fileTree.reveal(...).then(...) isn't guarded, so if a second pendingReveal arrives before the first resolves, the older resolution can run scrollIntoView after the newer one, landing the sidebar on the stale path. Also, a rejected reveal promise is swallowed silently. Capture the current nonce (or use an cancelled flag in the effect cleanup) before scheduling the scroll, and attach a .catch to surface failures.

Proposed fix
 	useEffect(() => {
 		prevSelectedRef.current = selectedFilePath;
 		if (!pendingReveal || !rootPath) return;
 		const { path, isDirectory } = pendingReveal;
-		void fileTree.reveal(path, { isDirectory }).then(() => {
-			requestAnimationFrame(() => {
-				scrollContainerRef.current
-					?.querySelector(`[data-filepath="${CSS.escape(path)}"]`)
-					?.scrollIntoView({ block: "center" });
-			});
-		});
+		let cancelled = false;
+		void fileTree
+			.reveal(path, { isDirectory })
+			.then(() => {
+				if (cancelled) return;
+				requestAnimationFrame(() => {
+					if (cancelled) return;
+					scrollContainerRef.current
+						?.querySelector(`[data-filepath="${CSS.escape(path)}"]`)
+						?.scrollIntoView({ block: "center" });
+				});
+			})
+			.catch((error) => {
+				if (cancelled) return;
+				toast.error("Couldn't reveal path", {
+					description: error instanceof Error ? error.message : String(error),
+				});
+			});
+		return () => {
+			cancelled = true;
+		};
 	}, [pendingReveal?.nonce]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx
around lines 327 - 338, The reveal effect can run out-of-order and swallow
errors; to fix it, capture the current pendingReveal nonce at the top of the
useEffect (e.g. const currentNonce = pendingReveal?.nonce) and use that value
inside the promise chain (or set a cancelled flag in cleanup) to ignore any
resolution whose nonce no longer matches before calling scrollIntoView; also
append a .catch to the fileTree.reveal(...) chain and log or surface the error
via the existing logger instead of silently swallowing it. Ensure you still
reference pendingReveal, fileTree.reveal, scrollContainerRef, CSS.escape and
prevSelectedRef as needed and keep the dependency on pendingReveal?.nonce so new
reveals cancel/ignore prior completions.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx (1)

296-296: Dead prevSelectedRef — remove it.

Per the AI summary, the prior “track previous selection” logic was removed in favor of the nonce-driven trigger. prevSelectedRef is still declared at line 296 and written at line 328, but it's never read anywhere in the file. Drop the ref and the in-effect assignment to avoid confusing future readers.

Proposed fix
-	const prevSelectedRef = useRef(selectedFilePath);
@@
 	useEffect(() => {
-		prevSelectedRef.current = selectedFilePath;
 		if (!pendingReveal || !rootPath) return;

Also applies to: 328-328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx
at line 296, Remove the unused ref prevSelectedRef and its assignment;
specifically delete the declaration "const prevSelectedRef =
useRef(selectedFilePath);" and the later in-effect write that sets
prevSelectedRef.current = selectedFilePath inside the FilesTab component so only
the nonce-driven trigger remains (leave selectedFilePath and all other logic
intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts`:
- Line 86: The URL click logic currently drops plain-click activations and only
forwards uri, so update the handlers to invoke onUrlClickRef.current (when
defined) with the full mouse event before executing the legacy gating logic and
fallback behavior; pass both the MouseEvent and the uri so the v2 handler can
distinguish modifiers (e.g., meta vs meta+shift). Apply this change for the
plain-click branch and the other URL-activation branch (both places referencing
onUrlClickRef) and ensure you short-circuit only if the v2 handler returns a
consumed result or indicates it handled the event.

---

Duplicate comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:
- Around line 327-338: The reveal effect can run out-of-order and swallow
errors; to fix it, capture the current pendingReveal nonce at the top of the
useEffect (e.g. const currentNonce = pendingReveal?.nonce) and use that value
inside the promise chain (or set a cancelled flag in cleanup) to ignore any
resolution whose nonce no longer matches before calling scrollIntoView; also
append a .catch to the fileTree.reveal(...) chain and log or surface the error
via the existing logger instead of silently swallowing it. Ensure you still
reference pendingReveal, fileTree.reveal, scrollContainerRef, CSS.escape and
prevSelectedRef as needed and keep the dependency on pendingReveal?.nonce so new
reveals cancel/ignore prior completions.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:
- Line 296: Remove the unused ref prevSelectedRef and its assignment;
specifically delete the declaration "const prevSelectedRef =
useRef(selectedFilePath);" and the later in-effect write that sets
prevSelectedRef.current = selectedFilePath inside the FilesTab component so only
the nonce-driven trigger remains (leave selectedFilePath and all other logic
intact).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13fa04bd-361a-4701-b6f2-05d7638b1ee6

📥 Commits

Reviewing files that changed from the base of the PR and between 213ea2a and 59a730f.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/link-providers/url-link-provider.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/link-providers/url-link-provider.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/link-providers/url-link-provider.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/components/FilesTab/FilesTab.tsx:338">
P2: Removing `rootPath` from this effect’s dependency list can drop reveal requests that fire before the workspace root path is loaded.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx:58">
P1: Re-adding a tier to an action no longer demotes the previous tier to Do nothing, so duplicate bindings are possible.</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts:194">
P1: Plain URL clicks are now ignored before the configured handler runs, so terminal links no longer open on a normal click.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

(tier: LinkTier, nextSlot: SlotValue) => {
const nextAction = fromSlot(nextSlot);
if (value[tier] === nextAction) return;
onChange({ ...value, [tier]: nextAction });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P1: Re-adding a tier to an action no longer demotes the previous tier to Do nothing, so duplicate bindings are possible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/settings/links/components/LinkTierMapper/LinkTierMapper.tsx, line 58:

<comment>Re-adding a tier to an action no longer demotes the previous tier to Do nothing, so duplicate bindings are possible.</comment>

<file context>
@@ -55,19 +55,7 @@ export function LinkTierMapper({
-				}
-			}
-			onChange(nextMap);
+			onChange({ ...value, [tier]: nextAction });
 		},
 		[value, onChange],
</file context>
Suggested change
onChange({ ...value, [tier]: nextAction });
const nextMap: LinkTierMap = { ...value, [tier]: nextAction };
if (nextAction !== null) {
for (const other of Object.keys(value) as LinkTier[]) {
if (other !== tier && value[other] === nextAction) {
nextMap[other] = null;
}
}
}
onChange(nextMap);
Fix with Cubic

},
onUrlClick: (_event, uri) => {
onUrlClick: (event, uri) => {
if (!event.metaKey && !event.ctrlKey) return;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P1: Plain URL clicks are now ignored before the configured handler runs, so terminal links no longer open on a normal click.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts, line 194:

<comment>Plain URL clicks are now ignored before the configured handler runs, so terminal links no longer open on a normal click.</comment>

<file context>
@@ -190,7 +190,9 @@ export function createTerminalInWrapper(options: CreateTerminalOptions = {}): {
 		},
-		onUrlClick: (_event, uri) => {
+		onUrlClick: (event, uri) => {
+			if (!event.metaKey && !event.ctrlKey) return;
+			event.preventDefault();
 			const handler = urlClickRef?.current;
</file context>
Fix with Cubic

- MarkdownEditor handleClickOn: when the URL tier resolves to null ("do
  nothing"), return false without preventing default. Previously the
  handler swallowed every anchor click, so users couldn't click into a
  link to position the cursor for editing.
- FilePaneHeaderExtras: add aria-label to the icon-only "Open in editor"
  button so screen readers announce it.
- FilesTab reveal effect: add a cancellation guard so a stale reveal
  promise doesn't scroll the sidebar back to an outdated path when a
  newer nonce fires mid-flight. Put rootPath back in deps so a reveal
  fired before the worktree path loads still runs once it arrives;
  fileTree stays out of deps to keep the loop fix.
Every setPendingReveal call already produces a new object reference, so
keying the FilesTab effect on the whole pendingReveal object re-runs it
for repeat reveals of the same path — no counter needed. Also drops the
dead prevSelectedRef that lingered from the old selectedFilePath guard.
@saddlepaddle saddlepaddle merged commit d006f60 into main Apr 22, 2026
14 checks passed
Kitenite added a commit that referenced this pull request May 4, 2026
Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632),
scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane
(#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635),
safer terminal preset defaults (#3546), and /pricing page (#3639). Expand
bug fixes with v2 git correctness, cross-fork PR misattribution, terminal
paste/Unicode/Shift+Enter, and security bumps.
Kitenite added a commit that referenced this pull request May 6, 2026
…-27) (#3792)

* docs: generate weekly changelog 2026-04-27

* docs: reframe weekly changelog around v2 public beta

Lead with v2 public beta + Settings → Experimental enable, restructure
around the v1→v2 migration story, sidebar overhaul, cross-workspace
terminals, and v2 chat. Pull in ~30 v2 PRs the bot missed and demote
non-v2 items (Hosts page, marketing menu) to a brief "Also this week".

* docs: pull in missed v2 features and bug fixes

Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632),
scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane
(#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635),
safer terminal preset defaults (#3546), and /pricing page (#3639). Expand
bug fixes with v2 git correctness, cross-fork PR misattribution, terminal
paste/Unicode/Shift+Enter, and security bumps.

* docs(changelog): add v2 public beta hero screenshot

* docs(changelog): add Settings → Experimental screenshot, compress hero

pngquant compression: v2-public-beta.png 704KB → 166KB (76%),
v2-enable-flag.png 160KB → 36KB (78%). No visible quality loss.

* docs(changelog): tighten v2 launch prose, condense bullet groups

* docs(changelog): reframe cloud-first pillar as remote workspaces

* docs(changelog): cut parallel-agents and honest-state pillars, fold into sub-sections

* docs(changelog): tweak title and lead phrasing

* docs(changelog): rewrite v2 launch lede around Twitter narrative

Pull the launch story (physical limits, 3 ex-CTOs, cloud workspaces)
into the lede, restructure pillars around Remote workspaces, Reimagined
diff view, and Superset CLI, and add v2-remote-workspaces and
v2-changes-pane screenshots to back the new sections.

* docs(changelog): add CLI install snippet and docs link

* docs(changelog): cut narrative lede, match standard changelog tone

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
saddlepaddle pushed a commit that referenced this pull request May 6, 2026
…-27) (#3792)

* docs: generate weekly changelog 2026-04-27

* docs: reframe weekly changelog around v2 public beta

Lead with v2 public beta + Settings → Experimental enable, restructure
around the v1→v2 migration story, sidebar overhaul, cross-workspace
terminals, and v2 chat. Pull in ~30 v2 PRs the bot missed and demote
non-v2 items (Hosts page, marketing menu) to a brief "Also this week".

* docs: pull in missed v2 features and bug fixes

Add v2 project setup section (#3566, #3605, #3606, #3592, #3626, #3632),
scheduled agent runs (#3576), Opus 4.7 (#3579), v1 review comments in pane
(#3596), configurable v2 link-click (#3600), Copy Branch Name (#3635),
safer terminal preset defaults (#3546), and /pricing page (#3639). Expand
bug fixes with v2 git correctness, cross-fork PR misattribution, terminal
paste/Unicode/Shift+Enter, and security bumps.

* docs(changelog): add v2 public beta hero screenshot

* docs(changelog): add Settings → Experimental screenshot, compress hero

pngquant compression: v2-public-beta.png 704KB → 166KB (76%),
v2-enable-flag.png 160KB → 36KB (78%). No visible quality loss.

* docs(changelog): tighten v2 launch prose, condense bullet groups

* docs(changelog): reframe cloud-first pillar as remote workspaces

* docs(changelog): cut parallel-agents and honest-state pillars, fold into sub-sections

* docs(changelog): tweak title and lead phrasing

* docs(changelog): rewrite v2 launch lede around Twitter narrative

Pull the launch story (physical limits, 3 ex-CTOs, cloud workspaces)
into the lede, restructure pillars around Remote workspaces, Reimagined
diff view, and Superset CLI, and add v2-remote-workspaces and
v2-changes-pane screenshots to back the new sections.

* docs(changelog): add CLI install snippet and docs link

* docs(changelog): cut narrative lede, match standard changelog tone

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
@Kitenite Kitenite deleted the settings-switch-links branch May 6, 2026 04:52
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