Skip to content

fix(layout): persist Browser and Comments panel toggles - #1940

Merged
giswqs merged 3 commits into
mainfrom
fix/issue-1935-persist-panel-layout
Aug 15, 2026
Merged

fix(layout): persist Browser and Comments panel toggles#1940
giswqs merged 3 commits into
mainfrom
fix/issue-1935-persist-panel-layout

Conversation

@giswqs

@giswqs giswqs commented Aug 15, 2026

Copy link
Copy Markdown
Member

Fixes #1935

Problem

The Settings → Layout toggles for the Browser and Comments panels acted only on the live right-panel registry. Nothing was written to desktopSettings.layout, and both registration hooks unconditionally called openRightPanel(...) on mount, so every launch reopened a panel the user had turned off.

The startup mode was a red herring: "Reopen the last project" restores layers and the camera, never these two panels. The other Layout settings (Layers panel, Style panel, toolbar labels, project info) were already persisted and did survive a restart.

Fix

  • DesktopLayoutSettings gains browserPanelVisible and commentsPanelVisible, both defaulting to true and normalized with the same strict-boolean rule as the existing keys, so settings saved before these keys existed keep today's behavior.
  • useRegisterBrowserPanel / useRegisterCommentsPanel seed the panel from that setting on mount instead of always opening it. They read the store rather than subscribing, so closing a panel from its own header is still a session-level action and is not written back as a preference.
  • The Settings toggles write the setting alongside the registry call. They apply live rather than on Save, so they patch the dialog draft too: the draft is snapshotted when the dialog opens and Save writes layout wholesale, which would otherwise revert the toggle the user just made in that same dialog.
  • Layout Reset now moves the two panels as well, since those rows render the live registry state rather than the draft.

Verification

Driven in the real app with Playwright, with us_cities.geojson loaded, in both dark and light themes:

  • Fresh settings: both rails present (defaults unchanged).
  • Toggle Comments off from the Settings → Layout submenu → commentsPanelVisible: false in storage; after reload the Comments rail is gone and Browser is untouched.
  • Uncheck Browser in the Layout dialog and press Save Settings → the setting stays false (the stale-draft path that would have reverted it).
  • Reload with both off: neither rail returns, the Layers panel is unaffected.
  • Re-enable each from Settings → the panel comes back collapsed on its rail and survives the next reload.
  • No new console warnings (no openRightPanel: no right panel registered).

tests/layout-panel-settings.test.ts covers the defaults, the round-trip of a disabled panel, the back-compat fallback for settings saved before the keys existed, and rejection of non-boolean values. Full frontend suite (6040 passing), npm run build, and pre-commit are green.

Summary by CodeRabbit

  • New Features

    • Browser and Comments panel visibility preferences are saved and restored across app restarts.
    • Settings changes are applied when saved and remain synchronized with the active layout.
    • Settings displays the current panel visibility, and resetting the layout restores both panels to visible.
  • Bug Fixes

    • Invalid or legacy visibility settings safely fall back to the default configuration.
    • Panel state is preserved when panels are collapsed, reopened, or removed during cleanup.

The Settings -> Layout toggles for the Browser and Comments right panels
acted only on the live right-panel registry. Nothing was written to
`desktopSettings.layout`, and both registration hooks unconditionally
opened their panel on mount, so every launch reopened a panel the user
had turned off. Startup mode made no difference: reopening the last
project restores layers and the camera, never these panels.

Both panels now have a persisted `layout.browserPanelVisible` /
`layout.commentsPanelVisible` setting (defaulting to on, so settings
saved before the keys existed keep today's behavior), the registration
hooks seed from it, and the Settings toggles write it. The toggles apply
live rather than on Save, so they patch the dialog draft as well; the
draft is snapshotted when the dialog opens and Save writes it wholesale,
which would otherwise revert the toggle the user just made. Reset moves
the panels too, since those two rows render the live registry state.

Fixes #1935
Copilot AI lite review requested due to automatic review settings August 15, 2026 16:16

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 44a6b39f-9edc-4877-8ca6-32582b6a6069

📥 Commits

Reviewing files that changed from the base of the PR and between c0678c2 and 66280e0.

📒 Files selected for processing (6)
  • apps/geolibre-desktop/src/components/layout/SettingsDialog.tsx
  • apps/geolibre-desktop/src/hooks/useRegisterBrowserPanel.ts
  • apps/geolibre-desktop/src/hooks/useRegisterCommentsPanel.ts
  • apps/geolibre-desktop/src/lib/persisted-right-panel.ts
  • packages/plugins/package.json
  • tests/persisted-right-panel.test.ts

📝 Walkthrough

Walkthrough

Changes

Panel visibility persistence

Layer / File(s) Summary
Layout settings contract and normalization
apps/geolibre-desktop/src/hooks/useDesktopSettings.ts, tests/layout-panel-settings.test.ts
Layout settings persist Browser and Comments visibility. Missing or non-boolean values use visible defaults.
Persisted panel registry synchronization
apps/geolibre-desktop/src/lib/persisted-right-panel.ts, packages/plugins/package.json, tests/persisted-right-panel.test.ts
New helpers synchronize persisted visibility with the right-panel registry. Tests cover startup state, user actions, collapse behavior, displacement, and cleanup.
Panel startup behavior
apps/geolibre-desktop/src/hooks/useRegisterBrowserPanel.ts, apps/geolibre-desktop/src/hooks/useRegisterCommentsPanel.ts
Browser and Comments registration use persisted visibility settings across restarts.
Settings dialog synchronization
apps/geolibre-desktop/src/components/layout/SettingsDialog.tsx
Layout checkboxes update draft values. Save applies the draft visibility to live panels.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 66280

This change persists the Browser and Comments panel visibility settings while preserving existing defaults and behavior; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant SettingsDialog
  participant DesktopSettingsStore
  participant PanelRegistration
  participant PanelRegistry
  SettingsDialog->>DesktopSettingsStore: save Browser or Comments visibility
  SettingsDialog->>PanelRegistry: apply live panel visibility
  PanelRegistration->>DesktopSettingsStore: read persisted visibility on startup
  PanelRegistration->>PanelRegistry: register panel with persisted state
  PanelRegistry->>DesktopSettingsStore: persist user close or reopen action
Loading

Possibly related PRs

Poem

A rabbit saves panels bright,
Browser and Comments set right.
Settings guide the startup state,
Closed panels now stay out of sight.
Hop! The registry keeps them straight.

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes persisting the Browser and Comments panel toggles, which is the primary change.
Linked Issues check ✅ Passed The changes implement issue #1935 by persisting Browser and Comments visibility, preserving dialog semantics, and covering defaults, restart behavior, and invalid values.
Out of Scope Changes check ✅ Passed All changes support issue #1935 through persistence helpers, panel integration, settings behavior, package export, and focused tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1935-persist-panel-layout

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


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.

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

🔍 Cloudflare PR preview

Item Value
Site https://7f3aa867.geolibre-preview.pages.dev
Demo app https://7f3aa867.geolibre-preview.pages.dev/demo/
Commit 66280e0

Comment on lines 948 to 955
const resetLayoutSettings = () => {
updateDraftLayoutSettings(DEFAULT_DESKTOP_LAYOUT_SETTINGS);
// The Browser/Comments checkboxes render the live registry state, not the
// draft, so reset has to move the panels themselves or those two rows would
// ignore the button.
toggleBrowserPanel(DEFAULT_DESKTOP_LAYOUT_SETTINGS.browserPanelVisible);
toggleCommentsPanel(DEFAULT_DESKTOP_LAYOUT_SETTINGS.commentsPanelVisible);
};

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.

resetLayoutSettings writes browserPanelVisible/commentsPanelVisible straight to the persisted store via toggleBrowserPanel/toggleCommentsPanel (through applyPanelVisibilityupdateSavedLayoutSettings), while the other four layout fields only land in draftDesktopSettings and are not persisted until saveSettings runs.

That means clicking Reset and then Cancel produces a partial reset: the two panel-visibility settings are permanently changed (survive the dialog close), but layerPanelVisible/stylePanelVisible/toolbarLabels/showProjectInfo silently revert to whatever was last saved, since the draft is discarded and re-seeded from the store the next time the dialog opens (line ~620). A user who resets and then backs out would end up with a half-applied reset with no obvious indication of that split.

Confidence: medium — this follows directly from the code, but it may be an accepted trade-off of the "panels apply live" design described in the PR.

Comment on lines +420 to +438
// Browser and Comments are dockable right panels, so their checkboxes read the
// live registry state (the user can also close them from their own header)
// while the toggle writes the matching persisted layout setting. The setting
// is what their registration hooks seed from on the next launch, so the
// toggle survives a restart (#1935).
const rightPanelState = useRightPanelState();
const browserPanelOpen = rightPanelState.visibleIds.includes(BROWSER_PANEL_ID);
const commentsPanelOpen = rightPanelState.visibleIds.includes(COMMENTS_PANEL_ID);
// These apply live rather than on Save, so the draft is patched alongside the
// saved settings: the draft was snapshotted when the dialog opened, and Save
// writes it wholesale, which would otherwise revert the toggle the user just
// made in this same dialog.
const applyPanelVisibility = (
key: "browserPanelVisible" | "commentsPanelVisible",
visible: boolean,
) => {
updateSavedLayoutSettings({ [key]: visible });
updateDraftLayoutSettings({ [key]: visible });
};

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.

The Browser/Comments checkboxes are bound to browserPanelOpen/commentsPanelOpen, which read the live right-panel registry (rightPanelState.visibleIds), not the draft settings. That registry can also change from outside this dialog — e.g. the user closes the panel from its own header, which is explicitly designed to be session-only and not written back to layout.browserPanelVisible/commentsPanelVisible (per the PR description).

Net effect: if a user closes a panel from its header, then opens Settings → Layout, the checkbox shows unchecked (matching the live state) even though the persisted setting is still true. If they then click Save Settings without touching that checkbox, the save writes the untouched draft value (true), so the panel reopens on the next launch — silently contradicting what the checkbox displayed in the dialog they just saved from.

This looks intentional per the PR's stated design, but it's a real display/persistence divergence worth confirming is the desired UX. Confidence: low-medium.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • resetLayoutSettings persists browserPanelVisible/commentsPanelVisible to the saved store immediately (via toggleBrowserPanel/toggleCommentsPanel), while the other four Layout fields stay in the draft until Save. Clicking Reset then Cancel leaves a partially-applied reset: the two panel flags stick, the rest revert. Confidence: medium. (inline comment posted)
  • The Browser/Comments checkboxes read live registry state, which can diverge from the draft that Save actually persists — e.g. closing a panel from its own header shows the checkbox unchecked in Settings, but Save (without touching that checkbox) still writes the old persisted value, so the panel reopens next launch despite what the dialog displayed. Likely intentional per the PR's stated design, but worth confirming. Confidence: low-medium. (inline comment posted)

Security

  • None found.

Performance

  • None found; the added logic is a handful of boolean reads/writes with no hot-path or loop concerns.

Quality

  • The new test file (tests/layout-panel-settings.test.ts) only covers normalizeDesktopSettings/DEFAULT_DESKTOP_LAYOUT_SETTINGS; the new interactive logic in SettingsDialog.tsx (applyPanelVisibility, resetLayoutSettings) and the mount-time gating in useRegisterBrowserPanel/useRegisterCommentsPanel have no direct test coverage. Confidence: low (existing suite style leans on the normalize layer, so this may be consistent with project convention).
  • New field ordering in DesktopLayoutSettings, DEFAULT_DESKTOP_LAYOUT_SETTINGS, and normalizeDesktopLayoutSettings is alphabetical and consistent across all three, matching the existing style. No issue.

CLAUDE.md

  • No violations found: no touched files fall under the special mirrored-constant/lockfile/menu-catalog rules, and no new user-facing strings were added (the checkboxes reuse existing t() keys).
  • Both hooks correctly avoided subscribing to the settings store for the mount-time seed (using getState() once), consistent with the project's general pattern of reading fresh state to avoid stale-closure bugs seen elsewhere in this file (updateSavedLayoutSettings).

Review follow-up. The Browser/Comments rows in the Layout dialog applied
live while the other four rows waited for Save, which left two seams:
Reset followed by Cancel stuck those two flags while reverting the rest,
and the rows rendered live registry state that Save did not write, so
closing a panel from its own header showed an unchecked box the dialog
then saved back as visible.

Both rows are now draft-backed like their neighbours. The draft seeds
them from the live registry when the dialog opens, so a panel closed
from its header still shows unchecked and Save persists exactly what was
displayed; Reset and Cancel behave as they do for every other row. Save
applies the committed values to the registry, skipping panels already in
the requested state so saving an untouched dialog cannot collapse an
expanded panel. The Settings dropdown keeps applying on the spot and
persisting in one step, since it has no Save to wait for.
@giswqs

giswqs commented Aug 15, 2026

Copy link
Copy Markdown
Member Author

Both review findings were real and are fixed in c0678c2. The underlying cause of each was the same: I had left the Browser/Comments rows in the Layout dialog applying live while the other four rows waited for Save, so they sat outside that dialog's draft/Save/Cancel contract.

Both rows are now draft-backed like their neighbours:

  • Reset then Cancel (finding 1): resetLayoutSettings is back to touching only the draft, so nothing is persisted until Save and Cancel discards the whole reset, panel flags included. Verified: unchecking two rows, pressing Reset, then Cancel leaves stored layout byte-identical and the panels untouched.
  • Checkbox/draft divergence (finding 2): the draft seeds those two keys from isRightPanelVisible(...) when the dialog opens rather than from the stored value. Closing a panel from its own header still shows an unchecked box, and Save now persists that unchecked value instead of writing the stale true back. Verified: close Browser from its header, open Settings (box is unchecked), Save, reload, and it stays closed.

Save applies the committed values to the registry, guarded on isRightPanelVisible(id) === show, so saving a dialog where those rows were untouched cannot collapse a panel the user had expanded. Verified with Comments expanded and an unrelated Layout row saved.

The Settings dropdown toggles are unchanged: they have no Save to wait for, so they move the panel and persist the preference in one step, matching the other live entries in that menu.

On the test-coverage note: agreed that the interactive logic is untested, and it is the existing convention here that these dialogs are covered through the normalize layer. The behaviors above were verified by driving the built app instead, in both dark and light themes. Full frontend suite (6040 passing), build, and pre-commit are green.

Comment on lines +624 to +634
// Browser and Comments are dockable panels the user can also close from the
// panel's own header, which is a session action that writes no setting. Seed
// those two rows from the live registry rather than the stored value so the
// dialog opens showing what is actually on screen; Save then persists
// exactly what the checkboxes showed.
seededSettings.layout = {
...seededSettings.layout,
browserPanelVisible: isRightPanelVisible(BROWSER_PANEL_ID),
commentsPanelVisible: isRightPanelVisible(COMMENTS_PANEL_ID),
};
setDraftDesktopSettings(seededSettings);

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.

This seeds the draft's browserPanelVisible/commentsPanelVisible from the live registry (isRightPanelVisible) rather than from the persisted desktopSettings.layout. That's intentional for showing "what's on screen" when the Layout tab is visited, but it has a side effect: saveSettings() (line ~1226) writes layout: draftDesktopSettings.layout wholesale on every Save, for any section of the dialog.

So the flow "user closes Browser/Comments from the panel's own header (a session-only action per the docstrings in useRegisterBrowserPanel.ts/useRegisterCommentsPanel.ts, which explicitly say this 'is not written back as a preference') → later opens Settings for something unrelated (e.g. map preferences, language) → clicks Save" will silently persist the panel as permanently hidden, even though the user never touched the Layout tab or these checkboxes. That contradicts the stated design intent and could surprise a user who intended the closure to be session-only.

Confidence: medium — this is a real, reachable path (PluginRightPanel.tsx's header close button calls closeRightPanel), but whether it's considered a bug vs. accepted "what's on screen becomes what's saved" behavior is a product-intent question worth confirming.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • Draft is seeded from the live registry state for browserPanelVisible/commentsPanelVisible, but saveSettings() writes draftDesktopSettings.layout wholesale on any Save. If a user closes the Browser/Comments panel from its own header (documented as a "session-only" action that isn't supposed to persist) and later opens Settings for an unrelated reason and hits Save, that session-only closure gets silently persisted as a permanent preference — contradicting the intent stated in useRegisterBrowserPanel.ts/useRegisterCommentsPanel.ts. Confidence: medium (apps/geolibre-desktop/src/components/layout/SettingsDialog.tsx:624-634, posted inline).

Security

  • None found.

Performance

  • None found; the added logic is O(1) boolean checks and registry lookups, no concerns.

Quality

  • The new draft-seeding / bail-out / registry-reapplication logic lives entirely in SettingsDialog.tsx and has no direct test coverage — only normalizeDesktopSettings is tested (tests/layout-panel-settings.test.ts). A test simulating "close panel via header → open Settings → Save without touching Layout" would have caught the issue above. Confidence: medium.
  • Otherwise the refactor (unifying Browser/Comments toggle logic into applyPanelVisibility/applyBrowserPanelVisibility/applyCommentsPanelVisibility) is clean and the bail-out guard (isRightPanelVisible(panelId) === show) correctly avoids re-collapsing a panel the user manually expanded. Confidence: high.

CLAUDE.md

  • No violations: reuses existing i18n keys (no new UI strings), uses existing Tailwind classes consistent with the rest of the file, and doesn't touch any of the mirrored-constant or generated-catalog areas called out in CLAUDE.md.

@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

🔍 GitHub Pages PR preview

Item Value
Site https://opengeos.org/pages-preview/GeoLibre/pr-1940/
Demo app https://opengeos.org/pages-preview/GeoLibre/pr-1940/demo/
Commit 66280e0

Note

GitHub Pages built this preview successfully, but its serving edge returned HTTP 403 when checked. The links may still be propagating.

Review follow-up. Seeding the dialog draft from the live registry fixed
the checkbox lying about what Save would write, but it moved the seam
rather than closing it: a panel closed from its own header stayed a
session-only state that an unrelated Save then converted into a
permanent preference.

Both directions now go through one place. `registerPersistedRightPanel`
seeds the panel from its setting at registration and subscribes to the
registry afterwards, writing every later visibility change back. For
these panels closing is not a transient collapse, it removes the rail
entry entirely and only Settings can restore it, so it is a preference
however it was reached. With the setting and the registry always in
step, the checkbox cannot disagree with either, and there is no
session-only state left for a Save to silently promote.

Two registry events are deliberately not mirrored: the emit from
registration (the panel is legitimately not visible yet, so the
subscription is attached after the seed) and the emit from unregistering
on unmount, which would otherwise persist false on every teardown. Being
displaced by another panel is not a close, so it writes nothing.

The dialog rows keep the draft semantics of the previous commit, and
Save re-applies them to the registry, skipping a panel already in the
requested state. `tests/persisted-right-panel.test.ts` covers the seed,
the header close, the collapse and displacement non-events, and the
teardown guard; the module imports the registry subpath so it stays a
leaf the suite can load without the plugin barrel.
@giswqs

giswqs commented Aug 15, 2026

Copy link
Copy Markdown
Member Author

Good catch, and it is the right read: seeding the draft from the live registry closed the previous round's finding but only moved the seam. A panel closed from its own header was still session-only state that an unrelated Save then promoted to a permanent preference.

Fixed in 66280e0 by removing the session-only state rather than trying to guard the Save path. The new apps/geolibre-desktop/src/lib/persisted-right-panel.ts seeds the panel from its setting at registration and then subscribes to the registry, writing every later visibility change back. For these two panels closing is not a transient collapse: it removes the rail entry entirely and only Settings can restore it, so it is a preference however it was reached. With the setting and the registry always in step there is nothing left for a Save to silently promote, and the checkbox cannot disagree with either.

Two registry events are deliberately not mirrored, both called out in the module doc:

  • the emit from registration itself, since the panel is legitimately not visible yet, so the subscription is attached after the seed;
  • the emit from unregistering on unmount, which would otherwise persist false on every teardown. unregisterRightPanel removes the panel before it emits, so the getRightPanel guard catches it, and the disposer unsubscribes before disposing.

Being displaced by another panel is not a close (the registry keeps a displaced panel in visibleIds), so it correctly writes nothing.

On the coverage point, which was fair on both rounds: tests/persisted-right-panel.test.ts now covers the seed in both directions, the header close, the collapse and displacement non-events, the teardown guard, and the bail-out that keeps an untouched Save from collapsing an expanded panel. The module imports @geolibre/plugins/right-panel-registry rather than the package barrel so it stays a leaf the suite can load, per the CLAUDE.md guidance about not dragging the whole plugin registry into the coverage denominator.

Verified in the built app in both themes: header close persists and survives reload; the dropdown and dialog checkboxes agree; Reset then Cancel persists nothing; Reset then Save reopens and persists; expanding does not flip the setting and an unrelated Save leaves an expanded panel expanded. 6050 frontend tests passing, build and pre-commit green.

Comment on lines +90 to +93
const unsubscribe = subscribeRightPanels(() => {
if (!getRightPanel(registration.id)) return;
setPanelVisibleInSettings(key, isRightPanelVisible(registration.id));
});

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.

Minor/low confidence: subscribeRightPanels fires on every registry mutation (any panel opening/closing/moving dock, not just this one), so this callback re-reads and attempts to persist this panel's own visibility on each such event. It's harmless today because setPanelVisibleInSettings bails when the value already matches, but it does mean unrelated churn in the panel registry (e.g. a plugin panel changing dock) triggers redundant getRightPanel/isRightPanelVisible lookups and a store-equality check for both Browser and Comments each time. Not worth blocking on, just flagging as a spot to scope the subscription if this ever becomes hot (e.g. frequent dock dragging).

@github-actions

Copy link
Copy Markdown
Contributor

Code review

I traced the full data flow: the new browserPanelVisible/commentsPanelVisible layout settings, their strict-boolean normalization and back-compat defaulting, the new persisted-right-panel.ts seed/mirror helper, both registration hooks, the Settings dialog's live (toolbar quick-menu) vs. draft (dialog) checkbox paths, the Save-time reconciliation (setDesktopSettings + applyRightPanelVisibility), and the two new test files against the actual right-panel-registry.ts semantics (single global activeId, displacement vs. close, collapse vs. hide). The mirroring logic (seed before subscribe, unsubscribe before unregister, equality guards to avoid redundant writes/collapsing an expanded panel) holds up correctly across the scenarios I traced, including displacement-by-another-panel and Strict Mode double-invoke.

Bugs: None found with reasonable confidence.

Security: No issues — this is local UI/layout preference state with no external input.

Performance:

  • Low confidence: registerPersistedRightPanel's subscribeRightPanels callback re-evaluates on every right-panel registry mutation, not just changes to its own panel id — harmless (guarded by an equality check before any store write) but a minor inefficiency worth a look if panel-registry churn ever becomes frequent (see inline comment).

Quality:

  • Note (not a defect): inside the Settings dialog's Layout section, the Browser/Comments checkboxes now write to the draft and only take effect on "Save Settings," whereas previously they applied live even inside the dialog. This is called out and intentionally tested in the PR description/tests (to fix the stale-draft-reverts-toggle bug), and it now matches how the other Layout rows already behave, so it reads as an intentional consistency fix rather than a regression.
  • The new leaf export (@geolibre/plugins/right-panel-registry) and the decision to test against it directly (avoiding the barrel's CSS/plugin-registry imports) correctly follows the project's documented coverage-floor guidance.

CLAUDE.md: No violations found — the leaf-module export pattern, strict-boolean settings normalization, and test placement all match existing conventions in this repo.

Overall this is a well-scoped, carefully reasoned fix with solid test coverage for the seed/mirror/round-trip/back-compat behaviors; I only had one very minor, low-confidence efficiency note to flag inline.

@giswqs
giswqs merged commit 6311ce4 into main Aug 15, 2026
17 checks passed
@giswqs
giswqs deleted the fix/issue-1935-persist-panel-layout branch August 15, 2026 17:01
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.

Desktop: Layout settings do not persist after restart when reopening last project

2 participants