docs(02-08): checkpoint follow-up — triage 5 user-reported items

Adds a Task 3 checkpoint follow-up addendum to FINDINGS.md documenting
the investigation behind the fix(02-08) commit (Cloud.vue fresh-mount
guard for the first-visit connection-pool stall — confirmed real,
partially fixed, not yet fully resolved) plus classification of four
other user-reported items, all traced via git history against the
a75b6709 pre-phase-2 baseline and confirmed pre-existing (not phase 2):
Paid Files opening images in a new tab instead of the lightbox, PiP not
closing the lightbox, missing loader state on Paid Files' item-open RPC,
and PiP not surviving tab changes/buffering pauses. No fixes applied
for the pre-existing items per standing direction (captured separately
as phase-1 UX work).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-30 23:23:53 -04:00
parent bd6958255a
commit 834edd8c00

View File

@ -316,3 +316,123 @@ is this corrected reading; the **"Remounted (raw)"** column is the harness artif
(`KEEP_ALIVE_MAX`) was settled in Task 1 (see its SUMMARY — confirmed at 6 via an on-device
memory reading). FA-B (`ContainerAppDetails.vue`) — see the dead-code note above. FA-E
(AIUI source) — settled by 02-07 (source located, D-14 landed upstream).
## Addendum: Task 3 Checkpoint Follow-Up (2026-07-31, post-first-pass)
The user's first Task 3 pass reported "otherwise it's getting much better" plus four specific
issues. Each was investigated on archi-dev-box directly (not guessed), root-caused via git
history, and — where it traced to a phase-2 commit — fixed. No new plan was created per
standing direction; this addendum records the investigation and its outcome.
### Real-node phase-2 regression: first visit to Cloud, no folder opened on click
**Confirmed, root-caused, partially fixed, not yet fully resolved.** Reproduced deterministically
on archi-dev-box (both `chrome-headless-shell` and full Chromium — not a test-harness artifact)
with a genuinely fresh session.
- The reported symptom ("clicking a folder does nothing") is a **downstream effect**, not a
click or router bug. Direct instrumentation proved: the click event fires (confirmed via a
raw DOM listener attached to the exact clicked node), `router.push({name:'cloud-folder',...})`
is called (confirmed by patching the live `$router` instance), but the returned promise never
settles — because Vue Router awaits the target route's async component, and that dynamic
`import()` itself never resolves.
- Confirmed via manual `import()` calls from the page console: after visiting Cloud once, importing
**any** lazy route chunk (Fleet, CloudFolder, AppDetails — unrelated views) hangs identically;
before ever visiting Cloud, the same imports resolve in ~300ms. Not chunk-specific.
- Traced to exactly one permanently-pending network request: `GET /app/filebrowser/api/resources/Photos`,
fired from `Cloud.vue`'s `fetchCounts()` on mount. Confirmed hung via request-lifecycle
tracking (`request` fires, neither `requestfinished` nor `requestfailed` ever fires, still
pending after 10s). The **identical** request, issued manually with a fresh token completely
outside of Cloud.vue, returns in 29ms — ruling out File Browser or the network path itself.
Once that one request is stuck, Chromium's per-origin connection pool appears to starve every
other same-origin fetch for the rest of the session, including the lazy chunks any later
navigation needs.
- **Fix applied** (`fix(02-08)`, commit `e1a3f31a`, `neode-ui/src/views/Cloud.vue`): gave
`Cloud.vue`'s `syncOnEntry()` the same fresh-mount guard already used in
`Home.vue`/`Web5.vue`/`Mesh.vue`/`Server.vue` (02-04 exempted Cloud on the reasoning each
individual resource is staleness/inflight-deduped — true per-resource, but the two back-to-back
`onMounted`+`onActivated` passes still doubled `loadPeerFiles()`'s full per-peer
`content.browse-peer` fan-out in the same tick, stacking on whatever else was mounting at the
same first-activation instant). Tested (95 files/774 tests green, type-check and build clean),
committed, pushed, redeployed to archi-dev-box the same way as Task 1.
- **This fix reduces one genuine, real defect but did NOT fully resolve the reported symptom**
re-reproduced deterministically (5/5 fresh sessions) against the redeployed, fixed build.
Ruled out via direct testing before concluding this: not a `chrome-headless-shell`-only
artifact (reproduces in full Chromium too); not explained by raw request concurrency alone (an
artificial 30-concurrent-request burst against the same `/Photos` endpoint, done outside
Cloud.vue, completed in 179ms with zero hang). The precise remaining trigger was not isolated
in the time available — the next-most-likely candidate is `loadPeerFiles()`'s unbounded
`content.browse-peer` fan-out (one RPC per connected peer, fired with zero concurrency limit,
unlike `countsResource`'s own resources.ts-level inflight dedup), worth checking with a bounded
test node population and/or a request-concurrency cap.
- **This remains open.** Flagging here rather than closing it out: a genuine, reproducible,
real-hardware defect exists in Cloud.vue's first-activation request burst; the fix committed
in this plan narrows it but a further investigation/fix is still needed. Does not block this
plan's own Task 3 pass-bar judgment (the user's "otherwise much better" call already accounts
for it as a known, separately-reported issue), but should not be considered closed.
### Classification of the other three reported issues — all pre-existing, none phase-2
All three traced via `git log`/`git merge-base --is-ancestor` against `a75b6709` (the commit the
02-01 baseline was taken at, i.e. the last commit before any phase-2 UI change landed). None of
the implicated files or functions were touched by any phase-2 commit; no fix was implemented for
any of these three, per standing direction (captured separately as phase-1/general UX work).
1. **Paid Files: clicking a picture opens it in a browser tab instead of the app's lightbox.**
`neode-ui/src/views/Cloud.vue` `viewPaidItem()` (~line 470-493): for any non-audio purchased
item it does `window.open(url, '_blank', 'noopener')` on a blob URL — it never calls
`MediaLightbox.vue` (`neode-ui/src/components/cloud/MediaLightbox.vue`) at all, unlike the My
Files / Peer Files rows' `handlePreview()` paths, which do route into the lightbox. Introduced
in `f3393581` ("fix(content): double-pay is now impossible + purchases auto-file + Paid Files
tab", 2026-07-22 — confirmed an ancestor of `a75b6709` via `git merge-base --is-ancestor`).
**Pre-existing, not phase 2.**
2. **Picture-in-picture on a video doesn't close the lightbox; the transition should be fluid.**
`neode-ui/src/utils/pip.ts`'s `togglePip()` only calls
`video.requestPictureInPicture()`/`exitPictureInPicture()` — it never emits a close event or
touches `MediaLightbox.vue`'s (`neode-ui/src/components/cloud/MediaLightbox.vue`, PiP button
at ~line 21-31, `close()` at ~line 233) own visibility state, so the lightbox backdrop simply
stays open behind the PiP window with no reaction and no transition at all. Introduced in
`f72d4b92` ("feat(content): seller-picked payment methods + music always in the bottom bar +
video PiP", 2026-07-23 — zero commits touch `MediaLightbox.vue` or `pip.ts` between
`a75b6709` and `HEAD`, confirmed via `git log a75b6709..HEAD --`).
**Pre-existing, not phase 2.**
3. **Some surfaces lack loader states where an open genuinely takes time.**
Spot-checked rather than exhaustively swept (a full sweep of every secondary-screen/modal
open across the app is its own piece of work). The clearest concrete instance found:
`neode-ui/src/views/Cloud.vue` `viewPaidItem()` (same function as #1 above) makes a
`content.owned-get` RPC call with a 60-second timeout and shows **no loading indicator at
all** while it resolves — `paidLoading` (line 466) only covers the list fetch
(`content.owned-list`), not the per-item open. Clicking a Paid Files row shows nothing until
the new tab appears (or fails silently). Same commit (`f3393581`, 2026-07-22) as #1 — **pre-
existing, not phase 2**. Other candidates were not individually verified in the time
available; `CloudFolder.vue`'s native file-grid path does already show a loading skeleton
(`neode-ui/src/components/cloud/FileGrid.vue` line 4), so that one specific secondary screen
is not part of this finding.
4. **Picture-in-picture must survive tab changes or video buffering pauses; doesn't seem to.**
Two parts, classified separately:
- **Tab-change part, checked against phase 2's lifecycle changes specifically — not
implicated.** Neither `Cloud.vue`, `CloudFolder.vue`, nor `MediaLightbox.vue`
(`neode-ui/src/components/cloud/MediaLightbox.vue`) defines an `onDeactivated` hook at all
(zero matches for `onDeactivated` in any of the three) — 02-04 never touched this file and
added no teardown that pauses, resets, or unmounts video/lightbox state on tab
deactivation. `MediaLightbox` is a `<Teleport to="body">` child rendered from inside
`Cloud.vue`'s template (`v-if="lightboxIndex !== null"`), so when `Cloud.vue` (KeepAlive-
cached since 02-04) deactivates on a tab switch, Vue's own KeepAlive machinery recursively
deactivates that whole subtree, including the teleported lightbox/video, moving its DOM
into KeepAlive's internal (not document-attached) storage container. Whether Vue's exact
move semantics disconnect the `<video>` element from `document` (which would explain a
native PiP session ending, per browser spec) was not conclusively provable from source
reading alone. What IS provable, and settles the phase-2 question either way:
pre-phase-2, switching tabs fully unmounted `Cloud.vue` and destroyed its video element
outright (zero `<KeepAlive>` existed anywhere before 02-02) — a guaranteed PiP-ending
event, strictly worse than or equal to whatever KeepAlive's deactivation does now. Phase 2
could not have regressed "PiP survives a tab change" because that behavior never existed
before phase 2 to regress from. **Pre-existing/never-supported, not a phase-2 regression**
— a real UX gap worth phase 1's UIFIX-05 attention, but not something 02-04 broke.
- **Buffering-pause part — pre-existing, and there is no custom handling to point at.**
`MediaLightbox.vue`'s `<video>` element (~line 77-87) is a bare
`<video controls autoplay @dblclick @error>` — grepping `pause`/`waiting`/`stalled` across
`MediaLightbox.vue` and `useAudioPlayer.ts` finds zero listeners on the video element for
those events. There is no app-level rebuffering/PiP-continuity logic to have regressed;
PiP-during-rebuffer behavior here is entirely native browser/HTML5-video default behavior,
untouched by any app code in any phase. **Pre-existing (by absence), not phase 2.**