You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Polish the desktop app launcher, sign-in, chat tools, and workspace chrome - #766
Desktop app UX pass: launcher chips/flyouts and Start tips, daemon recovery in a banner above the launch card, clearer sign-in (copy, browser fallback, Enter to start), long-repo headline layout, chat tool callouts with status greens, and workspace identity as a muted subtitle. Theme tokens split primary CTAs from success green and lift muted/faint contrast on dark canvas.
Where to look
LauncherPaneView / HomeViewModel (chips, recovery, Start tips, repo defaults); ChatTabView / ChatItems (tool groups); WorkspaceViewIdentitySubtitle beside main's work-context column. Easy to miss: App.axaml.cs keeps _launch so reauth invalidates the same client ServerClients wraps.
nortonandreev
changed the title
Polish the desktop app launcher, sign-in, and theme tokens
Polish the desktop app launcher, sign-in, chat tools, and workspace chrome
Sep 4, 2026
Split primary CTA tokens (KcapPrimary*) from success/status green (KcapSuccess*) so Start and selection no longer share one green.
Lift KcapMuted / KcapFaint so secondary copy meets usable contrast on canvas through border.
Force dark Fluent theme variant; pin chip / primary button hover and disabled chrome so Fluent’s PART_ContentPresenter pointer-over no longer wipes custom fills (light-on-light).
Placeholder text on embedded text boxes uses muted brush (launcher goal + chat composer readable on dark).
Soft pulse style for in-flight tool rows (Border.toolRunning).
Launcher / daemon recovery
Chip labels, repo flyout harness badges, and Start disabled tips (repo missing and/or connection blocked) with ToolTip.ShowOnDisabled.
Connection / daemon recovery lives in a warning banner above the launch card (not inside the composer).
One primary action at a time: Start daemon when the daemon looks down; Reconnect when connecting or skewed — never both.
One banner line (BannerMessage): a start/lifecycle message wins over the generic unreachable notice (sidebar already shows Unreachable); warning styling, not danger red.
Known mutation attention/storage tokens map to actionable human copy; unknown tokens stay log-only (no opaque “needs attention (token)” banner). cli_below_floor names the app↔CLI floor, not “too old for the daemon.”
Connection notices when there is no start failure yet: server lost, sign-in expired, daemon down / incompatible, finishing sign-in.
Prefer a recent real repo over the scratch menu row when choosing a default.
Dedup repo paths in the recent list / related surfaces.
Split headline: fixed question + muted/ellipsis repo subtitle; two-row footer so long paths don’t crush chips + Start.
Enter starts a session when the launcher form is ready.
Window sizing kept compatible with the work-context sidebar (MinWidth="1200", MinHeight="560").
Sign-in
Idle / busy copy polish; bottom browser-fallback panel with selectable URL plus open/copy.
Clearer feedback while the browser flow runs; punctuation/copy cleanup on error and unusable-ID strings.
Reauth path notifies home and server clients; launch client invalidate kept for the same instance ServerClients owns.
Workspace / session chrome
Declutter header: drop vendor chip and harness/transport meta (those live in the right pane); subtitle is checkout only (RepoLabelText).
Title fallback is the repo leaf only.
Layout preserved next to main’s work-context sidebar (Grid *,400 + WorkContextView).
Status pill on top for a lone in-flight call; grouped runs fold into a peek summary when there are two or more settled calls.
Status greens aligned to connected / disrupted status colors.
Spacing / pulse polish for live tool rows.
When a session ends, hide the composer and Send (keep the “This session has ended” line) and disable Stop as soon as status is Completed/Failed, not only after the agent leaves the snapshot.
Headless smoke: second render tick + center hit + larger tool-summary hit area so fold-expand clicks stay reliable on Windows CI.
Activity / tray / rail (supporting)
Activity status coloring aligned with the same greens.
Tray / rail / related view-model tweaks tied to repo labeling and chrome declutter.
Tests / hygiene
Smoke and unit coverage for launcher headline controls, Start tips, exclusive Start/Reconnect, single banner line, human attention copy, sign-in, tool groups, workspace subtitle, MainWindow sizing, work-context refresh queueing.
Unusable-ID assert pinned to HomeViewModel.UnusableIdMessage.
Polish desktop launcher, sign-in, chat tools, and workspace chrome
✨ Enhancement🐞 Bug fix🧪 Tests🕐 40+ Minutes
AI Description
• Refines launcher, sign-in, chat tools, activity, and workspace presentation.
• Unifies daemon recovery and refreshes authenticated clients after reauthorization.
• Normalizes repository identity and expands behavioral and headless UI coverage.
Diagram
graph TD
U["Desktop user"] --> L["Launcher pane"] --> H["Home state"] --> D["Daemon lifecycle"]
H --> C["Launch client"]
U --> S["Sign-in flow"] --> H
U --> W["Workspace views"] --> T["Chat tools"]
Loading
High-Level Assessment
The approach is appropriate for a UX-focused pass: it retains existing Avalonia and ReactiveUI boundaries, reuses MainWindow recovery commands in the launcher, and invalidates the existing launch client rather than introducing parallel authentication state. Replacing the code-built flyouts with a new reusable control was considered, but would add abstraction and migration risk beyond this PR's scope.
Files changed (52) +2342 / -708
Enhancement (26) +1523 / -538
App.axamlExpand the dark UX token and control style system+74/-8
Expand the dark UX token and control style system
• Forces the dark theme, improves muted-text contrast, and separates primary-action colors from success colors. Adds shared flyout, chip, primary, ghost, embedded-field, and running-tool styles.
ChatItems.csModel polished tool call cards and summaries+75/-4
Model polished tool call cards and summaries
• Adds detail-first row text, explicit running state, lone-call chrome, and folded summary previews. Single calls remain visible while multi-call groups can collapse settled rows.
MainWindowViewModel.csShare one daemon recovery state with the launcher+103/-26
Share one daemon recovery state with the launcher
• Makes Start and Reconnect mutually exclusive, publishes immediate click feedback, and maps raw attach reasons to actionable messages. Shares commands and lifecycle messages with HomeViewModel and hides the built-in default profile label.
WorkContextViewModel.csQueue refreshes and explain refresh availability+30/-4
Queue refreshes and explain refresh availability
• Keeps refresh enabled during reads and queues one follow-up request when clicked in flight. Adds contextual tooltip text for reading and unavailable states.
LauncherPaneView.axamlRecompose launcher headline, banner, and controls+125/-79
Recompose launcher headline, banner, and controls
• Moves daemon and sign-in recovery into a banner above the launch card. Splits the fixed headline from the repository subtitle, separates long repository chips from settings, and adds contextual Start tooltips.
LauncherPaneView.axaml.csPolish launcher flyouts and Enter-to-start+184/-65
Polish launcher flyouts and Enter-to-start
• Replaces Fluent menu flyouts with dark token-based picker panels for repositories, effort, and permissions. Handles Enter in the goal field, improves option labeling, and adds long-repository tooltip support.
MainWindow.axamlMove recovery into launcher and polish activity+91/-58
Move recovery into launcher and polish activity
• Removes duplicate daemon recovery chrome from the window header. Rebuilds the Activity flyout with aligned columns, scrolling, compact timestamps, tooltips, and a clearer empty state.
WorkContextView.axamlPolish work-context sections and refresh control+68/-26
Polish work-context sections and refresh control
• Adds larger interactive section headers, consistent hover states, and an accessible refresh hit target with contextual tooltip behavior. Tightens spacing and aligns session facts.
WorkspaceView.axamlFold workspace identity into a muted subtitle+21/-24
Fold workspace identity into a muted subtitle
• Removes redundant harness chrome and displays checkout identity as a muted subtitle beneath the title. Also stabilizes tab hover colors and aligns terminal actions with the primary palette.
App.axaml.csRefresh application clients after reauthentication+80/-14
Refresh application clients after reauthentication
• Retains the launch client so successful reauthentication can invalidate its authenticated hub and restart attachment. Also routes lifecycle attention into launcher recovery and replaces mutation tokens with actionable user-facing copy.
• Adds path normalization and a platform-aware comparer that ignores trailing directory separators. Checkout formatting and leaf extraction now use the normalized identity.
• Removes redundant vendor and transport badges from the workspace header, leaving the session title and checkout subtitle while work context carries technical identity.
• Adds headless coverage for lone and grouped tool cards, detail previews, status pills, permission states, and running animation. Hardens synthetic summary clicks across platforms.
HomeViewSmokeTests.csExercise launcher layout and keyboard behavior+162/-7
Exercise launcher layout and keyboard behavior
• Verifies the fixed headline and repository subtitle, recovery controls, disabled Start tooltip, and Enter-to-start behavior with and without a repository.
EnsureDefaultRepositoryAsync checks for an empty selection only before awaiting repository
discovery, then unconditionally applies the discovered default. If the user selects a repository
while that lookup is suspended, the startup task overwrites the explicit choice and restores the
wrong harness.
+ if (SelectedRepoPath.Length > 0) return;+ if (await PreferRecentRepositoryAsync() is not { Length: > 0 } recent) return;+ await SelectRepositoryAsync(recent);
Relevance
●●● Strong
A closely matching HomeViewModel persistence race was accepted in the same launcher work.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The guard runs before PreferRecentRepositoryAsync, which awaits the production repository store,
while picker callbacks can update the selection during that suspension. The subsequent unconditional
call to SelectRepositoryAsync assigns the discovered path and its harness regardless of the newer
user choice.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Prevent asynchronous default-repository discovery from overwriting a repository explicitly selected while the lookup was in progress.
## Issue Context
The constructor starts default selection asynchronously, and the real repository provider performs asynchronous file I/O. `EnsureDefaultRepositoryAsync` must only commit its result if the selection is still empty after discovery; concurrent default-selection calls should also be serialized or made idempotent.
## Fix Focus Areas
- src/Capacitor.App/ViewModels/HomeViewModel.cs[310-312]
- src/Capacitor.App/ViewModels/HomeViewModel.cs[464-487]
- src/Capacitor.App/ViewModels/HomeViewModel.cs[492-496]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
The PR adds a Back-compat comment and overload for previously classified availability without
identifying any persisted or supported source that still requires this shape. Repository search
finds no callers of this overload, so the deprecated compatibility shape is not observable.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
Rule 2897945 permits deprecated-shape comments only when they identify a concrete source that can
still produce the old form. The added comment only refers generically to existing callers, while the
overload has no call sites in the repository.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Remove the unused compatibility overload and its historical `Back-compat` comment, or document a concrete currently supported producer if one exists.
## Issue Context
No repository callers use the `NoticeFor(LaunchAvailability, bool)` overload, and the comment identifies no persisted data, configuration, or supported client that requires it.
## Fix Focus Areas
- src/Capacitor.App/ViewModels/HomeViewModel.cs[385-393]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
The modified OutcomeBrushConverter comment references spec §7, making its meaning depend on
external process metadata. Comments must describe current behavior without design/spec coordinates.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Remove the `spec §7` coordinate from the comment while retaining its current behavioral rationale.
## Issue Context
Comments must remain understandable without external design or process artifacts.
## Fix Focus Areas
- src/Capacitor.App/Views/Converters.cs[20-23]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
4. Repository failures become unobserved✓ Resolved🐞 Bug☼ Reliability
Description
The repository-row callback discards SelectRepositoryAsync, so state-loading failures detach from
the UI event and the selection or harness restoration silently fails as an unobserved task. The
replaced click handler awaited this operation.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new row callback explicitly discards the task. SelectRepositoryAsync awaits _state.LoadAsync
without catching failures, so an I/O or parsing exception propagates into the detached task rather
than an awaited event path.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Ensure asynchronous repository selection is awaited and failures are handled through the application's UI error path rather than becoming unobserved tasks.
## Issue Context
The new generic choice callback is synchronous, although repository selection loads persisted state asynchronously. Adapt the callback abstraction to support asynchronous operations and retain flyout-close behavior.
## Fix Focus Areas
- src/Capacitor.App/Views/LauncherPaneView.axaml.cs[110-114]
- src/Capacitor.App/ViewModels/HomeViewModel.cs[490-496]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
PresentOutcomeAsync now logs unmapped Attention outcomes without displaying them, but
AttentionCopyFor omits the production tokens running_without_daemon_pid and
daemon_running_outside_service. Those service-ownership failures therefore leave users without the
recovery banner or actionable diagnosis previously shown.
+ // Opaque tokens are for the log — a bare "needs attention (token)" banner helps nobody.+ Console.Error.WriteLine($"kcap: daemon mutation needs attention ({named}) — not shown in the UI");+ }
Relevance
●●● Strong
Accepted visibility-loss findings closely match prior accepted recovery and actionable-error
feedback.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The mutation lane emits both omitted tokens as AttentionRepair, and classification routes every
such outcome to RecoverySurface.Attention. The new fallback skips surface.Attention whenever
AttentionCopyFor returns null, which it does for both tokens.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Restore user-visible recovery messages for all production-generated daemon ownership repair outcomes.
## Issue Context
`AttentionRepair` always routes to the Attention surface, but the new token-to-copy mapping does not cover two tokens emitted by the mutation lane. Add actionable mappings and preferably enforce mapping coverage with tests so future production tokens cannot silently become log-only.
## Fix Focus Areas
- src/Capacitor.App/App.axaml.cs[998-1005]
- src/Capacitor.App/App.axaml.cs[1021-1049]
- src/Capacitor.App/Services/Mutation/DaemonMutationLane.cs[343-349]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Each rebuilt MainWindowViewModel calls HomeViewModel.AttachDaemonRecovery, which adds another
set of long-lived subscriptions retained until application shutdown. Since real window close
discards the window without disposing its view model, repeated close/reopen cycles retain old
recovery graphs and perform duplicate status processing.
+ // Launcher banner owns the chrome; share the same Start/Reconnect commands and start-message+ // lane so the pane never drifts from what MainWindow already drives.+ home?.AttachDaemonRecovery(+ StartDaemonCommand, RetryCommand, canStart, canRetry, _startMessageChanges);
Relevance
●● Moderate
Subscription lifetime concerns are credible, but historical evidence is indirect and window
ownership conventions remain uncertain.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new attachment subscribes Home to observables owned by each main-window view model, and Home
retains every OAPH/subscription in its application-lifetime composite. The coordinator discards a
closed window and later constructs another, but MainWindowViewModel is not disposable and no close
path removes the prior attachment.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
Give each main-window recovery attachment an explicit lifetime so closing or rebuilding the window removes subscriptions associated with the old view model.
## Issue Context
`AttachDaemonRecovery` stores every generated subscription in Home's application-lifetime composite, while the main-window coordinator can repeatedly create new view models. Return a disposable attachment, replace the previous attachment atomically, or make `MainWindowViewModel` disposable and invoke disposal on real close.
## Fix Focus Areas
- src/Capacitor.App/ViewModels/MainWindowViewModel.cs[277-307]
- src/Capacitor.App/ViewModels/HomeViewModel.cs[317-341]
- src/Capacitor.App/Services/MainWindowCoordinator.cs[29-43]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
7. Reconnect status never resolves✓ Resolved🐞 Bug☼ Reliability
Description
When Start daemon finds an already-running service, it publishes a permanent “Reconnecting…” status
and fire-and-forgets the attach retry. If that retry returns to Unreachable, the UI only replaces
the exact ReconnectingMessage value, so the banner continues to claim it is reconnecting until a
successful connection occurs.
+ _surface.Status("Daemon service is already running. Reconnecting…");
_ = _client.RestartLoopAsync();
Relevance
●● Moderate
The stale reconnect state is plausible, but no closely matching historical precedent establishes
team treatment.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The changed branch emits a different reconnect-in-progress string and does not await or observe
RestartLoopAsync. The view model clears messages only after Connected, and its Unreachable handler
replaces only ReconnectingMessage, not the lifecycle branch's new text.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The already-running daemon branch reports an in-progress reconnect, but does not provide failure feedback when the fire-and-forget reattach attempt fails. Ensure this status is replaced with actionable failure copy when attachment remains unreachable.
## Issue Context
`MainWindowViewModel` clears start messages only on Connected and only recognizes its own `ReconnectingMessage` sentinel when converting an unreachable retry into failure copy. The lifecycle's different message therefore remains indefinitely after a failed reconnect.
## Fix Focus Areas
- src/Capacitor.App/Services/DaemonLifecycleController.cs[665-668]
- src/Capacitor.App/ViewModels/MainWindowViewModel.cs[363-374]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
The WorkContext header’s pointer handler now belongs only to the label rather than its full-width
container. Clicking empty header space no longer initiates window dragging, while the prior
DockPanel handler covered the entire header.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new Grid has no pointer handler, whereas its TextBlock alone has PointerPressed. The handler
is designed to reject button-originated events and otherwise call WindowChrome.BeginDrag, so empty
Grid area has no equivalent route to start dragging.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The WorkContext header should remain a full-width window drag target except for interactive controls. The handler was moved from the surrounding header container onto the text label, leaving blank header chrome non-draggable.
## Issue Context
The existing handler already excludes Button descendants before calling `WindowChrome.BeginDrag`, so it is safe to attach it to the header Grid.
## Fix Focus Areas
- src/Capacitor.App/Views/WorkContextView.axaml[98-100]
- src/Capacitor.App/Views/WorkContextView.axaml.cs[15-20]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Review mode: 🧠 Deep: This broad desktop UX change spans 52 files and 185 hunks across launcher, authentication/reauth, daemon recovery, chat tooling, workspace navigation, styling, and tests, creating many independent paths where subtle regressions are easy to miss.
Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No GitHub issue — no Linear id for this PR.
What & why
Desktop app UX pass: launcher chips/flyouts and Start tips, daemon recovery in a banner above the launch card, clearer sign-in (copy, browser fallback, Enter to start), long-repo headline layout, chat tool callouts with status greens, and workspace identity as a muted subtitle. Theme tokens split primary CTAs from success green and lift muted/faint contrast on dark canvas.
Where to look
LauncherPaneView/HomeViewModel(chips, recovery, Start tips, repo defaults);ChatTabView/ChatItems(tool groups);WorkspaceViewIdentitySubtitlebeside main's work-context column. Easy to miss:App.axaml.cskeeps_launchso reauth invalidates the same clientServerClientswraps.Verification
dotnet build src/Capacitor.App/Capacitor.App.csproj— 0 warnings, 0 errors (after rebase onto main including Add the work-context sidebar to the desktop session workspace #763)dotnet run --project test/Capacitor.App.Tests.Unit/Capacitor.App.Tests.Unit.csproj -- --treenode-filter "/*/*/HomeViewModelTests/*"— passeddotnet run --project test/Capacitor.App.Tests.Unit/Capacitor.App.Tests.Unit.csproj -- --treenode-filter "/*/*/SignInStepViewModelTests/*"— passeddotnet run --project test/Capacitor.App.Tests.Unit/Capacitor.App.Tests.Unit.csproj -- --treenode-filter "/*/*/HostedHarnessCatalogTests/*"— passedVisuals