Files
archy/.planning/phases/01-federation-mesh-hardening/01-17-SUMMARY.md

16 KiB
Raw Permalink Blame History

phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, metrics
phase plan subsystem tags status requires provides affects tech-stack key-files key-decisions requirements-completed metrics
01-federation-mesh-hardening 17 ui
vue
responsive
tailwind
vitest
transport
security-signal
complete
phase plan provides
01-federation-mesh-hardening 14 Paid Files row treatment and the Cloud.vue/PeerFiles.vue shapes this plan audited
A recorded, browser-measured mobile verdict for all five transport-pill render sites in the cloud surfaces
flex-wrap + shrink-0 on the Cloud.vue peer-card badge row, so the transport badge drops to a second line intact instead of having its own text broken mid-label at 320px
neode-ui/src/views/__tests__/TransportPills.test.ts — a site-specific pin that fails the build if any transport pill is removed
A stated, reasoned decision (with evidence) that the file-level rows do NOT carry a per-file transport pill
cloud
peer-files
added patterns
Overflow-only responsive fix: flex-wrap on a badge row is inert whenever the row already fits, so it changes nothing at any width where the layout was already correct. Verified by diffing measured pill geometry pre/post at 390/320/1440 — identical everywhere except the case being fixed.
shrink-0 on the security-relevant badge: when a row must degrade, degrade by wrapping the whole badge rather than by compressing the badge until its label breaks. The transport WORD is the signal; the milliseconds are not.
Pin tests keyed to a render site, not to a string: each assertion targets a shape only that site produces (the peer card's `FIPS · 0.4s` form, the `hidden md:block` title block, the `md:hidden` copy), proven bidirectionally by deleting one pill at a time.
created modified
neode-ui/src/views/__tests__/TransportPills.test.ts
neode-ui/src/views/Cloud.vue
Cloud.vue's aggregated Peer Files rows do NOT get a per-file transport pill. Transport is measured per peer per browse (`cloud.peer-browse:<onion>.transport`), never per file; the aggregated list interleaves files from many peers sorted by filename, so a per-row pill would repeat one peer-level reading dozens of times while implying a per-file measurement the app never took (T-01-78). Each row already carries the peer-name pill and taps straight through to PeerFiles.vue, where the peer-level pill is shown once, correctly.
Cloud.vue's Paid Files rows do NOT get a transport pill — the stronger case. These rows come from the local purchase cache (content.owned-list); the bytes are already on this node and the current session may never have browsed that seller at all. Any pill here would be a stale or fabricated claim, a direct violation of the transparency prohibition.
PeerFiles.vue's per-file card bodies do NOT get a transport pill. Every file on that page came from the same peer over the same transport, which the header pill already states once.
Only Cloud.vue was modified. PeerFiles.vue passed the audit at both narrow widths and was left untouched — the plan forbids changing a site the audit passed.
UIFIX-01
duration tasks files-changed tests-added
~1h20m 2 2 13

Phase 1 Plan 17: Keep the FIPS/Tor pills forever, and make sure a phone shows them — Summary

Audited all five transport-pill render sites in a real Chromium at 390×740 and 320×640 with RPC interception supplying transport data; fixed the one site that degrades badly at the narrowest supported width; and pinned every pill with a site-specific vitest suite so removing one fails the build.

Method

The mock backend's content.browse-peer returns no transport field, so no pill renders against it. Rather than change the mock (out of this plan's file boundary), the audit drove the already-running :8100 dev preview through Playwright with page.route() interception on /rpc/v1, injecting transport: 'fips' | 'tor' | <absent> per peer. That renders the real components with the real CSS and gives measurable geometry (getBoundingClientRect, scrollWidth vs clientWidth, computed display) instead of a subjective look. Harness lived in the scratchpad; nothing was written to the repo.

Per-site audit table

# Site File and line Renders on mobile Legible Action
S1 Peer card badge row, Folders tab Cloud.vue 311331 (pre-fix) Yes — 90×24 px pill, in viewport at both 390 and 320 Conditionally — fine with real trust labels (3669 px slack at 320), but the row cannot wrap, so a longer trust label squeezes the badge until its own text breaks mid-label FIXEDflex-wrap on the row, shrink-0 on the transport badge
S2 Peer Files aggregated rows Cloud.vue 199218 n/a — no transport pill exists at any width n/a No change — decision recorded below
S3 Paid Files rows Cloud.vue 150178 n/a — no transport pill exists at any width n/a No change — decision recorded below
S4 Header pill (desktop copy + md:hidden mobile copy) PeerFiles.vue 838 Yes — the md:hidden copy renders at 390 and 320 (41×20 px, 183253 px of slack); the hidden md:block desktop copy correctly does not Yes — labels are ≤ 4 chars (FIPS/Mesh/LAN/Tor), no truncation, no overlap No change — site passed
S5 Per-file card body PeerFiles.vue 152232 n/a — no transport pill exists at any width n/a No change — decision recorded below

Long-name robustness was exercised for S1/S2/S3/S4 with 5456 character peer names and 6570 character filenames: the peer name truncates in its own row above the badge row and never touches the pills, and the measured pill geometry was identical to the short-name run.

grep -rn "FIPS" src --include=*.vue confirms S1 and S4 are the only cloud-surface transport-pill render sites — CloudFolder.vue, named speculatively in the original todo, has none.

The S1 defect, measured

At 320×640 the badge row's container is 238 px. With real trust values the row fits:

Case trust label badge row height badge wrapped? verdict
baseline trusted TOR · 0.1s (90 px) 24 px no fits, 69 px slack
slow peer trusted TOR · 120.0s (105 px) 24 px no fits, 54 px slack
observer observer TOR · 120.0s 24 px no fits, 44 px slack
unverified unverified TOR · 120.0s 24 px no fits, 36 px slack
longer trust label pending verification TOR · 120.0s 40 px yes FAILS — badge compressed to 96 px, its text broken across two lines as TOR · / 120.0s

The row had flex items-center gap-2 with no wrapping and no shrink-0, so flexbox's only degradation path was to shrink both pills until their labels wrapped internally. That is the "truncates into meaninglessness" failure the plan forbids, and it lands on the transport badge — the security signal — not on something decorative.

The fix, measured

flex-wrap on the row + shrink-0 on the transport badge. Same case, after:

Case badge width badge wrapped? on same line as trust? row height
pending verification + TOR · 120.0s 105 px (full natural width) no no — dropped to line 2 intact 56 px

Every other case is byte-identical before and after (same row width, row height, trust width, badge width, same line). flex-wrap only takes effect when the row would otherwise overflow, which is exactly why it is safe.

Site decisions (the two open questions, settled)

Peer Files aggregated rows (S2): NO per-file pill. Transport in this codebase is a peer-level, per-browse reading — cloud.peer-browse:<onion> stores one transport and one latencyMs for the whole browse, and peerTransport(onion) reads exactly that. The aggregated list merges files from every peer and sorts by filename, so a per-row pill would render the same peer-level fact once per file (40 files from one FIPS peer ⇒ 40 identical pills) while implying a per-file measurement that was never taken — the precise claim threat T-01-78 forbids. At 320 px it would also have to compete with a filename that already truncates and the existing peer-name pill. The row already names its peer and taps through to PeerFiles.vue, where the peer-level pill is shown once and correctly. The existing peer-level pill is sufficient for these rows.

Paid Files rows (S3): NO pill. Stronger still. These rows come from the local purchase cache (content.owned-list) — the bytes are already on this node and were filed into Photos/Music/Documents at purchase time. There is no live transport for them, and the session may never have browsed that seller at all. Any pill here would be stale or fabricated. The honest treatment is the one already shipping: none.

PeerFiles per-file cards (S5): NO pill, for the same reason at smaller scale — every file on that page came from one peer over one transport, already stated once in the header.

All three decisions are pinned as absence assertions in the test file, with a comment stating that they encode a recorded decision and that deliberately adding a pill means updating this summary and the test together.

The pin (TransportPills.test.ts, 13 tests)

Nothing in the repo pinned these pills before. The suite opens with a plain-English header stating that the pills are a permanent, user-requested feature and that a failure here most likely means someone removed one, not that the test is stale.

Assertions are keyed to shapes only one site produces: the peer card's FIPS · 0.4s form (S1), the hidden md:block title block (S4 desktop), the md:hidden class (S4 mobile). Labels and colours are asserted against PeerFiles.vue's canonical transportPill mapping across all four transports (fips/mesh/lan/tor) rather than a duplicated table — grep -c 'transportPill' neode-ui/src/views/PeerFiles.vue is 9, unchanged (that file was not edited at all).

Unknown-transport cases assert no pill is fabricated at either site and that S1 keeps its existing Peer Node not-known treatment.

One test-isolation subtlety worth knowing: cloud.peer-browse:<onion> is a persist: true key that snapshots into sessionStorage, which outlives a per-test createPinia(). Without sessionStorage.clear() in beforeEach the transport from an earlier test leaks forward and the unknown-transport case passes against a stale FIPS reading. That is now explicit in the file.

Task 2 re-check

390×740 and 320×640, fixed site (S1): re-measured after the fix. In the fitting cases the pill geometry is identical to pre-fix; in the overflow case the badge now renders at its full 105 px on a second line with TOR · 120.0s intact and legible. Long-name case forced by injecting 5456 character peer names through the RPC interceptor, and the worst-case trust label (pending verification) forced by editing the rendered text node in the page — neither the mock backend nor the source was changed to produce them, exactly as the plan directs.

1440×900 desktop, per changed site: measured pill geometry was diffed pre-change vs post-change for every captured surface. Cloud.vue S1 at 1440: pill identical at 90×24 px, x=771 / x=1159, y=331 — the only textual difference is the live latency figure (0.1s vs 0.2s), which is a measurement, not layout. S2, S3 and PeerFiles.vue at 390/320 were byte-identical. Desktop rendering at the one changed site is unchanged. PeerFiles.vue was never modified.

Deliberate-removal check — the pin proven, bidirectionally:

Pill removed Result Site-specificity
Cloud.vue peer-card transport badge (706 chars, plus de-branching the orphaned v-else so the file still compiles) 3 tests failed, message: peer card transport pill is missing — see the header of this file S4 PeerFiles tests still passed
PeerFiles.vue md:hidden mobile pill copy (236 chars) 5 tests failed, message: the md:hidden mobile transport pill is missing — a phone would show no transport here S1 Cloud tests still passed

Both files were then restored and the suite passed again, 13/13. git status --short -- neode-ui/src/views/Cloud.vue neode-ui/src/views/PeerFiles.vue is empty — no leftover deliberate-removal edit.

A third variant is worth recording: deleting the pill without de-branching the v-else fails the suite as a Vue compile error (v-else/v-else-if has no adjacent v-if) rather than an assertion, so the careless version of the removal is caught too.

Verification

  • cd neode-ui && npx vitest run src/views/__tests__/TransportPills.test.ts13 passed.
  • cd neode-ui && npx vitest run104 files, 845 tests, all passed, including keepAliveTabs.test.ts.
  • cd neode-ui && npm run buildgreen (vue-tsc -b && vite build, built in 35.87 s).
  • Build is not a silent no-op: flex flex-wrap items-center gap-2 text-xs and inline-flex shrink-0 items-center gap-1.5 px-2 py-1 rounded-full are both present in the built Cloud-*.js chunk, the md:hidden pill class string is intact in PeerFiles-*.js, and .flex-wrap{ and .shrink-0{ are both emitted in the main CSS bundle.
  • RED/GREEN was demonstrated explicitly: with the fix reverted, the responsive assertion fails (transport pill must not be compressible) while the 12 pill-existence assertions still pass; with the fix applied, all 13 pass.

Deviations from Plan

None affecting behaviour. Two process notes:

  1. The plan's <output> says to git push gitea-ai main. The execution brief for this run explicitly forbids pushing, tagging and deploying. Not pushed — commit 8255b69a is local on main. Per CLAUDE.md's commit-and-push rule this work is not "done" until someone pushes it.
  2. Task 2 made no source changes (it is a verification task), so it has no commit of its own. Its acceptance criterion — a clean git status for both views — is satisfied.

Known Stubs

None. No placeholder, mock or empty-value path was introduced.

Threat Flags

None. No new network endpoint, auth path, file-access pattern or schema change. The one source change is two Tailwind utility classes and a comment.

Threat register status:

  • T-01-78 (pill claiming an unobserved transport) — mitigated: unknown-transport assertions at both render sites, canonical mapping reused, no per-file pill fabricated for the rows that have no live reading.
  • T-01-79 (mobile user cannot see a Tor arrival) — mitigated: every render site has a recorded verdict at 390 and 320.
  • T-01-80 (later cleanup silently deleting the pills) — mitigated and proven: the pin fails on removal at both sites, with site-specific failure messages.
  • T-01-81 (long peer name pushing the pill off screen) — mitigated: exercised deliberately at both narrow widths; the name truncates in its own row and never reaches the badge row.
  • T-01-SC (package installs) — no dependency added or installed.

Field note (not in scope, recorded for whoever picks up UIFIX-01 follow-up)

The user's report is dated 2026-07-30. Both pills predate it: the PeerFiles.vue mobile copy landed 9e3ac9ba (2026-07-20) and the Cloud.vue peer-card badge landed c83bade0 (2026-07-27). Since both render correctly at phone widths, the most likely reason a pill was absent on the day of the report is not layout but data: peerTransport() returns null and the pill does not render whenever the content.browse-peer fan-out never resolves. That first-visit stall was root-caused and fixed on the same day by e1a3f31a and 8fe6217b ("cap content.browse-peer fan-out — root cause of Cloud first-visit hang"), and it would bite harder on a phone than on a desktop. If the user still reports a missing pill after this ships, look at whether the browse resolved, not at the CSS.

Self-Check: PASSED

  • neode-ui/src/views/__tests__/TransportPills.test.ts — FOUND
  • neode-ui/src/views/Cloud.vue — FOUND, contains flex flex-wrap items-center gap-2 text-xs and inline-flex shrink-0
  • commit 8255b69a — FOUND in git log
  • neode-ui/src/views/PeerFiles.vue — unmodified, git status clean