Public Access
Record v5 in the design-import log, and let the working notes go
This commit is contained in:
@@ -1,168 +0,0 @@
|
|||||||
# 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 build** — `HostSecret.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 log** — `max(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 build`s 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.
|
|
||||||
@@ -165,6 +165,84 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference
|
|||||||
> | **Show more ⌄** | Not drawn as a disclosure. What it would hide — notes, the relay switch, forgetting the host key — is in the editor, one press away, and a second fold inside a pane that already scrolls is a second place for a field to be missing from. |
|
> | **Show more ⌄** | Not drawn as a disclosure. What it would hide — notes, the relay switch, forgetting the host key — is in the editor, one press away, and a second fold inside a pane that already scrolls is a second place for a field to be missing from. |
|
||||||
> | **Port Forwarding** in the sidebar | Nothing, for the third time in this document. |
|
> | **Port Forwarding** in the sidebar | Nothing, for the third time in this document. |
|
||||||
> | The host grid's toolbar avatar, share and tag-filter controls | Omitted, as in v3 and for the same reasons. |
|
> | The host grid's toolbar avatar, share and tag-filter controls | Omitted, as in v3 and for the same reasons. |
|
||||||
|
>
|
||||||
|
> ## The desktop's v5 — hosts, restyled, and two reversals
|
||||||
|
>
|
||||||
|
> A fifth pass, and the first that undoes as much of its own predecessors as it adds: a re-theme both heads
|
||||||
|
> share, pinned folders as a full feature, a last-connected time read off the connection log, and two pieces
|
||||||
|
> of earlier passes put back on the user's own instruction rather than kept.
|
||||||
|
>
|
||||||
|
> **The palette went purple, and three fonts are embedded that were only ever named before.** The shared
|
||||||
|
> scheme moves from v3's near-black-and-blue to a purple accent (`#5D42DE`, with a gradient and a glow of its
|
||||||
|
> own) over the same near-black canvas, and both heads recolour together — `Palette.axaml` is shared, and a
|
||||||
|
> colour is not allowed to mean something different on the two of them. What changed on the desktop alone is
|
||||||
|
> the type: Montserrat, JetBrains Mono and Material Icons are embedded as `AvaloniaResource` in the Shell
|
||||||
|
> project rather than requested by name and left to whatever the machine happens to have, the way `WithInterFont`
|
||||||
|
> alone used to leave Inter registered but unused. Montserrat is the default sans now, with Inter kept as its
|
||||||
|
> fallback rather than the whole answer; `MonoFont` gains JetBrains Mono at the front of its own list, ahead of
|
||||||
|
> the system-mono stack that is still behind it for a glyph JetBrains Mono does not cover. Android recolours
|
||||||
|
> with the shared palette and keeps its own default sans — fonts are per-head by decision, only colour is
|
||||||
|
> shared.
|
||||||
|
>
|
||||||
|
> **Flat sections replace the grid of group cards and the breadcrumb trail — reversing v3, on the user's own
|
||||||
|
> approval rather than a defect found in it.** v3's grid held one level of the group tree at a time, opened by
|
||||||
|
> a double-click, with a trail above it saying where you were; that model is retired outright rather than
|
||||||
|
> folded into anything; every group is now a heading on one flat board, in label order, exactly the flattening
|
||||||
|
> the phone's `SidebarRows` has always drawn — a headerless board for a groupless keychain, "No group" first
|
||||||
|
> where there is one, a chevron per heading and one Collapse All. Nothing here was found broken; the grid did
|
||||||
|
> what v3 asked for. The user asked for the phone's shape on the desktop too, and decision 2 in
|
||||||
|
> `hosts-v5-design-spec.md` is that request recorded.
|
||||||
|
>
|
||||||
|
> **Tag chips and a mono address line are back on the card — reversing v4, also by approval and not by
|
||||||
|
> defect.** v4 had replaced both with a two-line `ssh, root, pci, eu-west-1` subtitle and moved the address to
|
||||||
|
> the tooltip alone, on the reasoning that a card read while scanning forty machines wants the kind of machine
|
||||||
|
> more than its address. v5's own mock draws chips and an address on every card, and the user chose the mock's
|
||||||
|
> arrangement over v4's when the two were put side by side — so the card now carries both: the full address is
|
||||||
|
> still on the tooltip, as v4 left it, and a truncated mono copy sits under the name as well.
|
||||||
|
>
|
||||||
|
> **Pinned folders are a full feature, not a field with nowhere to be edited.** `HostSecret.PinnedPaths` is an
|
||||||
|
> ordered, deduplicated list merged per path the way `TagIds` is merged per tag, so two machines pinning
|
||||||
|
> different folders on one host both keep theirs. The drawer's QUICK ACCESS section edits it — a row per pin
|
||||||
|
> with a close box, an add field and button, staged on `VaultViewModel.EditorPinnedPaths` the way the tag
|
||||||
|
> picker stages `editorTagIds` — the card shows a pin-count badge once there is at least one, and the detail
|
||||||
|
> pane repeats the list read-only in its own fieldrow idiom. Clicking a pin above a connected terminal opens
|
||||||
|
> the SFTP tab with the remote pane navigated straight to that path, through the same connect path "Browse
|
||||||
|
> files" already used — a second authenticated connection, exactly as every SFTP session here is.
|
||||||
|
>
|
||||||
|
> **Last connected is read off the synced connection log, and it is deliberately narrow about when.**
|
||||||
|
> `max(StartedAt)` per `HostId` is computed on the hosts screen's own activation and again when a session ends,
|
||||||
|
> then restrung into words on a one-minute tick for as long as the screen stays visible — never on the sync
|
||||||
|
> loop, which is the same rule `LogsViewModel` states for the log itself: nobody is watching their own
|
||||||
|
> connection from an hour ago update on a screen they are not looking at. A connected host prints nothing here;
|
||||||
|
> the green dot already answers "right now", and printing an age beside it would be answering a question about
|
||||||
|
> a session that is not the one still running. **It is scoped to the active vault alone**, the same limitation
|
||||||
|
> `LogsViewModel` and `MainWindowViewModel.RecentConnections` already carry — a host filed in a second vault
|
||||||
|
> this session can read gets no ago-text until something on this screen reads more than one vault's connection
|
||||||
|
> history, which nothing does yet. And a session ending can, briefly, still show the previous entry: the
|
||||||
|
> recorder's own write is queued onto a background task rather than made inline, so a re-read landing before
|
||||||
|
> that write drains reads the log as it stood before the session it is reporting on closed. It self-heals on
|
||||||
|
> the next activation or the next session end, and closing the gap outright was judged not worth blocking a
|
||||||
|
> screen transition on the recorder's queue.
|
||||||
|
>
|
||||||
|
> **Quick connect grew to match, and quietly answers a different question than the mock's own row does.** The
|
||||||
|
> card widened from 520 to 640, gained the mock's `>_` prompt styling and a live dot per row the way the hosts
|
||||||
|
> board's own two-state dot works. What it does not draw is the mock's SSH/SFTP kind column — every palette
|
||||||
|
> connection here is SSH, so a constant printed as though it varied would be exactly the kind of decoration
|
||||||
|
> this document has refused before (the terminal's session footer, the snippet's kind badge); the auth word
|
||||||
|
> (credential/key/password) answers a question that is actually different per row instead.
|
||||||
|
>
|
||||||
|
> | v5 element | What ships instead |
|
||||||
|
> | --- | --- |
|
||||||
|
> | The status dot's third, amber state | ◆ **Still two states, on the same reasoning v3 and v4 both gave and v5 was asked to reconsider.** Green means a terminal is open on that host, grey means one is not; nothing here pings a machine, so the amber "reachable but not connected" state has no fact behind it to draw. |
|
||||||
|
> | The relay checkbox's mock copy | The pre-existing sentence, unchanged, "(not wired up yet)" included — restyled into the mock's own nested-card shape (radius 12, a title beside the box rather than under it) but not reworded. The mock's own copy implies a relay this client can dial; it cannot, for the reason `HostDrawer.axaml`'s own remarks give. |
|
||||||
|
> | The host card as a link straight to a terminal | Click still selects, double-click still connects, and the pencil still opens the pane — the mock's card-as-link is not adopted, because multi-select (Ctrl, Shift, the marquee band) depends on a plain click meaning "choose this one" rather than "go". |
|
||||||
|
> | Quick connect's SSH/SFTP kind column | The auth word — credential, key, or password — see above. |
|
||||||
|
> | A collapsed section staying collapsed after a restart | In memory only, for the running session. `settings.json` holds two scalars by decision — the terminal's text size and whether this machine checks for updates on its own — and collapse state is not judged worth a third. |
|
||||||
|
> | QUICK ACCESS's editor, on the phone | **Deferred; the data is not.** `HostSecret.PinnedPaths` is shared, synced and merged on both heads, so a pin made on the desktop reaches the phone and back — Android just has nowhere yet to add or remove one itself. |
|
||||||
|
> | Collapse All beside every section's own collapse chevron | Bound on every heading's view model and shown on only the first — `SidebarGroupHeader.IsFirstBoardSection` is what a virtualised list of sections uses in place of a control of the board's own that would otherwise have to sit above all of them. |
|
||||||
|
> | The QUICK ACCESS hint's claim that pins live in a sidebar | "Pinned folders appear above the terminal for this host." — no sidebar exists on this screen for the sentence to point at, so the shipped hint says where they actually draw. |
|
||||||
|
> | The mock's "Saving to **DodoTech ▾** vault" subtitle, with a picker's chevron inside a sentence | The pre-existing `DrawerSubtitle` wording — the vault's name alone, unchanged by this pass. A chevron inside running text implies the text itself is the control, which it is not: the vault picker is its own element, shown only while creating and only above one writable vault, as it always has been. |
|
||||||
|
> | The design's two deeper text steps, `#6D6F84` and `#5D5F74` | Not added as `Palette.axaml` keys. `TextGhost`, already repicked to `#7C7F98` for v3, is reused everywhere the design reaches for either — a resource nothing yet distinguishes from `TextGhost` is not free to carry, on the same reasoning the border ramp's own remarks give. |
|
||||||
|
|
||||||
Most of it landed. This file is the rest: every element of that design with nothing behind it, which
|
Most of it landed. This file is the rest: every element of that design with nothing behind it, which
|
||||||
project each piece would have to land in, and **what the shipped interface does instead**. That last
|
project each piece would have to land in, and **what the shipped interface does instead**. That last
|
||||||
@@ -301,7 +379,7 @@ field cannot be removed and stays as a permanently refused member; `SyncEndpoint
|
|||||||
| Status bar port forwards | client-ssh | Port forwarding. See below. | Omitted. |
|
| Status bar port forwards | client-ssh | Port forwarding. See below. | Omitted. |
|
||||||
| Status bar `sftp · 2 transfers` | client-app | Nothing now — file transfer is built. What is missing is the count reaching the status bar, which is a screen away from where the queue lives. | Omitted from the status bar. The queue itself is on the FILES screen, with a row per transfer. |
|
| Status bar `sftp · 2 transfers` | client-app | Nothing now — file transfer is built. What is missing is the count reaching the status bar, which is a screen away from where the queue lives. | Omitted from the status bar. The queue itself is on the FILES screen, with a row per transfer. |
|
||||||
| Status bar `locks in 09:41` | client-app | An idle auto-lock. See preferences below. | Omitted. |
|
| Status bar `locks in 09:41` | client-app | An idle auto-lock. See preferences below. | Omitted. |
|
||||||
| IBM Plex Mono / IBM Plex Sans | ui | Shipping the font files as `AvaloniaResource` and registering them. The design loads them from Google Fonts, which a desktop app cannot. | Inter (already embedded) for prose, and the system monospace stack the terminal already names. Named once in `App.axaml` as `MonoFont`, so the substitution is reversible in one place. |
|
| IBM Plex Mono / IBM Plex Sans | ui | Shipping the font files as `AvaloniaResource` and registering them. The design loads them from Google Fonts, which a desktop app cannot. | **Shipped, since v5, and not IBM Plex.** The v5 design asked for Montserrat, JetBrains Mono and Material Icons instead, and all three are embedded as `AvaloniaResource` in the Shell project rather than requested by name and left to whatever the machine happens to have. Montserrat is the default sans, with Inter — the earlier substitution — kept only as its fallback; `MonoFont` puts JetBrains Mono first, ahead of the system-monospace stack the terminal still falls back to. Named once each in `Program.cs` and `Palette.axaml`, so a later design's fonts are still a one-line swap. |
|
||||||
| `⌘K`, `⌥↵` | ui | Nothing; the design is Mac-flavoured. | `CTRL K`. Development is Windows-only today (`docs/platform-flags.md`). |
|
| `⌘K`, `⌥↵` | ui | Nothing; the design is Mac-flavoured. | `CTRL K`. Development is Windows-only today (`docs/platform-flags.md`). |
|
||||||
|
|
||||||
**Also worth knowing:** custom chrome went in as `SystemDecorations="BorderOnly"`, not by extending the
|
**Also worth knowing:** custom chrome went in as `SystemDecorations="BorderOnly"`, not by extending the
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
# Hosts v5 design import — spec and decisions
|
|
||||||
|
|
||||||
Source: Claude Design project `DodoSSH`, file `Hosts.dc.html` (1920x1080 desktop mock).
|
|
||||||
This file holds (a) the agreed decisions, (b) the palette mapping, (c) the raw design HTML at the bottom.
|
|
||||||
|
|
||||||
## Agreed decisions (user-approved)
|
|
||||||
|
|
||||||
1. **Full re-theme**: swap the shared palette (`src/DodoSSH.Client.Shell/Theme/Palette.axaml`) to the new
|
|
||||||
purple/near-black scheme — both heads recolor together. Embed Montserrat + JetBrains Mono +
|
|
||||||
Material Icons font files in the desktop head. Android keeps its default sans (fonts are per-head;
|
|
||||||
only colors are shared by decision); `MonoFont` gains JetBrains Mono at the front of its fallback list.
|
|
||||||
2. **Flat sections**: the desktop hosts screen becomes one collapsible section per group (nesting
|
|
||||||
flattened into one heading per group in label order, exactly like the phone's `SidebarRows`),
|
|
||||||
ungrouped hosts under "No group" first. Per-section collapse + Collapse All. Group cards and the
|
|
||||||
breadcrumb trail leave this screen. Group edit/move/delete move to a menu on the section heading.
|
|
||||||
Collapse state is in-memory only (settings.json stays two scalars by decision).
|
|
||||||
3. **Pinned folders (QUICK ACCESS)**: full feature. `HostSecret.PinnedPaths` (ordered strings,
|
|
||||||
per-path three-way merge like `TagIds`), drawer editor section, pin-count badge on cards, and a chip
|
|
||||||
strip above the terminal for the connected host; clicking a pin opens the SFTP tab with the remote
|
|
||||||
pane at that path (a second authenticated connection, as SFTP always is here).
|
|
||||||
4. **Last connected**: computed from the synced connection log (`max(StartedAt)` per `HostId`) on
|
|
||||||
hosts-screen activation and on `SessionEnded`; relative time ("just now" / "N min ago" / hours/days),
|
|
||||||
restrung on a 1-minute tick while visible, no background re-read.
|
|
||||||
|
|
||||||
## Deliberate deviations from the mock (do NOT implement the mock literally on these)
|
|
||||||
|
|
||||||
- **Relay checkbox copy keeps the honest wording** including "(not wired up yet)" — the design copy
|
|
||||||
implies a working relay; the client dial path does not exist yet. Keep the current sentence structure
|
|
||||||
in HostDrawer.axaml:516-535.
|
|
||||||
- **Status dot stays two states** (green = terminal open, grey = not). The design's amber has no
|
|
||||||
truthful meaning; nothing probes hosts.
|
|
||||||
- **Card click selects** (multi-select depends on it), double-click connects, pencil opens the detail
|
|
||||||
pane. The mock's card-as-link-to-terminal is not adopted.
|
|
||||||
- **Quick connect keeps the auth word** (credential/key/password) instead of the mock's SSH/SFTP kind
|
|
||||||
column — every palette connect is SSH, and a constant printed as a reading is refused in this repo.
|
|
||||||
- **Find box stays** (restyled) — it is the only search that crosses group boundaries.
|
|
||||||
- "New group…" moves to the foot of the Group ▼ filter flyout (the mock has no group-creation control).
|
|
||||||
- The QUICK ACCESS hint sentence ships as what is true: "Pinned folders appear above the terminal for
|
|
||||||
this host." (no "sidebar" claim).
|
|
||||||
|
|
||||||
## Palette mapping (design rgb → Palette.axaml key)
|
|
||||||
|
|
||||||
| Key | Old | New |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| CanvasColor | #0E1220 | #05050A (design body rgb(5,5,10)) |
|
|
||||||
| Chrome | #111629 | #10111E (window rgb(16,17,30)) |
|
|
||||||
| Sidebar | #0F1426 | #0D0D16 (drawer rgb(13,13,22)) |
|
|
||||||
| Panel | #111629 | #10111E |
|
|
||||||
| Raised | #151B31 | #101019 (card rgb(16,16,25)) |
|
|
||||||
| Field | #0B0F1D | #101019 (inputs rgb(16,16,25)) |
|
|
||||||
| Chip | #16204A | #1A1A28 (rgb(26,26,40)) |
|
|
||||||
| Hover/Active/ChromeHover | — | derive slightly lighter than their surfaces, e.g. Hover #14151E, Active #1E1E2C, ChromeHover #1E1E2C |
|
|
||||||
| Border | #1A2138 | #1E1E2C (rgb(30,30,44)) |
|
|
||||||
| BorderMid | #232B45 | #24243A (rgb(36,36,58)) |
|
|
||||||
| BorderHover | #2E3859 | #2C2C3E (rgb(44,44,62)) |
|
|
||||||
| BorderSubtle/BorderFaint | — | keep one step darker than Border, e.g. #17172", use judgment |
|
|
||||||
| Text | #FFFFFF | #FFFFFF |
|
|
||||||
| TextDim | #B4BCD4 | #EDEDF5 (rgb(237,237,245)) |
|
|
||||||
| TextFaint | #7B849F | #9C9EB4 (rgb(156,158,180)) |
|
|
||||||
| TextGhost | #5A6280 | #7C7F98 (rgb(124,127,152)); the design also uses #6D6F84 and #5D5F74 for deeper steps — add keys only if needed |
|
|
||||||
| AccentColor | #5B8CFF | #5D42DE (rgb(93,66,222)) |
|
|
||||||
| AccentText | #8FA7FF | #A78BFA |
|
|
||||||
| AccentInk | #0E1220 | #FFFFFF (text on accent is white in this design) |
|
|
||||||
| AccentSoft/AccentWash | 0.35/0.06 | same opacities over #5D42DE |
|
|
||||||
| NEW: AccentGradient | — | LinearGradientBrush 180deg #8D51E7 → #5D42DE |
|
|
||||||
| NEW: AccentGlow | — | for BoxShadow use: 0 0 22 0 #8D51E7 at ~35% alpha |
|
|
||||||
| Live | #3DDC97 | #34D399 (rgb(52,211,153) — the design's own green) |
|
|
||||||
| Warn | #FFB454 | #F59E0B (rgb(245,158,11)); WarnText #F5B942 |
|
|
||||||
| Danger | #E86A6A | keep |
|
|
||||||
|
|
||||||
Radii: cards 12, controls 10, small chips 5-6. Field/control height 40. Primary buttons use
|
|
||||||
AccentGradient + glow.
|
|
||||||
|
|
||||||
Monogram avatar hue pairs (bg/fg), assigned by stable hash of host label:
|
|
||||||
violet #241C4F/#A78BFA · green #103A2F/#34D399 · amber #423211/#F5B942 · gray #1E1E2C/#9C9EB4.
|
|
||||||
|
|
||||||
## Raw design HTML (Hosts.dc.html)
|
|
||||||
|
|
||||||
The `<sc-for>`/`{{ }}` bits are the design tool's templating; `style-hover` is a hover style.
|
|
||||||
Sample data lives in the script block at the bottom.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div data-screen-label="Hosts" style="width:1920px;height:1080px;...background:rgb(16,17,30);...">
|
|
||||||
<dc-import name="TitleBar" on-search="{{ openQuickConnect }}" hint-size="100%,53px"></dc-import>
|
|
||||||
<div style="display:flex;flex-direction:row;flex-grow:1;min-height:0;">
|
|
||||||
<dc-import name="NavRail" active="hosts" mode="ssh" hint-size="255px,100%"></dc-import>
|
|
||||||
<div style="flex-grow:1;...padding:34px 36px;...">
|
|
||||||
<!-- Header row: title + count chip | Group ▼, Tag ▼, + New host -->
|
|
||||||
<span style="font-weight:700;font-size:33px;letter-spacing:-0.5px;color:#fff;">Hosts</span>
|
|
||||||
<div style="width:34px;height:30px;border-radius:9px;background:rgb(26,26,40);">10</div>
|
|
||||||
<!-- Group / Tag: h40, radius 10, inset 1px rgb(44,44,62) border, hover border accent -->
|
|
||||||
<!-- + New host: h40, radius 10, AccentGradient, glow, white 13.5 semibold -->
|
|
||||||
|
|
||||||
<!-- Section heading row (margin 32 0 16): "No group · 6" 12.5 medium rgb(124,127,152)
|
|
||||||
right side: collapse icon + "Collapse All" / "Collapse" 12 medium rgb(93,95,116) -->
|
|
||||||
|
|
||||||
<!-- Host grid: 3 columns, gap 14 -->
|
|
||||||
<!-- Host card: h120, radius 12, bg rgb(16,16,25), inset 1px rgb(30,30,44), hover border accent
|
|
||||||
- avatar 32x32 radius 9 at 21,20 with 2-letter mono bold 12.5 monogram
|
|
||||||
- name 17 bold white + 9px status dot, at 68,18
|
|
||||||
- addr mono 12.5 rgb(109,111,132) + pin badge (h19 radius 5 bg rgb(26,26,40),
|
|
||||||
push_pin icon 11 + count 10.5) at 68,39
|
|
||||||
- tag chips bottom-left (h21 radius 5 bg rgb(30,30,44), 11 medium rgb(156,158,180))
|
|
||||||
- ago text bottom-right 12 rgb(93,95,116) -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Drawer (sc-if drawerOpen): w320, bg rgb(13,13,22), border-left rgb(26,26,40), padding 24 22
|
|
||||||
- "New host" 20 bold; subtitle "Saving to **DodoTech ▾** vault" 12.5
|
|
||||||
- section labels: 10px semibold letter-spacing 1.2 rgb(93,95,116): ADDRESS / GENERAL / CONNECTION / QUICK ACCESS
|
|
||||||
- ADDRESS: h42 radius 10 field with accent 1.5px inset border, ">_" prefix, mono value, blinking caret
|
|
||||||
- GENERAL: Name field h40; group dropdown ("No group" + expand_more); Add tag field + 40x40 add button; Notes h58
|
|
||||||
- CONNECTION: Username field + w78 port field ("22" mono) on one row; auth dropdown
|
|
||||||
("Password (ask each time)" + expand_more); relay checkbox card (radius 12, 18x18 checkbox,
|
|
||||||
title 13 semibold + body 11.5 rgb(124,127,152))
|
|
||||||
- QUICK ACCESS: per pin: h33 radius 9 row, folder icon + mono path 12.5 + close ✕;
|
|
||||||
add row: mono field "/path/to/folder" + 36x36 add button;
|
|
||||||
hint 11.5 rgb(93,95,116)
|
|
||||||
- footer: Save host (flex-grow, h44, AccentGradient + glow) + Cancel (w92 h44 ghost) -->
|
|
||||||
|
|
||||||
<!-- Quick connect (sc-if quickConnectOpen): full-window scrim rgba(5,5,10,0.6) + blur(3px),
|
|
||||||
card w640 radius 14 bg rgb(16,17,30) top-offset 160
|
|
||||||
- header row: ">_" accent mono + mono input "deploy@" + caret + "↵ connect · esc close"
|
|
||||||
- rows h44 radius 9: status dot 8px, name mono 13, addr mono 11.5, kind right
|
|
||||||
(selected row bg rgb(93,66,222), texts white) -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Sample data (for tone, not to be shipped): hosts like `prod-api-01 / deploy@203.0.113.14 / pins 4 /
|
|
||||||
tags production,api / 2 min ago`; pinned = `/var/www/app`, `/etc/nginx`, `/var/log/pm2`.
|
|
||||||
Reference in New Issue
Block a user