Skip to content

feat(desktop): promote "Create Section Below" to top-level on workspace menu#3537

Merged
saddlepaddle merged 2 commits into
mainfrom
creating-a-new-section-isnt-quite-the-expected-behavior-we-probably-need-to-have-it-be-create-sectio
Apr 17, 2026
Merged

feat(desktop): promote "Create Section Below" to top-level on workspace menu#3537
saddlepaddle merged 2 commits into
mainfrom
creating-a-new-section-isnt-quite-the-expected-behavior-we-probably-need-to-have-it-be-create-sectio

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 17, 2026

Summary

  • Section creation from a workspace's right-click menu was previously buried inside Move to Section → New Section, and the resulting section always appended to the end of the project. That made "create" and "move" feel like the same action and put the new section far from the workspace you acted on.
  • Promote Create Section Below to a top-level item on the workspace context menu. It inserts an empty section directly below the right-clicked workspace (or its parent section, if the workspace is already grouped), so the position matches the label.
  • The Move to Section submenu now only lists existing sections, and is hidden entirely when none exist. No more empty submenu.
  • useDashboardSidebarState.createSection gained an insertAfterWorkspaceId option that bumps subsequent top-level items (ungrouped workspaces + sections) by +1 to make room. Default append-at-end behavior preserved for the project-level menu.

Test plan

  • Right-click a top-level workspace → "Create Section Below" places an empty section directly below it
  • Right-click a workspace inside a section → new section appears below that parent section, workspace stays put
  • With no existing sections, "Move to Section" submenu does not render
  • Existing "Move to Section → {section}" and "Remove from Group" still work
  • Project context menu → "New Section" still appends at the end

Summary by cubic

Promote section creation to a top-level “Create Section Below” in the workspace context menu. New sections appear directly under the clicked workspace (or its parent section) for predictable placement.

  • New Features
    • Added “Create Section Below” to the workspace menu; inserts a blank section beneath the clicked workspace or its parent section.
    • “Move to Section” now lists only existing sections, is separated from create with a divider, and is hidden when none exist.
    • useDashboardSidebarState.createSection accepts insertAfterWorkspaceId to insert after a workspace and shift following top-level items; default append remains for the project menu.

Written for commit 43b8268. Summary will update on new commits.

Summary by CodeRabbit

  • Improvements
    • Made "Create Section Below" a top-level action in the workspace context menu.
    • Always show the "Create Section Below" item and add a separator when relevant for clearer menu structure.
    • Only display the "Move to Section" submenu when sections exist, and simplified its contents to list available sections.
    • New sections are positioned directly after the selected workspace for more intuitive placement.

…ce menu

The workspace context menu previously hid section creation inside the
"Move to Section" submenu, and the new section always appended to the
end of the project — making "create" and "move" feel like the same
action. Promote section creation to a top-level item, inserting an
empty section directly below the right-clicked workspace so it matches
the label, and leave "Move to Section" as a list of existing sections
(hidden entirely when none exist).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aaa96c59-72a9-4e1d-a758-e00148c7c9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 833eefc and 43b8268.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx

📝 Walkthrough

Walkthrough

Expose a top-level "Create Section Below" context-menu action and route it to a new placement-aware section creation flow; adjust menu rendering and update section-creation logic to accept an insertion anchor and compute tabOrder accordingly.

Changes

Cohort / File(s) Summary
Context Menu UI
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx
Added a standalone ContextMenuItem "Create Section Below" that calls onCreateSection. Made the "Move to Section" submenu conditional on sections.length > 0. Inserted a separator after "Create Section Below" when sections exist or the item is in a section; simplified submenu content to only list target sections that call onMoveToSection(section.id).
Action Handler Hook
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts
Reworked handleCreateSection to call createSection(projectId, { insertAfterWorkspaceId: workspaceId }) instead of creating a section and then moving the workspace into it. Removed the intermediate move step.
State Management
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
Changed createSection signature to createSection(projectId, options = { name?: string; insertAfterWorkspaceId?: string }). When insertAfterWorkspaceId is provided, compute an anchor tabOrder (from an ungrouped workspace or its anchored section), increment orders of subsequent ungrouped workspaces and sections in the same project, and set the new section's tabOrder to anchorTabOrder + 1. Fall back to existing getNextTabOrder behavior when no anchor is supplied.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ContextMenu
  participant ActionHook as useDashboardSidebarWorkspaceItemActions
  participant StateHook as useDashboardSidebarState (store)

  User->>ContextMenu: click "Create Section Below"
  ContextMenu->>ActionHook: onCreateSection(workspaceId, projectId)
  ActionHook->>StateHook: createSection(projectId, { insertAfterWorkspaceId: workspaceId })
  StateHook->>StateHook: compute anchorTabOrder, shift orders, set new section tabOrder
  StateHook-->>ActionHook: newSectionId
  ActionHook-->>ContextMenu: confirm/create complete
  ContextMenu-->>User: UI updated (new section shown)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A click to place a section neat and slow,
Below my workspace, in a tidy row.
TabOrder hops to make the order right,
New sections spring at morning light. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: promoting 'Create Section Below' to a top-level menu item on the workspace context menu.
Description check ✅ Passed The description covers all required template sections with clear content: Summary (comprehensive explanation), Related Issues (auto-included), Type of Change (implied as feature), Testing (detailed test plan provided), and Additional Notes (cubic summary included).

✏️ 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 creating-a-new-section-isnt-quite-the-expected-behavior-we-probably-need-to-have-it-be-create-sectio

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.

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/hooks/useDashboardSidebarState/useDashboardSidebarState.ts`:
- Around line 199-236: The current branch assumes insertAfterWorkspaceId
resolves to a valid workspace and uses a default anchorTabOrder of 0, causing
mass reorders if the id is stale; update the logic in useDashboardSidebarState
so you first retrieve anchorWorkspace and guard that it exists and its
sidebarState.projectId === projectId (check insertAfterWorkspaceId,
anchorWorkspace, and anchorWorkspace.sidebarState.projectId), and if the guard
fails fall through to the append-at-end branch (i.e., treat as no
insertAfterWorkspaceId) instead of computing anchorTabOrder = 0 and bumping
every item in collections.v2WorkspaceLocalState / collections.v2SidebarSections;
keep the subsequent reordering code unchanged but only execute it when the
anchor is valid.
🪄 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: a589987e-b4d1-4911-911a-cf7a939b77e0

📥 Commits

Reviewing files that changed from the base of the PR and between a3df489 and 833eefc.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts

Comment on lines +199 to +236
if (insertAfterWorkspaceId) {
const anchorWorkspace = collections.v2WorkspaceLocalState.get(
insertAfterWorkspaceId,
);
const anchorTabOrder = anchorWorkspace?.sidebarState.sectionId
? (collections.v2SidebarSections.get(
anchorWorkspace.sidebarState.sectionId,
)?.tabOrder ?? 0)
: (anchorWorkspace?.sidebarState.tabOrder ?? 0);

for (const workspace of collections.v2WorkspaceLocalState.state.values()) {
if (
workspace.sidebarState.projectId === projectId &&
workspace.sidebarState.sectionId === null &&
workspace.sidebarState.tabOrder > anchorTabOrder
) {
const nextOrder = workspace.sidebarState.tabOrder + 1;
collections.v2WorkspaceLocalState.update(
workspace.workspaceId,
(draft) => {
draft.sidebarState.tabOrder = nextOrder;
},
);
}
}
for (const section of collections.v2SidebarSections.state.values()) {
if (
section.projectId === projectId &&
section.tabOrder > anchorTabOrder
) {
const nextOrder = section.tabOrder + 1;
collections.v2SidebarSections.update(section.sectionId, (draft) => {
draft.tabOrder = nextOrder;
});
}
}

tabOrder = anchorTabOrder + 1;
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

Silent fallback when insertAfterWorkspaceId is unknown.

If insertAfterWorkspaceId does not resolve to a workspace (e.g., stale id, race between menu open and state change), anchorWorkspace is undefined, anchorTabOrder falls back to 0, every top-level workspace/section in the project gets bumped +1, and the new section is inserted at tabOrder = 1 (top of the project). That's a surprising outcome for a "Create Section Below X" action — it would look like the section was created at the top and unrelated items shifted.

Consider short-circuiting (or falling through to the append-at-end branch) when the anchor workspace is missing or belongs to a different projectId:

🛡️ Proposed defensive check
-		if (insertAfterWorkspaceId) {
-			const anchorWorkspace = collections.v2WorkspaceLocalState.get(
-				insertAfterWorkspaceId,
-			);
-			const anchorTabOrder = anchorWorkspace?.sidebarState.sectionId
-				? (collections.v2SidebarSections.get(
-						anchorWorkspace.sidebarState.sectionId,
-					)?.tabOrder ?? 0)
-				: (anchorWorkspace?.sidebarState.tabOrder ?? 0);
+		const anchorWorkspace = insertAfterWorkspaceId
+			? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
+			: undefined;
+		if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
+			const anchorTabOrder = anchorWorkspace.sidebarState.sectionId
+				? (collections.v2SidebarSections.get(
+						anchorWorkspace.sidebarState.sectionId,
+					)?.tabOrder ?? 0)
+				: anchorWorkspace.sidebarState.tabOrder;
📝 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 (insertAfterWorkspaceId) {
const anchorWorkspace = collections.v2WorkspaceLocalState.get(
insertAfterWorkspaceId,
);
const anchorTabOrder = anchorWorkspace?.sidebarState.sectionId
? (collections.v2SidebarSections.get(
anchorWorkspace.sidebarState.sectionId,
)?.tabOrder ?? 0)
: (anchorWorkspace?.sidebarState.tabOrder ?? 0);
for (const workspace of collections.v2WorkspaceLocalState.state.values()) {
if (
workspace.sidebarState.projectId === projectId &&
workspace.sidebarState.sectionId === null &&
workspace.sidebarState.tabOrder > anchorTabOrder
) {
const nextOrder = workspace.sidebarState.tabOrder + 1;
collections.v2WorkspaceLocalState.update(
workspace.workspaceId,
(draft) => {
draft.sidebarState.tabOrder = nextOrder;
},
);
}
}
for (const section of collections.v2SidebarSections.state.values()) {
if (
section.projectId === projectId &&
section.tabOrder > anchorTabOrder
) {
const nextOrder = section.tabOrder + 1;
collections.v2SidebarSections.update(section.sectionId, (draft) => {
draft.tabOrder = nextOrder;
});
}
}
tabOrder = anchorTabOrder + 1;
const anchorWorkspace = insertAfterWorkspaceId
? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
: undefined;
if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
const anchorTabOrder = anchorWorkspace.sidebarState.sectionId
? (collections.v2SidebarSections.get(
anchorWorkspace.sidebarState.sectionId,
)?.tabOrder ?? 0)
: anchorWorkspace.sidebarState.tabOrder;
for (const workspace of collections.v2WorkspaceLocalState.state.values()) {
if (
workspace.sidebarState.projectId === projectId &&
workspace.sidebarState.sectionId === null &&
workspace.sidebarState.tabOrder > anchorTabOrder
) {
const nextOrder = workspace.sidebarState.tabOrder + 1;
collections.v2WorkspaceLocalState.update(
workspace.workspaceId,
(draft) => {
draft.sidebarState.tabOrder = nextOrder;
},
);
}
}
for (const section of collections.v2SidebarSections.state.values()) {
if (
section.projectId === projectId &&
section.tabOrder > anchorTabOrder
) {
const nextOrder = section.tabOrder + 1;
collections.v2SidebarSections.update(section.sectionId, (draft) => {
draft.tabOrder = nextOrder;
});
}
}
tabOrder = anchorTabOrder + 1;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts`
around lines 199 - 236, The current branch assumes insertAfterWorkspaceId
resolves to a valid workspace and uses a default anchorTabOrder of 0, causing
mass reorders if the id is stale; update the logic in useDashboardSidebarState
so you first retrieve anchorWorkspace and guard that it exists and its
sidebarState.projectId === projectId (check insertAfterWorkspaceId,
anchorWorkspace, and anchorWorkspace.sidebarState.projectId), and if the guard
fails fall through to the append-at-end branch (i.e., treat as no
insertAfterWorkspaceId) instead of computing anchorTabOrder = 0 and bumping
every item in collections.v2WorkspaceLocalState / collections.v2SidebarSections;
keep the subsequent reordering code unchanged but only execute it when the
anchor is valid.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

This PR improves the workspace context-menu UX by surfacing "Create Section Below" as a top-level action and decluttering "Move to Section" to only appear when sections exist.

Key changes:

  • Context menu (DashboardSidebarWorkspaceContextMenu): "Create Section Below" is promoted from a nested submenu item to a direct menu item. The "Move to Section" submenu is conditionally rendered only when sections.length > 0, removing the empty-submenu problem.
  • Action hook (useDashboardSidebarWorkspaceItemActions): handleCreateSection no longer calls moveWorkspaceToSection after creation — the workspace stays in place; only the new section is inserted.
  • State hook (useDashboardSidebarState): createSection now accepts an insertAfterWorkspaceId option. When provided, it reads the anchor workspace's top-level tabOrder (falling through to its parent section's tabOrder when the workspace is grouped), bumps all subsequent top-level ungrouped workspaces and sections by +1, then inserts the new section immediately below. The original append-at-end path (called from the project-level "New Section" menu) is preserved unchanged. The API change is fully backward-compatible — existing callers that pass no second argument still work correctly.

Confidence Score: 5/5

Safe to merge — the logic is sound and backward-compatible; one P2 defensive-coding suggestion but no blocking bugs.

All three test-plan scenarios are correctly implemented. The insertAfterWorkspaceId bump logic handles both grouped and ungrouped anchor workspaces correctly, the new section is inserted at the right position, and the project-level append path is untouched. The API signature change is fully backward-compatible. The only concern (undefined anchor fallback to tabOrder = 0) is an unlikely edge case and not a production-blocking issue.

useDashboardSidebarState.ts — review the anchorWorkspace undefined fallback behavior in the insertAfterWorkspaceId branch.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts Adds insertAfterWorkspaceId option to createSection; logic for bumping subsequent items is correct, but the fallback to anchorTabOrder = 0 when the anchor workspace is missing could silently place the new section at the top of the sidebar.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx Promotes "Create Section Below" to a top-level menu item and guards "Move to Section" submenu behind sections.length > 0; clean change with no issues.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts Removes the old moveWorkspaceToSection call after creating a section; now delegates positioning entirely to createSection via insertAfterWorkspaceId. Backward-compatible with the project-level menu path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User right-clicks workspace] --> B[DashboardSidebarWorkspaceContextMenu]
    B --> C{"sections.length > 0?"}
    C -- Yes --> D[Show 'Move to Section' submenu\nwith existing sections only]
    C -- No --> E[Hide 'Move to Section']
    B --> F["'Create Section Below' top-level item"]
    F --> G[handleCreateSection\nin useDashboardSidebarWorkspaceItemActions]
    G --> H["createSection(projectId, { insertAfterWorkspaceId })"]
    H --> I{insertAfterWorkspaceId\nprovided?}
    I -- Yes --> J[Resolve anchor workspace\nfrom v2WorkspaceLocalState]
    J --> K{Is anchor\nin a section?}
    K -- Yes --> L[anchorTabOrder = parent section's tabOrder]
    K -- No --> M[anchorTabOrder = workspace's tabOrder]
    L --> N[Bump ungrouped workspaces\nand sections with tabOrder > anchorTabOrder by +1]
    M --> N
    N --> O["Insert section at tabOrder = anchorTabOrder + 1"]
    I -- No --> P[getNextTabOrder from existing sections\nAppend at end]
    P --> O
    O --> Q[Workspace stays in place\nNew empty section appears below]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
Line: 199-207

Comment:
**Missing guard for unknown anchor workspace**

If `insertAfterWorkspaceId` is provided but the workspace is not found in `v2WorkspaceLocalState` (e.g., stale state, timing edge case), `anchorWorkspace` is `undefined`. Both optional chains resolve to `undefined`, so `anchorTabOrder` silently defaults to `0`. This causes every top-level workspace and section in the project to be bumped by +1 and the new section to land at `tabOrder = 1` (the very top of the sidebar) — the opposite of the user's intent.

Consider adding a guard to fall back to the append behavior when the anchor cannot be resolved:

```ts
if (insertAfterWorkspaceId) {
    const anchorWorkspace = collections.v2WorkspaceLocalState.get(
        insertAfterWorkspaceId,
    );
    if (!anchorWorkspace) {
        // Anchor not found – fall back to append
        const sectionOrders = Array.from(
            collections.v2SidebarSections.state.values(),
        ).filter((item) => item.projectId === projectId);
        tabOrder = getNextTabOrder(sectionOrders);
    } else {
        const anchorTabOrder = anchorWorkspace.sidebarState.sectionId
            ? (collections.v2SidebarSections.get(
                    anchorWorkspace.sidebarState.sectionId,
                )?.tabOrder ?? 0)
            : anchorWorkspace.sidebarState.tabOrder;
        // ... bump loop and tabOrder assignment ...
    }
}
```

This also applies if the section pointed to by `sidebarState.sectionId` has been deleted — the `?.tabOrder ?? 0` fallback inside the ternary has the same problem.

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

Reviews (1): Last reviewed commit: "feat(desktop): promote "Create Section B..." | Re-trigger Greptile

Comment on lines +199 to +207
if (insertAfterWorkspaceId) {
const anchorWorkspace = collections.v2WorkspaceLocalState.get(
insertAfterWorkspaceId,
);
const anchorTabOrder = anchorWorkspace?.sidebarState.sectionId
? (collections.v2SidebarSections.get(
anchorWorkspace.sidebarState.sectionId,
)?.tabOrder ?? 0)
: (anchorWorkspace?.sidebarState.tabOrder ?? 0);
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 Missing guard for unknown anchor workspace

If insertAfterWorkspaceId is provided but the workspace is not found in v2WorkspaceLocalState (e.g., stale state, timing edge case), anchorWorkspace is undefined. Both optional chains resolve to undefined, so anchorTabOrder silently defaults to 0. This causes every top-level workspace and section in the project to be bumped by +1 and the new section to land at tabOrder = 1 (the very top of the sidebar) — the opposite of the user's intent.

Consider adding a guard to fall back to the append behavior when the anchor cannot be resolved:

if (insertAfterWorkspaceId) {
    const anchorWorkspace = collections.v2WorkspaceLocalState.get(
        insertAfterWorkspaceId,
    );
    if (!anchorWorkspace) {
        // Anchor not found – fall back to append
        const sectionOrders = Array.from(
            collections.v2SidebarSections.state.values(),
        ).filter((item) => item.projectId === projectId);
        tabOrder = getNextTabOrder(sectionOrders);
    } else {
        const anchorTabOrder = anchorWorkspace.sidebarState.sectionId
            ? (collections.v2SidebarSections.get(
                    anchorWorkspace.sidebarState.sectionId,
                )?.tabOrder ?? 0)
            : anchorWorkspace.sidebarState.tabOrder;
        // ... bump loop and tabOrder assignment ...
    }
}

This also applies if the section pointed to by sidebarState.sectionId has been deleted — the ?.tabOrder ?? 0 fallback inside the ternary has the same problem.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts
Line: 199-207

Comment:
**Missing guard for unknown anchor workspace**

If `insertAfterWorkspaceId` is provided but the workspace is not found in `v2WorkspaceLocalState` (e.g., stale state, timing edge case), `anchorWorkspace` is `undefined`. Both optional chains resolve to `undefined`, so `anchorTabOrder` silently defaults to `0`. This causes every top-level workspace and section in the project to be bumped by +1 and the new section to land at `tabOrder = 1` (the very top of the sidebar) — the opposite of the user's intent.

Consider adding a guard to fall back to the append behavior when the anchor cannot be resolved:

```ts
if (insertAfterWorkspaceId) {
    const anchorWorkspace = collections.v2WorkspaceLocalState.get(
        insertAfterWorkspaceId,
    );
    if (!anchorWorkspace) {
        // Anchor not found – fall back to append
        const sectionOrders = Array.from(
            collections.v2SidebarSections.state.values(),
        ).filter((item) => item.projectId === projectId);
        tabOrder = getNextTabOrder(sectionOrders);
    } else {
        const anchorTabOrder = anchorWorkspace.sidebarState.sectionId
            ? (collections.v2SidebarSections.get(
                    anchorWorkspace.sidebarState.sectionId,
                )?.tabOrder ?? 0)
            : anchorWorkspace.sidebarState.tabOrder;
        // ... bump loop and tabOrder assignment ...
    }
}
```

This also applies if the section pointed to by `sidebarState.sectionId` has been deleted — the `?.tabOrder ?? 0` fallback inside the ternary has the same problem.

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

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.

1 issue found across 3 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/hooks/useDashboardSidebarState/useDashboardSidebarState.ts">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts:199">
P2: When `insertAfterWorkspaceId` doesn't resolve to a workspace (stale ID or race condition), `anchorWorkspace` is `undefined` and `anchorTabOrder` falls back to `0`. This causes every top-level item with `tabOrder > 0` to be bumped and the new section to land at the top of the project — silently corrupting the sidebar order.

Guard against this by checking that `anchorWorkspace` actually resolved before entering the bump logic, and fall through to the append-at-end branch otherwise:

```ts
const anchorWorkspace = insertAfterWorkspaceId
  ? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
  : undefined;
if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
  // ... bump logic with anchorTabOrder
} else {
  // append-at-end
}
```</violation>
</file>

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

Comment on lines +199 to +202
if (insertAfterWorkspaceId) {
const anchorWorkspace = collections.v2WorkspaceLocalState.get(
insertAfterWorkspaceId,
);
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P2: When insertAfterWorkspaceId doesn't resolve to a workspace (stale ID or race condition), anchorWorkspace is undefined and anchorTabOrder falls back to 0. This causes every top-level item with tabOrder > 0 to be bumped and the new section to land at the top of the project — silently corrupting the sidebar order.

Guard against this by checking that anchorWorkspace actually resolved before entering the bump logic, and fall through to the append-at-end branch otherwise:

const anchorWorkspace = insertAfterWorkspaceId
  ? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
  : undefined;
if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
  // ... bump logic with anchorTabOrder
} else {
  // append-at-end
}
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/hooks/useDashboardSidebarState/useDashboardSidebarState.ts, line 199:

<comment>When `insertAfterWorkspaceId` doesn't resolve to a workspace (stale ID or race condition), `anchorWorkspace` is `undefined` and `anchorTabOrder` falls back to `0`. This causes every top-level item with `tabOrder > 0` to be bumped and the new section to land at the top of the project — silently corrupting the sidebar order.

Guard against this by checking that `anchorWorkspace` actually resolved before entering the bump logic, and fall through to the append-at-end branch otherwise:

```ts
const anchorWorkspace = insertAfterWorkspaceId
  ? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
  : undefined;
if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
  // ... bump logic with anchorTabOrder
} else {
  // append-at-end
}
```</comment>

<file context>
@@ -182,25 +182,71 @@ export function useDashboardSidebarState() {
 				].value;
 
+			let tabOrder: number;
+			if (insertAfterWorkspaceId) {
+				const anchorWorkspace = collections.v2WorkspaceLocalState.get(
+					insertAfterWorkspaceId,
</file context>
Suggested change
if (insertAfterWorkspaceId) {
const anchorWorkspace = collections.v2WorkspaceLocalState.get(
insertAfterWorkspaceId,
);
const anchorWorkspace = insertAfterWorkspaceId
? collections.v2WorkspaceLocalState.get(insertAfterWorkspaceId)
: undefined;
if (anchorWorkspace && anchorWorkspace.sidebarState.projectId === projectId) {
Fix with Cubic

@saddlepaddle saddlepaddle merged commit 78b7dc8 into main Apr 17, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

MocA-Love pushed a commit to MocA-Love/superset that referenced this pull request Apr 17, 2026
…ce menu (superset-sh#3537)

* feat(desktop): promote "Create Section Below" to top-level on workspace menu

The workspace context menu previously hid section creation inside the
"Move to Section" submenu, and the new section always appended to the
end of the project — making "create" and "move" feel like the same
action. Promote section creation to a top-level item, inserting an
empty section directly below the right-clicked workspace so it matches
the label, and leave "Move to Section" as a list of existing sections
(hidden entirely when none exist).

* refactor(desktop): separate "Create Section Below" from "Move to Section" with divider
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 17, 2026
…ction-below

upstream取り込み: Create Section Below を top-level に昇格 (superset-sh#3537)
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 17, 2026
Upstream commits processed (cherry-picked, then adapted where needed):

- 07c1ee0 fix(desktop): Cmd+O firing open-in twice on v1 workspace route (superset-sh#3511)
  → PR #302 (with fork tearoff-window adaptation for Cmd+O)
- 4a1f41a chore(deps): upgrade tanstack/db + electric, drop durable-streams patch (superset-sh#3509)
  → PR #303 (fork keeps fstream patch)
- a3df489 feat(desktop): v2 PR checkout via widened checkout procedure (superset-sh#3525)
  → PR #304 (clean)
- c504a50 feat(desktop): v2 file editor — foundation, views, and stability pass (superset-sh#3526)
  → PR #310 (foundation: 58 files path-checkout)
  → PR #311 (adaptation: 20 files manual port with SpreadsheetViewer/memo/fork-hotkeys preserved)
- 78b7dc8 feat(desktop): promote "Create Section Below" to top-level on workspace menu (superset-sh#3537)
  → PR #308

Record merge so upstream/main..main shows 0 behind.
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