diff --git a/docs/QA_LOOP_LOG.md b/docs/QA_LOOP_LOG.md index 25ab005..0fd49fb 100644 --- a/docs/QA_LOOP_LOG.md +++ b/docs/QA_LOOP_LOG.md @@ -17,3 +17,4 @@ Each iteration appends one row. `OPEN:` items are unfixed bugs for later pickup. | 2026-07-19 | RE-VERIFY G4/G5+F4 after backend fixes | PASS — G4 block/unblock full cycle on-device; G5 blocked-content surfaces; F4 avatar upload end-to-end incl. Save (twice) | Backend fixes confirmed from the mobile side: block events now consumed (block from feed-card "Post options" > confirm > "Blocked @…" snackbar > PDS record social.coves.actor.block written > refresh removes ALL test-aggregator posts from Discover), unblock endpoint returns 200 (was 404) — "Unblocked @…" snackbar and posts return after refresh; uploadBlob fixed (0feddc1): Me > Edit Profile > Change Avatar > gallery > uCrop > Save now succeeds ("Profile updated successfully", NO 403, NO sign-out, session intact), new 1000x1000 JPEG blob served by appview and rendered in the profile header after relaunch. Fixed (mobile): round-1 unblock design gap CLOSED — ProfileViewerState.fromJson only read blocked/blockUri but the backend sends viewer.blocking (record URI), and nothing seeded BlockProvider from profile data, so after a restart a blocked user's profile menu showed "Block" and NO unblock surface existed anywhere (feeds/comments now filter correctly); profile screen now seeds block state from viewer.blocking (seed-only, never clobbers optimistic state) → profile card menu shows "Unblock @handle" (79f3637, verified on-device pre/post-fix). G5: blocked-author post detail (deep link) renders clean "Post Unavailable — This post is from an account you've blocked" + Go Back, no crash (app-bar title says "Not Found" — minor copy nit); blocked author's comments are filtered OUT of threads entirely (no marker; seeded a test-aggregator comment on a mari post to exercise this — comment left in place as seed data, first non-mari comment author); blocked profile itself renders unfiltered by design. OPEN: thread header comment count includes blocked comments ("1 Comment" over an empty list — backend count vs filtered list, cosmetic); no "Blocked accounts" management list, so reaching a blocked user's profile to unblock still requires a direct route (deep link/mention) — product gap, mitigated by the profile-menu unblock. Suite: NEW g4_block_unblock (self-contained block>menu-flips-to-Unblock>unblock>restored; filtering not assertable in-flow — social.coves scheme routes to the OAuth CallbackActivity so Maestro openLink can't deep-link MainActivity); f4 extended through Save (repeated PASS). QUIRKS: card "Post options" index is per-screen order — index 1 after scrolling Rubin into view, guarded by asserting the menu names @test-aggregator before tapping; avatar shown immediately after save can be the PREVIOUS one (instant refetch races firehose indexing — appears on next profile load); test-aggregator PDS password is test-password-12345 (scripts/setup_dev_aggregator.go). Static: analyze 536 = baseline; test/models+providers+screens 368 pass (+4 new ProfileViewerState tests). End state: mari signed in on home feed, zero block records, avatar = QA test image. | 79f3637, ddd366d | | 2026-07-19 | B — Home Feed (ROUND 2) | PASS — full-suite regression gate (all 28 ordered flows green across gate + post-fix re-runs) + deeper checks 2a–2d + queued cosmetic fix | Fixed (app): Join button reverting to "Join" despite subscribe 200 — the post-toggle community.get refetch races firehose indexing and still reports the pre-toggle viewer state; setInitialSubscriptionState clobbered the optimistic update (repro'd via d3: subscribe 200 at t, refetch at t → subscribed=false, "✓ Indexed subscription" at t+1s; DB row present while UI showed Join). Provider now keeps user-toggled communities authoritative for the session (f4b9fb6, d3 re-verified on-device). Fixed (app, queued cosmetic): blocked-post app bar said "Not Found" over a "Post Unavailable" body — NotFoundError hardcoded the app-bar title; now uses the passed title (9df9035, verified via block > deep-link > screenshot > unblock). Fixed (suite): flows OUTSIDE flowsOrder still execute at the end of a workspace run — g1 clearState wiped the session mid-suite and f5 ran without its deep-link precursor; both tagged `standalone` + config excludeTags (semantics proven on a synthetic workspace). Seed-post drift broke d2 (NASA delays below the fold → scrollUntilVisible), e4 (card peeking over the bottom edge → tap resolves to its off-screen centre and hits the bottom nav → centerElement, repro'd 3x then green), g4 hardened the same way pre-emptively. DEEPER CHECKS: 2a For You populates after subscribing to !science and empties to "No posts yet" after unsubscribing (committed as b6); 2b feed-like carries into detail, detail-unlike updates the feed card (committed as b7); 2c one scroll session renders text, image+external-link (kite.kagi.com bars), Streamable video, and the Bluesky-embed card (only seeded one is 175d deep — verified via deep link; butterfly card + nested link render clean); 2d fast double-tap on like never double-counts (VoteProvider pending guard swallows tap 2 in flight; on the fast local backend tap 2 can legitimately toggle off — either way score returns to exact baseline after refresh), 3x refresh spam + scroll-while-refreshing: no crash, no dupes. OPEN: a1's OAuth Custom Tab lingers in recents; back-exiting the app surfaces a dead "This sign-in session has expired" page (cosmetic, killed via force-stop com.android.chrome; consider closing the tab post-callback). OPEN (re-confirmed from round-1 G): once feeds/comments filter a blocked user there is NO in-app unblock surface (their thread comment is hidden entirely, count still says "1 Comment") — cleanup required deleting the block record via PDS as mari; "Blocked accounts" management screen still the product gap. FLAKES (documented, passed on retry): d4 blank-white launch 30s after signout (1x, transient); b7-style cold-start stale semantics (standard retry-launch wrapper added to b6/b7). Static: analyze 536 = baseline; test/widgets+providers 190 pass (loader tests updated for the title fix). End state: mari signed in on home feed, zero blocks (user+community), zero subscriptions. | f4b9fb6, 9df9035, 1a4b404 | | 2026-07-20 | C — Post Detail & Comments (ROUND 2 deep-chain, final loop iteration) | PASS — checks 7+8 (deep chains + focused thread) with the headline load-more bug FOUND+FIXED; c1–c3 regression green; NEW c4/c5 committed (2x stable each) | PERMANENT FIXTURE (do not delete): "QA deep chain fixture" post at://did:plc:7qijxb47zixbu3xqgdmruaui/social.coves.community.post/3mr2pxaxgq422 in !science — 20-level alternating mari/test-aggregator chain (now 21 after the in-flow reply test), a WIDE parent with 10 direct replies (7 seeded + 3 topups 08–10 added this run), 6 deleted tombstones under chain depth 01 (exercise the sibling cap + deleted placeholders), and a user-added 10-deep chain (see below). HEADLINE FIX: the per-comment "Load more replies" button had NO handler anywhere (CommentThread.onLoadMoreReplies was never passed by any screen — logcat: "Load more replies tapped (no handler provided)"), so replies past the per-parent sibling cap (5) or fetch depth (10) were unreachable: chain depths 2–20 were invisible dead data. Wired end-to-end via the lexicon's getComments parentRkey subtree fetch: provider merges the subtree into the tree (new ThreadViewComment.copyWith/replaceDescendant), per-node loading spinner, snackbar on failure; createComment now hydrates the parent subtree when a nested reply lands past the caps; ALSO fixed en route: at:// URIs cannot be Uri.parse()d (DID colons parse as an invalid port — caught by the new provider tests). FOCUSED THREAD (check 8): screen held an immutable snapshot — replies posted inside it never appeared, subtrees stayed truncated at the parent fetch depth; now hydrates the anchor's live subtree on entry and re-fetches after reply/delete. Verified on-device: chain 01→20 fully browsable (load-more → depth 6 render cap → "Read 1 more reply" → 3 chained focused screens to the tail), depth-21 reply posted from INSIDE the deepest focused view appears immediately at correct depth, back-stack returns through each focused screen to the detail at position, cold-start deep link renders the thread (no crash), collapse/expand verified at depth 1 and depth 10 (+1/+10 badges), indent rails clean at all depths, fling scroll through the loaded chain: 0 janky frames (gfxinfo window). QUEUED FIX: header no longer shows "1 Comment" over an empty list — when the thread loads successfully and every comment is filtered (blocked author), the empty state renders instead (verified via block → refresh → empty state → unblock → count restored, incl. profile-menu Unblock). USER SEED INVESTIGATION (mid-run request): the 10 "wide reply 10: horizontal direct replies" records are chain-parented IN THE PDS RECORDS (each reply.parent = the previous comment, first = post, ~3s apart) — the app renders them faithfully as a 10-deep chain; not an app bug (the post composer always parents to the post; ReplyScreen parents to the tapped comment) — looks like scripted seeding that reused each create response as the next parent. Wide intent honored by topping the WIDE parent up to 10 true siblings; both dimensions verified coexisting in one thread and codified in c4 (wide: cap → load-more → all 10 → collapse +10) + c5 (deep: tombstone-guarded load-more → depth 10 boundary → focused stack to depth 20 → back-stack). OPEN (backend): post stats commentCount counts deleted + viewer-filtered comments (fixture shows 48 incl. 6 tombstones; partially-filtered threads still overcount — viewer-aware stats needed). OPEN (backend, cosmetic): stats.replyCount is direct-children only, so collapsing a 9-descendant chain shows "+1". OPEN (product): no deep-link/permalink route to a comment or focused thread (only /post/:postUri) — check 8's cold-load verified as post cold-load + in-app navigation. SUITE: c1–c3 hardened for seed growth (60s feed scroll, retry-launch on c1/c2, centerElement before the NASA card tap — a plain tap started landing on the author row → profile). QUIRK: long-press collapse on a comment whose tap target shifted mid-test can open ReplyScreen; a long-press in the composer then triggers the Android stylus-IME promo sheet (dismiss with Cancel, not BACK). Static: analyze 535 (baseline 536; removed an orphaned generated mock file test/providers/feed_provider_test.mocks.dart whose source test no longer exists), flutter test test/widgets+services+providers 414 pass (+3 new loadMoreReplies tests, nested-reply test now asserts subtree hydration). End state: mari signed in on home feed, aggregator unblocked, fixture extended (permanent). | ab1d775, a2c8a58, (flows commit follows) | +| 2026-07-21 | REVIEW LOOP — /second-opinion × 5 sections over all unpushed work | Sections: 1 auth/networking, 2 comments/post-detail, 3 communities/moderation state, 4 compose/profile/shell, 5 QA infra. 6-reviewer panels (Claude specialty stand-ins + pragma:security/type-design + Codex gpt-5.6-sol + Kimi K3) on 1-2; 3-reviewer panels on 3-4; inline hygiene pass on 5. | Headline fixes: reply-pagination cursor was discarded (load-more refetched page 1 forever — unanimous 6/6 finding); OAuth cancel-sniffing could reclassify real server errors as quiet cancels; token-redaction charset leaked token tails + an unredacted error-body debugPrint; deep-reply verify loop checked an unsatisfiable tree; user-toggled authority unified across subscription/block providers; reply drafts now survive system back. ~90 new tests incl. first provider unit coverage, shell back matrix, focused-thread suite unskipped. On-device Maestro validation: first runs failed 6/6 twice on an emulator semantics-bridge wedge (app itself healthy — diagnosed via screenshot+logcat); driver reinstall restored the bridge, a1 green, c-flows re-run pending. | fa0d8d9, 4d57498, 6477756, ee09a95 |