fix(ui): close review findings from the switch-and-scroll integration

- Markdown DOM cache: key includes a content-length fingerprint so an
  edited or reverted part re-materializing under the same id cannot
  restore stale DOM, memoization uses scalar identities instead of the
  part object (store reducers recreate part objects on unrelated updates,
  which re-ran the async render pipeline for identical content), and a
  probe with a mismatched locale/directory no longer destroys the entry
  it failed to claim.
- Sidebar bootstrap: the layout-level sync owner only knows known
  directories, so expanded projects bootstrapped serialized at background
  priority. The visible collection now publishes a second, expansion-
  aware demand owner, restoring concurrent hydration for expanded
  projects and worktree groups.
- Settings: local changes still sitting in the debounce buffer are not
  yet tracked as mutations, so a settings GET racing the debounce window
  briefly reverted them; reconciled results now reapply the pending
  buffer.
This commit is contained in:
Bohdan Triapitsyn
2026-08-26 01:03:47 +03:00
parent ac880e8e62
commit 448ecc12b8
4 changed files with 51 additions and 7 deletions
@@ -10,6 +10,8 @@ import { useArchivedAutoFolders } from '../folders/useArchivedAutoFolders';
import { ProjectSessionSelectionEffect } from '../projects/useProjectSessionSelection';
import type { WorktreeMetadata } from '@/types/worktree';
import { useRecentSessionCollection, useSessionProjectCollection } from './sessionCollection';
import { buildSessionBootstrapDemands } from './sessionBootstrapDemands';
import { useChildStoreManager } from '@/sync/sync-context';
import { createSessionOwnershipIndex } from '../sessions/sessionOwnership';
import { useProjectSessionLists } from '../projects/useProjectSessionLists';
import { useSessionSidebarSections } from '../projects/useSessionSidebarSections';
@@ -192,6 +194,26 @@ const VisibleSessionProjects: React.FC<SessionProjectCollectionProps> = ({ topol
buildGroupSearchText,
foldersMap,
});
// Second bootstrap-demand owner: the layout-level useSessionListSync keeps
// every known directory alive at background priority even when the sidebar
// is hidden, but only the visible collection knows which projects and
// groups are EXPANDED. Without this owner, expanded projects bootstrapped
// serialized at background priority (one directory at a time) instead of
// concurrently at expanded priority.
const childStores = useChildStoreManager();
const expansionDemandOwner = `session-collection-expansion:${React.useId()}`;
React.useEffect(() => {
childStores.setBootstrapDemand(expansionDemandOwner, buildSessionBootstrapDemands({
projectSections,
activeProjectId: view.activeProjectId,
collapsedProjects: projectView.collapsedProjects,
collapsedGroups: projectView.collapsedGroups,
currentDirectory: null,
currentSessionDirectory: null,
}));
return () => childStores.clearBootstrapDemand(expansionDemandOwner);
}, [childStores, expansionDemandOwner, projectSections, projectView.collapsedProjects, projectView.collapsedGroups, view.activeProjectId]);
const source = view.useGroupedSections ? sectionsForRender : flatSectionsForRender;
const sectionsForSidebarRender = React.useMemo(() => view.showInlineArchived ? source : source.map((section) => (
section.groups.some((group) => group.isArchivedBucket)