Skip to content

fix(explorer): sort direction reliability + stability fixes against hangs - #3079

Open
gprot42 wants to merge 15 commits into
spacedriveapp:mainfrom
gprot42:fix/explorer-sort-direction
Open

fix(explorer): sort direction reliability + stability fixes against hangs#3079
gprot42 wants to merge 15 commits into
spacedriveapp:mainfrom
gprot42:fix/explorer-sort-direction

Conversation

@gprot42

@gprot42 gprot42 commented Jul 8, 2026

Copy link
Copy Markdown

fix: stability fixes to prevent application hangs (explorer + core)

  • Explorer: bound directory_listing to 10k results + removed unconditional
    client-side sortFiles for directory views (backend already sorts; avoids
    main-thread work + memory blowup on huge dirs)
  • Core: replaced sync Path::exists / metadata / canonicalize in async paths
    (add_location, validate, volume_for_path, local backend, etc.) with
    tokio equivalents or spawn_blocking
  • Eliminated risky block_on calls:
    • file_delete protocol now fully async (like file_transfer)
    • extension host uses block_in_place for WASM host fns
  • Guarded viewMode/sort auto-adjust effect in explorer context to prevent
    re-render/query churn from setSortBy identity changes
  • Bonus: non-blocking canonicalize in file transfer/delete paths

These are the quick wins + high-risk items (#2 block_on, #3 large dirs,
#4 churn) plus related blocking FS fixes identified while investigating
hangs.

Builds on the A-Z/Z-A sort direction reliability work.

Add sort_direction to directory listing queries and per-tab sortOrder state
so clicking Name (or any sort field) reverses between ascending and
descending order instead of staying locked at A-Z.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Explorer sorting now carries sort direction through tab state, menus, and directory listing queries. Core and network filesystem checks use async-safe operations, while Tauri startup, sidebar dialogs, sources navigation, and settings styling receive targeted UI and config updates.

Changes

Sort direction feature

Layer / File(s) Summary
Core directory sorting
core/src/ops/files/query/directory_listing.rs, apps/cli/src/domains/file/mod.rs, core/tests/normalized_cache_fixtures_test.rs
Adds optional sort_direction to DirectoryListingInput, initializes it to None, and applies effective direction defaults in indexed and in-memory sorting; callers pass sort_direction: None.
Tab sort state and utilities
packages/interface/src/components/TabManager/TabManagerContext.tsx, packages/interface/src/routes/explorer/sortUtils.ts
Adds sortOrder to tab explorer state, version tracking for explorer-state updates, tab normalization for legacy adapters tabs, and sort helpers for direction conversion, labels, and client-side sorting.
Explorer sort handlers
packages/interface/src/routes/explorer/context.tsx
Derives sortOrder, updates per-tab sort state, and exposes setSortOrder, handleSortChange, and toggleColumnSort through explorer context.
Explorer sort UI wiring
packages/interface/src/routes/explorer/ExplorerView.tsx, packages/interface/src/routes/explorer/SortMenu.tsx, packages/interface/src/routes/explorer/views/ListView/ListView.tsx, packages/interface/src/routes/explorer/views/ListView/useTable.tsx
Routes sort interactions through the new handlers, renders sort direction labels, rotates the list caret based on sortOrder, and passes table data directly.
Explorer query sort_direction
packages/interface/src/routes/explorer/hooks/useExplorerFiles.ts, packages/interface/src/routes/explorer/hooks/useExplorerKeyboard.ts, packages/interface/src/routes/explorer/views/ColumnView/Column.tsx, packages/interface/src/routes/explorer/views/ColumnView/ColumnView.tsx, packages/interface/src/routes/explorer/views/KnowledgeView.tsx, packages/interface/src/routes/explorer/views/SizeView/SizeView.tsx
Reads sortOrder in explorer hooks and views, converts it to sort_direction, and applies it to directory listing queries; virtual listings also use sortFiles client-side.

Async filesystem safety

Layer / File(s) Summary
Host and volume async calls
core/src/infra/extension/host_functions.rs, core/src/volume/manager.rs, core/src/volume/backend/local.rs
Wraps host function blocking work in block_in_place and switches volume path canonicalization and existence checks to async filesystem APIs.
Location path checks
core/src/location/manager.rs, core/src/location/mod.rs
Uses async existence checks for location validation and creation, and moves physical-location inode lookup into spawn_blocking.
Network path authorization
core/src/service/network/protocol/file_delete.rs, core/src/service/network/protocol/file_transfer.rs
Converts file delete and transfer path authorization to async/blocking-task canonicalization, updates allowed-path collection, and adjusts async tests.

Tauri boot and dependency setup

Layer / File(s) Summary
Tauri shell and startup
apps/tauri/index.html, apps/tauri/src-tauri/src/main.rs
Adds inline shell styling and placeholder content, extends startup readiness polling, and adds a release-only delayed window-show fallback.
Tauri styles and stubs
apps/tauri/src/index.css, apps/tauri/src/stubs/debug.ts, apps/tauri/src/stubs/spacebot-api-client.ts
Adds Tauri overlay styling, extra Tailwind sources, and browser stubs for debug and @spacebot/api-client.
Vite resolution
apps/tauri/vite.config.ts
Adds Bun-aware CJS interop resolution, aliases the new stubs and optional spacebot source, expands dependency optimization, and clears rollup externalization for the API client.

Spaces and sources UI flow

Layer / File(s) Summary
Space dialogs
packages/interface/src/components/SpacesSidebar/AddGroupModal.tsx, packages/interface/src/components/SpacesSidebar/CreateSpaceModal.tsx, packages/interface/src/components/SpacesSidebar/SpaceSettingsModal.tsx, packages/interface/src/components/SpacesSidebar/SpaceSwitcher.tsx, packages/interface/src/components/SpacesSidebar/spacePresets.ts
Moves space preset arrays into shared constants, adds a space settings dialog, and changes dropdown/dialog interactions to close through dialog state and defer modal opening until the menu closes.
Sources navigation
packages/interface/src/components/TabManager/TabNavigationSync.tsx, packages/interface/src/routes/sources/Adapters.tsx, packages/interface/src/routes/sources/index.tsx, packages/interface/src/routes/sources/useOpenAdaptersPicker.ts
Normalizes /sources/adapters as “Sources”, routes back navigation directly to /sources, and opens the adapters picker through a hook that also clears stale adapters tabs.

Settings styling

Layer / File(s) Summary
Settings colors
packages/interface/src/Settings/pages/AdvancedSettings.tsx, packages/interface/src/Settings/pages/ServicesSettings.tsx, packages/interface/src/routes/settings/index.tsx
Switches warning panels and inactive sidebar styling from amber utilities to status-warning and updated sidebar text utilities.

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

Poem

I’m a rabbit with a sorting grin,
I hop where asc and desc begin.
The carrots line up, neat and true,
And async paths go thump-thump through.
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description summarizes the work, but it omits the required 'Closes #(issue)' line from the template. Add the missing 'Closes #(issue)' section and link the relevant issue number, or create one and reference it here.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the explorer sort-direction and hang-stability changes without being misleading.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
packages/interface/src/routes/explorer/hooks/useExplorerKeyboard.ts (1)

49-56: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add folders_first to the directory listing query for keyboard navigation consistency.

The query input is missing folders_first: viewSettings.foldersFirst, while useExplorerFiles (line 223) and Column (line 141) both include it. When foldersFirst is enabled, this hook's files array (line 63) will be in a different order than the displayed file list, causing arrow-key navigation to select the wrong file.

viewSettings is already destructured at line 22, so the fix is a one-line addition.

Proposed fix
 				sort_by: sortBy as DirectorySortBy,
 				sort_direction: toSortDirection(sortOrder),
+				folders_first: viewSettings.foldersFirst,
 			}
 		: null!,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/interface/src/routes/explorer/hooks/useExplorerKeyboard.ts` around
lines 49 - 56, Add folders_first to the directory listing query in
useExplorerKeyboard so keyboard navigation matches the displayed list when
foldersFirst is enabled. Update the input object built from currentPath to
include folders_first: viewSettings.foldersFirst, keeping it consistent with
useExplorerFiles and Column, and use the already available viewSettings value in
this hook.
🧹 Nitpick comments (1)
packages/interface/src/components/TabManager/TabManagerContext.tsx (1)

87-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consolidate duplicate SortOrder type definition.

SortOrder is defined identically in both this file (line 87) and sortUtils.ts (line 4). While structurally compatible, having two independent definitions risks future divergence. Consider importing from a single source — either have TabManagerContext.tsx import SortOrder from sortUtils.ts, or extract it to a shared types module.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/interface/src/components/TabManager/TabManagerContext.tsx` around
lines 87 - 93, Consolidate the duplicate SortOrder definition by using a single
shared type source instead of declaring it again in TabManagerContext.tsx.
Update the TabExplorerState-related types to import SortOrder from sortUtils.ts,
or move SortOrder into a shared types module and import it from both places,
keeping the existing TabExplorerState and sortUtils symbols aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/interface/src/routes/explorer/hooks/useExplorerKeyboard.ts`:
- Around line 49-56: Add folders_first to the directory listing query in
useExplorerKeyboard so keyboard navigation matches the displayed list when
foldersFirst is enabled. Update the input object built from currentPath to
include folders_first: viewSettings.foldersFirst, keeping it consistent with
useExplorerFiles and Column, and use the already available viewSettings value in
this hook.

---

Nitpick comments:
In `@packages/interface/src/components/TabManager/TabManagerContext.tsx`:
- Around line 87-93: Consolidate the duplicate SortOrder definition by using a
single shared type source instead of declaring it again in
TabManagerContext.tsx. Update the TabExplorerState-related types to import
SortOrder from sortUtils.ts, or move SortOrder into a shared types module and
import it from both places, keeping the existing TabExplorerState and sortUtils
symbols aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c2a58558-146f-4483-90c3-344031611a87

📥 Commits

Reviewing files that changed from the base of the PR and between 60369e9 and 05baaa1.

⛔ Files ignored due to path filters (1)
  • packages/ts-client/src/generated/types.ts is excluded by !**/generated/**, !**/generated/**
📒 Files selected for processing (14)
  • apps/cli/src/domains/file/mod.rs
  • core/src/ops/files/query/directory_listing.rs
  • core/tests/normalized_cache_fixtures_test.rs
  • packages/interface/src/components/TabManager/TabManagerContext.tsx
  • packages/interface/src/routes/explorer/ExplorerView.tsx
  • packages/interface/src/routes/explorer/context.tsx
  • packages/interface/src/routes/explorer/hooks/useExplorerFiles.ts
  • packages/interface/src/routes/explorer/hooks/useExplorerKeyboard.ts
  • packages/interface/src/routes/explorer/sortUtils.ts
  • packages/interface/src/routes/explorer/views/ColumnView/Column.tsx
  • packages/interface/src/routes/explorer/views/ColumnView/ColumnView.tsx
  • packages/interface/src/routes/explorer/views/KnowledgeView.tsx
  • packages/interface/src/routes/explorer/views/ListView/ListView.tsx
  • packages/interface/src/routes/explorer/views/SizeView/SizeView.tsx

gprot42 added 2 commits July 8, 2026 19:08
- Explorer: bound directory_listing to 10k results + removed unconditional
  client-side sortFiles for directory views (backend already sorts; avoids
  main-thread work + memory blowup on huge dirs)
- Core: replaced sync Path::exists / metadata / canonicalize in async paths
  (add_location, validate, volume_for_path, local backend, etc.) with
  tokio equivalents or spawn_blocking
- Eliminated risky block_on calls:
  - file_delete protocol now fully async (like file_transfer)
  - extension host uses block_in_place for WASM host fns
- Guarded viewMode/sort auto-adjust effect in explorer context to prevent
  re-render/query churn from setSortBy identity changes
- Bonus: non-blocking canonicalize in file transfer/delete paths

These are the quick wins + high-risk items (spacedriveapp#2 block_on, spacedriveapp#3 large dirs,
spacedriveapp#4 churn) plus related blocking FS fixes identified while investigating
hangs.

Builds on the A-Z/Z-A sort direction reliability work.
@gprot42 gprot42 changed the title fix(explorer): toggle sort direction on column header and sort menu fix(explorer): sort direction reliability + stability fixes against hangs Jul 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/interface/src/routes/explorer/sortUtils.ts (1)

33-80: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle created in packages/interface/src/routes/explorer/sortUtils.ts.
Virtual listings can keep the "created" sort selected, but sortFiles falls through to default, so creation-time ordering never applies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/interface/src/routes/explorer/sortUtils.ts` around lines 33 - 80,
The sortFiles fallback in sortUtils.ts does not handle the "created" sort
option, so virtual listings with that selection currently fall through to the
default path. Update the sortFiles switch to include a "created" case alongside
"name", "modified", "size", and "type", and compare the file creation timestamp
field used by File objects so the client-side fallback matches daemon sorting.
Keep the existing direction and foldersFirst behavior intact when adding the new
case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/interface/src/routes/explorer/sortUtils.ts`:
- Around line 33-80: The sortFiles fallback in sortUtils.ts does not handle the
"created" sort option, so virtual listings with that selection currently fall
through to the default path. Update the sortFiles switch to include a "created"
case alongside "name", "modified", "size", and "type", and compare the file
creation timestamp field used by File objects so the client-side fallback
matches daemon sorting. Keep the existing direction and foldersFirst behavior
intact when adding the new case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2da8269c-2c1b-40dd-ad77-a6a9bab1f4a3

📥 Commits

Reviewing files that changed from the base of the PR and between 05baaa1 and a5f2185.

📒 Files selected for processing (14)
  • core/src/infra/extension/host_functions.rs
  • core/src/location/manager.rs
  • core/src/location/mod.rs
  • core/src/service/network/protocol/file_delete.rs
  • core/src/service/network/protocol/file_transfer.rs
  • core/src/volume/backend/local.rs
  • core/src/volume/manager.rs
  • packages/interface/src/components/TabManager/TabManagerContext.tsx
  • packages/interface/src/routes/explorer/ExplorerView.tsx
  • packages/interface/src/routes/explorer/SortMenu.tsx
  • packages/interface/src/routes/explorer/context.tsx
  • packages/interface/src/routes/explorer/hooks/useExplorerFiles.ts
  • packages/interface/src/routes/explorer/sortUtils.ts
  • packages/interface/src/routes/explorer/views/ListView/useTable.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/interface/src/routes/explorer/ExplorerView.tsx
  • packages/interface/src/routes/explorer/context.tsx

- Add immediate dark background styles + loading hint in index.html
  so the transparent Tauri window no longer flashes native grey before
  Vite/React paint.

- Add virtual pure-JS stubs in vite.config.ts for:
  - @spacebot/api-client (when sibling repo missing)
  - hast-util-to-jsx-runtime
  - style-to-js

  These were causing module resolution / CJS default export errors
  that left #root empty (grey screen).

- Extend optimizeDeps exclude for the problematic CJS transitives.

The real Tauri webview now gets a solid dark initial paint and
the module graph loads without errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/tauri/vite.config.ts`:
- Around line 32-36: The `@spacebot/api-client` fallback in the Vite config is
too minimal: it only exports an empty `apiClient`, but Spacebot routes still
call methods like `listTasks`, `channelMessages`, `listWorkers`,
`listPortalConversations`, `portalHistory`, `createPortalConversation`,
`portalSend`, `workerDetail`, `cancelProcess`, `ttsGenerate`, and
`webChatSendAudio`. Update the stub generated in `vite.config.ts` so the
exported `apiClient` includes no-op implementations for the full surface used by
the Spacebot UI, or alternatively gate/disable those routes when the sibling
repo is missing. Use the existing `apiClient`, `getEventsUrl`, and
`setServerUrl` exports as the anchor points when making the fallback consistent.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: afd6a2a7-4e85-4413-a577-3b8e1537f600

📥 Commits

Reviewing files that changed from the base of the PR and between a5f2185 and 241c3ca.

📒 Files selected for processing (2)
  • apps/tauri/index.html
  • apps/tauri/vite.config.ts

Comment thread apps/tauri/vite.config.ts Outdated

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added 1 comment

Comment thread apps/tauri/vite.config.ts Outdated
gprot42 added 11 commits July 9, 2026 00:04
Add Vite aliases and stubs for Bun-hoisted packages and the optional
@spacebot/api-client dependency so tauri:dev starts without resolve errors.
Opening the adapters picker via createTab left duplicate Sources/Adapters
tabs in the tab bar. Navigate within the current tab, normalize persisted
tabs on load, and keep the tab title as Sources while on /sources/adapters.
Cloud storage was only reachable from Overview. Expose the same Add Storage
entry point in the explorer so users can connect S3, Google Drive, and
other providers while browsing files.
Release builds start with visible:false. If the webview fails to boot,
force-show after 3s so the app is not stuck with zero windows.
Daemon startup often exceeds the previous 3s timeout because of volume
discovery and networking init before the RPC socket binds. Increase the
wait so the app does not fail launch with a hidden window.
Directory listings stopped client-side sorting for performance, but
ResourceChanged and ephemeral index events append files in discovery
order and scramble SQL/backend order. Re-apply locale-aware sort on the
capped result set, and use COLLATE NOCASE for SQL name sorts.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/interface/src/components/SpacesSidebar/SpaceSettingsModal.tsx`:
- Line 39: The SpaceSettingsModal close handler is mutating dialog state
directly via dialog.state.open, which should be replaced with the same
dialogManager.setState(dialog.id, { open: false }) pattern used elsewhere in
this PR. Update the code in SpaceSettingsModal to call dialogManager.setState
for closing the modal instead of changing dialog.state.open in place, keeping
the behavior consistent with AddGroupModal and using the existing dialogManager
API.

In `@packages/interface/src/components/TabManager/TabManagerContext.tsx`:
- Around line 348-382: The `setTabs` updater in `TabManagerContext` is impure
because it generates `crypto.randomUUID()` and triggers `setActiveTabId`,
`setExplorerStates`, and `setSelectionStates` inside the updater. Move UUID
creation and the other state updates out of the `setTabs` callback, then pass
the generated id into the updater so it only returns the next tabs array. Keep
the logic in the same tab-creation flow around `tabPath`, `deriveTitleFromPath`,
`newTab`, and the explorer/selection state initialization, but make the updater
itself deterministic.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 41306a17-a094-430d-aa78-6f4f37b8a64b

📥 Commits

Reviewing files that changed from the base of the PR and between 241c3ca and 12e1d10.

📒 Files selected for processing (22)
  • apps/tauri/src-tauri/src/main.rs
  • apps/tauri/src/index.css
  • apps/tauri/src/stubs/debug.ts
  • apps/tauri/src/stubs/spacebot-api-client.ts
  • apps/tauri/vite.config.ts
  • core/src/ops/files/query/directory_listing.rs
  • packages/interface/src/Settings/pages/AdvancedSettings.tsx
  • packages/interface/src/Settings/pages/ServicesSettings.tsx
  • packages/interface/src/components/SpacesSidebar/AddGroupModal.tsx
  • packages/interface/src/components/SpacesSidebar/CreateSpaceModal.tsx
  • packages/interface/src/components/SpacesSidebar/SpaceSettingsModal.tsx
  • packages/interface/src/components/SpacesSidebar/SpaceSwitcher.tsx
  • packages/interface/src/components/SpacesSidebar/spacePresets.ts
  • packages/interface/src/components/TabManager/TabManagerContext.tsx
  • packages/interface/src/components/TabManager/TabNavigationSync.tsx
  • packages/interface/src/routes/explorer/ExplorerView.tsx
  • packages/interface/src/routes/explorer/hooks/useExplorerFiles.ts
  • packages/interface/src/routes/explorer/sortUtils.ts
  • packages/interface/src/routes/settings/index.tsx
  • packages/interface/src/routes/sources/Adapters.tsx
  • packages/interface/src/routes/sources/index.tsx
  • packages/interface/src/routes/sources/useOpenAdaptersPicker.ts
✅ Files skipped from review due to trivial changes (4)
  • apps/tauri/src/stubs/debug.ts
  • packages/interface/src/Settings/pages/ServicesSettings.tsx
  • packages/interface/src/Settings/pages/AdvancedSettings.tsx
  • packages/interface/src/components/SpacesSidebar/spacePresets.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/interface/src/routes/explorer/sortUtils.ts
  • core/src/ops/files/query/directory_listing.rs

icon: selectedIcon,
color: selectedColor,
});
dialog.state.open = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use dialogManager.setState instead of direct dialog.state.open mutation.

AddGroupModal.tsx in this same PR was explicitly changed from dialog.state.open = false to dialogManager.setState(dialog.id, { open: false }). This new file should follow the same pattern for consistency and to avoid potential state batching issues with direct mutation.

🔧 Proposed fix
 		color: selectedColor,
 	});
-		dialog.state.open = false;
+		dialogManager.setState(dialog.id, { open: false });
 });
📝 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
dialog.state.open = false;
dialogManager.setState(dialog.id, { open: false });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/interface/src/components/SpacesSidebar/SpaceSettingsModal.tsx` at
line 39, The SpaceSettingsModal close handler is mutating dialog state directly
via dialog.state.open, which should be replaced with the same
dialogManager.setState(dialog.id, { open: false }) pattern used elsewhere in
this PR. Update the code in SpaceSettingsModal to call dialogManager.setState
for closing the modal instead of changing dialog.state.open in place, keeping
the behavior consistent with AddGroupModal and using the existing dialogManager
API.

Comment on lines 348 to +382

// Initialize explorer state for the new tab
setExplorerStates((prev) =>
new Map(prev).set(newTab.id, { ...DEFAULT_EXPLORER_STATE }),
);
setTabs((prev) => {
const existing = prev.find((tab) => tab.savedPath === tabPath);
if (existing) {
setActiveTabId(existing.id);
return prev;
}

const [pathname, search = ""] = tabPath.split("?");
const derivedTitle =
title ||
deriveTitleFromPath(pathname, search ? `?${search}` : "");

const newTab: Tab = {
id: crypto.randomUUID(),
title: derivedTitle,
icon: null,
isPinned: false,
lastActive: Date.now(),
savedPath: tabPath,
};

setExplorerStates((explorerPrev) =>
new Map(explorerPrev).set(newTab.id, {
...DEFAULT_EXPLORER_STATE,
}),
);

// Initialize empty selection state for the new tab
setSelectionStates((prev) => new Map(prev).set(newTab.id, []));
setSelectionStates((selectionPrev) =>
new Map(selectionPrev).set(newTab.id, []),
);

setTabs((prev) => [...prev, newTab]);
setActiveTabId(newTab.id);
setActiveTabId(newTab.id);
return [...prev, newTab];
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Move crypto.randomUUID() and state-setter calls out of the setTabs updater.

The updater function passed to setTabs contains impure side effects: crypto.randomUUID() is non-deterministic, and setActiveTabId, setExplorerStates, and setSelectionStates are called inside it. In React Strict Mode, updaters are double-invoked — the first call generates UUID1 and creates state entries for it, the second generates UUID2. React commits only the second result (UUID2), leaving orphaned explorerStates and selectionStates entries for UUID1.

Generating the UUID before the updater and passing it in makes the updater pure and eliminates the orphaned-state problem.

🔧 Proposed fix
 	const createTab = useCallback(
 		(title?: string, path?: string) => {
 			const tabPath = path ?? defaultNewTabPath;
+			const newTabId = crypto.randomUUID();
 
 			setTabs((prev) => {
 				const existing = prev.find((tab) => tab.savedPath === tabPath);
 				if (existing) {
 					setActiveTabId(existing.id);
 					return prev;
 				}
 
 				const [pathname, search = ""] = tabPath.split("?");
 				const derivedTitle =
 					title ||
 					deriveTitleFromPath(pathname, search ? `?${search}` : "");
 
 				const newTab: Tab = {
-					id: crypto.randomUUID(),
+					id: newTabId,
 					title: derivedTitle,
 					icon: null,
 					isPinned: false,
 					lastActive: Date.now(),
 					savedPath: tabPath,
 				};
 
 				setExplorerStates((explorerPrev) =>
-					new Map(explorerPrev).set(newTab.id, {
+					new Map(explorerPrev).set(newTabId, {
 						...DEFAULT_EXPLORER_STATE,
 					}),
 				);
 
 				setSelectionStates((selectionPrev) =>
-					new Map(selectionPrev).set(newTab.id, []),
+					new Map(selectionPrev).set(newTabId, []),
 				);
 
-				setActiveTabId(newTab.id);
+				setActiveTabId(newTabId);
 				return [...prev, newTab];
 			});
 		},
 		[defaultNewTabPath],
 	);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/interface/src/components/TabManager/TabManagerContext.tsx` around
lines 348 - 382, The `setTabs` updater in `TabManagerContext` is impure because
it generates `crypto.randomUUID()` and triggers `setActiveTabId`,
`setExplorerStates`, and `setSelectionStates` inside the updater. Move UUID
creation and the other state updates out of the `setTabs` callback, then pass
the generated id into the updater so it only returns the next tabs array. Keep
the logic in the same tab-creation flow around `tabPath`, `deriveTitleFromPath`,
`newTab`, and the explorer/selection state initialization, but make the updater
itself deterministic.

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