Repository navigation
Desktop shell: session rail - #666
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ack removed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A local attribute value outranks a style trigger, so the .selected/.holdsSelected setters could never paint. The resting Transparent now lives in a railRow base style declared ahead of them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AgentRowViewModel had no consumer beyond the deleted window projection (the tray's TrayMenuModel.Agents is an unrelated type), so it is fully deleted along with MainWindowViewModel's Agents/_agentsSource/RowComparer and the three now-unreferenced converters (GridEnabledOpacityConverter, EmptyStateVisibleConverter, HeaderRowVisibleConverter). GridEnabled stays: MainWindow.axaml still gates the status block's AgentCountText on it. `actions`/`ticker` ctor params stay unchanged though both are now fully unused by this VM, per plan. AgentGridTests.cs is deleted with its grid/row-projection assertions; its UptimeFormat boundary-table coverage moves to UptimeFormatTests.cs (UptimeFormat is still live via SessionCardViewModel/RailSessionViewModel) and its GridEnabled_reflects_attach_state test moves to MainWindowViewModelTests.cs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR Summary by QodoAdd session rail and tabless Home/Sessions desktop shell
AI Description
Diagram
High-Level Assessment
Files changed (34)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc48e63ccd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Re-clicking the open session must not tear down and rebuild a live attach. | ||
| if (CurrentWorkspace?.AgentId == agentId) return; |
There was a problem hiding this comment.
Expand the rail before returning for the current session
When the user collapses the worktree containing the open workspace, switches to Home, and clicks that session's card, this early return occurs after switching back to Sessions but before Rail.NotifySessionOpened. The existing workspace remains selected inside a collapsed group, leaving no visible selected rail row; notify the rail before taking the same-session no-op path.
Useful? React with 👍 / 👎.
| var line = raw.Trim(); | ||
| if (line.Length == 0) continue; | ||
| return line.Length <= 80 ? line : line[..79] + "…"; | ||
| } |
There was a problem hiding this comment.
Preserve Unicode scalars when truncating titles
When a prompt's first line exceeds 80 UTF-16 code units and a supplementary character crosses the cutoff—for example, 78 ASCII characters followed by an emoji and a suffix—line[..79] retains only the emoji's high surrogate. The resulting malformed title cannot preserve the original text and may be serialized with a replacement character; truncate by Unicode scalar or grapheme boundaries before appending the ellipsis.
Useful? React with 👍 / 👎.
Code Review by Qodo
1.
|
| /// RxSchedulers.MainThreadScheduler, which is not immediate in a bare test process — see | ||
| /// MainWindowViewModelTests' header comment. Every test here runs inside | ||
| /// AvaloniaSession.WithImmediateRxScheduler and carries [NotInParallel("AvaloniaSession")]. | ||
| public class RailSessionViewModelTests { |
There was a problem hiding this comment.
5. Rail session tests misplaced 📘 Rule violation ⚙ Maintainability
RailSessionViewModelTests is added at the test-project root even though its production owner is under ViewModels. This breaks the required mirrored production/test directory ownership.
Agent Prompt
## Issue description
The new `RailSessionViewModelTests` file does not mirror the production `ViewModels` directory.
## Issue Context
Move the test to the corresponding `ViewModels` directory in `Capacitor.App.Tests.Unit` while preserving its namespace and project ownership conventions.
## Fix Focus Areas
- test/Capacitor.App.Tests.Unit/RailSessionViewModelTests.cs[11-11]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| /// not immediate in a bare test process — see MainWindowViewModelTests' header comment. Every | ||
| /// test here runs inside AvaloniaSession.WithImmediateRxScheduler and carries | ||
| /// [NotInParallel("AvaloniaSession")]. | ||
| public class RailWorktreeViewModelTests { |
There was a problem hiding this comment.
6. Rail worktree tests misplaced 📘 Rule violation ⚙ Maintainability
RailWorktreeViewModelTests is added at the test-project root rather than mirroring the production ViewModels directory. This violates the required production ownership layout for tests.
Agent Prompt
## Issue description
The new `RailWorktreeViewModelTests` file does not mirror the production `ViewModels` directory.
## Issue Context
Move the test to the corresponding `ViewModels` directory in `Capacitor.App.Tests.Unit`.
## Fix Focus Areas
- test/Capacitor.App.Tests.Unit/RailWorktreeViewModelTests.cs[13-13]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
|
||
| namespace Capacitor.App.Tests.Unit; | ||
|
|
||
| public class SessionRailViewModelTests { |
There was a problem hiding this comment.
7. Session rail tests misplaced 📘 Rule violation ⚙ Maintainability
SessionRailViewModelTests is added at the test-project root instead of under a mirrored ViewModels directory. The new test layout therefore does not follow its production owner's directory structure.
Agent Prompt
## Issue description
The new `SessionRailViewModelTests` file does not mirror the production `ViewModels` directory.
## Issue Context
Move the test to the corresponding `ViewModels` directory in `Capacitor.App.Tests.Unit`.
## Fix Focus Areas
- test/Capacitor.App.Tests.Unit/SessionRailViewModelTests.cs[8-8]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| _countText = group.Cache.CountChanged | ||
| .Select(c => c == 1 ? "1 session" : $"{c} sessions") | ||
| .ToProperty(this, x => x.CountText, initialValue: "") |
There was a problem hiding this comment.
8. Initial repo count stays blank 🐞 Bug ≡ Correctness
RailRepoViewModel initializes CountText to an empty string and listens only for later CountChanged emissions, so a newly formed repository containing one session shows no count until that group gains or loses another session. Repository headers therefore omit the required session count for stable one-session groups.
Agent Prompt
## Issue description
Repository count text is blank when a group is created with its initial session because the count observable is not seeded from the current cache count.
## Issue Context
The sibling worktree implementation already seeds `CountChanged` with the current count and initializes its properties consistently.
## Fix Focus Areas
- src/Capacitor.App/ViewModels/RailRepoViewModel.cs[45-47]
- src/Capacitor.App/ViewModels/RailWorktreeViewModel.cs[83-88]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Rail, window chrome, and workspace header restyled to the Hosted Agents Shell artboard: Kcap surface/border tokens on the rail with caps repo headers, branch glyphs, guide-lined session indents and pill pips; the client area extends into the title bar (traffic lights over the rail, drag handled by the chrome strips); Home's status block, Sessions entry and Activity section themed; workspace header gets the vendor chip and pill tab. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The consent feed leaves the page flow: an Activity chip beside Sessions (top right) opens it as a flyout panel, whose open state drives the polling gate; leaving Home closes it. Rail, workspace header, and Home header fonts bumped ~1px toward Linear's density. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Home/Sessions icon buttons grow to 38x32 with a larger glyph; the greeting row's Search and New session placeholders go (real search is a later slice, and the button only focused the goal box sitting right below it). The rail's New session row stays - it is the navigation from Sessions back to the prompt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 9px text glyph rendered as a dot; two stroked Paths (right/down) in the muted color stay crisp at 12px. The orphaned ExpanderChevronConverter goes with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T3-style: the window boots on the Sessions surface, and the right
pane's empty state is a centered launcher (What should we build in
{repo}? over the composer with repo/harness chips) — asking for a new
session and having none selected are the same screen. The rail's New
session row deselects instead of navigating; the Home/Sessions switcher
goes. Home stays in the tree, hidden, its launcher card moved to
LauncherPaneView and the daemon status block + Activity chip to the
launcher pane header. The Activity gate becomes flyout-open AND
launcher-visible AND window-visible.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The launch box grows T3's remaining knobs: a free-text Model chip (blank = vendor default; resets when the harness changes — ids are vendor-specific) and an Effort menu (Default/low/medium/high/xhigh), both riding the wire fields the hub already carried. The daemon identity block leaves the launcher's top-left — the rail footer is the one indicator, now showing the tenant slug (the active profile's name) with the full identity on hover; only the self-hiding Start/Retry recovery strip remains in the pane. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rail only carries current sessions, so nothing hides by default — the canvas's collapsed-main rule goes (owner revision); collapsing is an explicit, sticky choice, and opening a session still beats it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The harness and model chips merge into an AgentChip ("Claude · Fable 5")
opening a T3-style picker: search box over vendor-grouped rows — each
vendor leads with its Default and follows with curated suggestions
(hardcoded catalog in HostedHarnessCatalog, exactly how T3 ships
Claude's; T3 lists Codex's live from codex app-server, which our daemon
could grow an IPC for later). Unavailable vendors stay listed but
disabled, and a non-empty search always offers itself verbatim as a
custom model id, so catalog drift never blocks a launch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendor icon rail (tinted monogram tiles, active tile raised) beside an underlined magnifier search over roomy bold rows with vendor sub-lines. Empty search shows the active tab's models; typing searches across vendors; the typed term stays offered verbatim as a custom id. The flyout presenter itself is the panel via the new kcapPanel style. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simple Icons path data (CC0) for claude, codex (OpenAI), cursor, copilot, gemini, and opencode — filled Paths tinted per vendor (monochrome brands in the text color, claude/gemini in their hues). Kiro/antigravity keep monogram tiles (no published mark) and pi gets the pi glyph. The AgentChip leads with the mark, T3-style. Geometries parse per call, never cached statically — Geometry carries the same thread affinity brushes do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four-angle cleanup (reuse/simplification/efficiency/altitude): - One WindowChrome.BeginDrag replaces three identical drag handlers. - AgentInstance caches Title once (SnapshotAgentsForStatus re-parsed the immutable Prompt on every status pulse); the cut is now surrogate-safe. - PlatformPaths owns the platform comparer + raw-leaf primitive that HomeViewModel, RepoLabel, and the rail VMs each re-typed. - SessionStatusDots.NeedsAttention is the one needs-you status rule. - Vendor tiles and the effort ladder move into HostedHarnessCatalog beside the model catalog (one file to add a vendor), with a guard test. - Dead weight out: GridEnabled (nothing binds it), the VM's unused actions/ticker ctor params (23 call sites trimmed), redundant SessionCount, duplicate selected/holdsSelected style blocks. - HomeViewModel.SetVendor centralizes the model-reset invariant; the holdsSelected probe stops allocating a HashSet per changeset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-desktop-shell-session-rail # Conflicts: # src/Capacitor.App/App.axaml.cs
CI's linear-id lint scans every tracked C# comment, and the comment rules ban ticket ids and evolution narration outright. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AI-2199
The navigation half of the desktop shell: a 310px session rail grouping repository → worktree → session, and the window restructure it forced — which, over the review iteration with the owner, landed T3-Code-shaped: the app lives on the Sessions surface, and the right pane's empty state IS the launcher.
What's here
AgentStatusDtogains a trailingtitle— the launch prompt's first non-blank line, truncated to 80 chars, stamped by the daemon inSnapshotAgentsForStatusat the wire boundary (same placemodelis normalized). Null from older daemons or prompt-less launches; rows fall back to the vendor line. The app's launch payload now also carries the chosenmodel/effortthrough the fields the hub already had.daemon.Agents(oneObserveOnat the top; inner group caches are mutated on the UI thread by the outer pipeline). Repo headers (caps leaf label, session count; No repository last with its explainer note), collapsible worktree rows (checkout-leaf label, branch glyph, count, amber needs-you pip on any Failed child), two-line session rows (title, vendor · model · age, status dot, guide-lined indent, raised selection). Everything starts expanded (owner revision of the canvas's collapsed-main default — the rail only carries current sessions); collapsing is an explicit sticky choice that survives DynamicData group recreation, and opening a session always re-expands its worktree. Footer: connection dot · word · tenant slug (the active profile name), full daemon identity on hover.TabControlis gone and the window boots into Sessions — rail | launcher pane ("What should we build in {repo}?" over one composer card with repo picker, a T3-style searchable harness+model picker (vendor icon rail with real Simple-Icons marks, curated model catalog inHostedHarnessCatalog, typed-custom escape hatch), an effort menu, Remember, Start). Opening a session swaps the launcher for the AI-2195 workspace (Back button removed — the rail is the navigation). The Agents tab is deleted (rail + workspace header supersede it); Activity survives as a flyout off a chip on the launcher header, its polling gated on flyout-open AND launcher-visible AND window-visible; Home stays in the tree but dormant (nothing navigates to it) until it earns its keep. The client area extends into the title bar; window default grows to 1200×760, all surfaces on the Kcap token palette per the design canvas.BuildAndShowMainWindowover the same daemon service and disposed mirroringHomeViewModelon both teardown paths.Spec (rides this PR):
docs/superpowers/specs/2026-08-25-ai2199-session-rail-design.md— also on the Linear issue, with an addendum comment recording the owner revisions made during review (launcher-as-empty-pane, dormant Home, all-expanded default, Activity flyout). Deferred: per-repo "+" quick-launch, branch names on worktree rows (wire field), dynamic Codex model listing via app-server, Home's full nav column, work-item lanes.Testing
title(+ model/effort payload cases); daemon-side serialized-payload tests for the truncation contract.🤖 Generated with Claude Code