Outcome
A reader who collapses sidebar sections sees them stay collapsed on first paint everywhere the collapsible sidebar exists — homepage, /docs, and /components — instead of watching sections paint open and snap shut.
Evidence
Collapsed sections persist correctly across reloads on the homepage (app shell) but not on /docs; /components shares the docs layout machinery and is presumed affected the same way.
What the investigation established
The transport is shared end-to-end and is not the suspect:
.SidebarSection hook writes one cookie (sidebar_sections, path=/) on every toggle.
OpenAgentsWeb.Plugs.SidebarSections (in :browser, so it serves /docs and /components too) reads that cookie into the session.
UserAuth.on_mount(:mount_current_user) assigns :sidebar_sections in every live session, including :docs and :components.
- All three layouts feed the same
Layouts.sidebar_section/1, whose seed is Map.get(state, id, default_open) — reader choice beats default.
Two real asymmetries remain as prime suspects:
- Section ids are namespaced per surface by title slug (
sidebar-section-<title-slug>), so a choice made on one surface never applies to another. The homepage shell and the docs nav share zero ids; each surface must be collapsed independently, and any mismatch between what the reader collapsed and what each layout renders reads as "forgot".
- The docs layout force-opens the active section:
open={Enum.any?(section.items, &(&1.slug == @active_page))} is passed as the default, and while reader choice overrides it via state, any gap in id or state delivery silently falls back to open-on-active — the opposite of collapsed-first.
Work
- Reproduce in a browser with a clean profile: collapse every section on
/docs, reload, record which reopen; do the same on /components and on the homepage for comparison.
- Trace whether
:sidebar_sections reaches the docs layout on a cold GET versus a LiveView navigation, and fix whichever hop loses it.
- Decide deliberately whether collapse state should be global (one namespace of ids across surfaces) or per surface; either answer is fine, but the current implicit per-surface behavior must become explicit and documented in
Plugs.SidebarSections.
- Keep the active-page override only where it is documented intent, and make sure it cannot mask a lost preference.
Acceptance criteria
- Collapsing sections on
/docs survives reload and navigation, exactly as the homepage behaves.
- Same for
/components.
- Whatever scoping rule is chosen (global or per surface), a test pins it.
Verification
LiveView tests seeding the session value and asserting first-paint open attributes for all three layouts; a manual pixel pass with a clean profile; mix precommit.
Outcome
A reader who collapses sidebar sections sees them stay collapsed on first paint everywhere the collapsible sidebar exists — homepage,
/docs, and/components— instead of watching sections paint open and snap shut.Evidence
Collapsed sections persist correctly across reloads on the homepage (app shell) but not on
/docs;/componentsshares the docs layout machinery and is presumed affected the same way.What the investigation established
The transport is shared end-to-end and is not the suspect:
.SidebarSectionhook writes one cookie (sidebar_sections,path=/) on every toggle.OpenAgentsWeb.Plugs.SidebarSections(in:browser, so it serves/docsand/componentstoo) reads that cookie into the session.UserAuth.on_mount(:mount_current_user)assigns:sidebar_sectionsin every live session, including:docsand:components.Layouts.sidebar_section/1, whose seed isMap.get(state, id, default_open)— reader choice beats default.Two real asymmetries remain as prime suspects:
sidebar-section-<title-slug>), so a choice made on one surface never applies to another. The homepage shell and the docs nav share zero ids; each surface must be collapsed independently, and any mismatch between what the reader collapsed and what each layout renders reads as "forgot".open={Enum.any?(section.items, &(&1.slug == @active_page))}is passed as the default, and while reader choice overrides it viastate, any gap in id or state delivery silently falls back to open-on-active — the opposite of collapsed-first.Work
/docs, reload, record which reopen; do the same on/componentsand on the homepage for comparison.:sidebar_sectionsreaches the docs layout on a cold GET versus a LiveView navigation, and fix whichever hop loses it.Plugs.SidebarSections.Acceptance criteria
/docssurvives reload and navigation, exactly as the homepage behaves./components.Verification
LiveView tests seeding the session value and asserting first-paint
openattributes for all three layouts; a manual pixel pass with a clean profile;mix precommit.