diff --git a/.changeset/lucky-beers-report.md b/.changeset/lucky-beers-report.md
new file mode 100644
index 000000000..2874c0f56
--- /dev/null
+++ b/.changeset/lucky-beers-report.md
@@ -0,0 +1,5 @@
+---
+'@powersync/diagnostics-app': patch
+---
+
+Improved query parameter warnings and limits
diff --git a/tools/diagnostics-app/src/app/views/sync-diagnostics.tsx b/tools/diagnostics-app/src/app/views/sync-diagnostics.tsx
index 52aee6ec2..79725f940 100644
--- a/tools/diagnostics-app/src/app/views/sync-diagnostics.tsx
+++ b/tools/diagnostics-app/src/app/views/sync-diagnostics.tsx
@@ -8,8 +8,9 @@ import { Spinner } from '@/components/ui/spinner';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription';
import { StreamsTable } from '@/components/widgets/StreamsTable';
-import { formatBytes } from '@/lib/utils';
+import { cn, formatBytes } from '@/lib/utils';
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { decodeTokenPayload, getTokenUserId } from '@/library/powersync/TokenConnector';
import { useQueryClient, useQuery as useTanstackQuery } from '@tanstack/react-query';
import { ChevronDown, ChevronUp, Eye, Info } from 'lucide-react';
@@ -90,6 +91,17 @@ const syncDiagnosticsKeys = {
/** When total_operations exceeds row_count by this factor, we show a warning that bucket history has accumulated and compacting may help. This is an abritrary threshold and indicates significant history buildup.*/
const BUCKET_HISTORY_THRESHOLD = 3;
+/**
+ * Default server-side limit for both the "too many buckets" and "too many parameter query results"
+ * limits (both reported as error PSYNC_S2305). These are two distinct limits that often coincide
+ * but can differ:
+ * - Duplicate parameter query results count individually toward the parameter result limit,
+ * but are de-duplicated in the bucket count.
+ * - Non-partitioning queries count 1 toward the bucket limit but not toward the parameter result limit.
+ * The default is 1000 but can be configured on the server.
+ */
+const DEFAULT_SYNC_LIMIT = 1000;
+
interface SyncStats {
bucketRows: any[] | null;
tableRows: any[] | null;
@@ -222,6 +234,7 @@ export default function SyncDiagnosticsPage() {
const totals = {
buckets: rows.length,
+ parameterized_buckets: rows.filter((row) => !row.name.endsWith('[]')).length,
row_count: rows.reduce((total, row) => total + row.row_count, 0),
downloaded_operations: rows.reduce((total, row) => total + row.downloaded_operations, 0),
total_operations: rows.reduce((total, row) => total + row.total_operations, 0),
@@ -272,7 +285,44 @@ export default function SyncDiagnosticsPage() {
- Buckets
+
+
+
+
+
+ Buckets
+
+
+
+
+ Two separate limits apply (both PSYNC_S2305, default {DEFAULT_SYNC_LIMIT.toLocaleString()}{' '}
+ each): bucket count and parameter query results.
+
+ Global buckets count only toward bucket count. Parameter query buckets (in either{' '}
+
+ Sync Rules
+ {' '}
+ or{' '}
+
+ Sync Streams
+
+ ) count toward both, but are de-duplicated client-side, so the server's parameter result count
+ may be higher.
+
+
+
+
+
Total Rows
Downloaded Ops
Total Ops
@@ -284,7 +334,22 @@ export default function SyncDiagnosticsPage() {
- {totals.buckets}
+
+ = 900 ? 'text-destructive' : totals.buckets >= 800 ? 'text-amber-600' : ''
+ )}
+ >
+ {totals.buckets.toLocaleString()}
+
+
+ / {DEFAULT_SYNC_LIMIT.toLocaleString()} (default)
+
+
+ {totals.parameterized_buckets.toLocaleString()} parameterized,{' '}
+ {(totals.buckets - totals.parameterized_buckets).toLocaleString()} global
+
+
{totals.row_count.toLocaleString()}
{totals.downloaded_operations.toLocaleString()}
{totals.total_operations.toLocaleString()}
@@ -300,7 +365,22 @@ export default function SyncDiagnosticsPage() {
Buckets
-
{totals.buckets}
+
+
= 900 ? 'text-destructive' : totals.buckets >= 800 ? 'text-amber-600' : ''
+ )}
+ >
+ {totals.buckets.toLocaleString()}
+
+
+ / {DEFAULT_SYNC_LIMIT.toLocaleString()} (default)
+
+
+ {totals.parameterized_buckets.toLocaleString()} parameterized,{' '}
+ {(totals.buckets - totals.parameterized_buckets).toLocaleString()} global
+
+
Total Rows
@@ -358,7 +438,8 @@ export default function SyncDiagnosticsPage() {
variant="ghost"
size="sm"
className="h-7 gap-1.5 text-muted-foreground"
- onClick={() => setShowTokenDialog(true)}>
+ onClick={() => setShowTokenDialog(true)}
+ >
View Token
@@ -401,7 +482,8 @@ export default function SyncDiagnosticsPage() {
variant="outline"
onClick={() => {
clearData();
- }}>
+ }}
+ >
Clear & Redownload
@@ -420,12 +502,35 @@ export default function SyncDiagnosticsPage() {
href="https://docs.powersync.com/maintenance-ops/compacting-buckets"
target="_blank"
rel="noopener noreferrer"
- className="underline hover:text-foreground">
+ className="underline hover:text-foreground"
+ >
Learn about compacting
)}
+ {totals.buckets >= 800 && (
+ = 900 ? 'border-destructive/50' : 'border-amber-500/50')}>
+ = 900 ? 'text-destructive' : 'text-amber-600')} />
+
+ = 900 ? 'text-destructive' : 'text-amber-600')}>
+ {totals.buckets >= 900 ? 'Critical: ' : 'Warning: '}
+
+ {totals.buckets.toLocaleString()} of {DEFAULT_SYNC_LIMIT.toLocaleString()} buckets used (PSYNC_S2305,
+ default limit). {totals.parameterized_buckets.toLocaleString()} are parameterized - at least that many
+ parameter query results on the server. Review your Sync Config to reduce buckets and parameter query
+ results for this user.{' '}
+
+ For troubleshooting steps, see the docs
+
+
+
+ )}
@@ -511,7 +616,8 @@ function TruncatedTablesList({ tables }: { tables: string }) {
e.stopPropagation();
setExpanded(!expanded);
}}
- className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors">
+ className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
+ >
{expanded ? (
<>