Say what the docs describe, in the order they describe it

A tidy across docs/, and every change is either something that had drifted
from the code or something a reader would trip over. Nothing here is a rewrite:
the audit documents keep their audit-time text, because that is what the scope
was decided against, and corrections are marked in place the way each file
already marks them.

The one that matters is crypto.md. Section 4.3's resourceType table stopped at
13 while CryptoSpec.AadResourceType has carried ConnectionLogEntry = 14,
ActivityLogEntry = 15 and ObjectStore = 16 since the logs and the buckets
shipped — and items have been sealed under all three. That file is normative, so
a table three item kinds behind the code is the sort of gap somebody
reimplementing this would build against. Nothing on the wire or on disk changes;
the list now says what the code has been doing, and it notes the thing that
makes it worth reading carefully — 14, 15 and 16 are the first members that are
not a fixed offset from their SyncEntityType counterparts, because 12 and 13
closed a hole and pushed the two enums apart.

manual-checks.md had Phase 3 in an order nobody could follow: 3.2b, then 3.2a,
then 3.4a, then 3.3. The letters are not the problem — they exist so a check can
keep its number for life, which is what lets HostGridTests cite 7.6 and
platform-flags.md cite 3.6-3.8 — so the sections are reordered and the numbers
are untouched, and the intro now says the convention out loud rather than
leaving the next person to infer it from two orphaned letters. Phases 8 and 9
got the horizontal rule every other phase has. The Phase 3 preamble still said
"a sidebar that now draws headings", which has been two different interfaces for
a while: the desktop's grid of cards and the phone's flat list.

7.6 cited MovingAHostToAGroup_FilesItAndLeavesItSelected, which was renamed in
the commit before this one. Every other Type.Member citation across all six
files was checked against src/ and tests/ the same way; those two were the only
ones pointing at nothing.

design-import-gaps.md's v3 section described the grid of cards without saying it
holds one level, and its Groups row still said the sidebar emits one heading per
group — true of the phone and not of the desktop. The team-groups row said a
host a teammate filed "shows under UNGROUPED", which is now only half the story
and the interesting half is why: the chip resolves through groupsById, which is
every readable vault, while the level resolves through Groups, which is the
active vault alone — so the card sits at the outermost level with its group's
name still on it. Verified against both call sites rather than reasoned about.

android-port.md's destination inventory is the desktop as it stood when the
audit was taken and is deliberately left that way, with a note at its head
saying so and two inline corrections where a reader would otherwise be misled:
the rail lost FILES and S3 to fixed tabs, and the hosts sidebar became a grid —
which is exactly why the headings this head draws are the phone's alone now.

adding-hosts-on-the-phone.md is a plan marked built, so its two open questions
are answered in place: GroupId did get into the merge test's with block, and the
server test that asserts a plaintext ParentId is refused survived under a name
that says what it means. Its "found on the way" note is marked still open,
because it is: LocalCacheProtector.For has no arm for ConnectionLogEntry,
ActivityLogEntry or ObjectStore, so ConflictStore.Record still throws for all
three. That outlived the phases that shipped them, which is the drift a note
like that exists to prevent.

No code changed, so the suite is untouched at 1522 passing.
This commit is contained in:
2026-08-04 10:15:02 +02:00
parent 2a56ae3efb
commit 27bb1deb5d
5 changed files with 83 additions and 33 deletions
+38 -27
View File
@@ -16,6 +16,12 @@ Three constraints put things on this list, and they are worth knowing before add
Each item says what to do, what a pass looks like, and what a failure would mean.
**On the numbering.** A check keeps its number for life, because code comments and other documents cite them
`HostGridTests` sends a reader to 7.6, `platform-flags.md` to 3.63.8. A check inserted later therefore
takes a letter rather than pushing its neighbours along: 3.2a and 3.2b sit between 3.2 and 3.3 and always
will. Add in the same way, and keep each one next to the check it belongs beside; a gap in the numbers means
a phase had nothing left for a person to do, which is the good outcome rather than an omission.
---
## Phase 1 — the shell and the tab strip
@@ -290,11 +296,12 @@ surface. Use the ← button, or drag the file to the local pane first.
## Phase 3 — Host groups and snippets
Two synced item kinds, a sidebar that now draws headings, and one new frame between the host process and the
renderer. The data half of all of that is covered: the payloads round-trip, the server refuses the plaintext
fields, the sidebar's grouping and the snippet policy are in `ShellFlowTests`, and both new screens are
measured. What is left here is the part that only exists inside a WebView, plus the two-machine cases no
single-process test can reach.
Two synced item kinds, two ways of drawing a group — the desktop's grid of cards, one level at a time, and
the phone's flat list under headings — and one new frame between the host process and the renderer. The data
half of all of that is covered: the payloads round-trip, the server refuses the plaintext fields, both ways
of drawing a group and the snippet policy are in `ShellFlowTests`, and both new screens are measured. What is
left here is the part that only exists inside a WebView, plus the two-machine cases no single-process test
can reach.
### 3.1 A keychain with no groups looks exactly as it did
@@ -334,19 +341,6 @@ a filed host is worse than either: it is the box answering "no host matches that
keychain has got. A full-width bar with a chevron between the cards is the old grouping coming back through
`SidebarRows`; the desktop grid binds `VisibleHosts`. See `HostsScreen.axaml`.
### 3.2b Making something while standing inside a group
Open a group, then press **+ NEW HOST**, and afterwards **+ NEW GROUP**.
**Pass:** the host editor opens with that group already chosen in its group picker, and the saved host is on
the screen it was made on rather than somewhere the trail is not. The group editor likewise opens with that
group as its parent, so the new group is a card inside the one that is open.
**Failure means:** anything created inside a group disappearing the moment it is saved. That is the papercut
a level-at-a-time grid comes with, and `NewHost` / `NewGroup` are where it is answered. Note the deliberate
difference between them: the host editor also takes a merely *selected* card as its group, the group editor
takes only the group that is open.
### 3.2a A group inside a group, and the way back out
Make two groups and file one under the other with the parent picker in the group editor.
@@ -361,17 +355,18 @@ Pressing the **middle** crumb goes back one level rather than all the way out.
the flat `Groups` is the phone's and the lookups'. A group that cannot be reached at all is worse and is the
case `EffectiveParents` promotes: see 3.4a.
### 3.4a A parent that dangles, or a cycle · **needs two machines**
### 3.2b Making something while standing inside a group
Rare, and the reason it is here is that the repair for both is inside the group's own editor, which is
opened from its card. On A, file group X under group Y and sync. On B, sync, delete Y, sync. Back on A, sync.
For the cycle: with both machines offline, file X under Y on A and Y under X on B, then sync both.
Open a group, then press **+ NEW HOST**, and afterwards **+ NEW GROUP**.
**Pass:** in both cases every group still has a card at the outermost level, so every one of them can be
opened and its parent cleared. Nothing is missing from the grid, and the trail never loops.
**Pass:** the host editor opens with that group already chosen in its group picker, and the saved host is on
the screen it was made on rather than somewhere the trail is not. The group editor likewise opens with that
group as its parent, so the new group is a card inside the one that is open.
**Failure means:** a group drawn nowhere is a group that can never be repaired — the fix is locked inside the
state that needs fixing. See `VaultViewModel.EffectiveParents`.
**Failure means:** anything created inside a group disappearing the moment it is saved. That is the papercut
a level-at-a-time grid comes with, and `NewHost` / `NewGroup` are where it is answered. Note the deliberate
difference between them: the host editor also takes a merely *selected* card as its group, the group editor
takes only the group that is open.
### 3.3 Deleting a group with hosts in it
@@ -394,6 +389,18 @@ the group picker shows "(a group that is no longer here)" and *keeps it selected
**Failure means:** if the picker opened on "No group", saving has just unfiled the host — quietly, as a side
effect of an unrelated edit. That is the case `BuildGroupChoices` adds the placeholder for.
### 3.4a A parent that dangles, or a cycle · **needs two machines**
Rare, and the reason it is here is that the repair for both is inside the group's own editor, which is
opened from its card. On A, file group X under group Y and sync. On B, sync, delete Y, sync. Back on A, sync.
For the cycle: with both machines offline, file X under Y on A and Y under X on B, then sync both.
**Pass:** in both cases every group still has a card at the outermost level, so every one of them can be
opened and its parent cleared. Nothing is missing from the grid, and the trail never loops.
**Failure means:** a group drawn nowhere is a group that can never be repaired — the fix is locked inside the
state that needs fixing. See `VaultViewModel.EffectiveParents`.
### 3.5 A grouped host stays editable on an older build · **needs two builds**
Only worth doing before a release that ships alongside an older client. A host filed into a group is written
@@ -753,7 +760,7 @@ wheel. The pointer has to keep moving inside the band — a stationary pointer g
**Failure means:** headless Avalonia cannot synthesise a platform drag, so the picking up, the cursor and
the drop are covered by nothing. What *is* automated is the decision each drag event takes —
`HostGridTests.TheGroupCardsAreWhatAcceptsADroppedHost` raises a real `DragOver` over both kinds of card —
and the write at the end, `ShellFlowTests.MovingAHostToAGroup_FilesItAndLeavesItSelected`.
and the write at the end, `ShellFlowTests.MovingAHostToAGroup_FilesItAndTakesItOffTheLevelItCameFrom`.
### 7.7 A click still selects, and a double click still connects
@@ -804,6 +811,8 @@ add a bucket — then come back.
**Failure means:** the screen has gone back to copying the vault's lists once at unlock. Covered by
`TheTransfersScreen_FollowsTheVaultsHostList`; this is the version of it with a real picker in front of it.
---
## Phase 8 — Adding hosts on the phone
Nine checks, and the reason there are nine rather than none is worth stating: **the layout suite cannot see
@@ -902,6 +911,8 @@ Walk the sheet's two rows, the editor's fields, both pickers, the checkbox and t
**Failure means:** a control took its size from its glyph. The desktop suite's only tap-target assertion has
a floor of 20 pixels — set for a mouse — so copying it here would certify a target no thumb can hit.
---
## Phase 9 — Tag chips and the picker
The desktop half is measured by the layout suite, which now seeds three tags and puts all of them on one