Skip to content

refactor(desktop): move Workbar ownership below AppShell - #4692

Merged
Astro-Han merged 1 commit into
apache:mainfrom
theSatvik:refactor/workbar-controller-scope
Sep 27, 2026
Merged

Astro-Han merged 1 commit into
apache:mainfrom
theSatvik:refactor/workbar-controller-scope

Conversation

@theSatvik

@theSatvik theSatvik commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Move production useWorkbarController ownership from AppShellContent into a feature-owned WorkbarProvider, registered in controllerOwners as its only caller.
  • Add WorkbarShellRoot beside TaskEntryRoot / OverlaysRoot / SessionCollaborationDialogRoot. It owns a per-shell bridge and hands AppShellContent a projection, so the shell body calls no Workbar hook (AppShell gate: 34 → 33 hooks, 56 → 55 call sites).
  • The projection is stable command delegates plus the three values the shell still renders from: hidden companion Session ids (rail and palette), rightCollapsed (WorkHub dock) and ready (WorkHub navigation). The bridge replaces that state only when one of those values changes. Tab switches, panel topology and resize drags re-render the provider and the host model's readers, not the shell.
  • WorkbarHost and WorkbarTitlebarActions read the host model from the provider's context. The Workbar width reaches the frame and titlebar through an inherited --maka-session-workbar-width, so appShellFrameStyle no longer carries it. Storybook renders the environment-free WorkbarHostView / WorkbarTitlebarActionsView.
  • Workbar shortcuts, persistence, WorkHub, Work Board start claims and Terminal/Side Chat resource lifecycles are unchanged. The controller's input is the same object AppShellContent built before; it is now passed to the provider.

Refs #4582

Verification

Rebuilt on main (re-rebased onto c171eacd0 on 2026-09-27; the original branch was 231 commits behind; the controller had moved to toastApi, WorkHub and Work Board inputs in the meantime).

  • Node 24: npm run build:test, Desktop typecheck (including stories), build:renderer
  • Desktop test:dist: 2933/2933
  • Focused Workbar suites (workbar-provider-scope, workbar-boundary, workbar-controller, app-shell-frame-style): 45/45. The new provider-scope test drives the real WorkbarShellRoot → WorkbarProvider composition and asserts that a tool switch re-renders the host but not the shell, while hidden-fork and collapse changes re-render it exactly once.
  • check:renderer-architecture -- --base origin/main --strict-base passes; renderer architecture fixtures 112/112
  • AppShell hook gate, Astryx inventory, Windows test inventory, npm run lint, npm run format:check, Desktop/UI Knip, ASF headers, git diff --check
  • No screenshot: behavior-preserving ownership/render-scope refactor with no visual change.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code rebuilt this branch on current main (implementation, tests, documentation and local validation). The first revision was assisted by OpenAI Codex. The commit carries the Generated-by: Claude Code trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

🤖 Generated with Claude Code

@github-actions github-actions Bot added the effort/L Under 1000 readable lines label Sep 3, 2026

@hqhq1025 hqhq1025 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.

Reviewed exact head 14f0a65516ea0ad5e32a03fd1c62f24adf148d93 against base/current main 74a20f60c9a7bb6947e2c428f07ca2e83fad92a1.

I found no actionable P0-P3 issues in the reviewed scope.

The change moves useWorkbarController into the feature-owned provider (apps/desktop/src/renderer/features/workbar/ui/workbar-provider.tsx:70), exposes stable imperative shell commands plus the hidden-session external-store projection (apps/desktop/src/renderer/features/workbar/controller/workbar-shell-bridge.ts:51), and keeps the Session rail subscribed through useSyncExternalStore (apps/desktop/src/renderer/features/session-navigation/controller/use-session-navigation-reads.ts:73). I traced the active-session transition, Terminal cleanup, Side Chat cleanup, bridge publish/disconnect, titlebar/host context reads, AppShell command call sites, and E2E fixture path.

Local validation passed: focused Workbar/Session Navigation tests (25/25), Desktop full test suite under Node 24.18.1 (2062/2062), Desktop typecheck, production Desktop build, renderer architecture checks (71/71), AppShell hook ratchet, lint, format check, ASF headers, and git diff --check. The merge tree is clean and current main equals the PR base.

Hosted CI has not executed: the CI run is terminal action_required with no jobs, and only the label check is green. I also did not run an interactive Electron/macOS smoke test. This is a refactor, so the final design and merge decision remains with a human reviewer.

Automated review notice: This review was produced with AI assistance and does not replace independent human review.

@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks—moving ownership out of AppShell while keeping a single Workbar controller is a useful direction.

#4789 has now merged, so please rebase onto current main and adapt the provider/bridge to the new TabList and open/close menu. Keep the retired Tasks face, drag-reordering, and preview paths removed. Then validate the focused Workbar behavior and refresh CI; the earlier checks don't establish compatibility with the new shell.

AI-assisted review with Codex.

@theSatvik

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (2310035a) and adapted the provider/bridge to the post-#4789 Workbar surface. The current TabList/open-close menu stays intact; retired Tasks/preview paths were not restored. The bridge now carries the newer form-response and live-context-probe seams without moving controller ownership back into AppShell.

Validation on signed, GitHub-verified head d2e59fc2d:

  • full workspace + production renderer build passed
  • Desktop typecheck passed
  • 2,336/2,336 Desktop tests passed under Node 24.20.0
  • 28 focused Workbar/Session Navigation tests passed
  • renderer architecture gate passed against current origin/main (101 checks)
  • AppShell hook ratchet passed (39 hooks / 72 call sites)
  • Biome lint + format, Astryx inventory, and ASF headers passed

The branch is mergeable again; hosted CI is awaiting repository-side execution.

@hqhq1025 hqhq1025 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.

Reviewed exact head d2e59fc2dddedf32c8403d6ae6c486bdc05f7751 against base 2310035a38c1db9f210252a03ce2c741e86ff878 and current main 42fa4d070504e672173c46dd75f7fcabb88789f5.

The change moves useWorkbarController into a feature-owned provider, publishes stable shell commands plus an equality-selected hidden-session store through a per-shell bridge, and moves the host/titlebar reads to narrow contexts. I checked controller/resource lifecycles, bridge publication/disconnect behavior, Session rail updates, shell call sites, E2E fixture behavior, Storybook seams, and the architecture boundary.

No P0-P2 issue was found. One P3 architecture-guard gap is noted inline. Local verification passed: build:test; 28 focused Workbar/Session Navigation tests; Desktop 2336/2336; Desktop typecheck; renderer production build; renderer architecture 101/101; AppShell hook ratchet; Biome lint/format; ASF headers; and git diff --check. A StrictMode production-component probe also confirmed bridge command publication, hidden-session propagation to an ancestor external-store reader, and reset on unmount.

This head is not merge-ready: GitHub reports CONFLICTING / DIRTY against current main, with conflicts in app-shell.tsx, renderer-architecture.json, and the Astryx inventory. The hosted test check is green for this head, but the conflict resolution will require a new exact-head review. Interactive Electron/macOS behavior was not independently exercised.

Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.

Comment thread apps/desktop/src/renderer/features/workbar/ui/workbar-provider.tsx Outdated
@theSatvik
theSatvik force-pushed the refactor/workbar-controller-scope branch 3 times, most recently from 819c8b4 to 663abf3 Compare September 14, 2026 00:51
@theSatvik

Copy link
Copy Markdown
Contributor Author

Rebased onto current main — the branch had fallen 80 commits behind. Conflicts resolved and the tree is green again.

Three files conflicted:

  • app-shell.tsx — one hunk, where main still constructs useWorkbarController in the shell. Resolved in favour of this branch (the block moves into WorkbarProvider), keeping every other upstream change to the file. I verified the provider render site still passes all nine inputs main was passing to the controller — including shellObscured and modelChoices — so nothing regresses.
  • renderer-architecture.json and docs/astryx-surface-file-inventory.md — both generated, so rather than hand-picking either side I regenerated them from the post-rebase tree (check-renderer-architecture.mjs --write and generate-astryx-surface-inventory.mjs).

Verification, in the order the workspace requires (build:test first, since it builds the @maka/* packages the typecheck resolves against):

  • npm run build:test — pass
  • npm run typecheck — pass, 0 errors
  • npm --workspace @maka/desktop run build:renderer — pass, renderer entry output check passed
  • renderer architecture check — passed after regeneration
  • surface inventory — blocker=0 reimplementation=0 polish=2 aligned=276

@Astro-Han this is up to date with the post-#4789 surface again and ready for another look whenever you have time.

@Astro-Han Astro-Han 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.

Thanks for rebasing this. I reviewed 663abf3 and the ownership move looks sound: there is still one Workbar controller, with stable shell commands and a narrow hidden-session projection. A real Electron probe with the production provider/controller confirmed that toggling and opening a tool do not re-render the shell; hidden-session updates and unmount cleanup also worked under StrictMode. I found one P2 below.

The current required CI run also fails in Desktop Knip on the two unused type exports in features/workbar/testing.ts: WorkbarControllerCommands and WorkbarControllerSelectors. Please remove those exports and refresh the checks alongside the small fix below. This does not need another architecture rewrite.

Review assisted by Codex, with source tracing and focused real Electron probes; no installed-Desktop end-to-end resource test was run.

shellObscured: boolean;
modelChoices: readonly ChatModelChoice[];
reportError(title: string, description: string, sessionId: string): void;
reportError(sessionId: string, title: string, description?: string): void;

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.

[P2] Migrate the Terminal Stop error call to the new argument order

Could you also reorder the reportError call in closeTabs (lines 439–443)? The signature now takes (sessionId, title, description), and WorkbarProvider receives AppShell’s showSessionError directly, but the terminal.stop rejection still passes (stopFailed, localizedError, ownerSessionId). In a real Electron provider/controller probe, rejecting Stop for session a produced ["Could not stop terminal", "Could not stop terminal", "a"]. The toast therefore displays the session ID as its description and associates diagnostics with the error title instead of the real session. The two other call sites were migrated correctly. Please move ownerSessionId to the first argument here and assert the captured error arguments in the existing failed-close/retry test.

中文

停止 Terminal 失败是正常故障路径。接口已改成 sessionId、title、description,但 closeTabs 中这一处仍用旧顺序。真实 Electron 探针确认提示描述变成会话 ID,诊断关联的 sessionId 变成错误标题。把 ownerSessionId 移到第一个参数,并在已有失败关闭/重试测试中补错误参数断言即可。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this was a real bug on the old branch. By the time I rebuilt on main, #5374/#5352 had replaced reportError with a toastApi input, and the closeTabs Stop rejection now calls toastApi.error(stopFailed, message, undefined, { sessionId: ownerSessionId }). So the argument-order hazard is gone at the source rather than patched.

I still took your second ask: the existing failed-close/retry test now passes a recording toastApi and asserts that every Stop failure is reported with sessionId: 'a', and that the Session id never appears as the title or description (8e93a897d).

One side observation from that test, not changed here: pressing Close twice while the first Stop is in flight coalesces into one Stop request (TerminalCloseIntents), but each close attaches its own .catch, so one failed Stop shows two identical toasts. Happy to open a separate issue if that's worth fixing.

@theSatvik
theSatvik force-pushed the refactor/workbar-controller-scope branch 2 times, most recently from 9e38345 to 8e93a89 Compare September 26, 2026 20:07
@theSatvik

Copy link
Copy Markdown
Contributor Author

@Astro-Han thanks for the Electron probe and the review. Sorry this sat. By the time I got back to it, main had moved 231 commits and the Workbar controller had changed shape underneath the branch (toastApi, WorkHub, Work Board start claims, togglePosition). So I rebuilt the change on current main (538c37cb6) instead of forcing old hunks through. Head is now 8e93a897d.

Your two asks

  • P2 (Stop error argument order): main now reports through toastApi.error(…, { sessionId }), so the reordering hazard no longer exists. The failed-close/retry test now asserts the owner sessionId on the Stop failure. Details are in the thread.
  • Desktop Knip: testing.ts now re-exports the controller as a single named export plus only the two types tests use (UseWorkbarControllerInput, WorkbarController). The unused WorkbarControllerCommands/WorkbarControllerSelectors exports are gone. Desktop and UI Knip both pass.

What changed in the design: it now follows the shape #5505/#5509 settled on. WorkbarProvider is registered in controllerOwners as the sole useWorkbarController caller. A new WorkbarShellRoot sits beside TaskEntryRoot/OverlaysRoot/SessionCollaborationDialogRoot and hands AppShellContent a projection, so the shell body calls no Workbar hook (gate 35→34 hooks, 57→56 call sites). That keeps --strict-base happy without renaming a shell hook, and AppShell's token count is unchanged against base. The projection carries stable command delegates plus the three values the shell still renders from on current main: hidden fork ids, rightCollapsed (WorkHub dock) and ready (WorkHub navigation). The bridge only notifies when one of those changes. Tool switches, topology and resize drags stay inside the provider; the new scope test pins that through the real root→provider composition.

Verification: Desktop test:dist 2921/2921, typecheck incl. stories, build:renderer, strict-base architecture check + fixtures 112/112, hook gate, Astryx and Windows inventories, lint, format, Knip, ASF headers. The PR body has the full list.

@hqhq1025 hqhq1025 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.

Reviewed exact head 8e93a897df8ba287b3c8caea69d549e0b29a8221 (22 files). The Workbar controller now has one owner in WorkbarProvider (apps/desktop/src/renderer/features/workbar/ui/workbar-provider.tsx:34-56); WorkbarShellRoot and its bridge expose stable commands and only the shell-visible readiness, collapse, and hidden-Session state (workbar-shell-root.tsx:26-47, workbar-shell-bridge.ts:62-114). AppShell passes inputs instead of owning the controller (app-shell.tsx:2096-2119), while the host/titlebar read the provider model. The prior Terminal Stop error argument-order issue no longer applies to this base: the failure path passes { sessionId: ownerSessionId } to toastApi.error (use-workbar-controller.ts:661-669), and the updated test checks that Session ownership is not displayed as the error text (workbar-controller.test.ts:688-744). The old unused testing exports are narrowed in features/workbar/testing.ts:59-67. I found no substantiated P0–P3 issue in the inspected paths.

Node 24 clean install and build:test pass; 45 focused Workbar tests, 112 architecture-checker tests and the architecture check, knip --workspace apps/desktop, surface inventory, diff check, and merge-tree against current main pass. The PR is currently mergeable, but GitHub reports no checks for this head, so this is not a merge-readiness or human design approval. I did not run a packaged Electron or cross-platform interactive Workbar smoke; please validate those behaviors and the current-head CI before merging.

Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.

WorkbarProvider becomes the sole owner of useWorkbarController, registered
in controllerOwners, and WorkbarShellRoot hands AppShellContent a narrow
projection beside the Task Entry, Overlays and Session Collaboration roots.
The shell body calls no Workbar hook.

The shell keeps stable command delegates plus the three values it renders
from: hidden companion Session ids, right-panel collapse and readiness. The
bridge replaces that state only when one of them changes, so tab switches,
panel topology and resize drags re-render the provider and the host model's
readers instead of the shell. The Workbar width reaches the frame and the
titlebar through an inherited custom property. Storybook renders
environment-free WorkbarHostView and WorkbarTitlebarActionsView.

Refs apache#4582.

Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@theSatvik
theSatvik force-pushed the refactor/workbar-controller-scope branch from 8e93a89 to cca7908 Compare September 27, 2026 09:49
@theSatvik

Copy link
Copy Markdown
Contributor Author

Thanks @hqhq1025. main moved again overnight, so I rebased to clear a conflict in the generated Astryx inventory. The new head is cca79088e. git range-diff 8e93a897d…cca79088e differs from the head you reviewed only in the regenerated totals line of docs/astryx-surface-file-inventory.md (302→304 files). No source changed.

Re-run on the new head against current main: Desktop test:dist 2933/2933, typecheck incl. stories, build:renderer, --strict-base architecture check plus fixtures 112/112, AppShell hook gate (now 33 hooks / 55 call sites), Astryx and Windows inventories, Desktop/UI Knip, lint, format, and ASF headers.

As you noted, GitHub shows no checks for this head; the workflow runs are waiting for maintainer approval. @Astro-Han, could you approve the CI run when you have a moment?

@Astro-Han

Copy link
Copy Markdown
Contributor

CI approved and running!

@Astro-Han Astro-Han 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.

Re-checked at this head. cca79088 is a rebase of the previously reviewed 8e93a897 onto current main: git range-diff between the two shows the PR's own change is identical, apart from the regenerated totals line in docs/astryx-surface-file-inventory.md (301 → 302 files), which follows from main. The earlier review of 8e93a897 therefore still applies: Workbar controller ownership moves into WorkbarProvider, AppShell passes inputs, the Terminal Stop error path passes the session as options rather than as the message, and no P0–P3 issue was found. The branch merges cleanly; current-head CI is still running.


Automated review notice: This review was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

@Astro-Han Astro-Han 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.

Approved at @Astro-Han's explicit request: a behaviour-preserving refactor with no findings across several automated review rounds; this head is a rebase of the reviewed one. Merge follows once required CI passes.

@Astro-Han
Astro-Han merged commit 1e31d0a into apache:main Sep 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/L Under 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants