@canonry/canonry 4.158.0 → 4.160.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +3 -3
  2. package/THIRD_PARTY_NOTICES.md +102 -0
  3. package/assets/THIRD_PARTY_NOTICES.md +102 -0
  4. package/assets/agent-workspace/skills/aero/SKILL.md +16 -1
  5. package/assets/agent-workspace/skills/canonry/SKILL.md +17 -5
  6. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +67 -0
  7. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +231 -62
  8. package/assets/assets/{AuditHistoryPanel-McQfjoU9.js → AuditHistoryPanel-AtgoG2AH.js} +1 -1
  9. package/assets/assets/{BacklinksPage-Da8y1Wv8.js → BacklinksPage-DrOSF7Mu.js} +1 -1
  10. package/assets/assets/{HistoryPage-9MJfhRyT.js → HistoryPage-CGQXleJc.js} +1 -1
  11. package/assets/assets/{MeasurementPropertyPage-BiNAHEJW.js → MeasurementPropertyPage-C7D2OgIQ.js} +1 -1
  12. package/assets/assets/ProjectPage-Lp1VjNvL.js +12 -0
  13. package/assets/assets/{RunRow-DCG2LBlS.js → RunRow-BdzKKODo.js} +1 -1
  14. package/assets/assets/{RunsPage-ByWA_FSk.js → RunsPage-BU96L07S.js} +1 -1
  15. package/assets/assets/{SettingsPage-DdSeKAKF.js → SettingsPage-LEca0kDH.js} +1 -1
  16. package/assets/assets/SiteHealthSection-CKM4DUfE.js +4 -0
  17. package/assets/assets/{TrafficPage-5u8rPcBt.js → TrafficPage-tU9wl1F_.js} +1 -1
  18. package/assets/assets/{TrafficSourceDetailPage-Dm-fK87u.js → TrafficSourceDetailPage-DZTpnU9f.js} +1 -1
  19. package/assets/assets/extract-error-message-BAyN-ftk.js +1 -0
  20. package/assets/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  21. package/assets/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  22. package/assets/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  23. package/assets/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  24. package/assets/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  25. package/assets/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  26. package/assets/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  27. package/assets/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  28. package/assets/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  29. package/assets/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  30. package/assets/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  31. package/assets/assets/index-CACRaOmZ.css +1 -0
  32. package/assets/assets/index-Wqv7kvs-.js +83 -0
  33. package/assets/assets/{react-sigma_core.esm.min-ComIyDKi.js → react-sigma_core.esm.min-DlZOAc_w.js} +2 -2
  34. package/assets/assets/{vendor-lucide-BtYdosYk.js → vendor-lucide-nYKv-BB7.js} +1 -1
  35. package/assets/assets/{vendor-markdown-D274_wsI.js → vendor-markdown-Bn7fRwrx.js} +1 -1
  36. package/assets/assets/{vendor-radix-BxMXSwbC.js → vendor-radix-4UOaVWza.js} +1 -1
  37. package/assets/assets/{vendor-recharts-C4ktw_aE.js → vendor-recharts-D8rt5lKn.js} +1 -1
  38. package/assets/assets/{vendor-tanstack-pFxw0eGP.js → vendor-tanstack-Dock0eH1.js} +1 -1
  39. package/assets/index.html +7 -7
  40. package/dist/{chunk-DEVAEC2S.js → chunk-7M7T5QXS.js} +290 -16
  41. package/dist/{chunk-LK6YFHG4.js → chunk-HQAVEK42.js} +65 -15
  42. package/dist/{chunk-YP4K2KXH.js → chunk-X7CYY5Q7.js} +2687 -686
  43. package/dist/{chunk-4VZP64GZ.js → chunk-XGESWHFP.js} +660 -274
  44. package/dist/cli.js +333 -117
  45. package/dist/index.d.ts +37 -14
  46. package/dist/index.js +4 -4
  47. package/dist/{intelligence-service-LQ6ZRWMF.js → intelligence-service-6XQMM466.js} +2 -2
  48. package/dist/mcp.js +2 -2
  49. package/package.json +11 -9
  50. package/assets/assets/ProjectPage-mWSMjXrR.js +0 -15
  51. package/assets/assets/extract-error-message-CtxkAd6E.js +0 -1
  52. package/assets/assets/index-DUfg50Pl.js +0 -83
  53. package/assets/assets/index-cXb3mfeQ.css +0 -1
@@ -39,11 +39,12 @@ Adapters today:
39
39
  | [`cloud-run`](#connecting-a-cloud-run-source) | Pull | GCP Cloud Run request logs via Logging API | Any service running on Cloud Run |
40
40
  | [`wordpress`](#connecting-a-wordpress-source) | Pull | Canonry Traffic Logger REST endpoint | WordPress sites where you control wp-admin |
41
41
  | [`vercel`](#connecting-a-vercel-source) | Pull | Vercel project logs via the Vercel API | Sites deployed on Vercel |
42
- | [`cloudflare`](#connecting-a-cloudflare-source-direct-push) | Direct push | A zone Worker selects and sends edge events | Sites whose public traffic passes through Cloudflare |
42
+ | [`cloudflare`](#connecting-a-cloudflare-source) | Direct push or Queue pull | A zone Worker selects edge events; Canonry either receives them directly or pulls them from Cloudflare Queues | Sites whose public traffic passes through Cloudflare |
43
43
 
44
44
  Future adapters slot in by implementing the same contract.
45
- Cloud Run, WordPress, and Vercel pull logs during `traffic sync`.
46
- Cloudflare sends selected events from its Worker and does not use `traffic sync`.
45
+ Cloud Run, WordPress, Vercel, and Cloudflare Queue sources pull during
46
+ `traffic sync`. Cloudflare direct push sends selected events from its Worker
47
+ and does not use `traffic sync`.
47
48
 
48
49
  ## Connecting a Cloud Run source
49
50
 
@@ -195,31 +196,38 @@ Run `cnry traffic backfill <project> --source <id> --days N` (capped at
195
196
  history. It's an explicit operator action; the connect flow never pulls
196
197
  it implicitly.
197
198
 
198
- ## Connecting a Cloudflare source (direct push)
199
+ ## Connecting a Cloudflare source
199
200
 
200
- Cloudflare direct push runs a small ES-module Worker on the site's exact
201
- zone route. It applies a broad AI filter and sends selected edge events
202
- to Canonry in `ctx.waitUntil()`. The origin response remains independent
203
- from filtering, scheduling, and delivery errors. The event shape is also
204
- the contract for the later Queue pull transport.
201
+ Both Cloudflare modes run the same small ES-module Worker on the site's exact
202
+ zone route. It applies a broad AI filter and delivers selected edge events in
203
+ `ctx.waitUntil()`. The origin response remains independent from filtering,
204
+ scheduling, and delivery errors. Both modes use the same
205
+ `CloudflareEdgeEventBatch`, normalizer, classifier, receipts, and rollups.
205
206
 
206
- The shipped adapter does not pull Cloudflare analytics or request logs.
207
- Queue pull has a reserved transport mode, but it is not available yet.
207
+ - `direct-push` sends a signed batch to a public Canonry HTTPS receiver.
208
+ - `queue-pull` sends the batch to a Cloudflare Queue. The single-team Canonry
209
+ server pulls and acknowledges it through the Cloudflare Queues HTTP API.
210
+
211
+ The adapter does not pull Cloudflare analytics or request logs.
208
212
 
209
213
  ```mermaid
210
214
  flowchart LR
211
215
  request["Site request"] --> worker["Canonry Cloudflare Worker"]
212
216
  worker --> origin["Existing origin or Pages site"]
213
217
  origin --> response["Unchanged site response"]
214
- worker -. "signed selected event via ctx.waitUntil" .-> ingest["Canonry public HTTPS ingest"]
215
- ingest --> classify["Shared traffic classifier"]
218
+ worker -. "direct-push" .-> ingest["Canonry public HTTPS ingest"]
219
+ worker -. "queue-pull" .-> queue["Cloudflare Queue"]
220
+ queue --> pull["Canonry scheduled pull"]
221
+ ingest --> classify["Shared receipt and traffic pipeline"]
222
+ pull --> classify
216
223
  classify --> rollups["Hourly crawler, user-fetch, and referral rollups"]
217
224
  ```
218
225
 
219
226
  ### Setup flow
220
227
 
221
228
  1. Create or select a Canonry project with one exact canonical hostname.
222
- 2. Give the Canonry server a stable public HTTPS URL outside the site route.
229
+ 2. Choose delivery: direct push needs a stable public Canonry HTTPS URL outside
230
+ the site route; Queue pull needs a pre-created Queue and HTTP pull consumer.
223
231
  3. Authenticate Wrangler with the Cloudflare account that owns the zone.
224
232
  4. Inspect existing Worker routes for the exact canonical hostname.
225
233
  5. Run the local Cloudflare connect command with `--deploy`.
@@ -228,20 +236,23 @@ flowchart LR
228
236
 
229
237
  ### Current support boundary
230
238
 
231
- Direct push currently supports a local `canonry serve` instance. The CLI
232
- and server must read the same `~/.canonry/config.yaml` credential store.
233
- You can expose that local server through a stable HTTPS tunnel.
239
+ Both modes currently target one local, single-team `canonry serve` instance.
240
+ The Queue consumer is not a multi-tenant Cloudflare control plane. The CLI and
241
+ server must read the same `~/.canonry/config.yaml` credential store. Direct
242
+ push requires a stable public HTTPS receiver; Queue pull does not.
234
243
 
235
244
  The Cloud Run `apps/api` service has no Cloudflare credential store.
236
- Therefore, `apps/api` cannot retain the per-source bearer and HMAC values
237
- that deployment requires.
245
+ Therefore, `apps/api` cannot retain direct-push bearer/HMAC values or the
246
+ Queue API token.
238
247
 
239
- CAUTION: Use one authoritative server-traffic source for the canonical
240
- site. Overlapping adapters capture the same requests and double-count
241
- project totals. This is a current limitation. Queue support must define
242
- source coverage before it permits a transport change.
248
+ CAUTION: Use one authoritative server-traffic source for the canonical site.
249
+ Connecting any adapter while a sibling source is active creates or keeps a
250
+ paused staged source. `canonry traffic activate` validates the target's local
251
+ credential, atomically pauses every sibling, and moves or removes the one
252
+ traffic-sync schedule. Overlapping adapters outside this cutover can still
253
+ double-count project totals.
243
254
 
244
- ### Expose the Canonry receiver
255
+ ### Expose the Canonry receiver (direct push only)
245
256
 
246
257
  Complete the local dashboard setup and set its password before you expose
247
258
  Canonry. A tunnel makes the loopback server reachable from the public Internet.
@@ -342,18 +353,20 @@ Worker and keeps the origin available. See [Cloudflare Workers limits](https://d
342
353
  Free-plan classification uses user-agent, referer, and UTM evidence. Granular
343
354
  `request.cf.botManagement` scores require the Enterprise Bot Management add-on.
344
355
 
345
- ### Generate or deploy
356
+ ### Generate or deploy direct push
346
357
 
347
358
  ```bash
348
359
  # Generate secret-free worker.js and wrangler.toml in
349
360
  # ./canonry-cloudflare-<project-slug>:
350
361
  cnry traffic connect cloudflare <project> \
362
+ --delivery-mode direct-push \
351
363
  --zone-id <cloudflare-zone-id> \
352
364
  --account-id <cloudflare-account-id>
353
365
 
354
366
  # Deploy only the Worker after the route preflight.
355
367
  # Both safety acknowledgements are mandatory:
356
368
  cnry traffic connect cloudflare <project> \
369
+ --delivery-mode direct-push \
357
370
  --zone-id <cloudflare-zone-id> \
358
371
  --account-id <cloudflare-account-id> \
359
372
  --deploy --confirm-route --confirm-fail-open
@@ -413,6 +426,112 @@ Cloudflare connect is absent from MCP because deployment reads local
413
426
  secrets. An agent can guide the CLI command. It must not request, print,
414
427
  or send either secret through chat.
415
428
 
429
+ ### Generate or deploy Queue pull
430
+
431
+ Queue pull reuses the same edge filter without requiring a public Canonry
432
+ receiver. Create the Queue first. Then use `wrangler queues info` to get its ID.
433
+ Create a Cloudflare API token with **Account Queues Edit** for the owning
434
+ account. Put only the token in a mode-0600 file. Keep that file off shell argv.
435
+
436
+ ```bash
437
+ wrangler queues create canonry-traffic-<project>
438
+
439
+ # Workers Paid only: use this command to change the four-day default.
440
+ wrangler queues update canonry-traffic-<project> \
441
+ --message-retention-period-secs <seconds>
442
+
443
+ wrangler queues info canonry-traffic-<project>
444
+ wrangler queues consumer http add canonry-traffic-<project>
445
+
446
+ cnry traffic connect cloudflare <project> \
447
+ --delivery-mode queue-pull \
448
+ --zone-id <cloudflare-zone-id> \
449
+ --account-id <cloudflare-account-id> \
450
+ --queue-id <cloudflare-queue-id> \
451
+ --queue-name canonry-traffic-<project> \
452
+ --api-token-file <path-to-mode-0600-token-file> \
453
+ --retention-seconds <actual-queue-retention-seconds>
454
+
455
+ cnry traffic connect cloudflare <project> \
456
+ --delivery-mode queue-pull \
457
+ --zone-id <cloudflare-zone-id> \
458
+ --account-id <cloudflare-account-id> \
459
+ --queue-id <cloudflare-queue-id> \
460
+ --queue-name canonry-traffic-<project> \
461
+ --api-token-file <path-to-token-file> \
462
+ --retention-seconds <actual-queue-retention-seconds> \
463
+ --deploy --confirm-route --confirm-fail-open
464
+ ```
465
+
466
+ Read the Queue ID from the `wrangler queues info` output. Pass that exact value
467
+ to `--queue-id`.
468
+
469
+ The `queues update` command sets Cloudflare retention on Workers Paid. The
470
+ Canonry `--retention-seconds` option only records the actual value. It does not
471
+ change the Queue. Use `86400` for Workers Free, where retention is fixed at one
472
+ day. Workers Paid defaults to `345600` (four days). Paid accounts accept values
473
+ from 60 seconds through 14 days. If you change the paid Queue, use the same
474
+ value in both commands. See Cloudflare's [Queue limits](https://developers.cloudflare.com/queues/platform/limits/).
475
+
476
+ The HTTP-consumer command is required. The producer binding does not enable
477
+ HTTP pull. Cloudflare does not support an HTTP pull consumer in
478
+ `wrangler.toml`. Remove any existing Worker consumer before you enable HTTP
479
+ pull. One Queue cannot use both consumer types at the same time. See
480
+ Cloudflare's [pull-consumer setup](https://developers.cloudflare.com/queues/configuration/pull-consumers/).
481
+
482
+ The token stays only in Canonry's local credential store. It is not written to
483
+ the source row, Worker, TOML, command output, MCP, or Worker bindings. Wrangler
484
+ authentication remains the operator's active Wrangler profile. Canonry does
485
+ not reuse the Queue pull token for deployment.
486
+
487
+ The generated TOML contains one `[[queues.producers]]` binding named
488
+ `CANONRY_TRAFFIC_QUEUE` and no direct-push ingest URL or Worker secrets. After
489
+ the Worker is deployed and the route is ready, activate a staged source:
490
+
491
+ ```bash
492
+ cnry traffic activate <project> --source <source-id>
493
+ cnry traffic sync <project> --source <source-id>
494
+ ```
495
+
496
+ A new project with no other active traffic source activates immediately. A
497
+ mode change stays paused until the explicit activation command. Activation
498
+ pauses the prior source and points the project's one `traffic-sync` schedule at
499
+ Queue pull; activating direct push removes that Queue schedule.
500
+
501
+ For Queue-to-direct rollback, drain Queue pull until its backlog is empty,
502
+ switch the Cloudflare route, then activate direct push immediately. The route
503
+ change and Canonry activation are separate operator actions, so keep the
504
+ cutover window short.
505
+
506
+ ### Queue-pull acceptance test
507
+
508
+ Use this test after the Queue-pull source is active and the Worker route is
509
+ attached. The UTM request provides a deterministic classification signal.
510
+ If connect reports `activationRequired`, activate the source before this test:
511
+
512
+ ```bash
513
+ cnry traffic activate <project> --source <source-id>
514
+ ```
515
+
516
+ ```bash
517
+ CANONRY_QUEUE_SMOKE_PATH="/canonry-cloudflare-queue-smoke/$(date -u +%Y%m%dT%H%M%SZ)"
518
+
519
+ curl -sS -o /dev/null -w '%{http_code}\n' \
520
+ "https://www.example.com${CANONRY_QUEUE_SMOKE_PATH}?utm_source=chatgpt"
521
+
522
+ sleep 2
523
+ cnry traffic sync <project> --source <source-id>
524
+ cnry traffic events <project> --source <source-id> \
525
+ --kind all --since-minutes 120 --limit 5000 --format json
526
+ cnry traffic status <project> --format json
527
+ cnry doctor --project <project> --check 'traffic.source.*' --format json
528
+ ```
529
+
530
+ The manual sync must complete. The events output must contain the unique smoke
531
+ path as an AI referral. The status output shows `queueBacklogCount` and
532
+ `queueBacklogObservedAt`. The doctor must not report a credential failure. If
533
+ the path is absent, wait two seconds and repeat the sync up to ten times.
534
+
416
535
  ### Direct-push behavior and smoke test
417
536
 
418
537
  The Worker forwards a request candidate when at least one condition is true:
@@ -434,7 +553,7 @@ Direct push has bounded retries for network failures and HTTP 408, 425,
434
553
  429, and 5xx responses (250 ms, then 1 s). Delivery is asynchronous and
435
554
  never changes the site's response. If Canonry remains unavailable after
436
555
  the retry budget, the selected event is lost. Direct push has no durable
437
- edge buffer. This limit is the primary reason to add Queue pull next.
556
+ edge buffer. Use Queue pull when delivery needs a durable edge buffer.
438
557
 
439
558
  The receiver accepts at most 256 KiB per request. Its default budgets are
440
559
  6,000 requests per minute for each source and each caller IP. A bearer token
@@ -456,7 +575,7 @@ Raw samples retain the user-agent and normalized path. Do not put personal
456
575
  data in URL paths. Canonry replaces numeric, UUID, and long hexadecimal path
457
576
  segments with `:id`, but it retains other path segments.
458
577
 
459
- ### User acceptance test
578
+ ### Direct-push acceptance test
460
579
 
461
580
  1. Choose a stable, harmless origin path and create a unique smoke-run name.
462
581
  2. Before route attachment, capture the origin response and source rollups.
@@ -570,11 +689,26 @@ For a dedicated Canonry-generated Worker:
570
689
  1. Detach the exact site route from the Worker.
571
690
  2. Request an ordinary site page. Make sure that the origin status,
572
691
  headers, and body remain healthy.
573
- 3. Run `wrangler delete --config <artifact-directory>/wrangler.toml`.
574
- 4. Stop the tunnel if no other integration uses it.
575
- 5. Run the doctor. Expect source-health warnings until Canonry supports
692
+ 3. If this source uses Queue pull, run manual syncs until its backlog is zero.
693
+ 4. If a replacement source exists, activate it after the Queue is empty.
694
+ 5. If no replacement source exists, run
695
+ `cnry schedule remove <project> --kind traffic-sync`.
696
+ 6. If this source uses Queue pull, run
697
+ `wrangler queues consumer http remove <queue-name>`.
698
+ 7. Run `wrangler delete --config <artifact-directory>/wrangler.toml`.
699
+ 8. If this source uses direct push, stop its unused tunnel.
700
+ 9. Run the doctor. Expect source-health warnings until Canonry supports
576
701
  Cloudflare disconnect.
577
702
 
703
+ CAUTION: If no other producer uses the Queue, delete it only after the drain:
704
+
705
+ ```bash
706
+ wrangler queues delete <queue-name>
707
+ ```
708
+
709
+ If the API token is dedicated to Canonry, revoke it after you remove the HTTP
710
+ consumer.
711
+
578
712
  If Canonry code shares an existing Worker, remove only the Canonry code and
579
713
  bindings. Redeploy the prior Worker version. Do not delete the shared Worker.
580
714
 
@@ -589,30 +723,43 @@ source archival and local credential cleanup.
589
723
  | Wrangler preflight fails | Install the latest Wrangler. Run `wrangler whoami`. Then rerun connect. |
590
724
  | Worker deploys to the wrong account | Make the correct Wrangler profile active. Pass the matching `--account-id`. |
591
725
  | Route cannot attach | Make sure that the zone is active, DNS is proxied, and the exact route is unclaimed. Inspect every overlapping route. |
592
- | Receiver probe returns `403` | Remove the Access or WAF challenge from the exact ingest path. |
593
- | Worker log shows ingest `401` | Rerun connect from the credential-owning host. Make sure that system clocks differ by less than five minutes. |
594
- | No events arrive | Make sure that the route matches the exact host and `publicUrl` is reachable. Inspect AI Crawl Control, Access, WAF, and route precedence. Then send the UTM smoke request. |
726
+ | Direct-push receiver returns `403` | Remove the Access or WAF challenge from the exact ingest path. |
727
+ | Direct-push Worker log shows ingest `401` | Rerun connect from the credential-owning host. Make sure that system clocks differ by less than five minutes. |
728
+ | No direct-push events arrive | Make sure that the route matches the exact host. Make sure that `publicUrl` is reachable. Inspect AI Crawl Control, Access, WAF, and route precedence. Then send the UTM smoke request. |
729
+ | No Queue-pull events arrive | Run `wrangler queues info <queue-name>` and `wrangler queues consumer http list <queue-name>`. Make sure that the producer binding uses that Queue. Make sure that the account ID, Queue ID, and token scope match. Then run the Queue acceptance test. |
730
+ | Queue pull returns `403` | Create a token with **Account Queues Edit** for the Queue account. Reconnect with the token file. |
731
+ | Queue backlog remains above 1,000 | Run a manual sync. If the backlog grows again, shorten the 30-minute schedule interval. |
595
732
  | Some paths are absent | Inspect more-specific routes and terminating security rules. Integrate Canonry into those routes or document the excluded coverage. |
596
- | Ingest returns `429` | Wait for the rate window. The Worker retries twice, then drops the event. |
733
+ | Direct-push ingest returns `429` | Wait for the rate window. The Worker retries twice, then drops the event. |
597
734
  | Site returns Cloudflare error `1027` | Set the route to **Fail open**, or detach it. The account exhausted its daily Worker requests. |
598
735
  | Doctor reports stale or empty direct-push data | Do not run `traffic sync`. Inspect the route, receiver, and Worker logs. Then repeat the smoke request. |
599
736
 
600
- ### Queue pull seam (not shipped yet)
601
-
602
- Queue pull is the first follow-up for hosted or platform deployments with
603
- loopback-only tenant engines. Customer Workers cannot reach those engines.
604
- The future pull adapter will let Canonry read events without a public
605
- tenant-ingest endpoint.
606
-
607
- Before broad direct-push rollout, consider an edge sampling or batching control
608
- for high-volume generic bot traffic.
609
-
610
- `deliveryMode: direct-push` is explicit in the source config. The reserved
611
- `queue-pull` mode will keep the Worker filter, `CloudflareEdgeEventBatch`,
612
- canonical JSON encoding, normalizer, classifier, and rollups unchanged.
613
- Only delivery changes to a Queue binding, and Canonry gains a polling
614
- adapter. A source must use one mode at a time so the same edge event cannot
615
- arrive through both transports and double-count.
737
+ ### Queue pull delivery guarantees
738
+
739
+ Queue delivery is at least once. Canonry claims a durable source-scoped sync
740
+ lease, short-polls bounded batches, validates the transport-neutral event
741
+ schema and canonical host, and commits event receipts plus rollups in one DB
742
+ transaction. It acknowledges the Cloudflare lease only after that commit.
743
+
744
+ If acknowledgement fails after commit, Cloudflare can redeliver the batch.
745
+ The durable receipt makes the redelivery a no-op before Canonry acknowledges it
746
+ again. Unsupported V8 messages and malformed message bodies are acknowledged
747
+ as poison after their safe lease metadata is read; raw bodies and the API token
748
+ never enter errors or logs. A process crash leaves unacknowledged messages for
749
+ Cloudflare to redeliver after the visibility timeout.
750
+
751
+ The source lease rejects concurrent scheduler and operator drains. A stale
752
+ lease can be recovered after its expiry, and only its owner can release it.
753
+ Receipts remain for Cloudflare's current 14-day platform maximum plus a replay
754
+ margin. This stays safe even when the locally recorded retention is stale or
755
+ lower than the Queue's actual setting.
756
+
757
+ Each default Queue sync drains at most 10 batches of 100 messages: **1,000
758
+ messages per tick**. Doctor reports a residual of 1–1,000 messages as within
759
+ that budget. If no new messages arrive, the next scheduled sync can clear it.
760
+ Doctor warns when more than 1,000 messages remain because one default tick
761
+ cannot drain the residual. Run a manual sync to accelerate the drain. If the
762
+ backlog remains above 1,000, shorten the `traffic-sync` schedule interval.
616
763
 
617
764
  ## Syncing data
618
765
 
@@ -628,6 +775,12 @@ cnry traffic sync <project> --source <id>
628
775
  cnry traffic sync <project> --source <id> --since-minutes 4320 # 3 days
629
776
  ```
630
777
 
778
+ Cloudflare Queue pull is cursorless and ignores `--since-minutes`; each call
779
+ drains a bounded set of leased Queue messages. Repeat the command when you need
780
+ to accelerate a backlog, or let the auto-created 30-minute schedule continue
781
+ draining. Direct push still rejects `traffic sync` because it has no pull
782
+ transport.
783
+
631
784
  ### Unsticking a stuck source
632
785
 
633
786
  If a Vercel (or Cloud Run) source has been failing for so long that
@@ -654,9 +807,9 @@ above; clearing `lastError` for a transient WordPress failure also
654
807
  works. Archived sources are rejected — re-connect them with
655
808
  `cnry traffic connect ...` instead.
656
809
 
657
- Cross-sync dedupe via the `last_event_ids` ring buffer means re-running a
658
- sync over an overlapping window cannot double-count rolled-up hourly
659
- hits. Safe to schedule (see "Scheduling" below) or trigger from CI.
810
+ Time-window and cursor adapters use the `last_event_ids` overlap ring. Queue
811
+ pull uses durable event receipts sized to Queue retention. Both paths make a
812
+ repeated pull safe to schedule or trigger from CI.
660
813
 
661
814
  ## Inspecting source state
662
815
 
@@ -706,7 +859,7 @@ schema change, the stored rollups are untouched.
706
859
  | Project dashboard `/projects/:name/activity` | Live source table + 24h totals + GA4 referrals (combined view) |
707
860
  | Top-level `/traffic` route | Cross-project source admin (connect, sync, archive) |
708
861
  | `cnry report <project>` (HTML + SPA) | "AI Visibility — Server-Side" section, ranked above Indexing Health |
709
- | `cnry doctor --project <name>` | Source health checks, including Cloudflare `traffic.source.worker-version` deployment drift |
862
+ | `cnry doctor --project <name>` | Source health checks, including direct-push Worker drift and Queue-pull local credential/sync state |
710
863
  | MCP toolkit `traffic` | Read/status tools plus pull-source setup/sync tools. Cloudflare connect is local-CLI-only so Worker secrets cannot enter an MCP transcript. |
711
864
 
712
865
  ## Doctor signals
@@ -719,7 +872,9 @@ The doctor checks are adapter-agnostic. When they fail or warn:
719
872
  | `traffic.source.connected` | `traffic.source.all-errored` | Re-connect the source. The check's `details.lastError` shows the underlying reason. |
720
873
  | `traffic.source.recent-data` | `traffic.recent-data.stale` | For pull sources, run `cnry traffic sync …`. For Cloudflare direct push, inspect the route and receiver. |
721
874
  | `traffic.source.recent-data` | `traffic.recent-data.empty` | Inspect source configuration. For Cloudflare direct push, send the UTM smoke request and inspect Worker logs. |
722
- | `traffic.source.credentials` | `traffic.credentials.resolve-failed` | Reconnect from the host that owns the source credentials. |
875
+ | `traffic.source.queue-backlog` | `traffic.queue-backlog.within-drain-budget` | 1–1,000 Queue messages remain. If no new messages arrive, the next scheduled sync can drain them. Run a manual sync to accelerate. |
876
+ | `traffic.source.queue-backlog` | `traffic.queue-backlog.remaining` | More than 1,000 Queue messages remain. Run a manual sync. If the backlog persists, shorten the traffic-sync schedule. |
877
+ | `traffic.source.credentials` | `traffic.credentials.resolve-failed` | Reconnect from the host that owns the source credentials. Queue pull requires a non-empty Account Queues Edit token paired by source ID. |
723
878
  | `traffic.source.cache-blindspot` | `traffic.cache-blindspot.wordpress-plugin` | A WordPress source is connected, so the plugin cannot see cache-served page views. Exclude AI user-agents from the page cache and any CDN, or switch to a log/edge source. Warns only, not a failure. |
724
879
  | `traffic.source.worker-version` | `traffic.worker-version.waiting-for-first-event` | Send a smoke-test request through the Worker. Then run the doctor again. |
725
880
  | `traffic.source.worker-version` | `traffic.worker-version.stale` | Redeploy the generated Worker from the credential-owning host. Use `--deploy --confirm-route --confirm-fail-open`. Attach the route manually. |
@@ -728,14 +883,28 @@ Cloudflare doctor behavior is capability-driven: only
728
883
  `deliveryMode=direct-push` (and legacy Cloudflare rows with no mode) skips
729
884
  pull-watermark lag. The same mode enables `traffic.source.worker-version`.
730
885
  That check compares `configJson.workerVersion` with `lastWorkerVersion`.
731
- The reserved Queue pull mode uses pull checks and skips the version check.
886
+ Queue pull uses pull lag checks and skips the direct-push Worker-version check.
732
887
 
733
888
  ## Scheduling
734
889
 
735
- `cnry schedule` supports `--kind traffic-sync` for pull sources. Cloudflare
736
- direct push is event-driven and must not receive a traffic-sync schedule. Recurring syncs are
737
- safe because of the `last_event_ids` cross-sync dedupe ring buffer
738
- described above. Recommended cadence:
890
+ `cnry schedule` supports `--kind traffic-sync` for pull sources. Activation of
891
+ Queue, Cloud Run, WordPress, or Vercel creates or repoints this schedule at a
892
+ 30-minute cadence.
893
+ Cloudflare direct push is event-driven and must not receive a traffic-sync
894
+ schedule. Recurring syncs are safe because of the adapter's overlap ring or
895
+ durable Queue receipts.
896
+
897
+ Keep the auto-created 30-minute schedule for Queue pull. The Worker sends one
898
+ selected event in each Queue message. At its 1,000-message limit, this cadence
899
+ drains about 0.56 selected events per second. If the Queue backlog remains
900
+ above 1,000, run a manual sync. Then shorten the interval:
901
+
902
+ ```bash
903
+ cnry schedule set <project> --kind traffic-sync \
904
+ --source <source-id> --cron "*/10 * * * *"
905
+ ```
906
+
907
+ Use these cadences only for Cloud Run, WordPress, and Vercel:
739
908
 
740
909
  | Cadence | Use case |
741
910
  |---|---|
@@ -798,8 +967,8 @@ domains, or PII are surfaced.
798
967
  no client IP, so every Vercel crawler hit is unverified by
799
968
  construction (UA-only). A Vercel source reading 100% unverified is
800
969
  expected, not a misconfiguration.
801
- - **Four adapters ship: Cloud Run, WordPress, Vercel, and Cloudflare
802
- direct push. More adapters are planned.** The doctor checks and report renderer are
970
+ - **Four adapters ship: Cloud Run, WordPress, Vercel, and Cloudflare with
971
+ direct-push and queue-pull delivery. More adapters are planned.** The doctor checks and report renderer are
803
972
  adapter-agnostic — adding a new adapter is just a new entry in
804
973
  `traffic_sources.source_type` and a `TrafficSourceValidator`
805
974
  registration.
@@ -1 +1 @@
1
- import{r as a,u as h,j as e}from"./vendor-tanstack-pFxw0eGP.js";import{cv as L,cw as P,bQ as E,k as u,B as x,d as v,T as k}from"./index-DUfg50Pl.js";const o=100;function M(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function F({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>M(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.target.value),placeholder:"Search actions, entities, or details",className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong placeholder-mono-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(k,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.jsxs("span",{className:"mt-0.5 block max-w-40 truncate font-mono text-[11px] text-faint",title:t.actorSession,children:["Session ",t.actorSession]}):null]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.jsx("pre",{className:"max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-xs text-secondary",children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{F as A};
1
+ import{r as a,u as h,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cc as L,cd as P,bx as E,k as u,B as x,d as v,T as k}from"./index-Wqv7kvs-.js";const o=100;function M(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function H({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>M(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.target.value),placeholder:"Search actions, entities, or details",className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong placeholder-mono-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(k,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.jsxs("span",{className:"mt-0.5 block max-w-40 truncate font-mono text-[11px] text-faint",title:t.actorSession,children:["Session ",t.actorSession]}):null]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.jsx("pre",{className:"max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-xs text-secondary",children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{H as A};
@@ -1 +1 @@
1
- import{r as n,j as e}from"./vendor-tanstack-pFxw0eGP.js";import{cJ as _,ag as H,cK as U,cL as K,cM as J,d as x,T as j,i as u,B as w,f as F,cN as V,cO as Y,aj as X,cP as Z}from"./index-DUfg50Pl.js";import{D as ee,T as se,E as te,P as ae,y as ne,u as re}from"./vendor-lucide-BtYdosYk.js";import"./vendor-radix-BxMXSwbC.js";import"./vendor-recharts-C4ktw_aE.js";import"./vendor-markdown-D274_wsI.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,L]=n.useState([]),[m,$]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[M,k]=n.useState(!1),[P,l]=n.useState(null),[A,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,Q,z,W]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),K().catch(()=>[]),J().catch(()=>null)]);h(s),i(r),y(Q),L(z),$(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){E(!0),l(null),o(null);try{const s=await V();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function q(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await Y(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof X&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function G(s){l(null),o(null);try{await Z(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const O=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:P})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:A})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:F(T),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),O&&e.jsx("div",{className:"mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(se,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-sm text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsx("p",{className:"mt-1 text-secondary",children:"Sync this release again, or sync the newest release, before refreshing individual projects."})]})]})}),e.jsxs("div",{className:"mt-4 rounded border border-base bg-bg-elevated/40 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wide text-muted",children:"Auto-detected release"}),m?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:m.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",S(m.vertexBytes),", edges ",S(m.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:B?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:le,target:"_blank",rel:"noopener noreferrer",className:"mt-1 inline-flex items-center gap-1 text-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:["Browse all Common Crawl web-graph releases",e.jsx(te,{className:"h-3 w-3","aria-hidden":!0})]})]}),!u()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:p||!t?.duckdbInstalled||!m&&!N.trim(),onClick:F(q),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),p?"Queuing…":"Run sync"]}),e.jsxs(g,{label:"What does Run sync do?",children:[e.jsxs("span",{className:"block",children:["Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),", then runs a single DuckDB query that extracts referring domains for every project in this workspace."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["First time for a release: ",e.jsx("span",{className:"text-strong",children:"~10–20 min download + ~5 min query"}),". Re-running the same release later: ",e.jsx("span",{className:"text-strong",children:"skips download, just re-queries"})," (~5 min)."]})]})]})]}),u()?null:M?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>v(s.target.value),disabled:p,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{v(""),k(!1)},disabled:p,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>k(!0),disabled:p,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-sm text-secondary mt-2",children:"Install DuckDB first to enable sync."})]})]}),e.jsxs("details",{className:"page-section-divider group",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:"Technical details and cache"}),e.jsxs("div",{className:"mt-4",children:[e.jsx("p",{className:"text-sm text-secondary mb-3",children:"Cached releases support project re-extracts. Pruning a release does not remove saved backlink results."}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Sync status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Size"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Last used"}),!u()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(j,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:S(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!u()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
1
+ import{r as n,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cq as _,ag as H,cr as U,cs as K,ct as V,d as x,T as j,i as u,B as w,f as F,cu as Y,cv as J,aj as X,cw as Z}from"./index-Wqv7kvs-.js";import{D as ee,T as se,E as te,P as ae,v as ne,t as re}from"./vendor-lucide-nYKv-BB7.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,$]=n.useState([]),[m,L]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[q,k]=n.useState(!1),[A,l]=n.useState(null),[P,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,z,O,W]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),K().catch(()=>[]),V().catch(()=>null)]);h(s),i(r),y(z),$(O),L(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function M(){E(!0),l(null),o(null);try{const s=await Y();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function T(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await J(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof X&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function G(s){l(null),o(null);try{await Z(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const Q=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:A})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:P})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:F(M),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),Q&&e.jsx("div",{className:"mt-4 rounded border border-caution-800/60 bg-caution-950/20 p-3",children:e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx(se,{className:"h-4 w-4 text-caution-400 shrink-0 mt-0.5","aria-hidden":!0}),e.jsxs("div",{className:"text-sm text-neutral leading-relaxed",children:[e.jsx("p",{className:"font-medium text-caution-200",children:"Cached files for this release are missing."}),e.jsx("p",{className:"mt-1 text-secondary",children:"Sync this release again, or sync the newest release, before refreshing individual projects."})]})]})}),e.jsxs("div",{className:"mt-4 rounded border border-base bg-bg-elevated/40 p-3",children:[e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] uppercase tracking-wide text-muted",children:"Auto-detected release"}),m?e.jsxs("p",{className:"text-sm text-strong mt-0.5",children:[e.jsx("code",{className:"text-heading",children:m.release}),e.jsxs("span",{className:"ml-2 text-xs text-muted",children:["Vertex ",S(m.vertexBytes),", edges ",S(m.edgesBytes)]})]}):e.jsx("p",{className:"text-sm text-muted mt-0.5",children:B?"Checking Common Crawl…":"Could not find a release. Choose one below."}),e.jsxs("a",{href:le,target:"_blank",rel:"noopener noreferrer",className:"mt-1 inline-flex items-center gap-1 text-xs text-secondary hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:["Browse all Common Crawl web-graph releases",e.jsx(te,{className:"h-3 w-3","aria-hidden":!0})]})]}),!u()&&e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsxs(w,{type:"button",size:"sm",disabled:p||!t?.duckdbInstalled||!m&&!N.trim(),onClick:F(T),children:[e.jsx(ae,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),p?"Queuing…":"Run sync"]}),e.jsxs(g,{label:"What does Run sync do?",children:[e.jsxs("span",{className:"block",children:["Downloads the auto-detected (or chosen) Common Crawl release (~16 GB) to"," ",e.jsx("code",{className:"text-neutral",children:"~/.canonry/cache/commoncrawl/"}),", then runs a single DuckDB query that extracts referring domains for every project in this workspace."]}),e.jsxs("span",{className:"mt-2 block text-secondary",children:["First time for a release: ",e.jsx("span",{className:"text-strong",children:"~10–20 min download + ~5 min query"}),". Re-running the same release later: ",e.jsx("span",{className:"text-strong",children:"skips download, just re-queries"})," (~5 min)."]})]})]})]}),u()?null:q?e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx("input",{type:"text",className:"flex-1 min-w-[240px] rounded border border-strong bg-transparent px-2.5 py-1.5 text-sm text-strong placeholder-mono-600 focus:border-mono-500 focus:outline-none",placeholder:"cc-main-2026-jan-feb-mar",value:N,onChange:s=>v(s.target.value),disabled:p,autoFocus:!0}),e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>{v(""),k(!1)},disabled:p,children:"Cancel"})]}):e.jsx("button",{type:"button",className:"text-sm text-secondary hover:text-neutral focus:text-neutral focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",onClick:()=>k(!0),disabled:p,children:"Use a different release →"})]}),!t?.duckdbInstalled&&e.jsx("p",{className:"text-sm text-secondary mt-2",children:"Install DuckDB first to enable sync."})]})]}),e.jsxs("details",{className:"page-section-divider group",children:[e.jsx("summary",{className:"cursor-pointer text-sm font-medium text-secondary hover:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500 rounded",children:"Technical details and cache"}),e.jsxs("div",{className:"mt-4",children:[e.jsx("p",{className:"text-sm text-secondary mb-3",children:"Cached releases support project re-extracts. Pruning a release does not remove saved backlink results."}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Sync status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Size"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Last used"}),!u()&&e.jsx("th",{className:"px-4 py-2 font-medium sr-only",children:"Actions"})]})}),e.jsxs("tbody",{children:[c.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:s.syncStatus?e.jsx(j,{tone:C(s.syncStatus),children:s.syncStatus}):e.jsx("span",{className:"text-faint",children:"—"})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:S(s.bytes)}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.lastUsedAt)}),!u()&&e.jsx("td",{className:"px-4 py-2 text-right",children:e.jsxs("div",{className:"inline-flex items-center gap-1",children:[e.jsxs(w,{type:"button",variant:"outline",size:"sm",onClick:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
@@ -1 +1 @@
1
- import{j as e}from"./vendor-tanstack-pFxw0eGP.js";import{A as s}from"./AuditHistoryPanel-McQfjoU9.js";import"./index-DUfg50Pl.js";import"./vendor-radix-BxMXSwbC.js";import"./vendor-recharts-C4ktw_aE.js";import"./vendor-lucide-BtYdosYk.js";import"./vendor-markdown-D274_wsI.js";import"./vendor-yaml-GWJz5H2G.js";function l(){return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"History"}),e.jsx("p",{className:"page-subtitle",children:"Changes to projects and settings."})]})}),e.jsx(s,{})]})}export{l as HistoryPage};
1
+ import{j as e}from"./vendor-tanstack-Dock0eH1.js";import{A as s}from"./AuditHistoryPanel-AtgoG2AH.js";import"./index-Wqv7kvs-.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-lucide-nYKv-BB7.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function l(){return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"History"}),e.jsx("p",{className:"page-subtitle",children:"Changes to projects and settings."})]})}),e.jsx(s,{})]})}export{l as HistoryPage};