WIP: Pilot safe OpenCode pull request reviews #1

Draft
lambadalambda wants to merge 21 commits from issue/109-opencode-review-bot-pilot into master

Summary

  • track the isolated OpenCode review bot pilot
  • use this pull request for the first live requestable review

Testing

  • pending bot deployment and live review
## Summary - track the isolated OpenCode review bot pilot - use this pull request for the first live requestable review ## Testing - pending bot deployment and live review
Author
Owner

/oc review

/oc review
Collaborator

OpenCode Review

Execution completed for lambadalambda/pleromanet at 1a043729493a (job #6).

Findings

Medium

Unbounded growth of status-engagement tracking mapssrc/routes/app/[...path]/+page.svelte
The new revision-tracking structures (statusEngagementSnapshots, statusEngagementNotificationRevisions, statusEngagementNotificationArrivals, notificationEngagementSignatures, seenNotificationEngagementSignatures) are keyed by status/notification IDs and never pruned. invalidateStatusActionRequests resets them only on session/route invalidation, so a long-running session that views many posts will accumulate entries indefinitely. Consider evicting entries for status IDs no longer present in any loaded surface, or limiting to the most-recent N targets. This is the most concrete memory-growth concern introduced by the count-reconciliation feature.

Floating image preview is repositioned on every scroll/resize without throttlingsrc/lib/rebuild/CompactMediaPreview.svelte
positionFullPhoto is bound to window.scroll (capture), window.resize, visualViewport.resize, and visualViewport.scroll, plus image onload. Each invocation performs getBoundingClientRect() reads, multiple await tick() calls, and updates fullPhotoStyle. There is no throttle, debounce, or requestAnimationFrame coalescing. On lower-end devices and long hover sessions this can introduce jank, especially because the effect tears down and re-creates listeners on every fullPhoto/fullPhotoAnchor change. Consider an rAF-batched reposition and keeping listeners attached for the lifetime of the open preview (they are already gated by the open state).

Low

Misleading time-bucket labels in NotificationsPagesrc/lib/rebuild/NotificationsPage.svelte
bucketFor maps ages 1–6 h to "Today", 6–48 h to "Yesterday", and 2–7 days to "This week". The labels do not match the implied semantics and will be confusing for users (e.g., a 7-hour-old notification shows under "Yesterday"). Either rename the buckets (e.g., "Recent", "Today", "Earlier this week") or compute them against calendar days.

Redundant condition in WebSocket onerror handlersrc/lib/pleroma/streaming.ts

socket.onerror = (event) => {
    if (closed) return;
    if (!closed) onError?.(event);
};

The inner if (!closed) is unreachable because closed cannot change synchronously between the two checks. Collapse to a single onError?.(event); after the early return.

role="note" is non-standardsrc/lib/rebuild/CompactMediaPreview.svelte

<div class="compact-media-preview compact-media-hidden" role="note">Sensitive media</div>

note is not in the WAI-ARIA 1.2 vocabulary; some screen readers will ignore it. Use a standard role such as status, or omit role and rely on the visible text.

Notification actionable row loses pointer affordancesrc/app.css, src/lib/rebuild/NotifRow.svelte
The old rule .notif-row.actionable { cursor: pointer; } was removed and the actionable surface is now a real <button class="notif-row-text">. Browser default cursor for <button> is default, so the row no longer signals clickability. Add cursor: pointer to .notif-row-text:is(button) (or rely on the existing .notif-quote-open:is(button) pattern).

Two distinct buttons open the same notificationsrc/lib/rebuild/NotifRow.svelte
For actionable rows with a post, both .notif-row-text (the whole row) and .notif-quote-open (the excerpt) call onOpen?.(n). This creates redundant focusable targets and a duplicated "open" action in the a11y tree. Either expose a single activator or mark the secondary one tabindex="-1"/aria-hidden.

Double notification load after clearsrc/routes/app/[...path]/+page.svelte
clearNotifications first runs loadNotifications(session, { replace: true }) and then, in finally, unconditionally calls loadNotifications(session, { background: true }). The replace load already fetches authoritative state right after the clear; the second call is a duplicate network request in the common success path. Gate the background follow-up on a real signal (e.g., the replacement stream has opened with no fresh data) or skip it when the replace succeeded.

CompactMediaPreview portal lifecycle is fragilesrc/lib/rebuild/CompactMediaPreview.svelte
The portal action appends node to document.body and removes it on destroy. Svelte also tries to remove the node from its (now-moved) parent during {#if fullPhoto} teardown. The current ordering works today, but it depends on Svelte calling the action destroy before attempting DOM removal. Adding a comment or guarding the destroy to only remove when still attached would future-proof this against Svelte internals changes.

aria-label on visibility span may double-readsrc/lib/rebuild/PostVisibility.svelte
The outer <span> carries aria-label while wrapping an Icon plus a visible <span>{details.label}</span>. If Icon exposes its own accessible name, AT users may hear the label twice. Ensure the inner Icon is aria-hidden="true" (the component is not shown in the patch).

Informational

  • timeline-catch-up.ts and reconcileTimelineCatchUp are intricate but accompanied by a substantial, focused Playwright suite covering multi-page catch-up, concurrent streamed posts, empty-timeline recovery, route retention, and stale local vs. federated races. The behavior matches the tests; future maintainers will benefit from inline comments explaining the initialDataIds/initialQueuedIds overlap algorithm.
  • Notification clearing correctly bumps notificationStreamGeneration, gates applyStreamedNotification on both stream generation and notificationClearState === 'clearing', and re-checks isCurrentSessionRequest after each await. The in-flight-notification and delayed-stream tests exercise the important races.
  • statusReplyTargetId/matchesStatusReplyTarget correctly route boosted-source identity through thread reconciliation, ancestor replies, and reply-target expansion. Indexing byId by both id and statusReplyTargetId is safe under the assumption that source IDs are unique within a thread context.
  • compactExcerpt shortcode-boundary logic is exported and unit-tested, and is reused for chat excerpts after send — a nice consolidation of the prior chatExcerpt helper.
  • Custom-emoji propagation is consistently applied to the audit-identified surfaces (notifications, CW summaries, pinned posts, chat rows, mention suggestions, lightbox attribution, profile context, settings preview), and hidden-emoji/no-request assertions back the privacy claims.
  • openPleromaTimelineStream changes (open-timeout, detach-on-close, idempotent close) are well-covered by the rewritten client tests, including the new "socket never opens" replacement case.

Overall

A large, well-structured patch that delivers a coherent set of features (fit-images, compact media previews with floating full-image view, notifications-page redesign, visibility badges, timeline stream catch-up, thread loading/scroll/restoration, count reconciliation, custom-emoji audit fixes, clear-all with generation-safe catch-up). Test coverage is thorough and traces cleanly to the per-issue acceptance criteria. No security, data-integrity, or correctness defects were identified. The most actionable items are the unbounded engagement-snapshot maps (Medium), the unthrottled floating-preview positioning (Medium), and a handful of low-severity UX/a11y/style nits listed above.

<!-- opencode-review:v1:e5ca34182856808ac1c0fd91f32bb793 --> <!-- opencode-reviewed-head:1a043729493ab28d92eab6f44c9b9684cc2b33d8 --> ## OpenCode Review _Execution completed for `lambadalambda/pleromanet` at `1a043729493a` (job #6)._ ## Findings ### Medium **Unbounded growth of status-engagement tracking maps** — `src/routes/app/[...path]/+page.svelte` The new revision-tracking structures (`statusEngagementSnapshots`, `statusEngagementNotificationRevisions`, `statusEngagementNotificationArrivals`, `notificationEngagementSignatures`, `seenNotificationEngagementSignatures`) are keyed by status/notification IDs and never pruned. `invalidateStatusActionRequests` resets them only on session/route invalidation, so a long-running session that views many posts will accumulate entries indefinitely. Consider evicting entries for status IDs no longer present in any loaded surface, or limiting to the most-recent N targets. This is the most concrete memory-growth concern introduced by the count-reconciliation feature. **Floating image preview is repositioned on every scroll/resize without throttling** — `src/lib/rebuild/CompactMediaPreview.svelte` `positionFullPhoto` is bound to `window.scroll` (capture), `window.resize`, `visualViewport.resize`, and `visualViewport.scroll`, plus image `onload`. Each invocation performs `getBoundingClientRect()` reads, multiple `await tick()` calls, and updates `fullPhotoStyle`. There is no throttle, debounce, or `requestAnimationFrame` coalescing. On lower-end devices and long hover sessions this can introduce jank, especially because the effect tears down and re-creates listeners on every `fullPhoto`/`fullPhotoAnchor` change. Consider an `rAF`-batched reposition and keeping listeners attached for the lifetime of the open preview (they are already gated by the open state). ### Low **Misleading time-bucket labels in NotificationsPage** — `src/lib/rebuild/NotificationsPage.svelte` `bucketFor` maps ages 1–6 h to "Today", 6–48 h to "Yesterday", and 2–7 days to "This week". The labels do not match the implied semantics and will be confusing for users (e.g., a 7-hour-old notification shows under "Yesterday"). Either rename the buckets (e.g., "Recent", "Today", "Earlier this week") or compute them against calendar days. **Redundant condition in WebSocket onerror handler** — `src/lib/pleroma/streaming.ts` ```js socket.onerror = (event) => { if (closed) return; if (!closed) onError?.(event); }; ``` The inner `if (!closed)` is unreachable because `closed` cannot change synchronously between the two checks. Collapse to a single `onError?.(event);` after the early return. **`role="note"` is non-standard** — `src/lib/rebuild/CompactMediaPreview.svelte` ```svelte <div class="compact-media-preview compact-media-hidden" role="note">Sensitive media</div> ``` `note` is not in the WAI-ARIA 1.2 vocabulary; some screen readers will ignore it. Use a standard role such as `status`, or omit `role` and rely on the visible text. **Notification actionable row loses pointer affordance** — `src/app.css`, `src/lib/rebuild/NotifRow.svelte` The old rule `.notif-row.actionable { cursor: pointer; }` was removed and the actionable surface is now a real `<button class="notif-row-text">`. Browser default cursor for `<button>` is `default`, so the row no longer signals clickability. Add `cursor: pointer` to `.notif-row-text:is(button)` (or rely on the existing `.notif-quote-open:is(button)` pattern). **Two distinct buttons open the same notification** — `src/lib/rebuild/NotifRow.svelte` For actionable rows with a post, both `.notif-row-text` (the whole row) and `.notif-quote-open` (the excerpt) call `onOpen?.(n)`. This creates redundant focusable targets and a duplicated "open" action in the a11y tree. Either expose a single activator or mark the secondary one `tabindex="-1"`/`aria-hidden`. **Double notification load after clear** — `src/routes/app/[...path]/+page.svelte` `clearNotifications` first runs `loadNotifications(session, { replace: true })` and then, in `finally`, unconditionally calls `loadNotifications(session, { background: true })`. The replace load already fetches authoritative state right after the clear; the second call is a duplicate network request in the common success path. Gate the background follow-up on a real signal (e.g., the replacement stream has opened with no fresh data) or skip it when the replace succeeded. **`CompactMediaPreview` portal lifecycle is fragile** — `src/lib/rebuild/CompactMediaPreview.svelte` The `portal` action appends `node` to `document.body` and removes it on destroy. Svelte also tries to remove the node from its (now-moved) parent during `{#if fullPhoto}` teardown. The current ordering works today, but it depends on Svelte calling the action `destroy` before attempting DOM removal. Adding a comment or guarding the destroy to only remove when still attached would future-proof this against Svelte internals changes. **`aria-label` on visibility span may double-read** — `src/lib/rebuild/PostVisibility.svelte` The outer `<span>` carries `aria-label` while wrapping an `Icon` plus a visible `<span>{details.label}</span>`. If `Icon` exposes its own accessible name, AT users may hear the label twice. Ensure the inner `Icon` is `aria-hidden="true"` (the component is not shown in the patch). ### Informational - **`timeline-catch-up.ts` and `reconcileTimelineCatchUp`** are intricate but accompanied by a substantial, focused Playwright suite covering multi-page catch-up, concurrent streamed posts, empty-timeline recovery, route retention, and stale local vs. federated races. The behavior matches the tests; future maintainers will benefit from inline comments explaining the `initialDataIds`/`initialQueuedIds` overlap algorithm. - **Notification clearing** correctly bumps `notificationStreamGeneration`, gates `applyStreamedNotification` on both stream generation and `notificationClearState === 'clearing'`, and re-checks `isCurrentSessionRequest` after each `await`. The in-flight-notification and delayed-stream tests exercise the important races. - **`statusReplyTargetId`/`matchesStatusReplyTarget`** correctly route boosted-source identity through thread reconciliation, ancestor replies, and reply-target expansion. Indexing `byId` by both `id` and `statusReplyTargetId` is safe under the assumption that source IDs are unique within a thread context. - **`compactExcerpt` shortcode-boundary logic** is exported and unit-tested, and is reused for chat excerpts after send — a nice consolidation of the prior `chatExcerpt` helper. - **Custom-emoji propagation** is consistently applied to the audit-identified surfaces (notifications, CW summaries, pinned posts, chat rows, mention suggestions, lightbox attribution, profile context, settings preview), and hidden-emoji/no-request assertions back the privacy claims. - **`openPleromaTimelineStream` changes** (open-timeout, detach-on-close, idempotent close) are well-covered by the rewritten client tests, including the new "socket never opens" replacement case. ## Overall A large, well-structured patch that delivers a coherent set of features (fit-images, compact media previews with floating full-image view, notifications-page redesign, visibility badges, timeline stream catch-up, thread loading/scroll/restoration, count reconciliation, custom-emoji audit fixes, clear-all with generation-safe catch-up). Test coverage is thorough and traces cleanly to the per-issue acceptance criteria. No security, data-integrity, or correctness defects were identified. The most actionable items are the unbounded engagement-snapshot maps (Medium), the unthrottled floating-preview positioning (Medium), and a handful of low-severity UX/a11y/style nits listed above.
Author
Owner

/oc rerun

/oc rerun
Author
Owner

/oc rerun

/oc rerun
Author
Owner

/oc rerun

/oc rerun
Author
Owner

/oc rerun

/oc rerun
Author
Owner

/oc rerun

/oc rerun
docs: archive OpenCode review pilot
All checks were successful
opencode/review OpenCode execution completed
1a04372949
Author
Owner

/oc rerun

/oc rerun
All checks were successful
opencode/review OpenCode execution completed
This pull request is marked as a work in progress.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin issue/109-opencode-review-bot-pilot:issue/109-opencode-review-bot-pilot
git switch issue/109-opencode-review-bot-pilot

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff issue/109-opencode-review-bot-pilot
git switch issue/109-opencode-review-bot-pilot
git rebase master
git switch master
git merge --ff-only issue/109-opencode-review-bot-pilot
git switch issue/109-opencode-review-bot-pilot
git rebase master
git switch master
git merge --no-ff issue/109-opencode-review-bot-pilot
git switch master
git merge --squash issue/109-opencode-review-bot-pilot
git switch master
git merge --ff-only issue/109-opencode-review-bot-pilot
git switch master
git merge issue/109-opencode-review-bot-pilot
git push origin master
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lambadalambda/pleromanet!1
No description provided.