Skip to content

Commit 6bfab14

Browse files
SawyerHoodclaude
andcommitted
Sync sidebar organization, ordering, and navigation preferences through the server
Add a synced-preference atom that holds each UI preference in memory, hydrates from the server, and writes through a per-key queue with revision checks: queued edits compose as operations evaluated against the server value at the submitted revision, functional updates re-apply on conflict, cache updates stay revision-monotonic, and the atom reconciles to the cache when its queue drains. The sidebar waits for the preferences query alongside the project list instead of painting a default layout first. A one-shot migration uploads the value found in the old browser storage once when the server has none, then deletes that copy. Convert the organization mode, chronological sort, three section orders, navigation entry order and visibility, and both provider pickers, and stop persisting normalized orders on mount. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 4230713 commit 6bfab14

24 files changed

Lines changed: 1530 additions & 549 deletions

‎apps/app/src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { RouteNavigationProvider } from "./components/ui/app-route-anchor";
1313
import { RouteNavigationIndicator } from "./components/ui/route-navigation-indicator";
1414
import { AppNavigationUrlHost } from "./lib/url-open-routing";
1515
import { NativeShellReporter } from "./lib/native-shell";
16+
import { UiPreferencesSync } from "@/lib/ui-preferences/UiPreferencesSync";
1617
import { AppFileExternalNavigationHost } from "./components/plugin/AppFileExternalNavigationHost";
1718
import { useAppTheme } from "./hooks/useAppTheme";
1819
import { useFaviconColorSync } from "./lib/favicon-color-preference";
@@ -364,6 +365,7 @@ export function App() {
364365
<AppFileExternalNavigationHost>
365366
<HashNavigationScroll />
366367
<NativeShellReporter />
368+
<UiPreferencesSync />
367369
<Routes>
368370
<Route
369371
path={AUTH_CALLBACK_ROUTE_PATH}

‎apps/app/src/components/plugin/PluginNavSidebarItems.test.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,10 @@ describe("PluginNavSidebarItems", () => {
773773
expect(screen.getByTestId("sidebar-navigation-more-row")).not.toBeNull();
774774

775775
unmount();
776-
renderSidebarItems();
776+
renderSidebarItems({
777+
storedOrder: store.get(pluginNavPanelOrderAtom),
778+
storedVisibleKeys: store.get(pluginNavVisiblePanelKeysAtom),
779+
});
777780
expect(panelRowNames(labels)).toEqual(["Two", "Three", "Four"]);
778781
expect(screen.queryByRole("button", { name: "One" })).toBeNull();
779782

@@ -804,8 +807,9 @@ describe("PluginNavSidebarItems", () => {
804807
});
805808

806809
expect(visibleRowKeys()).toEqual(["__bb__/new-thread", "docs/main"]);
807-
expect(store.get(pluginNavVisiblePanelKeysAtom)).toEqual([
808-
"__bb__/new-thread",
810+
expect(store.get(pluginNavVisiblePanelKeysAtom)).toEqual(["docs/main"]);
811+
expect(store.get(pluginNavPanelOrderAtom)).toEqual([
812+
"tasks/main",
809813
"docs/main",
810814
]);
811815
});
@@ -821,13 +825,11 @@ describe("PluginNavSidebarItems", () => {
821825

822826
expect(visibleRowKeys()).toEqual(["__bb__/new-thread", "tasks/main"]);
823827
expect(store.get(pluginNavVisiblePanelKeysAtom)).toEqual([
824-
"tasks/main",
825828
"__bb__/new-thread",
826829
]);
827830
expect(store.get(pluginNavPanelOrderAtom)).toEqual([
828831
"__bb__/new-thread",
829832
"docs/main",
830-
"tasks/main",
831833
]);
832834
});
833835

‎apps/app/src/components/plugin/PluginNavSidebarItems.tsx‎

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -275,29 +275,19 @@ function PluginNavSidebarItemList({
275275
[ordered, visibleKeys],
276276
);
277277

278-
useEffect(() => {
279-
if (haveSameOrder(storedOrder, normalizedOrder)) return;
280-
setStoredOrder(normalizedOrder);
281-
}, [normalizedOrder, setStoredOrder, storedOrder]);
282-
283-
useEffect(() => {
284-
if (
285-
storedVisibleKeys === null ||
286-
normalizedVisibleKeys === null ||
287-
haveSameOrder(storedVisibleKeys, normalizedVisibleKeys)
288-
) {
289-
return;
290-
}
291-
setStoredVisibleKeys(normalizedVisibleKeys);
292-
}, [normalizedVisibleKeys, setStoredVisibleKeys, storedVisibleKeys]);
293-
294278
const orderedKeys = useMemo(
295279
() => ordered.map(getPluginNavPanelKey),
296280
[ordered],
297281
);
298282

283+
const persistNormalizedOrder = useCallback(() => {
284+
if (haveSameOrder(storedOrder, normalizedOrder)) return;
285+
setStoredOrder(normalizedOrder);
286+
}, [normalizedOrder, setStoredOrder, storedOrder]);
287+
299288
const setPanelVisible = useCallback(
300289
(key: string, isVisible: boolean) => {
290+
persistNormalizedOrder();
301291
setStoredVisibleKeys(
302292
togglePluginNavPanelVisibility(
303293
normalizedVisibleKeys ?? visibleKeys,
@@ -306,7 +296,12 @@ function PluginNavSidebarItemList({
306296
),
307297
);
308298
},
309-
[normalizedVisibleKeys, setStoredVisibleKeys, visibleKeys],
299+
[
300+
normalizedVisibleKeys,
301+
persistNormalizedOrder,
302+
setStoredVisibleKeys,
303+
visibleKeys,
304+
],
310305
);
311306

312307
const handleDragEnd = useCallback(

‎apps/app/src/components/plugin/pluginNavSidebarAtoms.test.ts‎

Lines changed: 0 additions & 178 deletions
This file was deleted.

0 commit comments

Comments
 (0)