Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user