Skip to content

fix(desktop): self-heal project re-add roots#6158

Merged
SivanCola merged 5 commits into
esengine:main-v2from
SivanCola:fix/project-readd-self-heal
Jul 7, 2026
Merged

fix(desktop): self-heal project re-add roots#6158
SivanCola merged 5 commits into
esengine:main-v2from
SivanCola:fix/project-readd-self-heal

Conversation

@SivanCola

@SivanCola SivanCola commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • compare desktop project roots with the platform-aware path matcher
  • canonicalize stale project registry entries when a project is added again
  • keep project pinning and sidebar ordering resilient to equivalent root spellings
  • extend the same equivalence to runtime lookups that cross spelling sources: blank-tab reuse, open-topic collection, session-binding workspace notices, topic prepend/repair, indexed blank-topic reuse, and ListWorkspaces' Current flag
  • fold case in topicSummaryKey (same key form as agent.CanonicalSessionPath) so sidebar topic runtime status never splits across the tab, registry, and session-meta root spellings
  • fold Windows case in config.WorkspaceSlug so equivalent spellings of one workspace no longer split its projects/<slug> session/memory state directories; internal/memory's duplicate slugify now routes through it. No migration needed: NTFS resolves directory names case-insensitively, so the folded slug opens the same directory
  • sync open project tabs to the registry's canonical root spelling after registry writes (registerProjectRoot / rename / color), persist the tab snapshot, and emit project-tree:changed — the frontend keeps comparing plain strings against a single source of truth instead of learning platform path semantics
  • fold the startup-migration and recovery-GC seen maps (desktopMCPMigrationRoots, recoverLegacyProjectSidebarRoots, recoveryGCDirs) with projectRootKey so case-split roots are not scanned or appended twice
  • keep the global sidebar token out of path comparisons in normalizeSidebarOrder
  • add regression coverage: equivalent-root merging, re-adding a removed project, Windows-only case-folding tests (*OnWindows) including tab re-spell sync, WorkspaceSlug fold/preserve tests, plus a windows-latest desktop CI leg to run the Windows-only tests

Root Cause

The desktop project registry used exact string equality for several root-path operations. If an existing record used an equivalent spelling of the same folder, later remove/re-add, pin, or reorder operations could miss the stale entry instead of repairing it. On Linux/macOS normalizeProjectRoot already collapses dot segments and trailing separators, so the reachable spelling split is Windows case differences (filepath.Abs does not fold case) — which is why the contract tests are Windows-only and need the new CI leg. The same key-splitting shape also affected config.WorkspaceSlug (persistent per-project state directories) and the frontend's exact workspaceRoot comparisons, both fixed here at the owner level.

Known limitations

  • macOS APFS is case-insensitive by default, but sameDesktopPath only folds case on Windows, so same-folder case variants can still split on macOS. This matches the existing sameDesktopPath semantics.
  • Symlinked spellings of the same folder (e.g. /var vs /private/var) are not treated as equivalent; nothing resolves symlinks today.

Checks

  • go test ./... (root module, includes the WorkspaceSlug fold/preserve tests)
  • cd desktop && go test .
  • cd desktop && go test . -run 'OnWindows$' -v (runs on the new desktop-windows CI leg; skips elsewhere)
  • gofmt -l / go vet clean on both modules
  • git diff --check

Cache

Cache-impact: none - internal/memory/store.go only routes the project-slug computation through config.WorkspaceSlug (deduping a copy of slugify). The slug names the on-disk memory storage directory; it is never emitted into the provider-visible system prompt, and off Windows the produced bytes are identical to before. The MEMORY.md index text that loads into the cached prefix is unchanged.
Cache-guard: ./scripts/cache-guard.sh (TestReleaseCacheHitGuard) passed; no prompt-construction, tool-schema, or memory-index-format bytes changed.
System-prompt-review: No system-prompt surface touched - the change is a storage-path helper dedup, so the cached system-prompt prefix is byte-identical on all platforms except the Windows storage directory casing.

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) config Configuration & setup (internal/config) labels Jul 7, 2026
merge-order-check added 5 commits July 8, 2026 01:44
… Windows contract

The registry-side matcher still left same-shape exact comparisons on
paths that cross spelling sources:

- fold case in topicSummaryKey (matching agent.CanonicalSessionPath's
  key form) so sidebar topic runtime status never splits across the
  tab, registry, and session-meta root spellings
- match roots with sameProjectRoot in blank-tab reuse, open-topic
  collection, session-binding workspace notices, topic prepend/repair,
  indexed blank-topic reuse, and ListWorkspaces' Current flag
- keep the global sidebar token out of path comparisons in
  normalizeSidebarOrder

The re-add regression tests spelled the equivalent root with
filepath.Join(root, "."), which Join cleans back to the identical
string, so they also passed on the unpatched base. Spell the dot
segment by hand, and add Windows-only case-folding tests (*OnWindows)
plus a windows-latest desktop CI leg to run them - the ubuntu leg can
never reach the EqualFold branch.
…b root spellings

WorkspaceSlug fed the raw absolute path into the slug, so on Windows
C:\Foo and c:\foo produced two projects/<slug> state directories and
the same workspace's session list and memory-compiler state split.
Fold case there (matching agent.CanonicalSessionPath's key form) and
route internal/memory's duplicate slugify through it. Existing
mixed-case slug directories keep working: NTFS resolves directory
names case-insensitively, so the folded slug opens the same directory
and no migration is needed. Unix slugs are unchanged - case-different
paths there are genuinely different directories.

The frontend compares tab.workspaceRoot against tree node roots with
exact string equality, so a registry re-spell (addProject adopting the
caller's spelling) could strand open tabs on the old spelling and
break ownership highlighting and session filters. Sync open project
tabs to the registry's canonical spelling after every registry write
(registerProjectRoot / rename / color paths), persist the tab
snapshot, and emit project-tree:changed so the frontend refreshes -
keeping one string form as the single source of truth instead of
teaching the frontend platform-aware path semantics.

Also fold the startup-migration and recovery-GC seen maps
(desktopMCPMigrationRoots, recoverLegacyProjectSidebarRoots,
recoveryGCDirs) with projectRootKey so case-split roots are not
scanned or appended twice, and cover the tab re-spell sync with a
Windows-only regression test on the desktop-windows CI leg.
config.WorkspaceSlug now folds case on Windows so equivalent spellings
of one workspace share a slug; TestStoreForSlug asserted the unfolded
spelling and failed on the windows-latest leg.
…meProjectRoot

Five project-root comparisons still used
normalizeProjectRoot(a) == normalizeProjectRoot(b), which does not
fold case on Windows — so a re-add under a different case spelling
could miss the existing session/tab/migration match:

- topicSessionMatchMatchesTarget (session meta vs registry root)
- tabMatchesTopicTarget (tab reuse for same topic)
- tabInWorkspace (tab-in-workspace predicate)
- reconcileTabWithLiveBinding (controller root vs tab root guard)
- legacySessionScopeMatchesMigrationTarget (migration scope check)

Switch all five to sameProjectRoot and add a Windows-only regression
test that plants a session with the original-case root in its meta,
re-registers the project under a flipped-case root, and verifies
findTopicSessionForTarget still resolves the original session.
@SivanCola SivanCola force-pushed the fix/project-readd-self-heal branch from 3cde774 to 824f232 Compare July 7, 2026 17:46
@SivanCola SivanCola marked this pull request as ready for review July 7, 2026 18:07
@SivanCola SivanCola requested a review from esengine as a code owner July 7, 2026 18:07
@SivanCola SivanCola merged commit 8357312 into esengine:main-v2 Jul 7, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration & setup (internal/config) desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant