Skip to content

fix(agent-sidebar): restore width+open on reload, add deleteConversation#305

Merged
I-am-nothing merged 2 commits into
mainfrom
fix/agent-sidebar-state
Jun 14, 2026
Merged

fix(agent-sidebar): restore width+open on reload, add deleteConversation#305
I-am-nothing merged 2 commits into
mainfrom
fix/agent-sidebar-state

Conversation

@I-am-nothing

Copy link
Copy Markdown
Contributor

What

Fixes three independent cross-host agent-sidebar defects (all against v0.5.6), plus the review follow-ups.

Defect 1 — width not restored on reload

SidebarProvider's mount fetch only set lastOpenWidth, never the rendered sidebarWidth, so a persisted width was stranded until the next open gesture (close-then-reopen restored it; a reload did not). Now the fetched width is reconciled into rendered state via a guarded functional updater (setSidebarWidth((cur) => (cur <= 0 ? stored : cur))) so a user gesture that lands before the async fetch is never clobbered — first writer of a real width wins.

Defect 2 — open flag dropped on reload

AppShell derived visibility purely from width, with no controlled-open prop, so the shell always painted closed regardless of the persisted open flag both host session hooks already expose. Added open? / onOpenChange? to AppShellProps, derive isOpen = open ?? (sidebarWidth > 0), seed/collapse width via an effect when controlled open flips, and fire onOpenChange from the FAB (open) and close handlers. Uncontrolled (open === undefined) behavior is unchanged.

Defect 3 — remove conversation had no transport

The trash-icon delete button already shipped but nothing backed it. Added deleteConversation to the AgentChatClient contract and useAgentChat (optimistic history removal + open-thread clear, rollback on failure), re-exported through useAgentSession.

Root cause

Width and open were two separate pieces of state, but only width was being rehydrated and only width drove rendering. The persisted open flag had no path into the rendered shell, and the persisted width landed in reopen-memory (lastOpenWidth) rather than the rendered sidebarWidth. Reload reads both from the server but the shell discarded both signals. The fix routes the persisted width into rendered state and adds a controlled-open contract so the host's persisted open flag actually paints.

Review follow-ups (second commit)

  • BLOCKING fix: the legacy persistKey localStorage unit test (SidebarProvider.test.tsx > "does not read localStorage at module load") still asserted width === 0 after mount — the pre-fix behavior. Defect 1's reconcile now restores the persisted 500 into the rendered width over a defaultWidth of 0, so the assertion + comment were updated to expect 500. The equivalent server-persistence test was already updated; this legacy path was missed and was landing main red (1/768 failing). Suite is now green (768/768).
  • Consistency: deleteConversation rollback switched from a full-list overwrite to a MERGE updater (mirrors renameConversation), re-inserting the removed row at its original index — preserves any conversation created between the optimistic drop and a failed network call. Behavior-identical in the common case.
  • Clarity: two AppShell.tsx comments tightened (controlled-open effect dep rationale; the load-bearing sidebarWidth > 0 panel guard). No behavior change.

Verification

  • pnpm test — 768/768 passing (was 767/768 with 1 red)
  • pnpm typecheck — clean
  • pnpm build — clean

Versioning

No package.json version bump and no CHANGELOG touch. These kit changes ride a later 0.5.7 rollup release, not this PR.

DRAFT — cross-repo merge order

This sidebar fix is part of a coordinated cross-repo change. Land in this order:

  1. api-platform
  2. api-client-shared
  3. web-ui-kit ← this PR
  4. web-applications
  5. web-account

Why draft: web-ui-kit depends on the upstream repos' unpublished releases landing first (api-platform, then api-client-shared). Keep this as a draft until those two are merged and the dependent release/contract is available. Un-draft once api-platform and api-client-shared have landed and this can ride the 0.5.7 rollup release into web-applications / web-account.

🤖 Generated with Claude Code

I-am-nothing and others added 2 commits June 14, 2026 13:38
Three independent cross-host agent sidebar defects, all against v0.5.6:

1. Width not restored on reload. SidebarProvider's mount fetch only set
   lastOpenWidth, never the rendered sidebarWidth, so a persisted width was
   stranded until the next open gesture (close-then-reopen restored it; reload
   did not). Reconcile the fetched width into rendered state via a guarded
   functional updater (cur <= 0) so a user gesture landing before the async
   fetch is never clobbered.

2. Open flag dropped on reload. AppShell derived visibility purely from width,
   with no controlled-open prop, so the shell always painted closed regardless
   of the persisted open flag both host session hooks already expose. Add
   open?/onOpenChange? to AppShellProps, derive isOpen = open ?? (width > 0),
   seed/collapse width via an effect when controlled open flips, and fire
   onOpenChange from the open (FAB) and close handlers. Uncontrolled
   (open undefined) behavior is unchanged.

3. Remove conversation had no transport. The trash-icon delete button already
   shipped but nothing backed it. Add deleteConversation to the AgentChatClient
   contract and useAgentChat (optimistic history removal + thread clear when the
   open conversation is deleted, rollback on failure), re-exported through
   useAgentSession.

No version bump — rides a later rollup release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-ups on the agent-sidebar reload/delete fix:

- SidebarProvider.test.tsx: the legacy localStorage `persistKey` unit test
  ("does not read localStorage at module load") still asserted width=0 after
  mount, the pre-fix behavior. Defect #1's reconcile (setSidebarWidth((cur) =>
  cur <= 0 ? stored : cur)) now restores a persisted 500 into the rendered
  width over a defaultWidth of 0, so the assertion + comment are updated to
  expect 500. The equivalent server-persistence test was already updated; this
  legacy path was missed and was landing main red.

- useAgentChat.deleteConversation: switch the rollback from a full-list
  overwrite to a MERGE updater (mirrors renameConversation), re-inserting the
  removed row at its original index — preserves any conversation created
  between the optimistic drop and the failed network call instead of
  clobbering it. Behavior-identical in the common case.

- AppShell.tsx: clarify two comments (controlled-open effect dep rationale;
  the load-bearing `sidebarWidth > 0` panel guard) — no behavior change.

No version bump — rides a later rollup release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Storybook Preview: https://mirrorstack-ai.github.io/web-ui-kit/pr/305/

github-actions Bot added a commit that referenced this pull request Jun 14, 2026
@I-am-nothing I-am-nothing marked this pull request as ready for review June 14, 2026 06:19
@I-am-nothing I-am-nothing merged commit a4b6919 into main Jun 14, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in MirrorStack Platform Jun 14, 2026
github-actions Bot added a commit that referenced this pull request Jun 14, 2026
I-am-nothing added a commit that referenced this pull request Jun 14, 2026
Rollup of #304 (empty-state icon bg) + #305 (sidebar width/open restore + deleteConversation). Bump version + CHANGELOG; the release label publishes 0.5.7 to GitHub Packages on merge.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant