Skip to content

editor: the panel on screen is the store's, and a collapsed sidebar opens onto the one picked - #1046

Closed
Aymericr wants to merge 3 commits into
mainfrom
feedback/sidebar-review
Closed

Aymericr wants to merge 3 commits into
mainfrom
feedback/sidebar-review

Conversation

@Aymericr

@Aymericr Aymericr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

Follow-up to #1036 (the rail panel and preferences survive a reload). It fixes what the review of that work found.

  • activeSidebarPanel is the panel on screen. When the rail lacks the panel picked in a workspace, the layout shows its first panel. Two cases: a plugin panel joins only once the scene's plugins load, or the plugin is gone. Until now the store kept the missing id, so host code that reads it disagreed with the screen. Examples in Community: the getting-started guide could keep asking for the Build panel while Build was open, and on mobile the navbar hid Share for an Item builder the screen no longer showed. useShownSidebarPanel (lib/sidebar-panel.ts) now writes the panel it shows into activeSidebarPanel and leaves the pick in sidebarPanels, so the pick still comes back once the rail has it. This removes the old "adopt the shown tab" effect, and with it the stale-closure path from the Bugbot thread on editor: the rail panel and preferences survive a reload #1036.
  • A collapsed sidebar opens onto the panel picked. Both the rail click and openSidebarPanel expanded first and switched panels second. For a moment, the panel left open was the open one. Community arms the paint brush when Paint opens, so reopening a sidebar collapsed on Paint onto Chat armed Paint on the way: that cleared the selection, replaced the paint material and left the Site phase. The new showSidebarPanel(id) picks first, then expands at a usable width. The rail click and openSidebarPanel use it, and it is exported for hosts' own entry points.
  • B and F open their panel before they arm their tool (hooks/use-keyboard.ts). A host may drop the armed tool when the panel changes (Community does), so B or F pressed from any other panel opened Build or Items in select mode. Since the rail now reopens on the panel left open, that is the first thing a reload offers.
  • Preferences storage (lib/hydration-gated-storage.ts):
    • A blob that fails to parse now counts as none. Before, hydration failed and hasHydrated() stayed false for the session, so nothing was saved again and the bad blob was never replaced.
    • A save of the blob the tab already holds is skipped. persist writes the whole state on every set, so a second tab's next update, even one that changed no preference, replaced the panel picked in the first.
  • wiki/architecture/plugin-authoring.md: activeSidebarPanel is the panel on screen, showSidebarPanel, and a tab saves its preferences only when they change.

Why

From the owner's feedback (plan row D1, verbatim):

D1: "Editor active sidebar menu or open menu should remain in state and survive page reload (like if user is on chat it should remember where when reload or navigated to another part of the web app)"

#1036 made the panel survive a reload. The review of it (Claude and GPT-6 Astra) found these defects, each of them reachable after a reload.

How to test

In the Community app (hosted PR pascalorg/private-editor#896):

  1. Open Paint, click Paint again to collapse it, press Esc, then 1 (Site). Click Chat. Chat opens, select mode stays on and the phase stays Site.
  2. Open Chat and reload. Press B: Build opens with Rectangle armed. Click Paint, press F: Items opens with the item tool armed.
  3. Store a panel the rail lacks (for example a plugin panel, then open a project without that plugin). Build shows, and the guide and navbar read Build. Open a project with the plugin: its panel comes back.
  4. Put invalid JSON in localStorage['pascal-editor-ui-preferences'] and reload. The editor opens on its defaults, and the next change saves a valid blob.

Automated, run from the private-editor worktree root against this branch:

  • bun test editor/packages/editor/src/store/opens-in-select-mode.test.ts editor/packages/editor/src/hooks/use-keyboard.test.ts: 35 pass. Each of the four new tests fails without its fix:
    • the rail stand-in: Expected "build", Received "nature"
    • the unreadable blob: hydration never finishes
    • the second tab: its panel is overwritten
    • B and F: they land in select mode
  • bun test editor/packages/editor/src: 2061 pass, 19 fail. All 19 failures are in lib/history.test.ts and fail the same way on main when run from the private-editor root: its child process prints a THREE_CJS_DEPRECATED warning to stderr, which the test reads as a failure.
  • bunx turbo run check-types --filter='./editor/packages/*': 9/9.
  • biome check on the changed files with this repo's config: clean.

Screenshots / screen recording

Not added here. The behaviour was checked in the running Community app; the hosted PR lists what was checked.

Deployment notes

Supersedes

Nothing. It follows #1036.

Review

Fixed:

  • Reopening a collapsed sidebar armed Paint on the way to another panel.
  • B and F lost their tool when pressed from another panel.
  • The stored panel and the screen disagreed while the rail lacked the pick.
  • A failed hydration stopped every later save.
  • A stale tab overwrote another tab's panel.
  • Comments now state the rule in the present tense, not past behaviour.

Rejected:

  • "Picking a swatch on a restored Paint does not arm the brush": false. selectMaterial calls the model's setActivePaintMaterial, which is bound to armMaterialPaint (lib/material-paint-panel-model.ts). Checked in the app, and pinned by the hosted e2e.
  • "Fall back to in-memory storage when localStorage is unavailable, and catch write failures": unchanged from before editor: the rail panel and preferences survive a reload #1036, which used the same createJSONStorage.
  • "Reconcile preferences across tabs through storage events": out of D1's scope. The last real preference change wins, and an update that changes no preference no longer writes.
  • "Re-expanding the same mobile sheet should arm Items or Paint": this matches main before editor: the rail panel and preferences survive a reload #1036 (the tab does not change and the panel stays mounted), and D1 does not ask for it.
  • "Persist the mobile sheet's closed state": that would be a new preference. The mobile sheet height has never been stored.

Checklist

  • I've tested this locally with bun dev (tested through the Community dev server, not this repo's bun dev)
  • My code follows the existing code style (run bun check to verify) (biome check on the changed files)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches editor UI state, keyboard shortcuts, and localStorage persistence across tabs; behavior changes are user-visible but covered by new tests and follow-on to sidebar persistence work.

Overview
Aligns sidebar state with what users actually see and fixes ordering bugs when reopening panels or using keyboard shortcuts.

activeSidebarPanel now matches the panel on screen. useShownSidebarPanel writes the displayed tab into activeSidebarPanel when the rail is missing a remembered pick (e.g. plugin tabs not loaded yet), while sidebarPanels keeps the preference so it restores once the tab appears.

showSidebarPanel(id) (exported) sets the panel before expanding a collapsed sidebar; rail clicks and openSidebarPanel use it so hosts do not briefly mount the wrong panel (e.g. Paint arming on the way to Chat).

B and F open Build/Items before arming wall/item tools so hosts that reset tools on panel change still end in the right mode.

Persisted UI storage (hydration-gated-storage): corrupt localStorage no longer blocks hydration forever; writes are skipped when the serialized blob is unchanged so another tab’s stale in-memory state does not overwrite a newer saved panel.

Reviewed by Cursor Bugbot for commit 3450f45. Bugbot is set up for automated code reviews on this repo. Configure here.

Aymericr and others added 3 commits October 12, 2026 22:51
A host may drop the armed tool when the rail panel changes (the community
does, with any placement in flight). B and F armed first, so pressed from
any other panel they opened Build or Items in select mode. Now that the rail
reopens on the panel left open, that is the first thing a reload offers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, and a tab saves only what changed

A blob that failed to parse left `persist.hasHydrated()` false for the
session, so the gate never opened: nothing was saved again, and the bad blob
was never replaced. It now counts as none, hydration finishes on the
defaults, and the next save replaces it.

`persist` saves the whole state on every set, so a second tab replaced the
panel picked in the first on its next update, even one that changed no
preference. A save of the blob the tab already holds is now skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed sidebar opens onto the one picked

While the rail lacks the panel picked in a workspace (a plugin panel joins
once the scene's plugins load, or the plugin is gone), the layout showed its
first panel but the store kept the missing id, so host readers (the getting
started guide, the navbar, the agent's view of the editor) disagreed with the
screen. The store now follows what shows; the pick stays in `sidebarPanels`
and comes back once the rail has it.

Reopening a collapsed sidebar expanded it before switching panels, so the
panel left open opened for a moment, and a host that arms a panel's tool as
it opens (Paint) armed it on the way to another one. `showSidebarPanel(id)`
picks first, then expands; the rail click and `openSidebarPanel` use it, and
hosts get it for their own entry points.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Aymericr

Copy link
Copy Markdown
Contributor Author

Superseded by #1048, which landed the same three sidebar review commits (1e1a8a9, f140579, f88b020) on editor main.

@Aymericr Aymericr closed this Oct 13, 2026
@Aymericr
Aymericr deleted the feedback/sidebar-review branch October 13, 2026 14:44
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