247 lines
16 KiB
Markdown
247 lines
16 KiB
Markdown
---
|
||
phase: 01-federation-mesh-hardening
|
||
plan: 17
|
||
type: execute
|
||
wave: 8
|
||
depends_on: ["01-14"]
|
||
files_modified:
|
||
- neode-ui/src/views/Cloud.vue
|
||
- neode-ui/src/views/PeerFiles.vue
|
||
- neode-ui/src/views/__tests__/TransportPills.test.ts
|
||
autonomous: true
|
||
requirements: [UIFIX-01]
|
||
gap_closure: true
|
||
|
||
must_haves:
|
||
truths:
|
||
- "Every place the cloud surfaces show a file's transport state shows it at mobile widths too — a phone user can see whether a file came over FIPS or over Tor (UIFIX-01)"
|
||
- "The pills are pinned by a test, so a future cleanup or refactor that removes one fails the suite instead of shipping (UIFIX-01 — 'kept, never removed')"
|
||
- "A peer whose transport is not yet known renders the existing not-known treatment rather than a fabricated pill (UIFIX-01 empty edge)"
|
||
- "A pill never truncates into meaninglessness or overlaps its neighbour at the narrowest supported width — it wraps or compacts instead"
|
||
- "Desktop rendering of every pill is unchanged: same text, same colours, same position, same spacing"
|
||
- "Every render site of the transport pill in the cloud surfaces has a recorded mobile verdict — no site is left unchecked"
|
||
prohibitions:
|
||
- statement: "The transport pill MUST NOT claim a transport the app has not actually observed — it renders from the recorded result of the last real browse, and a missing or stale reading shows the not-known treatment rather than defaulting to the more reassuring value"
|
||
category: transparency
|
||
- statement: "Nothing on these views may change except the transport pills' responsive rendering — file rows, peer cards, buttons, badges, counts, tabs and every animation stay exactly as they are, and desktop is untouched"
|
||
category: safety
|
||
artifacts:
|
||
- path: neode-ui/src/views/__tests__/TransportPills.test.ts
|
||
provides: "A render-site pin for every FIPS/Tor pill, so removal breaks the build"
|
||
min_lines: 40
|
||
key_links:
|
||
- from: neode-ui/src/views/PeerFiles.vue
|
||
to: neode-ui/src/views/Cloud.vue
|
||
via: "both read the same recorded browse transport for a peer, so the pill means the same thing wherever it renders"
|
||
pattern: "transport"
|
||
---
|
||
|
||
<objective>
|
||
Keep the FIPS/Tor pills forever, and make sure a phone shows them.
|
||
|
||
Purpose: UIFIX-01 is a BLOCKER with two halves. The user explicitly values these pills ("really
|
||
helpful") and asked that no future cleanup remove them — that half is solved by pinning them with a
|
||
test, which nothing in the repo does today. The other half is that at mobile widths they are hidden or
|
||
cramped, so exactly the users least able to judge their connection cannot see whether a file arrived
|
||
over the fast encrypted mesh or over Tor. The planner could not determine which specific render site
|
||
fails on a phone, so this plan audits every site rather than guessing at one.
|
||
Output: a complete, recorded per-site mobile verdict; a fix at every failing site; and a test that
|
||
makes their removal a build failure.
|
||
</objective>
|
||
|
||
<execution_context>
|
||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||
@$HOME/.claude/gsd-core/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/STATE.md
|
||
@.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md
|
||
@.planning/phases/01-federation-mesh-hardening/01-14-SUMMARY.md
|
||
</context>
|
||
|
||
## Artifacts this phase produces
|
||
|
||
Created or changed by **this plan**:
|
||
|
||
| Symbol | Kind | File |
|
||
|---|---|---|
|
||
| responsive transport-pill rendering | changed template classes at the failing sites | `neode-ui/src/views/Cloud.vue`, `neode-ui/src/views/PeerFiles.vue` |
|
||
| `neode-ui/src/views/__tests__/TransportPills.test.ts` | new vitest suite — the "never remove these" pin | new file |
|
||
|
||
<tasks>
|
||
|
||
<task type="tracer" tdd="true">
|
||
<name>Task 1: Audit every transport-pill site and fix the ones a phone cannot read</name>
|
||
<files>neode-ui/src/views/Cloud.vue, neode-ui/src/views/PeerFiles.vue, neode-ui/src/views/__tests__/TransportPills.test.ts</files>
|
||
<precondition>The local dev preview can be started (`cd neode-ui && npm run dev:mock` serves the UI on :8100 against the mock backend) with peer data present — jsdom cannot tell you whether a pill is cramped, only whether it exists</precondition>
|
||
<read_first>
|
||
- `neode-ui/src/views/Cloud.vue` lines 280-324 — the peer cards in the Folders tab. The badge row
|
||
is `flex items-center gap-2 text-xs` holding the trust pill and, when
|
||
`peerTransport(peer.onion)` is known, the transport pill rendering
|
||
`FIPS`/`TOR` plus a latency figure, with a `Peer Node` text fallback when it is not known. Note
|
||
the row has no wrapping and no responsive treatment at all.
|
||
- `neode-ui/src/views/Cloud.vue` lines 199-218 — the Peer Files aggregated list rows. Each row
|
||
shows a category icon, filename, size and price, and a peer-name pill — and no transport pill,
|
||
even though these rows are files from peers. Decide, and record, whether this is a site that
|
||
should carry one: the requirement is about a user seeing a file's transport state.
|
||
- `neode-ui/src/views/Cloud.vue` lines 150-178 — the Paid Files rows, for the same decision.
|
||
- `neode-ui/src/views/PeerFiles.vue` lines 8-38 — the header. There is a desktop title block
|
||
(`hidden md:block`) carrying the pill, and a separate `md:hidden` copy of the pill added
|
||
specifically so mobile still sees it. Read the comment above it: someone already fixed one half
|
||
of this. Confirm whether that copy actually renders and is legible on a phone today.
|
||
- `neode-ui/src/views/PeerFiles.vue` lines 640-676 — `transportPill`, the single source of the
|
||
label, colour classes and tooltip for `fips` / `mesh` / `lan` / `tor` / unknown. This is the
|
||
canonical mapping; anything this plan adds must use it rather than re-deriving colours.
|
||
- `neode-ui/src/views/PeerFiles.vue` lines 152-232 — the per-file card body, which shows an access
|
||
badge and action buttons, for the same site decision.
|
||
- `neode-ui/src/views/dashboard/__tests__/keepAliveTabs.test.ts` — the house convention for a
|
||
structural pin test, and the file the standing rule requires stay green.
|
||
</read_first>
|
||
<behavior>
|
||
- Mounting each surface with a known transport renders the transport pill with its label text.
|
||
- Mounting with an unknown transport renders the existing not-known treatment and no pill.
|
||
- Removing the pill from any audited site makes the suite fail — that is the whole point of the
|
||
test, so write each assertion so it is specific to a site, not satisfied by any pill anywhere.
|
||
- The pill's label and colour come from the canonical mapping, not from a duplicated table.
|
||
</behavior>
|
||
<action>
|
||
Start with the audit, because the fix depends on it. Start the dev preview and open each of the
|
||
sites listed above at a phone viewport (390×740 is the reference; also check 320×640, the narrowest
|
||
the app supports). For each site record: does the pill render at all, is its text fully readable,
|
||
does it overlap or push anything, and does it survive a long peer name or a long filename. Put the
|
||
result in the SUMMARY as a table with columns: site, file and line, renders on mobile, legible,
|
||
action.
|
||
|
||
Then fix every site the audit marked as failing, and only those. The likely shapes, depending on
|
||
what you find: let the badge row wrap (`flex-wrap`) so a pill drops to a second line instead of
|
||
overflowing; drop the latency figure from the pill at small widths while keeping the transport word,
|
||
since the word is the security-relevant part and the milliseconds are not; or render a compact pill
|
||
variant on mobile the way `PeerFiles.vue`'s header already renders a mobile-specific copy. Choose
|
||
per site based on what you actually saw, and record why. Do not apply a responsive change to a site
|
||
the audit passed — an unnecessary change to a working desktop layout is exactly what the standing
|
||
rule forbids.
|
||
|
||
Settle the two open site questions rather than leaving them: whether the Peer Files aggregated
|
||
rows and the Paid Files rows should carry a transport pill. Both list files that came from peers,
|
||
and the requirement is about a user seeing a file's transport state — but the aggregated rows show
|
||
files from many peers at once, and a per-row pill may be the honest answer or may be noise. Make a
|
||
decision, state the reasoning, and if the answer is yes, implement it using the canonical mapping
|
||
and add it to the pin test. If the answer is no, record why the existing peer-level pill is
|
||
sufficient for those rows.
|
||
|
||
Write the pin test as you go: one assertion per confirmed render site, each keyed to something that
|
||
identifies that site specifically, plus a comment at the top of the file saying in plain words that
|
||
these pills are a user-requested permanent feature and that a failure here means someone removed
|
||
one, not that the test is stale.
|
||
|
||
Change nothing else on either view. Desktop rendering must be untouched at every site, including
|
||
the ones you fix.
|
||
</action>
|
||
<verify>
|
||
<automated>cd neode-ui && test -f src/views/__tests__/TransportPills.test.ts && npx vitest run src/views/__tests__/TransportPills.test.ts</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- The test file exists and `cd neode-ui && npx vitest run src/views/__tests__/TransportPills.test.ts` exits 0 (the `test -f` guard is required — `vitest.config.ts` sets `passWithNoTests: true`).
|
||
- The suite has at least one site-specific assertion per render site the audit confirmed, and at least one unknown-transport case asserting no pill is fabricated.
|
||
- The SUMMARY contains the per-site audit table, with a row for each of the five sites named in `read_first` and an explicit action for each.
|
||
- The SUMMARY records the decision and reasoning for the Peer Files aggregated rows and the Paid Files rows.
|
||
- `grep -c 'transportPill' neode-ui/src/views/PeerFiles.vue` is unchanged or higher — the canonical mapping was reused, never replaced.
|
||
- `cd neode-ui && npx vitest run` exits 0 — every existing suite, including `keepAliveTabs.test.ts`, stays green.
|
||
</acceptance_criteria>
|
||
<done>Every transport-pill site has a recorded mobile verdict, the failing ones are fixed, and a test makes their removal a build failure.</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: Re-check the fixed sites on a phone viewport and confirm desktop is untouched</name>
|
||
<files>neode-ui/src/views/__tests__/TransportPills.test.ts</files>
|
||
<precondition>Task 1's changes are in the working tree and the dev preview can be restarted against them</precondition>
|
||
<read_first>
|
||
- The audit table Task 1 wrote into the SUMMARY — it is the checklist for this task.
|
||
- `neode-ui/DEV-SCRIPTS.md` lines 1-40 — starting and stopping the preview.
|
||
</read_first>
|
||
<action>
|
||
Re-open every site the audit marked as fixed at both 390×740 and 320×640 and confirm the pill now
|
||
renders fully and legibly, with a long peer name and a long filename present so the overflow case
|
||
is actually exercised — if the mock data has no long names, edit the rendered text in the element
|
||
inspector to force it rather than changing the mock backend, and say so.
|
||
|
||
Then confirm desktop is untouched. Open each changed site at 1440×900 and compare against the
|
||
pre-change build. State in the SUMMARY that each changed site renders identically on desktop, or
|
||
name what moved and fix it — the standing rule is that the only visual change this phase ships is
|
||
the one the user asked for.
|
||
|
||
Finally, confirm the pin does its job: temporarily delete one pill from one site, run the suite,
|
||
and confirm it fails. Restore the pill and confirm the suite passes again. Record both results.
|
||
A pin that does not fail when the thing it pins is removed is not a pin.
|
||
</action>
|
||
<verify>
|
||
<automated>cd neode-ui && npx vitest run && npm run build</automated>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `cd neode-ui && npx vitest run` exits 0 and `cd neode-ui && npm run build` exits 0.
|
||
- The SUMMARY records the 390×740 and 320×640 re-check for every fixed site, including the long-name case and how it was forced.
|
||
- The SUMMARY states explicitly, per changed site, that desktop rendering at 1440×900 is unchanged.
|
||
- The SUMMARY records the deliberate-removal check: which pill was removed, that the suite failed, and that it passed again after restoring.
|
||
- `git status --short -- neode-ui/src/views/Cloud.vue neode-ui/src/views/PeerFiles.vue` shows no leftover deliberate-removal edit.
|
||
</acceptance_criteria>
|
||
<done>The pills are readable on the narrowest supported phone, desktop is unchanged, and the pin is proven to actually fail on removal.</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
## Planner Assumptions (flagged, unresolved)
|
||
|
||
- **The planner could not identify which specific site fails on mobile.** Reading the source showed
|
||
`PeerFiles.vue`'s header already carries a mobile-specific pill copy added for exactly this reason,
|
||
and `Cloud.vue`'s peer-card badge row has no responsive treatment at all — the latter is the most
|
||
likely culprit, but "most likely" is not evidence. Task 1 is therefore an audit that fixes what it
|
||
finds, rather than a fix aimed at a guessed target. If the audit finds every site already renders
|
||
correctly, that is a legitimate outcome for the mobile half — record it with the evidence, and the
|
||
"kept, never removed" half of the requirement is still fully delivered by the pin test.
|
||
- **Whether the aggregated Peer Files rows and the Paid Files rows should carry their own pill is a
|
||
genuine product question**, not something the planner should decide from a file read. Task 1
|
||
requires a stated decision with reasoning either way, so the answer is recorded rather than assumed.
|
||
|
||
<threat_model>
|
||
## Trust Boundaries
|
||
|
||
| Boundary | Description |
|
||
|----------|-------------|
|
||
| observed browse transport → security claim shown to the user | The pill is a security signal: it tells the user whether their file moved over the encrypted mesh or over Tor |
|
||
| peer-supplied names → rendered alongside the pill | Long or hostile peer names share the row the pill lives in |
|
||
|
||
## STRIDE Threat Register
|
||
|
||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||
| T-01-78 | Spoofing | a pill claiming a transport that was not actually used, so a user trusts a channel they should not | high | mitigate | The prohibition requires the pill to render only from the recorded browse result; an unknown-transport test case asserts no pill is fabricated, and the canonical mapping is reused rather than duplicated |
|
||
| T-01-79 | Information Disclosure | a mobile user unable to see that a file arrived over Tor and acting as though it were the trusted mesh path | high | mitigate | This is the requirement itself; Task 1's audit covers every render site and Task 2 re-checks each fix at the two narrowest supported widths |
|
||
| T-01-80 | Tampering | a later cleanup silently deleting the pills again | high | mitigate | The pin test asserts per site, and Task 2 proves the pin actually fails when a pill is removed |
|
||
| T-01-81 | Spoofing | a long peer-supplied name pushing the pill off screen so it is effectively absent on mobile | medium | mitigate | Task 2 requires the long-name case to be exercised deliberately at both narrow widths, not just whatever the mock data happens to contain |
|
||
| T-01-SC | Tampering | npm/pip/cargo installs | high | mitigate | This plan installs nothing — template class changes and one vitest file. If an implementation choice would add a dependency, stop: RESEARCH.md's Package Legitimacy Audit must cover it first, with a blocking human checkpoint for any `[ASSUMED]`/`[SUS]` entry |
|
||
</threat_model>
|
||
|
||
<verification>
|
||
- `cd neode-ui && npx vitest run` — green, including `keepAliveTabs.test.ts`.
|
||
- `cd neode-ui && npm run build` — green.
|
||
- Per-site audit table plus the 390×740 / 320×640 re-check and the 1440×900 desktop comparison, all recorded.
|
||
- The deliberate-removal check confirming the pin fails on removal.
|
||
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- Every transport-pill render site has an evidence-backed mobile verdict.
|
||
- Every failing site is fixed, and no passing site was touched.
|
||
- Desktop rendering is unchanged everywhere.
|
||
- A test pins the pills so removing one breaks the build, and that pin is proven to work.
|
||
</success_criteria>
|
||
|
||
<output>
|
||
Create `.planning/phases/01-federation-mesh-hardening/01-17-SUMMARY.md` when done, recording the audit
|
||
table, the two site decisions with reasoning, the re-check observations, and the deliberate-removal
|
||
result.
|
||
Stage by explicit path, commit, and `git push gitea-ai main`.
|
||
</output>
|