Refactor premium cloud sync repositories - #124
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (6)**/*.{ts,tsx,vue}📄 CodeRabbit inference engine (AGENTS.md)
Files:
test/**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,ts,tsx,vue,json,css}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{js,ts,tsx,vue}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.ts📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (4)📓 Common learnings📚 Learning: 2026-05-30T19:57:20.193ZApplied to files:
📚 Learning: 2026-05-30T19:57:20.193ZApplied to files:
📚 Learning: 2026-05-30T19:57:20.193ZApplied to files:
🔇 Additional comments (12)
📝 WalkthroughWalkthroughThis PR centralizes premium cloud sync into PremiumCloudRepository, migrates premium runtime state to Nuxt useState, rewires PostSave and the saved-posts page to use the repository, adds PocketBase Playwright mocks and new integration/unit tests, and updates docs and a devDependency pin. ChangesPremium Cloud Sync Repository Refactoring
Possibly Related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
app/composables/usePremiumCloudSync.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. app/repositories/PremiumCloudRepository.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. test/assets/premium-cloud-sync.test.tsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/components/pages/posts/post/PostSave.vue`:
- Around line 12-25: The save button can be clicked before the cloud-synced list
finishes loading because you call initialize() in onMounted and use
getSavedPost() synchronously; change startup to use initializeInBackground() for
fire-and-forget (so it won't produce unhandled rejections) and ensure the UI
gates actions until initialization completes: expose the initialization state
from usePremiumCloudSync (or use the existing initialize promise) and make
postInSavedList/isPostSaved respect that flag so the save button is disabled or
onClick is no-op until initialization finishes; update usages of getSavedPost,
postInSavedList and isPostSaved to check the init-complete flag (or preload
initialize higher up) before allowing savePost/deleteSavedPost to run.
In `@app/composables/usePremiumCloudSync.ts`:
- Line 60: The savedPostList useState is keyed globally causing different users
to share cached saved-post IDs and leading getSavedPost()/deleteSavedPost() to
operate on stale data; update the useState key in the composable (where
savedPostList is created) to include the current user's unique id (e.g.,
useState<ISimplePocketbasePost[]>(`premium-saved-post-list-${userId}`, ...)) so
the cache is user-scoped, ensure initialize() uses the same user-scoped key when
loading, and update clearAuthBoundRuntimeState() to also reset/clear the owner
id and clear the user-scoped savedPostList so switches between accounts cannot
reuse the previous user's cached entries.
In `@app/pages/premium/saved-posts/`[domain].vue:
- Around line 51-60: The current logic constructs booruList (computed booruList)
only with project.urls.production.hostname and then later throws when route
param [domain] isn't found; instead, update the route handling that uses
booruList (the computed booruList and the code path that currently throws for
missing booru) to treat unknown legacy domains as the production hostname: when
the incoming route param domain does not match any entry in booruList,
programmatically redirect (or replace) to the same route but with domain =
project.urls.production.hostname while preserving the existing query object
(route.query), rather than throwing; ensure this redirect uses the same
navigation method used elsewhere (router.replace or similar) so bookmarks/old
links seamlessly resolve to the production booru.
In `@app/repositories/PremiumCloudRepository.ts`:
- Around line 456-462: The deleteCollectionRecords method currently iterates
records and calls collection.delete for each row, causing N sequential HTTP
deletes and partial failures; change it to use the repository's batch delete
capability: fetch ids via collection.getFullList (as already done), then send
them in batched calls (using the existing batch API on this.client or
collection.batch/deleteBatch method) instead of per-record await
collection.delete, handling errors per-batch and retry/backoff as needed to
avoid partial clears; update deleteCollectionRecords to build id chunks and call
the batch delete helper so
clearTagCollections/deleteCloudData/clearBoorus/deleteAccount use efficient
batched deletes.
- Around line 327-337: The current subscribeToPremiumCloudChanges uses
Promise.all which rejects on the first subscribe failure and leaks earlier
active subscriptions; change the logic to subscribe incrementally (call
this.subscribeToCollection for each premiumCloudCollections entry one-by-one or
via Promise.allSettled), collect successful unsubscribe handles as you go, and
if any subscribe fails perform cleanup by calling all collected unsubscribe
handles (use Promise.all to await those) before rethrowing the error; reference
subscribeToPremiumCloudChanges and subscribeToCollection when making these
changes and ensure the returned teardown always returns a function that awaits
all active unsubscribe handles.
- Around line 292-303: saveCustomBlockList currently only updates records[0] and
leaves any extra rows; modify it to enforce a single-record invariant: call
listBlockLists(), build payload via customBlockListPayloadFromState(this.userId,
tags), get collection via
this.client.collection(premiumCloudCollections.blocklists), then if
records.length > 0 update the first record with collection.update(record.id,
payload) and delete all other records (loop over records.slice(1) and call
collection.delete(id)); if no existing record, create one with
collection.create(payload); ensure in both branches any extra records are
removed so only one record remains.
- Around line 240-257: In PremiumCloudRepository (the method calling
this.client.collection(premiumCloudCollections.posts).getList), remove
skipTotal: true from the getList options so PocketBase will populate
response.totalItems/response.totalPages, or if you must keep skipTotal then do
not use response.totalItems/response.totalPages in the returned meta (derive
totals or set them to null/undefined); update the return meta block
(meta.items_count, meta.total_items, meta.current_page, meta.total_pages,
meta.items_per_page) accordingly so it no longer exposes bogus -1 values when
skipTotal is set.
In `@test/i18n/audience-locales.test.ts`:
- Line 5: Replace the recomputed non-default locale list in the test with the
canonical exported value: import and use prefixedLocaleCodes from config/i18n.ts
instead of recalculating via locales.filter(...).map(...); update the test to
reference prefixedLocaleCodes (rather than locales and defaultLocale) so the
test stays in sync with the source definition.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3c8fcaf2-78aa-45ef-ac74-9b22b26030c1
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
AGENTS.mdapp/assets/js/PremiumCloudSync.tsapp/components/pages/posts/post/PostSave.vueapp/composables/useBlockLists.tsapp/composables/useBooruList.tsapp/composables/usePocketbase.tsapp/composables/usePremiumCloudSync.tsapp/composables/useTagCollections.tsapp/pages/premium/saved-posts/[domain].vueapp/plugins/030.pocketbase.tsapp/repositories/PremiumCloudRepository.tspackage.jsontest/assets/premium-cloud-sync-repository.test.tstest/assets/premium-cloud-sync.test.tstest/composables/lazy-toast-runtime.test.tstest/composables/premium-cloud-sync-runtime.test.tstest/i18n/audience-locales.test.tstest/i18n/premium-cloud-sync-locales.test.tstest/pages/premium-cloud.test.tstest/pages/premium-sign-in.test.tstest/pocketbase-mock.ts
💤 Files with no reviewable changes (4)
- test/pages/premium-sign-in.test.ts
- test/composables/premium-cloud-sync-runtime.test.ts
- test/composables/lazy-toast-runtime.test.ts
- test/i18n/premium-cloud-sync-locales.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{js,ts,tsx,vue,json}
📄 CodeRabbit inference engine (AGENTS.md)
Prettier formatting: 120-char print width, no semicolons, single quotes, trailing commas removed, single attribute per line in Vue templates
Files:
package.jsontest/i18n/audience-locales.test.tsapp/components/pages/posts/post/PostSave.vueapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Components in Nuxt are auto-imported without path prefix; import them as
<ComponentName>not<Folder/ComponentName>
Files:
test/i18n/audience-locales.test.tsapp/components/pages/posts/post/PostSave.vueapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Query filters use flat bracket keys (
filter[sort],filter[rating], etc.); do not re-addqsfor nested query objects unless the URL contract changesUse
useLazyToast()to lazy-loadvue-sonner; the first toast must wait forClientToasterto mount (plainnextTick()can fire before the component loads)Empty cloud state means 'no user-authored cloud override'; do not seed PocketBase from local defaults during initial load; only write after explicit user edits
PocketBase realtime subscriptions echo local writes; separate write requests from realtime-triggered refreshes when debugging sync performance
Files:
test/i18n/audience-locales.test.tsapp/components/pages/posts/post/PostSave.vueapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
For URL validation/parsing, prefer
URL.canParse()orURL.parse()over constructortry/catch; useURL.parse()when the parsed object is needed
Files:
test/i18n/audience-locales.test.tsapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Do not add service worker logic; the service worker is intentionally disabled with
selfDestroying: true
Files:
test/i18n/audience-locales.test.tsapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
test/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Tests use
@nuxt/test-utilswith Playwright insidedescribeblocks that callawait setup({ browser: true })Use
debugBrowserOptionsimported fromtest/helper.tsfor headful Playwright playback with slowMo in testsPlain Vitest suites that import app modules directly do not get Nuxt's runtime alias resolution; keep repository/pure modules importable through relative paths
For premium and PocketBase flows, use a real authenticated browser session for final investigation when possible; unit tests prove behavior but real-browser traces catch request bursts and state changes
Files:
test/i18n/audience-locales.test.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use PocketBase batch writes for multi-record replacement/reorder operations instead of one HTTP write per changed row
Files:
test/i18n/audience-locales.test.tsapp/composables/useBlockLists.tstest/assets/premium-cloud-sync.test.tstest/pages/premium-cloud.test.tstest/assets/premium-cloud-sync-repository.test.tsapp/plugins/030.pocketbase.tsapp/composables/useTagCollections.tstest/pocketbase-mock.tsapp/composables/useBooruList.tsapp/assets/js/PremiumCloudSync.tsapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.tsapp/composables/usePocketbase.ts
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
OG image URLs must be absolute; build them dynamically with
useRequestURL().originon the server onlyCanonical URLs must point to production (
https://r34.app/…) even when served from clone domains; useproject.urls.productionfor canonicalsSchema.org breadcrumb item URLs should stay local/locale-relative; do not convert breadcrumb URLs to
project.urls.productionUse
<NuxtImg>with the custom imgproxy provider; images are deliberately generated at 1x density only (webp format)Keep
@formkit/auto-animateroute-scoped unless used broadly; use localvAutoAnimateimports on premium CSR pages to avoid including the runtime in first-load entryFor state immediately persisted after VueUse
moveArrayElement(), build the reordered array synchronously instead of reading it onnextTick
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vue
app/pages/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Use
useSeoMetafor page-specific SEO tags (title, description) in the page component
Files:
app/pages/premium/saved-posts/[domain].vue
app/pages/**/premium/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Saved posts use the same premium cloud realtime runtime as tag collections, custom boorus, and custom blocklist; empty saved-post state means no saved posts
Files:
app/pages/premium/saved-posts/[domain].vue
app/pages/premium/saved-posts/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
In
/premium/saved-posts, unsaving a post should not remove the row or prune cached infinite-query data; keep the viewer stable and let the save button update immediately
Files:
app/pages/premium/saved-posts/[domain].vue
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T09:09:27.992Z
Learning: Prefer high-impact, measurable performance optimizations; verify changes with production builds, tests, request traces, and Lighthouse before committing
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T09:09:27.992Z
Learning: Do not add app-level HTML compression unless a direct-origin deployment needs it and the change is verified with headers, byte sizes, warm TTFB, and Lighthouse
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T09:09:27.992Z
Learning: Keep the global TanStack Vue Query plugin unless a larger measured payoff appears; a route-scoped experiment did not provide sufficient gains
🔇 Additional comments (5)
package.json (1)
85-85: LGTM!AGENTS.md (1)
167-168: LGTM!app/plugins/030.pocketbase.ts (1)
9-51: LGTM!test/pocketbase-mock.ts (1)
64-176: LGTM!test/pages/premium-cloud.test.ts (1)
1-122: LGTM!
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/pages/premium/saved-posts/[domain].vue (1)
358-375:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUser-scope the saved-post query cache.
This query key is route-scoped but not account-scoped. After a logout/login or account switch, TanStack Query can reuse the previous user's cached
/premium/saved-postspages for up to five minutes, which exposes another account's saved posts until the refetch lands. Include the PocketBase user id in the key, or clear all['saved-posts']queries during auth-bound cleanup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/pages/premium/saved-posts/`[domain].vue around lines 358 - 375, The query key for useInfiniteQuery (the queryKey array built around 'saved-posts', selectedBooru, selectedTags, selectedFilters, selectedPage.value, postsPerPage.value) is missing account scoping so cached saved-posts can leak between users; fix by including the current PocketBase user id (or other auth UID) in that queryKey (e.g., add currentUser.id or authUserId to the array used by useInfiniteQuery) or, alternatively, add a cleanup step that clears queryClient.removeQueries(['saved-posts']) on logout/account switch to ensure per-account isolation.test/assets/premium-cloud-sync-repository.test.ts (1)
30-39:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMake the fake
getList()honorpageandperPage.Line 38 always returns the full collection, so these repository paging tests won't catch off-by-one or next-page regressions.
Proposed fix
getList: vi.fn(async (page = 1, perPage = 30, ...args: unknown[]) => { calls.push({ collection: name, method: 'getList', args: [page, perPage, ...args] }) + const start = (page - 1) * perPage + const end = start + perPage return { page, perPage, totalItems: records[name].length, totalPages: records[name].length ? Math.ceil(records[name].length / perPage) : 0, - items: records[name] + items: records[name].slice(start, end) } }),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/assets/premium-cloud-sync-repository.test.ts` around lines 30 - 39, The fake getList implementation returns the entire collection regardless of page/perPage; update the getList mock (the vi.fn for getList) to compute totalItems = records[name].length, totalPages = totalItems ? Math.ceil(totalItems / perPage) : 0 and set items = records[name].slice((page - 1) * perPage, (page - 1) * perPage + perPage) so the returned items reflect the requested page and perPage; keep the existing calls.push({ collection: name, method: 'getList', args: [...] }) and default parameters (page = 1, perPage = 30).app/composables/usePremiumCloudSync.ts (1)
171-176:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftScope or cancel the latest-only write queues per authenticated user.
These queued callbacks outlive the account that enqueued them. If user A leaves a pending
setTagCollections()/setUserBooruList()flush behind and user B signs in before it drains, the callback will run againstrepository.valuefor user B with user A's payload. That can leak or overwrite premium cloud data across accounts.As per coding guidelines, "Auth-bound premium useState caches must be explicitly scoped or owner-guarded by PocketBase user id to prevent exposing one account's data during auth switches."
Also applies to: 200-205, 366-421
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/composables/usePremiumCloudSync.ts` around lines 171 - 176, Queued cloud-write callbacks (e.g., those created via saveTagCollectionsToCloud used by setTagCollections/setUserBooruList) are not bound to the authenticating user and can run after an auth switch, causing cross-account data leakage; modify saveTagCollectionsToCloud (and the similar queues at lines noted) to scope or cancel per-user by capturing the current PocketBase user id when enqueuing and: 1) store that owner id with the queued callback, 2) on execution compare it to the current repository.value/auth user id and skip/abort if they differ, and 3) cancel any pending callbacks for the previous user when sign-out or a new sign-in occurs (or when enqueuing a new owner-scoped "latest-only" flush) so repository.value, runtime.value.cloudBacked, tryEnsureRealtimeSubscription, and similar methods only run against the owning user’s context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@app/composables/usePremiumCloudSync.ts`:
- Around line 171-176: Queued cloud-write callbacks (e.g., those created via
saveTagCollectionsToCloud used by setTagCollections/setUserBooruList) are not
bound to the authenticating user and can run after an auth switch, causing
cross-account data leakage; modify saveTagCollectionsToCloud (and the similar
queues at lines noted) to scope or cancel per-user by capturing the current
PocketBase user id when enqueuing and: 1) store that owner id with the queued
callback, 2) on execution compare it to the current repository.value/auth user
id and skip/abort if they differ, and 3) cancel any pending callbacks for the
previous user when sign-out or a new sign-in occurs (or when enqueuing a new
owner-scoped "latest-only" flush) so repository.value,
runtime.value.cloudBacked, tryEnsureRealtimeSubscription, and similar methods
only run against the owning user’s context.
In `@app/pages/premium/saved-posts/`[domain].vue:
- Around line 358-375: The query key for useInfiniteQuery (the queryKey array
built around 'saved-posts', selectedBooru, selectedTags, selectedFilters,
selectedPage.value, postsPerPage.value) is missing account scoping so cached
saved-posts can leak between users; fix by including the current PocketBase user
id (or other auth UID) in that queryKey (e.g., add currentUser.id or authUserId
to the array used by useInfiniteQuery) or, alternatively, add a cleanup step
that clears queryClient.removeQueries(['saved-posts']) on logout/account switch
to ensure per-account isolation.
In `@test/assets/premium-cloud-sync-repository.test.ts`:
- Around line 30-39: The fake getList implementation returns the entire
collection regardless of page/perPage; update the getList mock (the vi.fn for
getList) to compute totalItems = records[name].length, totalPages = totalItems ?
Math.ceil(totalItems / perPage) : 0 and set items = records[name].slice((page -
1) * perPage, (page - 1) * perPage + perPage) so the returned items reflect the
requested page and perPage; keep the existing calls.push({ collection: name,
method: 'getList', args: [...] }) and default parameters (page = 1, perPage =
30).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c0330d5d-3b7d-4ddb-81f0-acb98cf227e9
📒 Files selected for processing (9)
AGENTS.mdapp/components/pages/posts/post/PostSave.vueapp/composables/usePremiumCloudSync.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tstest/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pages/premium-cloud.test.tstest/pocketbase-mock.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use Nuxt 4 (SSR, Nitro server) with Vue 3 + TypeScript
Use useLazyToast() to lazy-load vue-sonner and render ClientToaster; wait for ClientToaster to mount before calling toast.* with proper async handling
Files:
app/components/pages/posts/post/PostSave.vuetest/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pocketbase-mock.tsapp/pages/premium/saved-posts/[domain].vuetest/pages/premium-cloud.test.tsapp/composables/usePremiumCloudSync.tsapp/repositories/PremiumCloudRepository.ts
**/*.{js,ts,tsx,vue,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier for formatting with 120-char print width, no semicolons, single quotes, trailing commas removed, single attribute per line in Vue templates
Files:
app/components/pages/posts/post/PostSave.vuetest/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pocketbase-mock.tsapp/pages/premium/saved-posts/[domain].vuetest/pages/premium-cloud.test.tsapp/composables/usePremiumCloudSync.tsapp/repositories/PremiumCloudRepository.ts
**/*.{js,ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use Nuxt flat ESLint via
@nuxt/eslintfor linting
Files:
app/components/pages/posts/post/PostSave.vuetest/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pocketbase-mock.tsapp/pages/premium/saved-posts/[domain].vuetest/pages/premium-cloud.test.tsapp/composables/usePremiumCloudSync.tsapp/repositories/PremiumCloudRepository.ts
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Register custom imgproxy provider for and deliberately generate images at 1x density only (webp format) to reduce bandwidth
Canonical URLs must point to production (https://r34.app/…) even when served from clone domains; use project.urls.production for canonicals
Use
@nuxt/imagev2 module API for image preload priority with preload: { fetchPriority: 'high' } instead of patching rendered HTML in NitroKeep
@formkit/auto-animateroute-scoped (local vAutoAnimate imports) unless used broadly to reduce entry bundle size
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vue
{app/components/**/*.vue,app/pages/**/*.vue,app/layouts/**/*.vue}
📄 CodeRabbit inference engine (AGENTS.md)
Components are auto-imported without path prefix (pathPrefix: false); import as not <Input/DomainSelector>
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vue
test/**/*.{test,spec}.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use Vitest +
@nuxt/test-utilswith Playwright browser mode for testing
Files:
test/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pages/premium-cloud.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
For URL validation/parsing, prefer URL.canParse() or URL.parse() over constructor try/catch; use URL.parse() when parsed URL object is needed
Files:
test/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pocketbase-mock.tstest/pages/premium-cloud.test.tsapp/composables/usePremiumCloudSync.tsapp/repositories/PremiumCloudRepository.ts
test/**/*.{test,spec}.ts
📄 CodeRabbit inference engine (AGENTS.md)
Tests use
@nuxt/test-utilswith Playwright inside describe blocks calling await setup({ browser: true })Debug mode: import debugBrowserOptions from test/helper.ts for headful playback with slowMo
Plain Vitest suites that import app modules directly do not get Nuxt runtime alias resolution; keep repository/pure modules importable through relative paths or import from app path
Files:
test/assets/premium-cloud-sync-repository.test.tstest/i18n/audience-locales.test.tstest/pages/premium-cloud.test.ts
{app/pages/**/*.vue,app/composables/**/*.ts}
📄 CodeRabbit inference engine (AGENTS.md)
Query filters use flat bracket keys (filter[sort], filter[rating], etc.) with Vue Router default query handling; do not re-add qs for nested route.query.filter objects unless URL contract changes
Files:
app/pages/premium/saved-posts/[domain].vueapp/composables/usePremiumCloudSync.ts
{app/composables/**/*[Pp]remium*.ts,app/pages/premium/**/*.vue}
📄 CodeRabbit inference engine (AGENTS.md)
Auth-bound premium useState caches must be explicitly scoped or owner-guarded by PocketBase user id to prevent exposing one account's data during auth switches
Files:
app/pages/premium/saved-posts/[domain].vueapp/composables/usePremiumCloudSync.ts
app/pages/premium/saved-posts/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
In /premium/saved-posts, unsaving a post should not remove the row or prune cached infinite-query data to keep the viewer stable; save button updates immediately with row disappearing on reload
Files:
app/pages/premium/saved-posts/[domain].vue
{app/composables/**/*.ts,app/pages/premium/**/*.vue,server/api/**/*.ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use PocketBase batch writes for multi-record replacement/reorder operations instead of one HTTP write per changed row
Files:
app/pages/premium/saved-posts/[domain].vueapp/composables/usePremiumCloudSync.ts
{app/pages/premium/**/*.vue,app/composables/**/*.ts}
📄 CodeRabbit inference engine (AGENTS.md)
VueUse moveArrayElement() applies array move on nextTick; for state immediately persisted, build the reordered array synchronously instead of reading before VueUse applies move
Files:
app/pages/premium/saved-posts/[domain].vueapp/composables/usePremiumCloudSync.ts
app/composables/**/*[Cc]loud*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Empty cloud state means no user-authored cloud override; do not seed PocketBase from local defaults during initial load, only after explicit user edits
Files:
app/composables/usePremiumCloudSync.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Require Node 24 (package.json engines) and pnpm ≥ 11.2.2 (packageManager pins expected version)
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Clone repository with --recursive flag to include Git submodule at app/assets/lib/rule-34-shared-resources
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Keep HOST=127.0.0.1 in local .env for pnpm dev to avoid IPv6 resolution issues
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Non-premium SPA navigations keep direct image path; validate image delivery in an environment where imgproxy can resolve the source URL
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Prefer high-impact, measurable optimizations over small rewrites; verify substantial performance changes with production build, tests, request traces, and Lighthouse
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Do not add app-level HTML compression unless direct-origin deployment needs it; verify with headers, byte sizes, warm TTFB, and Lighthouse (Cloudflare Brotli-compresses responses)
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Service worker is intentionally disabled (selfDestroying: true); do not add service worker logic
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Source map uploads only happen in production Docker builds (requires SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN build args)
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: For premium and PocketBase flows, use a real authenticated browser session for final investigation when possible; real-browser traces catch request bursts and auth flows unit tests miss
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: PocketBase realtime subscriptions echo local writes; inspect real network traces to separate write requests from realtime-triggered refreshes
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Saved posts use the same premium cloud realtime runtime as tag collections, custom boorus, and custom blocklist; empty cloud state means no saved posts
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Docker production build: multi-stage build uses pnpm install --frozen-lockfile; production stage copies only .output/ (no node_modules)
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: Docker production build: set SENTRY_UPLOAD_SOURCE_MAPS=false to skip source map uploads if SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN are not available
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-02T10:34:15.916Z
Learning: NITRO_PRESET build arg selects the deployment target in Docker production build
🔇 Additional comments (1)
test/i18n/audience-locales.test.ts (1)
3-5: LGTM!
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/composables/usePremiumCloudSync.ts (1)
46-48:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInvalidate queued cloud writes when sync state is cleared.
These queues live at module scope, and their callbacks mutate
tagCollections.value/userBooruList.valueafter awaiting the repository write.clearSyncedLocalState()resets the atoms but never invalidates queued or in-flight callbacks, so a late save can repopulate cleared data afterdeleteCloudData()/deleteAccount(). If auth changes first, that queued callback can also run against the next session because it resolvesrepository.valueat execution time. Add a generation/token check before and after the awaited write, and bump it insideclearSyncedLocalState().Also applies to: 125-130, 154-159, 295-303
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/composables/usePremiumCloudSync.ts` around lines 46 - 48, The module-scoped queues (saveTagCollectionsToCloud, saveBoorusToCloud created via createLatestAsyncQueue) can run after clearSyncedLocalState and repopulate tagCollections.value / userBooruList.value or run against a new session because they resolve repository.value at execution time; modify each queued callback to capture a local generation/token (e.g., read a new numeric generation variable) before the awaited repository write, then re-check that the generation/token is unchanged after the await before mutating tagCollections.value or userBooruList.value, and increment/reset that generation inside clearSyncedLocalState(); apply the same pattern to the other module-scoped queues mentioned around the other ranges so all in-flight/queued saves are invalidated when state is cleared.app/repositories/PremiumCloudRepository.ts (1)
82-99:⚠️ Potential issue | 🟠 Major | ⚡ Quick winOnly reject the queue when the latest payload fails.
flush()keeps the first error for the whole drain. If save A fails, save B is queued, and B succeeds, the returned promise still rejects with A's error. Downstream callers then show a failure toast even though the latest state was persisted and local state was already updated. Clear/replace the stored error after a later successful save so the promise reflects the outcome of the newest payload.Suggested change
async function flush() { let error: unknown while (hasLatestPayload) { const payload = latestPayload as T latestPayload = undefined hasLatestPayload = false try { await save(payload) + error = undefined } catch (caughtError) { - error ??= caughtError + error = caughtError } } if (error) { throw errorAlso applies to: 102-109
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/repositories/PremiumCloudRepository.ts` around lines 82 - 99, The flush() loop currently preserves the first failure via error ??= caughtError which causes flush to reject even if a subsequent save succeeds; change the logic so that after a successful await save(payload) you clear any prior error (set error = undefined) and on failures overwrite the stored error (use error = caughtError) so the promise outcome reflects the newest payload; apply the same fix to the analogous block referenced around lines 102-109 (replace the conditional accumulation with overwrite on catch and clear on successful save).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@app/composables/usePremiumCloudSync.ts`:
- Around line 46-48: The module-scoped queues (saveTagCollectionsToCloud,
saveBoorusToCloud created via createLatestAsyncQueue) can run after
clearSyncedLocalState and repopulate tagCollections.value / userBooruList.value
or run against a new session because they resolve repository.value at execution
time; modify each queued callback to capture a local generation/token (e.g.,
read a new numeric generation variable) before the awaited repository write,
then re-check that the generation/token is unchanged after the await before
mutating tagCollections.value or userBooruList.value, and increment/reset that
generation inside clearSyncedLocalState(); apply the same pattern to the other
module-scoped queues mentioned around the other ranges so all in-flight/queued
saves are invalidated when state is cleared.
In `@app/repositories/PremiumCloudRepository.ts`:
- Around line 82-99: The flush() loop currently preserves the first failure via
error ??= caughtError which causes flush to reject even if a subsequent save
succeeds; change the logic so that after a successful await save(payload) you
clear any prior error (set error = undefined) and on failures overwrite the
stored error (use error = caughtError) so the promise outcome reflects the
newest payload; apply the same fix to the analogous block referenced around
lines 102-109 (replace the conditional accumulation with overwrite on catch and
clear on successful save).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 95fd0b3f-ed84-402d-bc17-dd50ab3b1347
📒 Files selected for processing (6)
AGENTS.mdapp/components/pages/posts/post/PostSave.vueapp/composables/usePremiumCloudSync.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tstest/assets/premium-cloud-sync-repository.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/{app,server}/**/*.{vue,ts}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript in Vue components, Nuxt pages, composables, and server API routes
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Import Vue components without path prefix using flat auto-import naming (e.g.,
<DomainSelector>not<Input/DomainSelector>)Use
@nuxt/imagev2 module API for image preload priority with preload: { fetchPriority: 'high' } instead of patching rendered HTML in Nitro
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vue
**/*.{vue,ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Prettier with 120-char print width, no semicolons, single quotes, trailing commas removed, and single attribute per line in Vue templates
Use ESLint flat config via
@nuxt/eslintfor linting
Files:
app/components/pages/posts/post/PostSave.vuetest/assets/premium-cloud-sync-repository.test.tsapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
**/{app,server}/**/*.{ts,tsx,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Use flat bracket keys for query filters (e.g., filter[sort], filter[rating]) with Vue Router's default query handling, not qs library
Use useLazyToast() to lazy-load vue-sonner and wait for ClientToaster to mount before calling toast.* methods
Use PocketBase batch writes for multi-record replacement and reorder operations instead of one HTTP write per changed row
Build reordered arrays synchronously in state that is immediately persisted, not relying on VueUse moveArrayElement() nextTick behavior
Files:
app/components/pages/posts/post/PostSave.vueapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
test/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use
@nuxt/test-utilswith Playwright in describe blocks that call await setup({ browser: true }) for browser testingMock server-side API calls via test-only Nitro plugin at test/server-mocks/plugin.ts injected through nuxt.config.ts
Import debugBrowserOptions from test/helper.ts for headful playback with slowMo in debug mode
Keep repository and pure modules importable through relative paths in plain Vitest suites that do not have Nuxt runtime alias resolution
Use plain Vitest suites that import app modules directly; keep repository modules importable through relative paths since they do not have Nuxt runtime alias resolution
Files:
test/assets/premium-cloud-sync-repository.test.ts
app/pages/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Use useSeoMeta for page-specific tags (title, description) in page components
Files:
app/pages/premium/saved-posts/[domain].vue
app/pages/premium/**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
Keep
@formkit/auto-animateroute-scoped unless used broadly; prefer local vAutoAnimate imports on premium CSR pages
Files:
app/pages/premium/saved-posts/[domain].vue
**/{app,server}/**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Use URL.canParse() or URL.parse() for URL validation and parsing instead of constructor try/catch
Files:
app/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Saved posts use the same premium cloud realtime runtime as tag collections, custom boorus, and the custom blocklist
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Use canonical URLs pointing to production (https://r34.app/…) via project.urls.production for SEO, even when served from clone domains
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Keep breadcrumb item URLs locale-relative and local, not absolute production URLs
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Do not seed PocketBase from local defaults during initial load; only write premium cloud records after explicit user edits
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Do not remove rows or prune cached infinite-query data when unsaving a post; keep the viewer stable for scroll/progress preservation
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Clone the repository with --recursive to include Git submodule at app/assets/lib/rule-34-shared-resources
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Prefer high-impact, measurable optimizations over small rewrites; keep battle-tested dependencies unless a clear, measured payoff exists
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Verify production build changes with relevant tests, request traces, and Lighthouse before deciding changes are worth keeping
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Do not add app-level HTML compression unless direct-origin deployment needs it and change is verified with headers, byte sizes, warm TTFB, and Lighthouse
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Keep the global TanStack Vue Query plugin unless larger measured payoff appears
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Do not add service worker logic; the service worker is intentionally disabled with selfDestroying: true
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Use real authenticated browser sessions for premium and PocketBase flows in final investigation when possible, not just unit tests
Learnt from: CR
Repo: Rule-34/App
Timestamp: 2026-06-03T09:04:47.258Z
Learning: Validate image delivery in an environment where imgproxy can resolve the source URL, especially for non-premium SPA navigations
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Saved posts use the same premium cloud realtime runtime as tag collections, custom boorus, and the custom blocklist
Applied to files:
app/components/pages/posts/post/PostSave.vueAGENTS.mdapp/pages/premium/saved-posts/[domain].vueapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Only write premium cloud records after explicit user edits, not from local defaults during initial load
Applied to files:
AGENTS.mdapp/repositories/PremiumCloudRepository.tsapp/composables/usePremiumCloudSync.ts
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Empty cloud state means no user-authored cloud override; do not seed PocketBase from local defaults during initial load
Applied to files:
AGENTS.mdapp/composables/usePremiumCloudSync.ts
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Applies to app/pages/posts/[domain]/index.vue : Fix canonical query parameters via `server/plugins/fix-canonical-queries.ts` for SSR and `useHead` in `app/pages/posts/[domain]/index.vue` for CSR until upstream i18n module fixes `canonicalQueries`
Applied to files:
app/pages/premium/saved-posts/[domain].vue
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Applies to server/middleware/redirect-to-posts.get.ts : Legacy redirect: `server/middleware/redirect-to-posts.get.ts` redirects `/?domain=x&page=…&tags=…` → `/posts/x?page=…&tags=…` (301)
Applied to files:
app/pages/premium/saved-posts/[domain].vue
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Applies to app/components/**/*.vue : Components are auto-imported without path prefix — use `<DomainSelector>` not `<Input/DomainSelector>`
Applied to files:
app/pages/premium/saved-posts/[domain].vue
📚 Learning: 2026-05-30T19:57:20.193Z
Learnt from: CR
Repo: Rule-34/App PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-30T19:57:20.193Z
Learning: Applies to **/*.{ts,tsx,vue} : Use PocketBase batch writes for multi-record replacement/reorder operations instead of one HTTP write per changed row
Applied to files:
app/repositories/PremiumCloudRepository.ts
🔇 Additional comments (1)
app/components/pages/posts/post/PostSave.vue (1)
12-22: LGTM!Also applies to: 47-60
Summary
PremiumCloudRepositoryand stop using localStorage for cloud-owned saved posts, tag collections, custom boorus, and custom blocklists./premium/saved-postson the hardcoded production domain and remove saved-post domain discovery.Test Plan
corepack pnpm format:checkcorepack pnpm lintcorepack pnpm typecheckcorepack pnpm test:typecheckcorepack pnpm testcorepack pnpm buildgit diff --checkNotes
pnpm checkwrapper is blocked on this machine because nested barepnpmresolves to Homebrew pnpm 11.5.0 while the repo pins 11.2.2; the same gate components were run directly throughcorepack pnpm.Summary by CodeRabbit
New Features
Refactor
Documentation
Tests
Chores