Original Request
Pre-fetch visual diffs in background with skeleton loading
Context: In packages/core/src/components/DiffBox.vue, the visual diff is fetched from MediaWiki's compare API on-demand when the user switches to the visual diff tab. This blocks the UI for 1-3 seconds on slow networks. Since the review feed already knows the next few revisions, visual diffs for upcoming revisions could be pre-fetched in the background while the user reviews the current edit. Meanwhile, a skeleton/shimmer UI should be shown instead of a blank loading state. This aligns with the project's established pattern of lazy loading with UI skeletons (per CLAUDE.md guidelines). The wikitext diff can be shown immediately as a fallback while the visual diff loads.
Agent's Two Cents (could be wrong)
Everything below is the AI agent's best guess based on the current codebase.
Take with a grain of salt — the original request above is the only thing that came from a human.
Problem / Motivation
DiffBox.vue currently calls MediaWiki's action=compare API with difftype=inline only when the user switches to the visual diff tab or when the revision changes while already on that tab. This creates a synchronous loading bottleneck of 1-3 seconds on slow connections. The review feed (useReviewFeed.ts) already maintains a ranked pool of upcoming revisions via poolRemaining, meaning the next N revisions are known in advance — but their visual diffs are never pre-fetched.
Proposed Solution
-
Background pre-fetch service: Introduce a composable (e.g., useVisualDiffCache) that watches the review feed's poolRemaining and pre-fetches visual diffs for the top 2-3 upcoming revisions in the background. The cache should be keyed by wiki:revId and have a TTL to avoid stale entries.
-
Skeleton/shimmer loading UI: Replace the current plain-text "Loading diff..." state in DiffBox.vue with an animated skeleton placeholder that approximates the shape of a diff table (rows of varying-width shimmer bars). This gives immediate visual feedback.
-
Wikitext fallback: When the user switches to visual diff mode, show the wikitext diff immediately (already available via the diffHtml prop) with a subtle indicator that the visual diff is loading. Swap in the visual diff once it arrives from the cache or network.
Dependencies & Potential Blockers
- MediaWiki's
action=compare API has no documented rate limit for anonymous requests with origin=*, but aggressive pre-fetching (more than 3-5 concurrent requests) could trigger Wikimedia's global rate limiting. Pre-fetch concurrency should be capped.
- The pre-fetch cache will increase memory usage proportional to the number of cached diffs. A bounded LRU or simple map with TTL eviction is needed.
- No new external dependencies or credentials required.
How to Validate
Scope Estimate
medium
Key Files/Modules Likely Involved
packages/core/src/components/DiffBox.vue — add skeleton UI, integrate with cache
packages/core/src/composables/useReviewFeed.ts — expose upcoming revisions for pre-fetch
packages/core/src/composables/useVisualDiffCache.ts (new) — pre-fetch logic and cache management
packages/web/src/pages/FeedPage.vue — wire up pre-fetch composable at the page level
packages/core/src/__tests__/components/DiffBox.test.ts — expand tests for skeleton and cache states
Rough Implementation Sketch
- Create
useVisualDiffCache composable that:
- Accepts a reactive list of upcoming
ScoredRevision items
- Maintains a
Map<string, string> of wiki:revId → visual diff HTML
- Watches the list and fetches diffs for the top N items not yet cached (concurrency-limited)
- Exposes a
getCachedDiff(wiki, revId) method and a reactive has(wiki, revId) check
- Evicts entries older than a configurable TTL or beyond a max cache size
- Extract the
fetchVisualDiff logic from DiffBox.vue into a shared utility so both the component and the cache can use it
- In
DiffBox.vue, check the cache first before making a network request; add skeleton markup with CSS shimmer animation for the loading state
- Show
diffHtml (wikitext) as an immediate fallback with a "Loading visual diff..." badge overlay
Open Questions
- Should the pre-fetch happen at the page level (FeedPage/ReviewPage) or inside DiffBox itself? Page-level seems cleaner since DiffBox shouldn't know about the feed.
- How many revisions ahead should be pre-fetched? 2-3 seems reasonable but may need tuning.
- Should cached diffs persist to
localStorage alongside the pool cache, or stay in-memory only? localStorage would survive page reloads but adds serialization cost for potentially large HTML strings.
- Should pre-fetching be disabled on metered connections (
navigator.connection.saveData)?
Potential Risks or Gotchas
- MediaWiki's inline diff (
difftype=inline) is rendered server-side and can return large HTML blobs for big edits. Caching 3 of these simultaneously could use significant memory.
- If the user skips through revisions quickly, pre-fetch requests for skipped revisions become wasted bandwidth. An abort controller pattern should cancel in-flight fetches for revisions the user has moved past.
- The
action=compare endpoint occasionally returns empty bodies for certain revision pairs (page creations, deleted revisions). The cache needs to handle these gracefully rather than retrying endlessly.
Original Request
Agent's Two Cents (could be wrong)
Problem / Motivation
DiffBox.vuecurrently calls MediaWiki'saction=compareAPI withdifftype=inlineonly when the user switches to the visual diff tab or when the revision changes while already on that tab. This creates a synchronous loading bottleneck of 1-3 seconds on slow connections. The review feed (useReviewFeed.ts) already maintains a ranked pool of upcoming revisions viapoolRemaining, meaning the next N revisions are known in advance — but their visual diffs are never pre-fetched.Proposed Solution
Background pre-fetch service: Introduce a composable (e.g.,
useVisualDiffCache) that watches the review feed'spoolRemainingand pre-fetches visual diffs for the top 2-3 upcoming revisions in the background. The cache should be keyed bywiki:revIdand have a TTL to avoid stale entries.Skeleton/shimmer loading UI: Replace the current plain-text "Loading diff..." state in
DiffBox.vuewith an animated skeleton placeholder that approximates the shape of a diff table (rows of varying-width shimmer bars). This gives immediate visual feedback.Wikitext fallback: When the user switches to visual diff mode, show the wikitext diff immediately (already available via the
diffHtmlprop) with a subtle indicator that the visual diff is loading. Swap in the visual diff once it arrives from the cache or network.Dependencies & Potential Blockers
action=compareAPI has no documented rate limit for anonymous requests withorigin=*, but aggressive pre-fetching (more than 3-5 concurrent requests) could trigger Wikimedia's global rate limiting. Pre-fetch concurrency should be capped.How to Validate
DiffBox.test.tstests still pass, plus new tests for the cache composableScope Estimate
medium
Key Files/Modules Likely Involved
packages/core/src/components/DiffBox.vue— add skeleton UI, integrate with cachepackages/core/src/composables/useReviewFeed.ts— expose upcoming revisions for pre-fetchpackages/core/src/composables/useVisualDiffCache.ts(new) — pre-fetch logic and cache managementpackages/web/src/pages/FeedPage.vue— wire up pre-fetch composable at the page levelpackages/core/src/__tests__/components/DiffBox.test.ts— expand tests for skeleton and cache statesRough Implementation Sketch
useVisualDiffCachecomposable that:ScoredRevisionitemsMap<string, string>ofwiki:revId → visual diff HTMLgetCachedDiff(wiki, revId)method and a reactivehas(wiki, revId)checkfetchVisualDifflogic fromDiffBox.vueinto a shared utility so both the component and the cache can use itDiffBox.vue, check the cache first before making a network request; add skeleton markup with CSS shimmer animation for the loading statediffHtml(wikitext) as an immediate fallback with a "Loading visual diff..." badge overlayOpen Questions
localStoragealongside the pool cache, or stay in-memory only? localStorage would survive page reloads but adds serialization cost for potentially large HTML strings.navigator.connection.saveData)?Potential Risks or Gotchas
difftype=inline) is rendered server-side and can return large HTML blobs for big edits. Caching 3 of these simultaneously could use significant memory.action=compareendpoint occasionally returns empty bodies for certain revision pairs (page creations, deleted revisions). The cache needs to handle these gracefully rather than retrying endlessly.