Files
DodoSSH/HANDOFF-hosts-v5.md
T

12 KiB
Raw Blame History

Handoff: Hosts v5 design import — execution in progress

Written 2026-08-07, mid-execution, because the session hit its usage limit. A future session should read this file plus hosts-v5-design-spec.md (same directory) and resume at "Where things stand".

What this is

Implementing the Claude Design project screen Hosts.dc.html (https://claude.ai/design/p/b20d580c-eae5-4cdd-bfae-88f669b5f8a3?file=Hosts.dc.html) in the desktop Avalonia client. Worktree: .claude/worktrees/connection-host-default-text-9e501d, branch claude/hosts-design-implementation-22f161. Nothing is committed yet — all progress is uncommitted working-tree changes.

hosts-v5-design-spec.md holds the palette mapping table, the design's measurements, the monogram hue pairs, and — critically — the agreed deviations from the mock (relay copy stays honest, dot stays two-state, card click selects rather than connects, quick-connect keeps the auth word, find box stays, "New group…" moves into the Group ▼ flyout, quick-access hint drops the "sidebar" claim).

User-approved decisions (asked and answered, do not re-ask)

  1. Full re-theme — shared palette swapped to purple/near-black; Montserrat + JetBrains Mono + Material Icons embedded in the shared Shell project; Android recolors via the shared palette but keeps its own default sans.
  2. Flat sections — desktop hosts screen becomes one collapsible section per group (nesting flattened phone-style, label order), "No group" first, Collapse All; group cards + breadcrumbs go. Collapse state in-memory only.
  3. Pinned folders: full buildHostSecret.PinnedPaths, drawer QUICK ACCESS editor, pin-count card badge, chip strip above the terminal; clicking a pin opens the SFTP tab with the remote pane at that path.
  4. Last connected from the connection logmax(StartedAt) per HostId on hosts-screen activation and on SessionEnded; relative time, restrung on a 1-minute tick; live sessions show the green dot instead.

Execution method the user chose: Sonnet subagents, one wave at a time (waves 2+ serialize because they all touch src/DodoSSH.Client.Shell/ViewModels/VaultViewModel.cs, ~11.8k lines, shared with the Android head).

Where things stand

Wave 1 — DONE and verified (full dotnet build DodoSSH.slnx: 0 errors)

Domain (complete, 264/264 tests pass in tests/DodoSSH.Client.Domain.Tests):

  • New src/DodoSSH.Client.Domain/PinnedPathList.cs — immutable, insertion-ordered, ordinal-deduped (keeps first occurrence), Empty, Create(...), ToPathMap() for the merge.
  • HostSecret.PinnedPaths (default Empty), constants MaxPinnedPathLength = 1024, MaxPinnedPaths = 32, validation wired into TryValidate (blank/control-char/oversize refused; relative paths allowed).
  • HostSecretMerge.MergePinnedPaths — per-element three-way merge; result order = base, then local additions, then remote additions.
  • HostSecretCodec: PinnedPathsSchemaVersion = 7 (now current), field written last and null when empty; old payloads decode as pinning nothing.
  • UI usage: add = host with { PinnedPaths = PinnedPathList.Create([.. host.PinnedPaths, p]) }; remove via Create(host.PinnedPaths.Where(...)).

Theme (complete):

  • src/DodoSSH.Client.Shell/Theme/Palette.axaml fully re-colored per the spec table. New resources: AccentGradient (#8D51E7→#5D42DE), AccentGlow (BoxShadows 0 0 22 0 #598D51E7), IconFont (Material Icons, reference glyphs BY CODEPOINT, e.g. push_pin ). AccentInk is now white. Live #34D399. Essay comments updated where the recolor falsified them.
  • Fonts embedded at src/DodoSSH.Client.Shell/Assets/Fonts/ (Montserrat 4 statics, JetBrains Mono 3, MaterialIcons-Regular; licenses alongside; <AvaloniaResource Include="Assets/Fonts/**"/> added to the Shell csproj). Program.cs default sans → avares://DodoSSH.Client.Shell/Assets/Fonts#Montserrat with Inter fallback; MonoFont → JetBrains Mono first. tests/DodoSSH.Client.App.Layout.Tests/HeadlessApp.cs updated to keep its documented font parity with Program.BuildAvaloniaApp.
  • App.axaml: tile/section radius 12, buttons/inputs radius 10, chips 6; Button.accent uses gradient + glow (glow zeroed on :disabled). Deliberately skipped: raising global control MinHeight to 40 (too risky unverified — style the hosts screen explicitly instead).
  • Known stale doc: docs/design-import-gaps.md:304 still says Inter/system-mono — wave 5 fixes docs.

Wave 2 — WAS RUNNING when the session ended: state UNKNOWN, verify before building on it

A Sonnet agent was mid-flight restructuring the hosts screen. Its changes may be complete, partial, or absent in the working tree. Before anything else: git status + git diff --stat, look at src/DodoSSH.Client.App/Views/HostsScreen.axaml and VaultViewModel.cs, build, and run tests/DodoSSH.Client.App.Layout.Tests + tests/DodoSSH.Client.App.Tests. If its work is partial or broken, either finish it by hand or reset those files and re-dispatch.

Wave 2's full brief (re-usable as a subagent prompt):

  • Flat sections per approved decision 2. Reuse/generalize the phone's flattening (SidebarRows / SidebarGroupHeader / ToggleGroup / in-memory collapsedGroups ~line 1251). No-groups keychain renders headerless (documented invariant). Section heading: {label} · {count}, vault name beside it when >1 vault, per-section collapse, "Collapse All" on the first heading row. Group Edit/Move/Delete on a heading menu (that order; no "Open"). Group cards, GroupTrail, double-click-open, drag-host-onto-group-card all go (desktop only — phone must not regress; it shares the VM).
  • PRESERVE: multi-select (marquee Band + chosen-hosts ops + the three action panels), keyboard nav, context menus, conflict panel, NoVisibleHostsMessage, find box (only search that crosses group boundaries).
  • Toolbar: "Hosts" title + count chip; Group ▼ / Tag ▼ checkable filter flyouts ("New group…" at the Group flyout's foot); + New host accent button. Host passes tag filter if it wears ANY checked tag; empty sections hide; filters ∩ find ∩ collapse compose.
  • New card: 32×32 monogram avatar (two letters, stable-hash into the spec's four hue pairs, JetBrains Mono bold), name 17 bold + two-state dot, mono user@hostname line (full address stays on tooltip), pin badge (IconFont  + HostRowViewModel.PinCount reading Host.PinnedPaths.Count) only when >0, tag chips bottom-left, LastConnectedText [ObservableProperty] slot bottom-right (EMPTY — wave 4 fills it), keep warn Badge/VaultBadge/✓/ pencil→OpenHostPaneCommand. Click selects, double-click connects. Responsive card widths.
  • Update layout + behavioral tests; add coverage for flattening order, no-groups invariant, collapse, filter composition, monogram stability.

Wave 3 — NOT STARTED: drawer + pinned folders UI

  • Restyle src/DodoSSH.Client.App/Views/HostDrawer.axaml to the mock: width 304→320, section labels ADDRESS / GENERAL / CONNECTION / QUICK ACCESS (10px semibold, letterspacing 1.2), "Saving to {vault} vault" inline picker (same gating: creating only + >1 writable vault), address field first (mono, accent inset border), Name/group/tags/notes, username + 78px port on one row, auth ComboBox, relay checkbox card — KEEP the honest copy including "(not wired up yet)" (HostDrawer.axaml:516-535); do not adopt the mock's relay text. Footer: Save host (accent/gradient, flex) + Cancel (92px ghost).
  • QUICK ACCESS editor: rows (folder glyph  via IconFont, mono path, ✕ remove), mono add field + add button, hint sentence exactly: "Pinned folders appear above the terminal for this host." Editor state on VaultViewModel following the editorTagIds pattern; enforce HostSecret.MaxPinnedPaths/MaxPinnedPathLength before TryValidate.
  • Detail pane (non-editing) should also show pinned paths read-only (design only shows the editor; match the pane's existing fieldrow idiom).
  • Terminal pin strip: chip row above the terminal for the connected host's pins, visible only when the active tab's host has pins. Clicking opens the SFTP tab with the remote pane navigated to that path for THAT host — needs an "open SFTP at path for host X" entry point on the transfers side (extend what the context menu's "Browse files" already does; see TransfersViewModel). SFTP is a second authenticated connection (documented, fine). Tab→host matching today is by label (MainWindowViewModel.RefreshConnectedHosts ~3179) — reuse that convention and its documented tradeoff.
  • Android: the field syncs; phone editor UI deferred (document in wave 5).

Wave 4 — NOT STARTED: last connected + quick connect

  • Last connected per approved decision 4. Read via VaultSession.ConnectionLog (ConnectionLogSecret: HostId?, StartedAt; entries written once at close). Compute on hosts screen activation + on TerminalWorkspace.SessionEnded; fill HostRowViewModel.LastConnectedText ("just now", "N min ago", "N hr ago", "N days ago"); 1-minute restring tick while the screen is visible; NO background log re-read (the log is deliberately not on the sync loop — LogsViewModel.cs:138-143). Precedent: MainWindowViewModel.RecentConnections ~1113.
  • Quick connect restyle (src/DodoSSH.Client.App/Views/QuickConnect.axaml + MainWindowViewModel ~1576-1730): card 520→~640 wide, radius 14, mono prompt >_ styling, row = 8px live dot (IsConnected exists on the row VMs' hosts) + mono name + mono address + auth word right (credential/key/password — NOT the mock's SSH/SFTP constant). Selected row fills Accent with white text. Footer hint kept.

Wave 5 — NOT STARTED: docs + full verification

  • New v5 section in docs/design-import-gaps.md, same format as v2/v3/v4: what shipped, then a table of mock elements refused/deviated (amber dot; relay copy; card-as-link; SSH/SFTP kind column; collapse persistence — in-memory, settings.json stays two scalars by decision; phone pinned-folder editor deferred; Collapse All placement; "sidebar" claim dropped from the quick-access hint). Fix the stale font row at ~line 304. Note the v3→v5 reversal (sections replace breadcrumbs) and the v4 reversal (chips + address back on the card) explicitly, with the user's approval as the reason.
  • Full dotnet build DodoSSH.slnx + ALL test projects. LF check over every touched file.

Conventions that bit or will bite

  • LF everywhere — one CRLF file fails the build via IDE0055; merges/agents reintroduce it. Verify by raw bytes, not shell grep (two agents got false readings from shell quoting).
  • Single test: dotnet test <proj> -- --filter-method '<name>' — plain --filter is silently ignored.
  • Essay-style comments are load-bearing; never leave one asserting something a change made false.
  • PublicAPI.Unshipped.txt analyzer exists only for DodoSSH.Contracts (not touched so far).
  • Don't run two dotnet builds concurrently (shared artifacts dir) — serialize agent builds.
  • The repo's honesty rule: nothing in the UI claims what the code doesn't do. This killed the mock's relay copy, amber dot, and SSH/SFTP kind column — keep applying it.

Suggested resume sequence

  1. git status / git diff --stat; assess wave 2's actual state (complete/partial/absent).
  2. Finish or re-dispatch wave 2; build + layout & app tests green before proceeding.
  3. Waves 3 → 4 sequentially (both touch VaultViewModel), each verified by build + tests.
  4. Wave 5 (docs + full sweep). Then review the whole diff, and commit in reviewable slices (suggested: domain / theme+fonts / hosts screen / drawer+pins / ago+palette / docs).
  5. Delete HANDOFF-hosts-v5.md and hosts-v5-design-spec.md before the branch is merged — they are working notes, not repo docs.