@canonry/canonry 6.3.0 → 7.0.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 (42) hide show
  1. package/README.md +3 -3
  2. package/assets/agent-workspace/skills/aero/SKILL.md +2 -2
  3. package/assets/agent-workspace/skills/aero/references/reporting.md +26 -23
  4. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +52 -21
  5. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +17 -6
  6. package/assets/assets/{AuditHistoryPanel-BUYWIjlG.js → AuditHistoryPanel-DBYsgVsn.js} +1 -1
  7. package/assets/assets/{BacklinksPage-aBcxpN_9.js → BacklinksPage-HIM0fmV7.js} +1 -1
  8. package/assets/assets/ChartPrimitives-CYlrwLJs.js +1 -0
  9. package/assets/assets/{HistoryPage-PoBuxiY_.js → HistoryPage-BjEVp-fk.js} +1 -1
  10. package/assets/assets/MeasurementPropertyPage-BnVDR2y_.js +1 -0
  11. package/assets/assets/ProjectPage-BhCM47KA.js +8 -0
  12. package/assets/assets/{RunsPage-CHHxOpzo.js → RunsPage-fJQe2qJc.js} +1 -1
  13. package/assets/assets/{SettingsPage-CNKnrLXC.js → SettingsPage-1yam5tl6.js} +1 -1
  14. package/assets/assets/{SiteHealthSection-C9RivT7G.js → SiteHealthSection-DysU2M6C.js} +3 -3
  15. package/assets/assets/{TrafficPage-CY-ibXnb.js → TrafficPage-BdNEscz8.js} +2 -2
  16. package/assets/assets/{TrafficSourceDetailPage-BgiPXubs.js → TrafficSourceDetailPage-CMEgr4Aw.js} +1 -1
  17. package/assets/assets/{extract-error-message-H1dzzfBP.js → extract-error-message-D2dhaJaW.js} +1 -1
  18. package/assets/assets/index-CgT2Btdn.css +1 -0
  19. package/assets/assets/index-D9ODLKpH.js +86 -0
  20. package/assets/assets/{react-sigma_core.esm.min-uiYtR_DU.js → react-sigma_core.esm.min-oP23IWpt.js} +1 -1
  21. package/assets/assets/v2-overview-adapter-DYatWs7N.js +1 -0
  22. package/assets/assets/{vendor-recharts-llkUFBGR.js → vendor-recharts-D-cUuLZo.js} +1 -1
  23. package/assets/index.html +3 -3
  24. package/dist/{chunk-OTROA7CN.js → chunk-A5PALGQD.js} +55 -22
  25. package/dist/{chunk-QVGG76UW.js → chunk-EZ54X4XH.js} +4386 -8275
  26. package/dist/{chunk-ZZEPF3A5.js → chunk-HQJ25LI7.js} +83 -19
  27. package/dist/{chunk-5GY5WBAF.js → chunk-LXWY4NKP.js} +3 -3
  28. package/dist/{chunk-EV5G5QRR.js → chunk-OOUDIVIU.js} +1224 -3003
  29. package/dist/{chunk-FSRVG2H7.js → chunk-RRIZ5AKI.js} +2 -2
  30. package/dist/{chunk-DERWXLHX.js → chunk-TUHQ5BBQ.js} +65 -16
  31. package/dist/cli.js +240 -178
  32. package/dist/{demo-server-5MNLI6GC.js → demo-server-LHHDVXQW.js} +5 -14
  33. package/dist/index.js +6 -6
  34. package/dist/{intelligence-service-5J36RBEM.js → intelligence-service-ZVKZLLKL.js} +2 -2
  35. package/dist/mcp.js +4 -4
  36. package/package.json +12 -12
  37. package/assets/assets/ChartPrimitives-B5okB5g-.js +0 -1
  38. package/assets/assets/MeasurementPropertyPage-DyjyqHac.js +0 -1
  39. package/assets/assets/ProjectPage-Mmv5YW-K.js +0 -8
  40. package/assets/assets/index-C17E5ZcA.css +0 -1
  41. package/assets/assets/index-DSCun3I9.js +0 -86
  42. package/assets/assets/v2-overview-adapter-BQokN2eb.js +0 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@ Canonry is an **agent-first (CLI, MCP, API), open-source AEO operating platform
11
11
 
12
12
  <p align="center">
13
13
  <a href="https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/measure-act.svg">
14
- <img src="https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/measure-act.svg" alt="AI, search, analytics, and traffic sources feed Canonry on schedules or on demand. Historical evidence across multiple domains and portfolios runs from baseline through the latest checks. Your agent reads evidence and sends commands to Canonry tools, while using its own tools for code and content. Canonry publishes to WordPress, submits to Google and Bing, and supplies the dashboard, reports, and webhooks. Site changes loop back into measurement." width="100%" />
14
+ <img src="https://raw.githubusercontent.com/Canonry/canonry/main/docs/images/measure-act.svg" alt="AI, search, analytics, and traffic sources feed Canonry on schedules or on demand. Historical evidence across multiple domains and portfolios runs from baseline through the latest checks. Your agent reads evidence and sends commands to Canonry tools, while using its own tools for code and content. Canonry publishes to WordPress, submits to Google and Bing, and supplies the dashboard, evidence exports, and webhooks. Site changes loop back into measurement." width="100%" />
15
15
  </a>
16
16
  </p>
17
17
 
@@ -190,7 +190,7 @@ Your agent coordinates the work through Canonry and its own tools.
190
190
 
191
191
  - **Improve the site:** Combine your agent's coding tools with Canonry's content, [WordPress publishing](docs/wordpress-setup.md), JSON-LD, and [indexing workflows](skills/canonry/references/indexing.md).
192
192
  - **Measure after changes:** Configure [independent schedules](skills/canonry/references/canonry-cli.md#scheduling--notifications) for data syncs and checks. Compare new results with earlier evidence.
193
- - **Report and follow up:** Generate [HTML reports or JSON evidence](skills/canonry/references/canonry-cli.md#reports). Send [webhook alerts](skills/canonry/references/canonry-cli.md#agent) to Discord, Slack, or your systems.
193
+ - **Export and follow up:** Read [AI visibility evidence](docs/report-retirement.md) and [traffic analytics](docs/report-retirement.md). Send [webhook alerts](skills/canonry/references/canonry-cli.md#agent) to Discord, Slack, or your systems.
194
194
 
195
195
  **An example job for your connected agent:**
196
196
 
@@ -220,7 +220,7 @@ Connect the sources you use. Each integration adds evidence or tools to the same
220
220
 
221
221
  Canonry is single-tenant. Run one instance for one operator or team. Keep unrelated teams on separate instances.
222
222
 
223
- The CLI and REST API are the primary interfaces. They expose measurements, diagnoses, actions, reports, and schedules.
223
+ The CLI and REST API are the primary interfaces. They expose measurements, diagnoses, actions, evidence exports, and schedules.
224
224
  OpenAPI is available at `GET /api/v1/openapi.json`.
225
225
 
226
226
  See the [deployment guide](docs/deployment.md) for reverse proxies, daemon mode, Docker, systemd, and Tailscale.
@@ -169,8 +169,8 @@ A real (non-probe) sweep is appropriate when the user explicitly asks to refresh
169
169
  and never follow a table with a paragraph that repeats its top rows.
170
170
  - Chat is not a report. No emoji, no rank medals, no `##` headings and no
171
171
  `---` rules inside an answer. A short bold line is the heaviest
172
- structure available; `references/reporting.md` governs the HTML report
173
- surface instead.
172
+ structure available; `references/reporting.md` governs requested weekly
173
+ and monthly documents instead.
174
174
  - The closing next step is a recommendation, not an offer. "Start with the
175
175
  Properties at zero coverage" beats "want me to drill into one?".
176
176
 
@@ -33,7 +33,7 @@ The class rates (`mention-rate-branded`, `mention-rate-non-brand`, and their `ci
33
33
 
34
34
  Never pool them into one headline. A branded question ("<brand> reviews") measures demand the brand already created: the answer names the brand because the question did, so a near-100% mention rate is the expected floor, not an achievement. A non-brand question ("best <category> for <use case>") measures demand to win, and it is the number that says whether the work is landing. A pooled figure mostly measures how famous the brand already is and hides whether anything moved.
35
35
 
36
- **This matters most for share of voice, and the split is now enforced.** `visibility-stats --share-of-voice`, `visibility-compare`, the project overview's Mention Share card and its breakdown chart, and the client report's mention landscape are all scoped to NON-BRAND queries by default. Every one of them echoes the class it served (`queryClass` / `scope`), and branded is returned beside the figure rather than inside it. Pass `--query-class branded` when you want brand recall.
36
+ **This matters most for share of voice.** `visibility-stats --share-of-voice`, `visibility-compare`, the project overview's Mention Share card and its breakdown chart default to NON-BRAND queries. Historical competitor landscapes require explicit `queryClass: "non-brand"` (CLI: `--query-class non-brand`) for competitive percentages. Omitting the class or requesting `all` returns pooled counts and null shares. Keep the returned class (`queryClass` / `scope`) beside each figure; request branded evidence separately for brand recall.
37
37
 
38
38
  Why it is enforced rather than advised: on a real basket (13 queries × 4 engines, 5 branded), the subject was named in 20 of 20 branded answers and 1 of 32 category answers. Pooled, the chart put them FIRST at ~42%. Non-brand, they were LAST at ~3%, behind all seven tracked competitors. Same run, opposite conclusion, and the pooled version is the one a client would have read as category leadership.
39
39
 
@@ -54,28 +54,31 @@ Two consequences for any report:
54
54
 
55
55
  A sweep crippled by a provider outage captures fewer questions and looks identical to a collapse in visibility. Before narrating any drop, check whether the latest run is `partial` and what its capture count was against the project's basket size. Report a capture failure as a capture failure.
56
56
 
57
- ## One-Command HTML Report
58
-
59
- When a client asks for a "current state" or "AEO report" without a specific custom narrative, prefer the bundled report instead of hand-rolling sections:
60
-
61
- ```bash
62
- cnry report <project> # writes canonry-report-<project>-YYYY-MM-DD.html in cwd
63
- cnry report <project> --output dist/aeo.html # custom path
64
- cnry report <project> --format json # raw payload, useful for narrating in chat
65
- ```
66
-
67
- The HTML is self-contained (inline CSS + SVG charts, no network dependencies) and leads visually with mention coverage (the primary gauge), then covers: executive summary, per-query × per-provider matrix (mention + citation), competitor landscape, AI citation sources, GSC + GA4 performance, social and AI referrals, indexing health, trend, prioritized insights, and recommended next steps. Same payload is available via `GET /api/v1/projects/<name>/report` and the `canonry_report` MCP tool — use `--format json` when you want to summarize specific numbers in a thread instead of attaching the file.
68
-
69
- Behaviors worth knowing before narrating numbers from the report:
70
- - **Mention is the primary metric; citation is secondary.** Narrate the mention figures first. `executiveSummary.mentionRate` is **per-query** — `mentionedQueryCount / totalQueryCount`, where a query counts as mentioned if any provider's answer text named the brand in the run. The report leads visually with mention coverage, but the machine field backing that headline is not confirmed in the CLI docs — [confirm field name against `cnry report --format json` / `ProjectReportDto`] before quoting it as the headline; `executiveSummary.mentionRate` / `mentionedQueryCount` are the confirmed per-query mention fields.
71
- - `executiveSummary.citationRate` is the **secondary**, per-query citation signal — `citedQueryCount / totalQueryCount`, where a query counts as cited if any provider in the run cited it. The denominator is total tracked queries (not (query × provider) pairs), so the rate stays comparable when provider count varies between runs. Use `citedQueryCount` / `totalQueryCount` directly when narrating ratios. Mention and citation are independent — never compute one from the other.
72
- - The same per-query definition powers every `citationsTrend[].citationRate` so trend deltas reflect real movement, not provider-mix variance.
73
- - `citationsTrend` excludes partial runs. A project with only one completed run shows `trend: "unknown"` — never claim a comparison that isn't there.
74
- - Project ownership and competitor tagging use subdomain-aware matching: `blog.example.com` counts as the project when `example.com` is the canonical domain or in `ownedDomains`; `blog.rival.com` is tagged `isCompetitor: true` when `rival.com` is tracked.
75
- - AI referral totals dedupe overlapping GA4 attribution dimensions (`session` / `first_user` / `manual_utm`).
76
- - Server-side AI referral sessions (the "AI Visibility — Server-Side" section) are server-log counts, not GA sessions. Before quoting one, run `cnry traffic referral-assessment <project> --start-date YYYY-MM-DD --end-date YYYY-MM-DD --format json` for the same dates (or read the silent `report.ai-referral-bursts` doctor check for the report month) and review any candidate bursts. Quote the unchanged headline (the assessment's `totals.countable`) beside the separate `totals.adjustedEstimate`; a candidate burst is not confirmed automation, and neither figure replaces GA.
77
-
78
- The hand-rolled templates below are still the right call when the user wants a focused weekly/monthly digest with custom regression and gain narratives that the bundled report doesn't surface.
57
+ ## Build summaries from stored evidence
58
+
59
+ Use the project overview for current state, `canonry_visibility_compare` for
60
+ calendar-month comparisons, and scoped measurement tools for Advanced
61
+ Properties and markets. Read `canonry_organic_evidence` for the stored GSC,
62
+ GA4, and server-traffic evidence ladder. The dedicated HTML Report surface has
63
+ been retired; generate the requested document from these existing sources.
64
+
65
+ For complete server-traffic totals and breakdowns, use
66
+ `canonry_traffic_analytics` (CLI: `cnry traffic analytics <project> --period 30
67
+ --format json`). Select 7, 14, 30, or 90 days, preserve its returned dates and
68
+ prior-window counts, and keep paid, organic, and unclassified arrivals separate.
69
+ Server sessions and GA sessions measure different evidence. Review
70
+ `canonry_traffic_referral_assessment` for the same dates before attributing
71
+ candidate bursts to people; an adjusted count is an estimate.
72
+
73
+ Keep mention and citation signals independent. Use the scoped tool's numerator,
74
+ denominator, and query class rather than recreating retired report metrics.
75
+ Retain missing evidence and partial-sweep qualifications. Preparing a summary
76
+ never authorizes a new sweep, provider read, or sync.
77
+
78
+ Content reads honor saved addressed recommendations. Inspect those records with
79
+ `canonry_content_dismissals` (CLI `cnry content dismissals <project>`). Saving
80
+ or removing one uses `canonry_content_dismiss` or `canonry_content_restore`
81
+ and requires explicit approval; preparing a summary does not authorize it.
79
82
 
80
83
  ## Weekly Report
81
84
 
@@ -74,11 +74,11 @@ cnry get <project> scores.mentionCoverage.value
74
74
  cnry get <project> scores.citationCoverage.value
75
75
  cnry get <project> insights[0].severity
76
76
  cnry get <project> latestRun.status
77
- cnry get <project> --from report scores.citationCoverage.value # pick a registered source
77
+ cnry get <project> --from overview scores.citationCoverage.value # pick a registered source
78
78
  cnry get <project> <path> --format json # raw JSON output
79
79
  ```
80
80
 
81
- Resolves a dot/bracket path against the project's overview (default `--from overview`) or any registered source — `report`, `traffic`, `discovery`, etc. Returns the scalar (or sub-tree) at the path so an agent can lift a single number without pulling a 30 KB JSON payload. Use `--from <source> .` to see the available top-level keys for that source.
81
+ Resolves a dot/bracket path against the project's overview (default `--from overview`) or any registered source: `overview`, `doctor`, `runs`, `queries`, or `competitors` Returns the scalar (or sub-tree) at the path so an agent can lift a single number without pulling a 30 KB JSON payload. Use `--from <source> .` to see the available top-level keys for that source.
82
82
 
83
83
  ### Locations
84
84
 
@@ -222,25 +222,6 @@ Legend: [C/c][M/m] C=cited c=not-cited M=mentioned m=not-mentioned –=no sna
222
222
 
223
223
  Summary: `Mentioned: X / Y` (primary) and `Cited: X / Y` (secondary) are reported independently — a query can be one, both, or neither.
224
224
 
225
- ## Reports
226
-
227
- ```bash
228
- cnry report <project> # write canonry-report-<project>-YYYY-MM-DD.html
229
- cnry report <project> --period 14 # time window: 7|14|30|90 days (default 30) — scopes GSC/GA/server-activity + period-over-period deltas
230
- cnry report <project> --output dist/aeo.html # custom path
231
- cnry report <project> --format json # raw report payload to stdout
232
- ```
233
-
234
- One-command client-facing AEO report. Bundles the latest visibility sweep, competitor landscape, AI citation sources, GSC + GA4 performance, social and AI referrals, indexing health, citations trend, prioritized insights, and recommended next steps into a self-contained HTML file (inline CSS + SVG charts, no network dependencies). Backed by `GET /api/v1/projects/<name>/report` and the `canonry_report` MCP tool.
235
-
236
- Behavior to know when narrating numbers from the report:
237
- - `executiveSummary.citationRate` is **per-query** — `citedQueryCount / totalQueryCount`, with a query counted as cited if any provider in the run cited it. The rate is invariant to provider count, so a gemini-only run and a 4-provider run can be compared honestly. The same definition powers `citationsTrend[].citationRate` so trend deltas track real movement, not provider-mix variance.
238
- - `citationsTrend` excludes partial runs to avoid skew. A project with only one completed run gets `trend: "unknown"` and the finding "No prior run to compare against." — not "Flat compared to the previous run."
239
- - Project ownership uses subdomain-aware matching against `project.canonicalDomain` plus any configured `ownedDomains`. `blog.example.com` and `brand.io` count as the project, not as external sources, when those rules apply.
240
- - Competitor tagging in `aiSourceOrigin.topDomains` uses the same subdomain-aware match — `blog.rival.com` is `isCompetitor: true` when `rival.com` is tracked.
241
- - AI referral totals dedupe overlapping GA4 attribution dimensions (`session` / `first_user` / `manual_utm`) by picking the largest dimension per `(date, source)`, breaking ties toward the `session` lens. Medium and traffic class are labels the lens assigns, not properties of the visit, so they are NOT part of the key: GA4 reports the manual-UTM lens with medium `(not set)` where the session lens reports `ai-assistant`, and keying on medium counted one visit twice. Two 10-session rows for the same tuple report 10 sessions, not 20.
242
- - GSC top-query CTR and avgPosition are impression-weighted, matching GSC's own metric semantics across multi-row queries.
243
-
244
225
  ## Results Export (historical observations)
245
226
 
246
227
  ```bash
@@ -380,6 +361,31 @@ cnry backfill insights <project> --from-run <id> --to-run <id> # backfill a ran
380
361
 
381
362
  > **Known gap (mention-first read):** `cnry health` is **citation-only** today — it has no mention dimension. For the primary mention-first read, use `cnry overview` and `cnry get <project> scores.mentionCoverage.value` / `cnry get <project> scores.mentionShare.value` until health is extended.
382
363
 
364
+ ### Content-target dismissals
365
+
366
+ ```bash
367
+ cnry content targets <project> --format json
368
+ cnry content dismissals <project> --format json
369
+ cnry content dismiss <project> <targetRef> --addressed-url https://example.com/page --note "Addressed" --format json
370
+ cnry content restore <project> <targetRef> --format json
371
+ ```
372
+
373
+ Use a `targetRef` from the content-target response. Dismissal records remain
374
+ project-scoped and hide that recommendation on later content reads. Listing
375
+ uses `GET /projects/:name/content/dismissals`; dismiss uses `POST` with
376
+ `targetRef` and optional `addressedUrl` and `note`. Re-dismissing updates those
377
+ optional values and the dismissal time. Restore uses
378
+ `DELETE /projects/:name/content/dismissals/:targetRef` and returns HTTP 204; CLI and
379
+ MCP acknowledge it as `{ targetRef, restored: true }`. A missing record returns
380
+ the API's not-found error.
381
+
382
+ All three commands support `json` and `jsonl`. The list JSON envelope is
383
+ `{ dismissals: [...] }`; JSONL streams its records. Dismiss and restore return
384
+ one document. MCP equivalents are `canonry_content_dismissals` (read),
385
+ `canonry_content_dismiss` (write), and `canonry_content_restore` (write), in the
386
+ `monitoring` toolkit. Dismiss and restore require write authority and explicit
387
+ approval; reading a recommendation does not authorize either action.
388
+
383
389
  ## Queries & Competitors
384
390
 
385
391
  ```bash
@@ -393,8 +399,14 @@ cnry query generate <project> --provider gemini --count 10 --save
393
399
 
394
400
  cnry competitor add <project> competitor1.com competitor2.com
395
401
  cnry competitor list <project>
402
+ cnry competitor landscape <project> --query-class non-brand --window 30d --format json
396
403
  ```
397
404
 
405
+ Historical competitor landscapes require explicit `--query-class non-brand`
406
+ for competitive percentages (REST `queryClass=non-brand`, MCP
407
+ `queryClass: "non-brand"`). Omitted class or `all` returns pooled counts and
408
+ null shares. Request branded evidence separately for brand recall.
409
+
398
410
  ## Target Measurement Plans
399
411
 
400
412
  ```bash
@@ -697,6 +709,7 @@ cnry traffic activate <project> --source <source-id>
697
709
  cnry traffic sync <project> --source <source-id> # pull adapters, including Cloudflare Queue pull
698
710
  cnry traffic sources <project> --format json
699
711
  cnry traffic status <project> --format json
712
+ cnry traffic analytics <project> --period 30 --format json # complete 7|14|30|90-day totals, prior-period changes and breakdowns
700
713
  cnry traffic events <project> --source <source-id> --format json
701
714
  cnry traffic referral-assessment <project> --start-date 2026-08-01 --end-date 2026-08-31 --burst-threshold 100 --ratio-threshold 3 --limit 100 --format json
702
715
 
@@ -746,6 +759,24 @@ bursts exist, reports `candidateGroups`, and carries these limits in its
746
759
  details. Keep GA evidence; this assessment does not establish a replacement
747
760
  human-visit count.
748
761
 
762
+ `traffic analytics` reads complete stored server-traffic aggregates through
763
+ `GET /api/v1/projects/<name>/traffic/analytics?period=30` and the
764
+ `canonry_traffic_analytics` MCP tool. JSON preserves `{ activity }` unchanged;
765
+ JSONL emits the same document. With no non-archived source, `activity` is null.
766
+ Connected sources without events return zero stored aggregate counts; those
767
+ zeros do not prove measurement coverage or a successful source sync. Retain
768
+ `coverageStart` (the earliest stored project observation, or null) and
769
+ `priorWindowComplete` (whether it reaches the prior window's start). Incomplete
770
+ prior recording withholds all headline, arrival-class, and operator `deltaPct`
771
+ values while preserving current and prior counts. These fields do not prove
772
+ continuous collection or matching source coverage across the two windows.
773
+ It retains crawler
774
+ verification, user fetches, paid/organic/unclassified arrivals, redirect hops,
775
+ operators, crawled paths, referral products, landing paths, daily history, and
776
+ equal prior-window changes. It does not start a sync or provider work. The
777
+ scope is project-wide for Simple and Advanced portfolios; there is no Property,
778
+ Target, or market attribution. Server sessions and GA sessions remain separate.
779
+
749
780
  ## Google Analytics 4
750
781
 
751
782
  GA4 integration supports service-account auth and OAuth (`canonry google connect <project> --type ga4`). With OAuth, `ga properties` lists the readable properties so the numeric id `ga connect --property-id` needs can be discovered without leaving canonry. The service account must have Viewer access on the GA4 property. `ga sync` writes to four DB tables (`gaTrafficSnapshots`, `gaAiReferrals`, `gaSocialReferrals`, `gaTrafficSummaries`); every subsequent read command queries the local store rather than re-fetching from GA4, so reads are fast and quotaless. AI-referral rows are tracked across 10 known providers (chatgpt, perplexity, claude, gemini, openai, anthropic, copilot, phind, you.com, meta.ai), three GA4 attribution dimensions (`session` / `first_user` / `manual_utm`), and joined to landing pages. Social referrals are split Organic vs Paid via GA4's `sessionDefaultChannelGroup`. All commands support `--format json`.
@@ -48,7 +48,7 @@ The silent `report.ai-referral-bursts` doctor advisory warns
48
48
  to review; otherwise it passes (`no-bursts`) with these coverage limits in its
49
49
  details. It skips a project whose traffic sources are all archived.
50
50
  A high quotient does not confirm automation, and this diagnostic does not
51
- replace GA or change any existing report headline.
51
+ replace GA or change the raw traffic totals.
52
52
 
53
53
  ## Architecture
54
54
 
@@ -1025,10 +1025,20 @@ schema change, the stored rollups are untouched.
1025
1025
  |---|---|
1026
1026
  | Project dashboard `/projects/:name/activity` | Live source table + 24h totals + GA4 referrals (combined view) |
1027
1027
  | Top-level `/traffic` route | Cross-project source admin (connect, sync, archive) |
1028
- | `cnry report <project>` (HTML + SPA) | "AI Visibility — Server-Side" section, ranked above Indexing Health |
1028
+ | `cnry traffic analytics <project> --period 30 --format json` | Complete stored 7/14/30/90-day totals, prior-window changes, operator/path/product breakdowns, and daily history |
1029
1029
  | `cnry doctor --project <name>` | Source health checks, including last-observed Worker drift for direct and Queue sources plus Queue local credential/sync state |
1030
1030
  | 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. |
1031
1031
 
1032
+ Traffic analytics aggregates every matching stored row before limiting ranked
1033
+ paths. A zero count means no matching events were stored for that metric and
1034
+ window; it does not prove source sync or measurement coverage. Preserve
1035
+ `coverageStart`, the earliest project
1036
+ observation or null, and `priorWindowComplete`, which tests whether recording
1037
+ began by the prior window's start. If false, headline, arrival-class, and
1038
+ operator changes are unavailable (`deltaPct: null`) while raw totals remain.
1039
+ These fields do not establish continuous collection or matching sources in
1040
+ both windows. Check source status and sync evidence before interpreting changes.
1041
+
1032
1042
  ## Doctor signals
1033
1043
 
1034
1044
  The doctor checks are adapter-agnostic. When they fail or warn:
@@ -1123,10 +1133,11 @@ domains, or PII are surfaced.
1123
1133
  citation store is domain-grain (`query_snapshots.cited_domains`). A
1124
1134
  future iteration that lands URL-grain citation evidence will extend
1125
1135
  the `topCrawledPaths` entry with a `citationState` flag. Until then,
1126
- treat the report's crawled-paths table as "engine attention" — the
1136
+ treat traffic analytics' crawled-paths table as "engine attention" — the
1127
1137
  signal is the bot fetched it, not whether it was cited.
1128
- - **Verified vs unverified.** The headline numbers count only
1129
- published-IP-range-verified hits. Unverified bots claim a known UA but
1138
+ - **Verified vs unverified.** Verified crawler counts include only
1139
+ published-IP-range-verified hits. Unverified crawler counts remain separate:
1140
+ those bots claim a known UA but
1130
1141
  do not match the operator's published IP ranges. The bot can be real or an
1131
1142
  imitator. Do not promote unverified counts in client-facing copy.
1132
1143
  **Vercel sources are a special case:** the Vercel pull API returns
@@ -1134,7 +1145,7 @@ domains, or PII are surfaced.
1134
1145
  construction (UA-only). A Vercel source reading 100% unverified is
1135
1146
  expected, not a misconfiguration.
1136
1147
  - **Four adapters ship: Cloud Run, WordPress, Vercel, and Cloudflare with
1137
- direct-push and queue-pull delivery. More adapters are planned.** The doctor checks and report renderer are
1148
+ direct-push and queue-pull delivery. More adapters are planned.** The doctor checks and traffic analytics are
1138
1149
  adapter-agnostic — adding a new adapter is just a new entry in
1139
1150
  `traffic_sources.source_type` and a `TrafficSourceValidator`
1140
1151
  registration.
@@ -1 +1 @@
1
- import{r as a,u as h,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{f0 as L,f1 as P,dY as E,w as u,B as x,r as v,T as M}from"./index-DSCun3I9.js";const o=100;function k(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(()=>k(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(M,{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
+ import{r as a,u as h,j as e}from"./vendor-tanstack-ZXXkndVj.js";import{dv as L,dw as P,cq as E,w as u,B as x,r as v,T as M}from"./index-D9ODLKpH.js";const o=100;function k(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,w]=a.useState(""),[p,N]=a.useState(""),S=a.useMemo(()=>k(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=>N(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(()=>w(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(M,{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-ZXXkndVj.js";import{ff as _,ax as H,fg as U,fh as J,fi as K,r as x,T as j,x as u,B as w,A as P,fj as V,fk as Y,aA as X,fl as Z}from"./index-DSCun3I9.js";import{I as ee,T as se,E as te,j as ae,O as ne,J as re}from"./vendor-lucide-N6vOpZmh.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-llkUFBGR.js";import"./vendor-markdown-B4giJcws.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,I]=n.useState(!1),[p,E]=n.useState(!1),[N,v]=n.useState(""),[M,k]=n.useState(!1),[A,l]=n.useState(null),[F,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(()=>[]),J().catch(()=>[]),K().catch(()=>null)]);h(s),i(r),y(Q),$(z),L(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){I(!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{I(!1)}}async function q(){const s=N.trim()||void 0;E(!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{E(!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."})]})}),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})}),F&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:F})}),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:P(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:P(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-ZXXkndVj.js";import{dK as _,ax as H,dL as U,dM as K,dN as J,r as x,T as j,x as u,B as w,A as F,dO as V,dP as Y,aA as X,dQ as Z}from"./index-D9ODLKpH.js";import{I as ee,T as se,E as te,j as ae,O as ne,J as re}from"./vendor-lucide-N6vOpZmh.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-markdown-B4giJcws.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,I]=n.useState(!1),[p,E]=n.useState(!1),[N,v]=n.useState(""),[M,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,G,z,W]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),K().catch(()=>[]),J().catch(()=>null)]);h(s),i(r),y(G),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(){I(!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{I(!1)}}async function q(){const s=N.trim()||void 0;E(!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{E(!1)}}async function O(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(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."}),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(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:()=>{O(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};
@@ -0,0 +1 @@
1
+ import{j as i}from"./vendor-tanstack-ZXXkndVj.js";import{R as k,C as I,a as R,e as A,X as _,Y as L,T as w,d as v}from"./vendor-recharts-D-cUuLZo.js";const O={contentStyle:{backgroundColor:"var(--chart-tooltip-bg, #18181b)",border:"1px solid var(--chart-tooltip-border, #3f3f46)",borderRadius:8,fontSize:12},labelStyle:{color:"var(--chart-tooltip-label, #e4e4e7)"},itemStyle:{color:"var(--chart-tooltip-item, #a1a1aa)"}},b={fill:"var(--chart-neutral-text-dim, #8b8b93)",fontSize:11},j="var(--chart-grid, #27272a)",p="var(--chart-axis, #27272a)",y=["var(--chart-series-1, #34d399)","var(--chart-series-2, #60a5fa)","var(--chart-series-3, #f472b6)","var(--chart-series-4, #facc15)","var(--chart-series-5, #a78bfa)","var(--chart-series-6, #fb923c)","var(--chart-series-7, #22d3ee)","var(--chart-series-8, #f87171)"],K={gemini:"#60a5fa",openai:"#34d399",claude:"#fb923c",perplexity:"#22d3ee",muse:"#818cf8",local:"#a78bfa"};function M(a,r=0){return K[a]??y[r%y.length]}const E={text:"var(--chart-neutral-text, #a1a1aa)",textDim:"var(--chart-neutral-text-dim, #8b8b93)",textFaint:"var(--chart-neutral-text-faint, #52525b)",surface:"var(--chart-neutral-surface, #27272a)",trackSubtle:"var(--chart-neutral-track-subtle, rgb(255 255 255 / 0.04))"},N={positive:"var(--chart-tone-positive, #34d399)",positiveDeep:"var(--chart-tone-positive-deep, #10b981)",caution:"var(--chart-tone-caution, #fbbf24)"};function U(a){return a}function f(a){const r=/^(\d{4})-(\d{2})-(\d{2})/.exec(a);return r?{year:Number(r[1]),month:Number(r[2]),day:Number(r[3])}:null}function Y(a){const r=String(a),e=f(r);return e?new Date(Date.UTC(e.year,e.month-1,e.day)).toLocaleDateString(void 0,{timeZone:"UTC",month:"short",day:"numeric",year:"numeric"}):r}function F(a){const r=f(String(a));return r?`${r.month}/${r.day}`:String(a)}function G(a){const r=f(String(a));return r?new Date(Date.UTC(r.year,r.month-1,r.day)).toLocaleDateString(void 0,{timeZone:"UTC",month:"short",day:"numeric"}):String(a)}function P(a){return new Date(a).toLocaleDateString(void 0,{month:"short",day:"numeric",year:"numeric"})}function V(a){return new Date(a).toLocaleDateString(void 0,{month:"short",day:"numeric"})}function W(a){return new Date(a).getFullYear()}function z(a){const r=new Date(a);return`${r.getMonth()+1}/${r.getDate()}`}function Z({data:a,xKey:r,series:e,height:x=260,xTickFormatter:S,labelFormatter:u,onSelectX:d,selectedX:m,accessibilityLayer:g=!0,ariaLabel:T}){const h=[...new Set(e.map(t=>t.axisId))],C=new Map(e.map(t=>[t.label,t.formatValue])),D=a.map((t,s)=>{const n={...t};for(const o of e){if(!o.trend)continue;const c=o.trend.startIndex??0,l=o.trend.endIndex??a.length-1;if(s<c||s>l){n[`${o.dataKey}__trend`]=null;continue}n[`${o.dataKey}__trend`]=l<=c?o.trend.start:o.trend.start+(o.trend.end-o.trend.start)*(s-c)/(l-c)}return n});return i.jsx(k,{width:"100%",height:x,children:i.jsxs(I,{data:D,accessibilityLayer:g,"aria-label":T,margin:{top:8,right:8,bottom:4,left:0},onClick:d?t=>{t?.activeLabel!==void 0&&d(String(t.activeLabel))}:void 0,style:d?{cursor:"pointer"}:void 0,children:[i.jsx(R,{stroke:j,strokeDasharray:"3 3",vertical:!1}),m!=null&&i.jsx(A,{x:m,yAxisId:h[0],stroke:E.text,strokeDasharray:"2 3",strokeWidth:1}),i.jsx(_,{dataKey:r,tick:b,stroke:p,tickFormatter:S,minTickGap:24}),h.map((t,s)=>{const n=e.find(o=>o.axisId===t);return i.jsx(L,{yAxisId:t,orientation:s===1?"right":"left",hide:s>1,reversed:n.inverted??!1,domain:n.inverted?[1,"auto"]:[0,"auto"],tick:b,stroke:p,width:48,tickFormatter:n.formatValue},t)}),i.jsx(w,{...O,labelFormatter:t=>u?u(String(t)):String(t),formatter:(t,s)=>{const n=String(s??"");return typeof t!="number"?[String(t??""),n]:[C.get(n)?.(t)??t.toLocaleString(),n]}}),e.filter(t=>t.trend).map(t=>i.jsx(v,{yAxisId:t.axisId,dataKey:`${t.dataKey}__trend`,name:`${t.label} trend`,stroke:t.color,strokeWidth:1.5,strokeDasharray:"5 4",strokeOpacity:.55,dot:!1,activeDot:!1,isAnimationActive:!1,legendType:"none",tooltipType:"none"},`${t.dataKey}-trend`)),e.map(t=>i.jsx(v,{yAxisId:t.axisId,dataKey:t.dataKey,name:t.label,stroke:t.color,strokeWidth:2,dot:!1,activeDot:{r:3},connectNulls:!1,isAnimationActive:!1},t.dataKey))]})})}export{j as C,Z as M,p as a,b,Y as c,O as d,y as e,F as f,E as g,N as h,z as i,P as j,G as k,W as l,V as m,U as o,M as p};
@@ -1 +1 @@
1
- import{j as e}from"./vendor-tanstack-ZXXkndVj.js";import{A as s}from"./AuditHistoryPanel-BUYWIjlG.js";import"./index-DSCun3I9.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-llkUFBGR.js";import"./vendor-lucide-N6vOpZmh.js";import"./vendor-markdown-B4giJcws.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-ZXXkndVj.js";import{A as s}from"./AuditHistoryPanel-DBYsgVsn.js";import"./index-D9ODLKpH.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-lucide-N6vOpZmh.js";import"./vendor-markdown-B4giJcws.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};
@@ -0,0 +1 @@
1
+ import{D as we,k as Pe,l as Ce,r as b,u as L,C as Ee,j as e,L as I}from"./vendor-tanstack-ZXXkndVj.js";import{cA as Le,cY as Me,ct as Se,cr as Ae,K as Re,cM as Te,cH as z,e4 as ke,e5 as qe,w as M,e6 as Ie,B as g,I as P,T as w,aJ as ae,aK as ne,d2 as H,e7 as Ue,e8 as Be,e9 as _e,j as $e,s as Oe,f as ce}from"./index-D9ODLKpH.js";import{m as Ve,a as he,U as Ke,M as Qe,A as Fe,u as me}from"./v2-overview-adapter-DYatWs7N.js";import{j as ze,o as He}from"./ChartPrimitives-CYlrwLJs.js";import{Z as De}from"./vendor-lucide-N6vOpZmh.js";import"./vendor-radix-twS1M3FF.js";import"./vendor-recharts-D-cUuLZo.js";import"./vendor-markdown-B4giJcws.js";import"./vendor-yaml-GWJz5H2G.js";const re=50,u={branded:{headline:"When they know your name",technical:"Branded queries"},"non-brand":{headline:"When they don't",technical:"Non-brand queries"}},We={plan_v1:"Setup update required",no_completed_run:"No completed measurement yet",no_population:"No queries of this type are assigned",evidence_incomplete:"Source evidence is incomplete",identity_ambiguous:Ke,not_applicable:"Not applicable for this Property"},ie={complete:{label:"Measured",tone:"positive"},partial:{label:"Partly measured",tone:"caution"},running:{label:"Measuring now",tone:"neutral"},queued:{label:"Measurement queued",tone:"neutral"},failed:{label:"Measurement failed",tone:"negative"},not_measured:{label:"Not measured",tone:"neutral"}},oe={assigned:{label:"Matches this Property",tone:"positive"},sibling:{label:"Matches another Property",tone:"caution"},ownedUnmapped:{label:"Site URL not in a Property",tone:"caution"},external:{label:"External URL",tone:"neutral"},ambiguous:{label:"Matches multiple Properties",tone:"caution"},invalid:{label:"Invalid URL",tone:"negative"}};function Ge(s){return[...s].sort((t,a)=>+(a.classification==="assigned")-+(t.classification==="assigned"))}function Ye(s){return`${s.expectedSlotId}:${s.usageEdgeId}`}function Ze({row:s}){return s.mentioned===null?e.jsxs("span",{className:"inline-flex flex-col items-start gap-0.5",children:[e.jsx(w,{tone:"neutral",children:"Not measured"}),e.jsx("span",{className:"text-xs text-muted",children:"No mention signal for this Property"})]}):e.jsx(w,{tone:s.mentioned?"positive":"neutral",children:s.mentioned?"Mentioned":"Not mentioned"})}const Je="—";function Xe({row:s}){return s.cited===null?e.jsxs("span",{className:"inline-flex flex-col items-start gap-0.5",children:[e.jsx(w,{tone:"neutral",children:"Not measured"}),e.jsx("span",{className:"text-xs text-muted",children:"Sources were not fully captured"})]}):e.jsx(w,{tone:s.cited?"positive":"neutral",children:s.cited?"Cited":"Not cited"})}function es({row:s}){return s.cited===null&&s.sources.length===0?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"The sources for this answer were not fully captured, so none can be shown."}):s.sources.length===0?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"This answer returned no source URLs at all."}):e.jsxs("details",{className:"mt-2","data-answer-sources":!0,children:[e.jsxs("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-mono-400",children:[he," (",s.sources.length,")"]}),e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsxs("caption",{className:"sr-only",children:["Source URLs for ",s.queryText]}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Match"}),e.jsx("th",{children:"URL"})]})}),e.jsx("tbody",{children:Ge(s.sources).map(t=>e.jsxs("tr",{children:[e.jsx("td",{children:e.jsx(w,{tone:oe[t.classification].tone,children:oe[t.classification].label})}),e.jsx("td",{children:e.jsx($e,{url:t.sourceUrl})})]},t.sourceUrl))})]})})]})}function ss({project:s,targetKey:t,row:a}){const r=L({..._e({client:M,path:{name:s},query:{targetKey:t,resultId:a.observationId}})});if(r.isPending)return e.jsx("p",{className:"py-2 text-sm text-secondary",role:"status","aria-live":"polite",children:"Loading the answer…"});if(r.isError)return e.jsx("p",{className:"py-2 text-sm text-secondary",role:"alert",children:"Could not load this answer."});const i=r.data?.answer;return i==null||i.trim()===""?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"This answer was measured, but its text was not stored."}):e.jsx("div",{className:"py-2",children:e.jsx(Fe,{headingLevel:4,copyable:!0,children:i})})}function B(s){return We[s.reason]??"Not measured"}function U({metric:s,emphasis:t=!1}){if(s.state==="unavailable")return e.jsxs("span",{className:"inline-flex flex-col gap-0.5",children:[e.jsx("span",{className:t?"text-lg font-semibold text-secondary":"text-sm font-medium text-secondary",children:"Not measured"}),e.jsx("span",{className:"text-sm text-secondary",children:B(s)})]});const a=ce(s.value),r=s.numerator===void 0||s.denominator===void 0?null:`${s.numerator} of ${s.denominator}`,i=me(s);return e.jsxs("span",{className:"inline-flex flex-col gap-0.5 tabular-nums",children:[e.jsx("span",{className:t?"text-lg font-semibold text-heading":"text-sm font-medium text-primary",children:a}),r?e.jsx("span",{className:"text-xs text-muted",children:r}):null,i?e.jsx("span",{className:"text-xs text-muted",children:i}):null]})}function le(s,t,a){return Ue({client:M,path:{name:s},query:{scope:"property",targetKey:t,queryClass:a}})}function de(s){return s?.properties.items.at(0)}function ts({measuredAt:s,unmeasuredReason:t,queryClass:a}){const r=s===void 0?null:s===null?"No completed sweep yet":`Measured ${ze(He(s))}`;return r===null&&t===void 0?null:e.jsxs("p",{className:"supporting-copy",children:[[r,t].filter(Boolean).join(" · "),r!==null&&t===void 0?` · ${u[a].technical.toLocaleLowerCase()} only`:""]})}function as({project:s,groups:t}){return t.length===0?null:e.jsxs("section",{"aria-labelledby":"property-market",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:"Competitive comparison"}),e.jsxs("h2",{id:"property-market",className:"text-base font-semibold text-heading",children:["Measured at the market level",e.jsx(P,{text:"Competitors are attached to a market rather than to a single Property, because one building has nobody to be compared against. Share of voice and competitor pressure are reported for the market this Property sits in."})]})]}),e.jsx(g,{asChild:!0,type:"button",size:"sm",variant:"outline",children:e.jsx(I,{to:"/projects/$projectName",params:{projectName:s},search:a=>z(H(a),{measurementScope:"project"}),children:"Open measurement overview"})})]}),e.jsx("ul",{className:"flex flex-wrap gap-2",children:t.map(a=>e.jsxs("li",{className:"rounded-md border border-default px-3 py-1.5 text-sm text-strong",children:[a.label,e.jsxs("span",{className:"ml-2 text-xs text-muted",children:[a.competitors.length," ",a.competitors.length===1?"competitor":"competitors"]})]},a.stableKey))})]})}function q({label:s,metric:t,failed:a=!1}){if(t===void 0)return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsx("p",{className:"aeo-hero-row-value text-base font-semibold text-secondary",children:a?"Unavailable":"…"}),a?e.jsx("div",{}):e.jsx("div",{className:"aeo-hero-row-bar","aria-hidden":"true"}),e.jsx("p",{className:"aeo-hero-row-detail",children:a?"Could not be loaded":"Loading"})]});if(t.state==="unavailable"){const l=B(t);return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsx("p",{className:"aeo-hero-row-value text-base font-semibold text-secondary",children:"Not measured"}),e.jsx("div",{}),e.jsx("p",{className:"aeo-hero-row-detail",children:l==="Not measured"?"":l})]})}const{figure:r,sign:i}=Oe(ce(t.value)),y=t.numerator===void 0||t.denominator===void 0?null:`${t.numerator} of ${t.denominator}`,o=me(t);return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsxs("p",{className:"aeo-hero-row-value text-heading",children:[r,i?e.jsx("span",{className:"text-faint",children:i}):null]}),e.jsx("div",{className:"aeo-hero-row-bar","aria-hidden":"true",children:e.jsx("div",{className:"metric-card-bar-fill progress-fill-neutral",style:{width:`${t.value*100}%`}})}),e.jsxs("p",{className:"aeo-hero-row-detail tabular-nums",children:[y??"",o?e.jsx("span",{className:"block",children:o}):null]})]})}function ns({branded:s,nonBrand:t,brandedFailed:a=!1,nonBrandFailed:r=!1}){return e.jsxs("section",{"aria-labelledby":"property-coverage-hero",children:[e.jsx("h2",{id:"property-coverage-hero",className:"sr-only",children:"Coverage for this Property"}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Non-brand · the demand to earn"}),e.jsx(q,{label:"Mentioned",metric:t?.mentionCoverage,failed:r}),e.jsx(q,{label:"Cited",metric:t?.citationCoverage,failed:r})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Branded · already named"}),e.jsx(q,{label:"Mentioned",metric:s?.mentionCoverage,failed:a}),e.jsx(q,{label:"Cited",metric:s?.citationCoverage,failed:a})]})]})]})}function rs({branded:s,nonBrand:t,brandedError:a,nonBrandError:r,onRetry:i}){const y=[{queryClass:"branded",row:s,isError:a},{queryClass:"non-brand",row:t,isError:r}];return e.jsxs("section",{"aria-labelledby":"property-brand-contrast",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"The gap"}),e.jsxs("h2",{id:"property-brand-contrast",className:"text-base font-semibold text-heading",children:["Named versus not named",e.jsx(P,{text:"Branded queries already contain your name, so an answer engine has an easy path back to you. Non-brand queries describe the need instead, and that is the demand you have to earn. Each row is measured only over the queries assigned to this Property in that class; a class with no assigned query reads Not measured rather than 0%."})]})]})}),e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[560px]",children:[e.jsx("caption",{className:"sr-only",children:"Mention and citation coverage for this Property, split by query class"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Query type"}),e.jsx("th",{children:"Mentioned in the answer"}),e.jsx("th",{children:"Cited as a source"})]})}),e.jsx("tbody",{children:y.map(({queryClass:o,row:l,isError:c})=>e.jsxs("tr",{children:[e.jsxs("td",{children:[e.jsx("span",{className:"block font-medium text-heading",children:u[o].headline}),e.jsx("span",{className:"mt-0.5 block text-xs text-muted",children:u[o].technical}),l&&c?e.jsxs("span",{className:"mt-2 flex flex-wrap items-center gap-2 text-sm text-caution",children:[e.jsx("span",{role:"status",children:"Refresh failed."}),e.jsxs(g,{type:"button",size:"sm",variant:"ghost",className:"h-11 px-3 text-sm md:h-11",onClick:()=>i(o),children:["Retry ",u[o].technical.toLocaleLowerCase()]})]}):null]}),l?e.jsxs(e.Fragment,{children:[e.jsx("td",{children:e.jsx(U,{metric:l.mentionCoverage,emphasis:!0})}),e.jsx("td",{children:e.jsx(U,{metric:l.citationCoverage,emphasis:!0})})]}):c?e.jsx("td",{colSpan:2,children:e.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsxs("span",{role:"alert",children:["Could not load ",u[o].technical.toLocaleLowerCase(),"."]}),e.jsxs(g,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>i(o),children:["Retry ",u[o].technical.toLocaleLowerCase()]})]})}):e.jsxs(e.Fragment,{children:[e.jsx("td",{children:e.jsx("span",{className:"text-sm text-secondary",children:"Loading…"})}),e.jsx("td",{children:e.jsx("span",{className:"text-sm text-secondary",children:"Loading…"})})]})]},o))})]})})]})}function is({row:s,queryClass:t,isError:a}){return e.jsxs("section",{"aria-labelledby":"property-providers",className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsx("div",{children:e.jsxs("h2",{id:"property-providers",className:"text-base font-semibold text-heading",children:["Which engines answer for this Property",e.jsx(P,{text:"Each row is measured over the queries that engine actually answered for this Property, so the rows are a split of the same population rather than parts that add up to the Property total. An engine that answered nothing for this Property is absent instead of shown at 0%."})]})})}),s===void 0&&a?e.jsxs("p",{className:"text-sm text-secondary",children:["Details for ",u[t].technical.toLocaleLowerCase()," are unavailable. Retry that query type above."]}):s===void 0?e.jsx("p",{className:"text-sm text-secondary",children:"Loading…"}):s.providers.length===0?e.jsxs("p",{className:"text-sm text-secondary",children:["No answer engine has measured ",u[t].technical.toLocaleLowerCase()," for this Property.",s.mentionCoverage.state==="unavailable"?` ${B(s.mentionCoverage)}.`:""]}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[520px]",children:[e.jsx("caption",{className:"sr-only",children:"Per-engine mention and citation coverage"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Engine"}),e.jsx("th",{children:"Mentioned"}),e.jsx("th",{children:"Cited"})]})}),e.jsx("tbody",{children:s.providers.map(r=>e.jsxs("tr",{children:[e.jsx("td",{className:"font-medium text-heading",children:r.provider}),e.jsx("td",{children:e.jsx(U,{metric:r.mentionCoverage})}),e.jsx("td",{children:e.jsx(U,{metric:r.citationCoverage})})]},r.provider))})]})})]})}function os(s){const t=s.questions.join(" · ");return s.questionsTruncated?`${t} +${s.questionTotal-s.questions.length} more`:t}function ls({project:s,targetKey:t,queryClass:a}){const r=L({...Be({client:M,path:{name:s},query:{targetKey:t,queryClass:a}})});if(r.isPending||r.isError)return null;const i=r.data?.competitors??[],y=r.data?.basis;return e.jsxs("section",{"aria-labelledby":"property-named-instead",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-named-instead",className:"text-base font-semibold text-heading",children:["Named instead of this Property",e.jsx(P,{text:"Counted from the answers that did not name this Property, so a name here is one an engine recommended in its place. Occurrences count answers, not positions: an engine naming the same rival in two answers counts twice, and one naming it twice in a single answer counts once."})]})}),i.length>0?e.jsxs("p",{className:"supporting-copy",children:[r.data?.total??i.length," named"]}):null]}),i.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No rival was named in the answers this Property missed. Nothing to compete with here yet."}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"Names the engines gave instead of this Property"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Named"}),e.jsx("th",{children:"Answers"}),e.jsx("th",{children:"Engines"}),e.jsx("th",{children:"Queries"})]})}),e.jsx("tbody",{children:i.map(o=>e.jsxs("tr",{children:[e.jsx("td",{className:"text-strong",children:o.name}),e.jsx("td",{className:"tabular-nums text-secondary",children:o.occurrences}),e.jsxs("td",{className:"text-secondary",children:[o.providers.join(", "),o.providersTruncated?` +${o.providerTotal-o.providers.length}`:""]}),e.jsx("td",{className:"text-right",children:e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[e.jsx("span",{className:"tabular-nums text-secondary",children:o.questionTotal}),e.jsx(P,{text:os(o)})]})})]},o.name))})]})}),y?.state==="available"?e.jsxs("p",{className:"supporting-copy mt-2",children:[y.targetMissResults," of ",y.answeredResults," answers to ",u[a].technical.toLocaleLowerCase()," did not name this Property."]}):null]})]})}function ds({questions:s,queryClass:t}){return e.jsxs("section",{"aria-labelledby":"property-questions",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-questions",className:"text-base font-semibold text-heading",children:[u[t].technical," assigned to this Property"]})}),e.jsxs("p",{className:"supporting-copy",children:[s.length," assigned"]})]}),s.length===0?e.jsxs("p",{className:"text-sm text-secondary",children:["No ",u[t].technical.toLocaleLowerCase()," are assigned. Add one in advanced measurement setup to measure this class."]}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"Queries assigned to this Property"}),e.jsx("thead",{children:e.jsx("tr",{children:e.jsx("th",{children:"Query"})})}),e.jsx("tbody",{children:s.map(a=>e.jsx("tr",{children:e.jsx("td",{className:"text-secondary",children:a})},a))})]})})]})}function cs({urls:s}){return e.jsxs("section",{"aria-labelledby":"property-urls",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-urls",className:"text-base font-semibold text-heading",children:["URLs that count as this Property",e.jsx(P,{text:"A cited source URL is credited to this Property when it matches one of these. The most specific matcher wins, so a URL covered by two Properties at the same specificity is flagged for review instead of being credited to either."})]})}),e.jsxs("p",{className:"supporting-copy",children:[s.length," configured"]})]}),s.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No URLs are configured for this Property."}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"URL matchers configured for this Property"}),e.jsx("thead",{children:e.jsx("tr",{children:e.jsx("th",{children:"URL"})})}),e.jsx("tbody",{children:s.map(t=>e.jsx("tr",{children:e.jsx("td",{className:"break-all text-secondary",children:t})},t))})]})})]})}function hs(s,t){const a=[s.marketKey?t??"the selected market":null,s.provider?`the ${s.provider} answer engine`:null,s.model?`the ${s.model} model`:null,s.location?`location ${s.location}`:null,s.from||s.to?"a date range":null,s.measurementRunId?"a saved sweep":null].filter(r=>r!==null);return a.length===0?null:`AI Visibility is filtered to ${a.join(", ")}. This page shows the latest measurement for ${Qe.allMarkets.toLocaleLowerCase()} and answer engines.`}function Ns(){const{projectName:s,targetKey:t}=we({strict:!1}),a=Pe(),r=Ce({strict:!1}),i=b.useMemo(()=>Le(r),[r]),[y,o]=b.useState(new Set),l=s??"",c=t??"",D=!!l&&!!c,W=b.useMemo(()=>Me("portfolio",Se(Ae())),[]),S=D&&W,{canWrite:_}=Re(),C=L({...Te({client:M,path:{name:l}}),enabled:S}),G=C.data?.active?.plan,$=G?.schemaVersion===2?G:null,O=i.queryClass==="branded"||i.queryClass==="non-brand"?i.queryClass:null,Y=n=>$?.assignments.some(d=>d.targetKey===c&&d.queryClass===n)??!1,h=O??(!Y("non-brand")&&Y("branded")?"branded":"non-brand");b.useEffect(()=>{!$||O||a({to:".",replace:!0,search:n=>z(n,{queryClass:h})})},[$,O,h,a]);const v=L({...le(l,c,"branded"),enabled:S}),f=L({...le(l,c,"non-brand"),enabled:S}),V=de(v.data),K=de(f.data),j=h==="branded"?v.data:f.data,E=h==="branded"?V:K,Z=(h==="branded"?v.isError:f.isError)&&E===void 0,A=j?.measurement.displayedRunId;ke(l,{view:"property",selection:{mode:"advanced",scope:"property",scopeKey:c,queryClass:h,limit:10,...A?{runId:A}:{}}});const xe=E?.mentionCoverage.state==="unavailable"?B(E.mentionCoverage):void 0,ue=j===void 0?void 0:j.measurement.completedAt??null,R={client:M,path:{name:l},query:{targetKey:c,queryClass:h,shape:qe.answers,limit:re,...A?{runId:A}:{}}},m=Ee({...Ie(R),enabled:S&&j!==void 0,initialPageParam:R,getNextPageParam:n=>n.answers?.nextCursor?{path:R.path,query:{...R.query,cursor:n.answers.nextCursor}}:void 0}),Q=C.data?.active??null,p=Q?.plan.schemaVersion===2?Q.plan:null,pe=Q!==null&&p===null,T=p?.targets.find(n=>n.stableKey===c)??null,J=hs(i,p?.reportingScopes?.find(n=>n.stableKey===i.marketKey)?.label),je=b.useMemo(()=>{if(!p)return[];const n=new Map(p.querySnapshots.map(d=>[d.queryId,d.queryText]));return[...new Set(p.assignments.filter(d=>d.targetKey===c&&d.queryClass===h).flatMap(d=>{const x=n.get(d.queryId);return x===void 0?[]:[x]}))].sort((d,x)=>d.localeCompare(x))},[p,c,h]),ye=b.useMemo(()=>T?.urlMatchers.map(Ve)??[],[T]),be=b.useMemo(()=>p?.groups.filter(n=>n.targetKeys.includes(c))??[],[p,c]),N=b.useMemo(()=>(m.data?.pages??[]).flatMap(x=>x.answers?.items??[]),[m.data]),X=m.data?.pages[0]?.answers?.totalEstimate??N.length,ge=(m.data?.pages??[]).length>0&&(m.data?.pages??[]).every(n=>n.answers===void 0),ve=m.data?.pages[0]?.measurement.state,k=e.jsxs(I,{to:"/projects/$projectName",params:{projectName:l},search:H,className:"inline-flex items-center gap-1 text-xs text-muted hover:text-strong",children:[e.jsx(De,{className:"size-3.5","aria-hidden":"true"}),"Back to AI Visibility"]});if(!D)return e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-muted",children:"Missing project name or Property key in URL."})});if(!W)return e.jsxs("div",{className:"page-container space-y-3",children:[k,e.jsx("p",{className:"text-sm text-muted",children:"This view is not available here."})]});if(C.isPending&&!C.isError)return e.jsx("div",{className:"page-container",children:e.jsxs("div",{role:"status","aria-live":"polite",children:[e.jsx("span",{className:"sr-only",children:"Loading Property"}),e.jsx("div",{className:"h-32 animate-pulse rounded-md bg-surface-subtle","aria-hidden":"true"})]})});const fe=C.isError&&C.data===void 0,ee=v.isError&&v.data===void 0,se=f.isError&&f.data===void 0;return fe||ee&&se?e.jsxs("div",{className:"page-container space-y-3",children:[k,e.jsx("p",{role:"alert",className:"text-sm text-negative",children:"Could not load this Property."}),e.jsx(g,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>{C.refetch(),v.refetch(),f.refetch()},children:"Try again"})]}):!p||!T?e.jsxs("div",{className:"page-container space-y-3",children:[k,e.jsx("p",{role:"status",className:"text-sm text-secondary",children:p?"This Property is not in the published setup. It may have been renamed or removed.":"A Property page needs a published advanced measurement setup. Republish setup from the project Portfolio tab."}),e.jsx(g,{asChild:!0,type:"button",variant:"outline",className:"h-11 px-4 text-sm md:h-11",children:e.jsx(I,{to:"/projects/$projectName/portfolio",params:{projectName:l},children:_?pe?"Republish setup":"Open measurement setup":"View measurement setup"})})]}):e.jsxs("div",{className:"page-container space-y-8",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[k,e.jsx("h1",{className:"page-title mt-2",children:T.label}),e.jsxs("p",{className:"page-subtitle",children:["Property in ",l,J?e.jsxs(e.Fragment,{children:[" · Filters not applied ",e.jsx(P,{text:J})]}):null]})]}),e.jsxs("div",{className:"page-header-right",children:[j?e.jsx(w,{tone:ie[j.measurement.state].tone,children:ie[j.measurement.state].label}):null,E&&E.flags>0?e.jsxs(w,{tone:"caution",children:[E.flags," flagged"]}):null]})]}),e.jsx(ns,{branded:V,nonBrand:K,brandedFailed:ee,nonBrandFailed:se}),e.jsx(rs,{branded:V,nonBrand:K,brandedError:v.isError,nonBrandError:f.isError,onRetry:n=>{n==="branded"?v.refetch():f.refetch()}}),j&&(j.measurement.state==="not_measured"||j.nextAction.kind==="run_measurement")?e.jsxs("section",{className:"flex flex-wrap items-center justify-between gap-3 border-y border-default py-4","aria-label":"Measurement next step",children:[e.jsx("p",{className:"text-sm text-secondary",children:ne()?ae:_?"Run a measurement from the project overview to collect this Property’s coverage and source evidence.":"This Property needs a new measurement before coverage and source evidence are available."}),e.jsx(g,{asChild:!0,type:"button",className:"h-11 px-4 text-sm md:h-11",children:e.jsx(I,{to:"/projects/$projectName",params:{projectName:l},search:H,children:_?"Go to AI Visibility":"View AI Visibility"})})]}):null,e.jsxs("div",{className:"flex flex-wrap items-end gap-4 border-y border-default py-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{htmlFor:"property-query-class",className:"block text-sm font-medium text-heading",children:"Query type"}),e.jsxs("select",{id:"property-query-class",value:h,onChange:n=>{const d=n.target.value==="branded"?"branded":"non-brand";a({to:".",search:x=>z(x,{queryClass:d})})},className:"h-11 rounded-md border border-default bg-surface px-3 text-sm text-primary focus:outline-none focus:ring-2 focus:ring-mono-400",children:[e.jsx("option",{value:"non-brand",children:u["non-brand"].technical}),e.jsx("option",{value:"branded",children:u.branded.technical})]})]}),e.jsx(ts,{measuredAt:ue,unmeasuredReason:xe,queryClass:h})]}),e.jsx(is,{row:E,queryClass:h,isError:Z}),e.jsx(ls,{project:l,targetKey:c,queryClass:h}),e.jsx(ds,{questions:je,queryClass:h}),e.jsx(cs,{urls:ye}),e.jsx(as,{project:l,groups:be}),e.jsxs("section",{"aria-labelledby":"property-evidence",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-evidence",className:"text-base font-semibold text-heading",children:["Answers the engines gave",e.jsx(P,{text:"One row per answer an engine gave for the queries assigned to this Property in the displayed measurement. Mentioned and cited are independent: an answer can name this Property without linking it, or link it without naming it. Answers that did neither are listed first, because those are what a gap is made of. Where the answer text was not stored the mention reads Not measured, never a zero. Open a row to read what the engine actually said, followed by the source URLs it returned, this Property's own first. The answer is where a miss becomes actionable: a query this Property was not named in may still have named a sibling building."})]})}),N.length>0?e.jsxs("p",{className:"supporting-copy",children:[N.length," of ",X]}):null]}),Z?e.jsxs("p",{className:"text-sm text-secondary",children:["Evidence for ",u[h].technical.toLocaleLowerCase()," is unavailable. Retry that query type above."]}):m.isPending&&N.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"Loading evidence…"}):m.isError&&N.length===0?e.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsx("span",{role:"alert",children:"Evidence could not be loaded."}),e.jsx(g,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>{m.refetch()},children:"Retry evidence"})]}):ve==="not_measured"?e.jsx("p",{className:"text-sm text-secondary",children:ne()?ae:"Not measured yet. Run a measurement to collect the answers for this Property."}):ge?e.jsx("p",{role:"alert",className:"text-sm text-caution",children:"This measurement was returned in an older format, so the answers cannot be shown here. The numbers above are unaffected."}):N.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No answers matched this Property in the displayed measurement."}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"property-answer-table-container overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table property-evidence-table",children:[e.jsx("caption",{className:"sr-only",children:"Answers measured for this Property"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Query"}),e.jsx("th",{children:"Mentioned in the answer"}),e.jsx("th",{children:"Cited as a source"}),e.jsx("th",{children:"Sources"}),e.jsx("th",{children:e.jsx("span",{className:"sr-only",children:"Sources detail"})})]})}),e.jsx("tbody",{children:N.map(n=>{const d=Ye(n),x=y.has(d),te=`property-answer-${encodeURIComponent(d)}`;return e.jsxs(b.Fragment,{children:[e.jsxs("tr",{className:"property-answer-summary",children:[e.jsxs("td",{className:"text-secondary",children:[e.jsx("h3",{className:"text-sm font-medium text-heading",children:n.queryText}),e.jsx("span",{className:"mt-1 block text-xs text-muted",children:[n.provider,n.location].filter(Boolean).join(" · ")}),n.historical||n.bridged?e.jsx("span",{className:"mt-1 flex",children:e.jsx(w,{tone:"caution",children:"Historical"})}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"property-evidence-mobile-label","aria-hidden":"true",children:"Mentioned"}),e.jsx(Ze,{row:n})]}),e.jsxs("td",{children:[e.jsx("span",{className:"property-evidence-mobile-label","aria-hidden":"true",children:"Cited"}),e.jsx(Xe,{row:n})]}),e.jsxs("td",{className:"tabular-nums text-secondary",children:[e.jsx("span",{className:"property-evidence-mobile-label","aria-hidden":"true",children:he}),n.cited===null?Je:n.sources.length]}),e.jsx("td",{className:"text-right",children:e.jsx(g,{type:"button",size:"sm",variant:"ghost",className:"h-auto min-h-11 max-w-full whitespace-normal py-2 text-left md:h-auto","aria-expanded":x,"aria-label":x?`Hide the answer for ${n.queryText}`:`Read the answer for ${n.queryText}`,"aria-controls":x?te:void 0,onClick:()=>o(Ne=>{const F=new Set(Ne);return F.delete(d)||F.add(d),F}),children:x?"Hide answer":"Read answer"})})]}),x?e.jsx("tr",{className:"property-answer-detail",children:e.jsxs("td",{id:te,colSpan:5,className:"bg-surface-subtle px-4",children:[e.jsx(ss,{project:l,targetKey:c,row:n}),e.jsx(es,{row:n})]})}):null]},d)})})]})}),m.hasNextPage?e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsxs("span",{children:["Showing ",N.length," of ",X]}),m.isFetchNextPageError?e.jsx("span",{role:"alert",children:"Could not load more evidence."}):null,e.jsx(g,{size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",disabled:m.isFetchingNextPage,onClick:()=>{m.fetchNextPage()},children:m.isFetchingNextPage?"Loading…":m.isFetchNextPageError?"Retry more evidence":`Show ${re} more`})]}):null]})]})]})}export{oe as EVIDENCE_LABELS,Ns as MeasurementPropertyPage};