@canonry/canonry 4.171.0 → 4.172.1
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.
- package/README.md +2 -1
- package/assets/agent-workspace/skills/canonry/SKILL.md +17 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +119 -1
- package/assets/agent-workspace/skills/canonry/references/google-marketing.md +141 -0
- package/assets/assets/{AuditHistoryPanel-BpIn6E7p.js → AuditHistoryPanel-BO_TSFPx.js} +1 -1
- package/assets/assets/{BacklinksPage-BvOWgx1l.js → BacklinksPage-CNrzKbiR.js} +1 -1
- package/assets/assets/{HistoryPage-BXaQyur8.js → HistoryPage-CvzLzfj3.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-Buj708lc.js → MeasurementPropertyPage-CUdAqft3.js} +1 -1
- package/assets/assets/ProjectPage-xbRWsne7.js +9 -0
- package/assets/assets/{RunRow-C9hp5yEp.js → RunRow-BLZsQTiY.js} +1 -1
- package/assets/assets/{RunsPage-DmkRH1iu.js → RunsPage-CTuTDUcn.js} +1 -1
- package/assets/assets/SettingsPage-BY-nM5uj.js +1 -0
- package/assets/assets/{SiteHealthSection-ZlYR3JtG.js → SiteHealthSection-br9-ha8G.js} +3 -3
- package/assets/assets/{TrafficPage-PxIetTWH.js → TrafficPage-BgX1QlXy.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BhF_leby.js → TrafficSourceDetailPage-D5bsBc6A.js} +1 -1
- package/assets/assets/extract-error-message-BywQFP69.js +1 -0
- package/assets/assets/{index-DyjfMyum.js → index-BpHZirJJ.js} +29 -29
- package/assets/assets/index-BsQGKWA6.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-CsNemdas.js → react-sigma_core.esm.min-Do4rkEBs.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-O226BLC7.js → chunk-3XN226N6.js} +62 -50
- package/dist/{chunk-IIFDS3K2.js → chunk-5MRE2TXR.js} +1 -1
- package/dist/{chunk-RR4KSDOD.js → chunk-7L4453BU.js} +2 -2
- package/dist/{chunk-5KGN224W.js → chunk-IAX4WRAR.js} +4 -4
- package/dist/{chunk-HJ7W2LXI.js → chunk-X63CLHYM.js} +11 -0
- package/dist/cli.js +41 -18
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-KT6K4ODY.js → intelligence-service-FSHZXSGK.js} +2 -2
- package/dist/mcp.js +3 -3
- package/package.json +10 -10
- package/assets/assets/ProjectPage-BwC29w2_.js +0 -9
- package/assets/assets/SettingsPage-BIyuj-gL.js +0 -1
- package/assets/assets/extract-error-message-Dd2su-BK.js +0 -1
- package/assets/assets/index-CHluSPlN.css +0 -1
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ Canonry gives operators and agents a full tactical view across AI visibility, si
|
|
|
24
24
|
- **Answer engines:** Canonry measures Gemini, ChatGPT, Claude, Perplexity, and OpenAI-compatible local models.
|
|
25
25
|
- **Agent workflows:** Use the [MCP adapter](docs/mcp.md), [Agent Plugin](docs/plugins.md), [external webhooks](skills/canonry/references/canonry-cli.md#agent), or built-in [Aero](skills/aero/SKILL.md).
|
|
26
26
|
- **Search and analytics:** Connect [Google Search Console](docs/google-search-console-setup.md), [Google Analytics 4](docs/google-analytics-setup.md), and [Bing Webmaster Tools](docs/bing-webmaster-setup.md).
|
|
27
|
+
- **Conversion measurement:** Audit [Google Ads and Google Tag Manager](docs/google-marketing.md) with read-only snapshots and declared conversion contracts.
|
|
27
28
|
- **Local presence:** Connect [Google Business Profile](skills/canonry/references/google-business-profile.md) for search terms, performance, lodging data, and booking actions.
|
|
28
29
|
- **Server traffic:** Capture events from [Cloudflare, Cloud Run, Vercel, and WordPress](skills/canonry/references/server-side-traffic.md).
|
|
29
30
|
- **Publishing and indexing:** Publish through [WordPress](docs/wordpress-setup.md), generate JSON-LD, and [submit sitemaps or URLs for indexing](skills/canonry/references/indexing.md).
|
|
@@ -175,7 +176,7 @@ See the [deployment guide](docs/deployment.md) for reverse proxies, daemon mode,
|
|
|
175
176
|
| **Aero — built-in agent** | [skills/aero/SKILL.md](skills/aero/SKILL.md) |
|
|
176
177
|
| **Agent Plugin — portable core + Codex / Claude adapters** | [docs/plugins.md](docs/plugins.md) |
|
|
177
178
|
| **MCP — Claude Desktop / Cursor / Codex** | [docs/mcp.md](docs/mcp.md) |
|
|
178
|
-
| **Integrations** | [GSC](docs/google-search-console-setup.md) · [GA4](docs/google-analytics-setup.md) · [Bing](docs/bing-webmaster-setup.md) · [Google Business Profile](skills/canonry/references/google-business-profile.md) · [WordPress](docs/wordpress-setup.md) · [Server-side traffic (Cloudflare direct push or Queue pull, Cloud Run, Vercel, WordPress)](skills/canonry/references/server-side-traffic.md) |
|
|
179
|
+
| **Integrations** | [GSC](docs/google-search-console-setup.md) · [GA4](docs/google-analytics-setup.md) · [Google Ads + GTM](docs/google-marketing.md) · [Bing](docs/bing-webmaster-setup.md) · [Google Business Profile](skills/canonry/references/google-business-profile.md) · [WordPress](docs/wordpress-setup.md) · [Server-side traffic (Cloudflare direct push or Queue pull, Cloud Run, Vercel, WordPress)](skills/canonry/references/server-side-traffic.md) |
|
|
179
180
|
| **Deployment** — reverse proxies, Docker, systemd, Tailscale | [docs/deployment.md](docs/deployment.md) |
|
|
180
181
|
| **API** | `GET /api/v1/openapi.json` |
|
|
181
182
|
| **Standalone skills bundle** for Claude Code / Codex | `cnry skills install` ([details](skills/canonry/SKILL.md)) |
|
|
@@ -67,6 +67,7 @@ tool or `403` by switching credentials.
|
|
|
67
67
|
- Implementing structured data (JSON‑LD)
|
|
68
68
|
- Diagnosing indexing gaps via Google Search Console / Bing Webmaster Tools
|
|
69
69
|
- Wiring server-side traffic (Cloudflare, Cloud Run, WordPress, Vercel) and GA4 referrals into a single AEO signal
|
|
70
|
+
- Inspecting Google Ads conversion goals and GTM live configuration as read-only conversion evidence
|
|
70
71
|
- Optimizing `llms.txt`, sitemaps, robots.txt for AI crawlers
|
|
71
72
|
- Submitting URLs to Google Indexing API and Bing IndexNow
|
|
72
73
|
- Analyzing competitor citation patterns
|
|
@@ -127,6 +128,17 @@ cnry get <project> --from report scores.citationCoverage.value
|
|
|
127
128
|
|
|
128
129
|
GA4 is a first-class signal alongside citation tracking. Connect once with `cnry ga connect <project> --property-id <id> --key-file <path>`; `cnry ga sync` then pulls daily landing-page traffic, AI-referral sessions across 10 known providers (chatgpt, perplexity, claude, gemini, openai, anthropic, copilot, phind, you.com, meta.ai), and social referrals split into Organic vs Paid via GA4's `channelGroup` — and persists everything into four DB tables (`gaTrafficSnapshots`, `gaAiReferrals`, `gaSocialReferrals`, `gaTrafficSummaries`). All read commands query that local store, so they are fast and quotaless once a sync has run. AI referrals are tracked across three GA4 attribution dimensions (session source / first-user source / manual UTM) and joined to landing pages, so you can see which page each AI provider sent traffic to. Use `cnry ga traffic` for the current snapshot, `cnry ga attribution --trend` for a unified channel-share overview with biggest-mover deltas, and `cnry ga ai-referral-history` / `cnry ga social-referral-history` for daily series. See `references/canonry-cli.md` for the full command catalog and return-shape details.
|
|
129
130
|
|
|
131
|
+
## Google Ads and GTM
|
|
132
|
+
|
|
133
|
+
Google Ads and GTM are separate, project-scoped sources for one conversion
|
|
134
|
+
contract. Provider snapshots are static configuration evidence. They do not
|
|
135
|
+
prove browser events, tag firing, or recorded conversions. Stored snapshots are
|
|
136
|
+
the safe default. A bounded live read needs `google-marketing.read-live`. Version
|
|
137
|
+
1 does not mutate Google Ads or edit, version, or publish GTM. Read
|
|
138
|
+
`references/google-marketing.md` before assessing or refreshing this evidence.
|
|
139
|
+
The operator must complete OAuth, resource selection, and contract creation.
|
|
140
|
+
Never request OAuth credentials or a Google Ads developer token.
|
|
141
|
+
|
|
130
142
|
## Server-Side Traffic
|
|
131
143
|
|
|
132
144
|
When the project ships behind a server you control, connect Cloud Run, WordPress,
|
|
@@ -170,12 +182,15 @@ Aero also wakes unprompted after every `run.completed` so insights and regressio
|
|
|
170
182
|
|
|
171
183
|
## Boundaries & Safety
|
|
172
184
|
|
|
173
|
-
- **Get explicit approval before every mutation or quota-consuming sweep
|
|
185
|
+
- **Get explicit approval before every mutation or quota-consuming sweep.** Also get approval before each live provider read. Stored reads and `--dry-run` previews are safe defaults.
|
|
174
186
|
- **Never touch live WordPress without explicit approval**
|
|
175
187
|
- **Back up `~/.canonry/config.yaml` before any config edit**
|
|
176
188
|
- **Never fabricate mention or citation data** — if a sweep hasn't run, say so; never coerce `answerMentioned` null → false (null = "not checked")
|
|
177
189
|
- **Client data stays private** — canonry repo is public; no real domains in issues
|
|
178
190
|
- **Respect API rate limits** — batch operations, avoid tight loops
|
|
191
|
+
- **Keep Google marketing read-only in v1** — stored evidence is the default.
|
|
192
|
+
`google-marketing.read-live` permits bounded provider reads only. It grants no
|
|
193
|
+
Google Ads mutation or GTM edit or publish authority.
|
|
179
194
|
|
|
180
195
|
## References
|
|
181
196
|
|
|
@@ -187,6 +202,7 @@ Aero also wakes unprompted after every `run.completed` so insights and regressio
|
|
|
187
202
|
| `references/wordpress-integration.md` | Connecting to WordPress, editing pages, pushing staging → live |
|
|
188
203
|
| `references/server-side-traffic.md` | Wiring server-side evidence from Cloudflare, Cloud Run, WordPress, and Vercel. Connect, inspect, sync pull sources, and troubleshoot. |
|
|
189
204
|
| `references/google-business-profile.md` | Connecting Google Business Profile for local AEO: access-form approval, GCP API enablement, the v4-reviews access gate, hotel lodging/place-action signals, data shapes, troubleshooting. |
|
|
205
|
+
| `references/google-marketing.md` | Inspecting first-class Google Ads and GTM evidence. Covers the conversion-integrity chain, static and runtime truth, live-read scope, v1 read-only boundary, and future write approvals. |
|
|
190
206
|
|
|
191
207
|
---
|
|
192
208
|
|
|
@@ -746,6 +746,124 @@ cnry gbp summary <project> [--location locations/{n}]
|
|
|
746
746
|
|
|
747
747
|
`gbp sync` produces a run with the standard statuses (`completed` / `partial` / `failed`); `partial` means some selected locations synced and others errored (the per-location errors are on the run). Non-lodging locations are skipped cleanly (Google answers the lodging call with HTTP 400, not 404). Reviews are **not** synced — the v4 Reviews API is producer-restricted by Google and unavailable on most projects; the Q&A API was retired (2025-11-03).
|
|
748
748
|
|
|
749
|
+
## Google Ads + Google Tag Manager conversion integrity
|
|
750
|
+
|
|
751
|
+
`google-ads` and `gtm` are separate first-class namespaces. **`ads` remains
|
|
752
|
+
OpenAI / ChatGPT Ads**; do not use it for Google Ads. Canonry v1 only reads
|
|
753
|
+
provider state: it never changes a Google Ads campaign/conversion action/goal,
|
|
754
|
+
and it never edits or publishes GTM.
|
|
755
|
+
|
|
756
|
+
```bash
|
|
757
|
+
# OAuth is an explicit same-browser operator flow. Start and confirm it from
|
|
758
|
+
# the project's Conversion Integrity dashboard; the CLI does not print OAuth URLs.
|
|
759
|
+
|
|
760
|
+
cnry google-ads status <project> # local connection + selection/freshness only
|
|
761
|
+
cnry google-ads customers <project> --format jsonl
|
|
762
|
+
cnry google-ads select <project> --customer <customer-id> [--login-customer <manager-id>]
|
|
763
|
+
cnry google-ads sync <project> # bounded read-only queries (GET + SearchStream POST) + redacted snapshots
|
|
764
|
+
cnry google-ads snapshots <project> --format jsonl
|
|
765
|
+
cnry google-ads snapshot <project> <snapshot-id>
|
|
766
|
+
cnry google-ads disconnect <project> # removes private credential; retains redacted evidence
|
|
767
|
+
|
|
768
|
+
cnry gtm status <project> # local connection + selection/freshness only
|
|
769
|
+
cnry gtm accounts <project> --format jsonl
|
|
770
|
+
cnry gtm containers <project> --account <account-id> --format jsonl
|
|
771
|
+
cnry gtm workspaces <project> --account <account-id> --container <container-id> --format jsonl
|
|
772
|
+
cnry gtm select <project> --account <account-id> --container <container-id> [--workspace <workspace-id>]
|
|
773
|
+
cnry gtm sync <project> # bounded provider GETs + redacted live/draft snapshots
|
|
774
|
+
cnry gtm snapshots <project> --format jsonl
|
|
775
|
+
cnry gtm snapshot <project> <snapshot-id>
|
|
776
|
+
cnry gtm disconnect <project> # removes private credential; retains redacted evidence
|
|
777
|
+
|
|
778
|
+
# One contract joins business meaning to the selected Google Ads action and GTM graph.
|
|
779
|
+
cnry conversion-tracking contracts <project> --format jsonl
|
|
780
|
+
cnry conversion-tracking contracts get <project> <contract-id>
|
|
781
|
+
cnry conversion-tracking contracts create <project> --input contract.json
|
|
782
|
+
cnry conversion-tracking contracts update <project> <contract-id> --input contract.json
|
|
783
|
+
cnry conversion-tracking contracts delete <project> <contract-id>
|
|
784
|
+
cnry conversion-tracking contracts integrity <project> <contract-id> --format jsonl
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Use this purchase contract as `contract.json`:
|
|
788
|
+
|
|
789
|
+
```json
|
|
790
|
+
{
|
|
791
|
+
"name": "Purchase completed",
|
|
792
|
+
"eventName": "purchase",
|
|
793
|
+
"googleAds": {
|
|
794
|
+
"customerId": "1234567890",
|
|
795
|
+
"conversionActionId": "987654321",
|
|
796
|
+
"conversionId": "AW-1234567890",
|
|
797
|
+
"conversionLabel": "purchase_label",
|
|
798
|
+
"campaignIds": [],
|
|
799
|
+
"requireBiddableGoal": true,
|
|
800
|
+
"requirePrimaryAction": true
|
|
801
|
+
},
|
|
802
|
+
"gtm": {
|
|
803
|
+
"accountId": "123456",
|
|
804
|
+
"containerId": "654321",
|
|
805
|
+
"tagId": "42",
|
|
806
|
+
"triggerIds": ["17"],
|
|
807
|
+
"variableIds": ["21", "22", "23"]
|
|
808
|
+
},
|
|
809
|
+
"runtime": {
|
|
810
|
+
"verificationRequired": true,
|
|
811
|
+
"requireTransactionId": true,
|
|
812
|
+
"requireValue": true,
|
|
813
|
+
"requireCurrency": true,
|
|
814
|
+
"productionHosts": ["example.com"]
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Replace every example ID with a canonical ID from the current snapshots. Use
|
|
820
|
+
the Ads customer ID without dashes, the conversion-action `id`, and each GTM
|
|
821
|
+
resource `id`. Do not use GTM resource paths or the public `GTM-...` container
|
|
822
|
+
ID.
|
|
823
|
+
|
|
824
|
+
Empty campaign, trigger, or variable arrays disable their corresponding
|
|
825
|
+
assertions. Canonry checks `requireBiddableGoal` only for listed campaigns. If
|
|
826
|
+
you do not know the exact GTM-facing values, omit `conversionId` and
|
|
827
|
+
`conversionLabel`. Do not supply server-owned IDs or timestamps.
|
|
828
|
+
|
|
829
|
+
Use the
|
|
830
|
+
[Google Marketing setup guide](https://github.com/Canonry/canonry/blob/main/docs/google-marketing.md)
|
|
831
|
+
for the operator workflow.
|
|
832
|
+
|
|
833
|
+
Stored snapshot and contract reads are local, redacted, and quota-free. Customer,
|
|
834
|
+
account, container, and workspace discovery plus sync are live Google reads; the
|
|
835
|
+
caller needs `google-marketing.read-live` in addition to normal write authority
|
|
836
|
+
for a sync. `google-ads sync` captures conversion actions and the **effective
|
|
837
|
+
per-campaign goal graph**; `gtm sync` captures sanitized live and selected-draft
|
|
838
|
+
configuration graphs. They do not prove a browser event fired or that Google Ads
|
|
839
|
+
recorded a conversion.
|
|
840
|
+
|
|
841
|
+
The integrity result is intentionally monotonic:
|
|
842
|
+
|
|
843
|
+
- `configured` means a contract exists but its static graph is unproven or inconsistent.
|
|
844
|
+
- `statically-consistent` means the stored Ads and GTM evidence agrees.
|
|
845
|
+
- `runtime-unverified` means the static graph agrees but runtime proof is missing.
|
|
846
|
+
- `observed` means static and trusted runtime evidence are both present.
|
|
847
|
+
|
|
848
|
+
The default Canonry runtime does not store runtime evidence in version 1. A
|
|
849
|
+
runtime-required contract therefore stops at `runtime-unverified`.
|
|
850
|
+
|
|
851
|
+
A selected GTM draft workspace produces stored draft evidence. Integrity uses
|
|
852
|
+
the live container graph and does not assess the draft graph.
|
|
853
|
+
|
|
854
|
+
Treat unrecognized GTM custom HTML/templates as `unknown` / needs-review, never
|
|
855
|
+
as a pass. An Ads action's `primaryForGoal` flag is not proof that a campaign
|
|
856
|
+
effectively bids toward it; inspect the effective campaign goal evidence.
|
|
857
|
+
|
|
858
|
+
`--format jsonl` streams one record per line for Google Ads customers and
|
|
859
|
+
snapshots, GTM accounts/containers/workspaces and snapshots, and
|
|
860
|
+
conversion-tracking contracts. Each line is stamped with `project`; live
|
|
861
|
+
discovery rows also include `fetchedAt`. Integrity JSONL streams deterministic
|
|
862
|
+
findings, each stamped with `project`, `contractId`, `integrityStatus`, and
|
|
863
|
+
`evaluatedAt`. Status, sync, one-snapshot, one-contract, connect/select, and
|
|
864
|
+
disconnect commands emit their normal JSON document when passed `--format
|
|
865
|
+
jsonl`.
|
|
866
|
+
|
|
749
867
|
## OpenAI ads (ChatGPT ads)
|
|
750
868
|
|
|
751
869
|
Paid-surface data for the project's connected OpenAI ad account. Ads render only in the ChatGPT consumer UI (never in API answers), so the Advertiser API is the only window into the paid layer. Money is integer micros in all stored/JSON data; insights `ctr`/`cpcMicros` are derived server-side and `null` on zero denominators. Paid metrics are "paid"/"sponsored" — never conflate with organic `cited`/`mentioned`.
|
|
@@ -1092,7 +1210,7 @@ Every command takes `--format`:
|
|
|
1092
1210
|
- **`json`** — one pretty-printed JSON document (the full envelope). Stable contract.
|
|
1093
1211
|
- **`jsonl`** — newline-delimited JSON: the command's **primary collection**, one self-contained record per line. The agent-friendly machine format — no envelope key to guess (`.checks` vs `.results` vs `.rows`), no `jq` flattening, greppable line by line.
|
|
1094
1212
|
|
|
1095
|
-
`jsonl` is supported by every **collection** command — one whose primary output is a list: `insights`, `runs`, `evidence`, `history`, `query/keyword/competitor list`, `notify list/events`, `google` reads (`performance`, `performance-daily`, `inspections`, `coverage-history`, `deindexed`, `status`, `properties`, `list-sitemaps`), `bing` reads (`coverage-history`, `inspections`, `performance`, `sites`), `ga` reads (`ai-referral-daily`, `ai-referral-history`, `social-referral-history`, `session-history`, `coverage`), `ads geo search` and `ads conversions` reads, `traffic events/sources/status`, `discover list/show`, `content targets/sources/gaps/map`, `backlinks list/releases`, `project list/locations`, `key list`, `agent memory list`, `agent providers`, `sources` (streams the ranked cited-domain list), and `doctor`. (`content brief` is an object command — `jsonl` degrades to its JSON document.)
|
|
1213
|
+
`jsonl` is supported by every **collection** command — one whose primary output is a list: `insights`, `runs`, `evidence`, `history`, `query/keyword/competitor list`, `notify list/events`, `google` reads (`performance`, `performance-daily`, `inspections`, `coverage-history`, `deindexed`, `status`, `properties`, `list-sitemaps`), `bing` reads (`coverage-history`, `inspections`, `performance`, `sites`), `ga` reads (`ai-referral-daily`, `ai-referral-history`, `social-referral-history`, `session-history`, `coverage`), `google-ads` customer/snapshot reads, `gtm` account/container/workspace/snapshot reads, conversion-tracking contracts and integrity findings, `ads geo search` and `ads conversions` reads, `traffic events/sources/status`, `discover list/show`, `content targets/sources/gaps/map`, `backlinks list/releases`, `project list/locations`, `key list`, `agent memory list`, `agent providers`, `sources` (streams the ranked cited-domain list), and `doctor`. (`content brief` is an object command — `jsonl` degrades to its JSON document.)
|
|
1096
1214
|
|
|
1097
1215
|
Each `jsonl` line re-injects the envelope context it would otherwise lose, so a line lifted out still self-describes:
|
|
1098
1216
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Google Ads and Google Tag Manager
|
|
2
|
+
|
|
3
|
+
Use this reference to inspect Google Ads and Google Tag Manager (GTM) as one
|
|
4
|
+
conversion-tracking system.
|
|
5
|
+
|
|
6
|
+
## Scope
|
|
7
|
+
|
|
8
|
+
Canonry treats `google-ads` and `gtm` as separate, project-scoped providers.
|
|
9
|
+
`ads` remains the OpenAI/ChatGPT Ads namespace. Version 1 reads provider data
|
|
10
|
+
and assesses evidence. It does not mutate a Google provider.
|
|
11
|
+
|
|
12
|
+
## Shared integrity chain
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
declared conversion contract
|
|
16
|
+
|
|
|
17
|
+
+--> Google Ads action -> effective campaign goals -> bounded metrics
|
|
18
|
+
|
|
|
19
|
+
+--> GTM live tag -> triggers + variables
|
|
20
|
+
\ /
|
|
21
|
+
\ /
|
|
22
|
+
static assessment
|
|
23
|
+
|
|
24
|
+
website event -> GTM firing -> Google Ads conversion receipt
|
|
25
|
+
|
|
|
26
|
+
runtime observation
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The contract names the application event, Google Ads conversion action, campaign
|
|
30
|
+
goals, GTM tag, triggers, variables, and required runtime evidence.
|
|
31
|
+
An action marked primary is not necessarily an effective, biddable campaign goal.
|
|
32
|
+
|
|
33
|
+
| Evidence | It establishes | It does not establish |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| Contract plus Ads and GTM snapshots | Whether static configuration agrees with the declared conversion | A browser event occurred, a tag fired, or Google Ads recorded a conversion |
|
|
36
|
+
| Runtime observation | Required event, GTM, and Ads evidence was observed | A mismatched static configuration is correct |
|
|
37
|
+
|
|
38
|
+
An observed runtime signal never repairs an unproven static graph. Opaque custom
|
|
39
|
+
HTML and templates stay `unknown` or `needs-review`.
|
|
40
|
+
|
|
41
|
+
## Integrity status
|
|
42
|
+
|
|
43
|
+
- `configured`: A contract exists, but the static graph is unproved or inconsistent.
|
|
44
|
+
- `statically-consistent`: Static checks pass and runtime verification is not required.
|
|
45
|
+
- `runtime-unverified`: Static checks pass, but required runtime evidence is absent.
|
|
46
|
+
- `observed`: Static checks pass and trusted runtime evidence is present.
|
|
47
|
+
|
|
48
|
+
Inspect findings with the status. `configured` is not a pass state.
|
|
49
|
+
|
|
50
|
+
The default Canonry runtime does not store runtime observations in version 1.
|
|
51
|
+
A runtime-required contract therefore stops at `runtime-unverified`.
|
|
52
|
+
|
|
53
|
+
## Operator setup
|
|
54
|
+
|
|
55
|
+
The operator completes setup in the dashboard:
|
|
56
|
+
|
|
57
|
+
1. Configure the Web application OAuth client in **Settings → Google OAuth**.
|
|
58
|
+
2. Register the exact Google Ads and Tag Manager redirect URI.
|
|
59
|
+
3. Open **Project → Conversions**.
|
|
60
|
+
4. Connect Google Ads and enter the developer token.
|
|
61
|
+
5. Select the Ads manager and customer context.
|
|
62
|
+
6. Connect Tag Manager.
|
|
63
|
+
7. Select the account and container.
|
|
64
|
+
8. If stored draft evidence is required, select a draft workspace.
|
|
65
|
+
9. Sync both providers.
|
|
66
|
+
10. Declare the conversion contract.
|
|
67
|
+
|
|
68
|
+
Use the
|
|
69
|
+
[Google Marketing setup guide](https://github.com/Canonry/canonry/blob/main/docs/google-marketing.md)
|
|
70
|
+
for Google Cloud requirements, a contract example, Doctor commands, and
|
|
71
|
+
troubleshooting.
|
|
72
|
+
|
|
73
|
+
The operator must not give an agent OAuth credentials or a developer token.
|
|
74
|
+
OAuth starts and finishes in the same signed-in dashboard browser.
|
|
75
|
+
|
|
76
|
+
## Agent procedure
|
|
77
|
+
|
|
78
|
+
1. Call `canonry_load_toolkit` with `{ "name": "google-ads" }`.
|
|
79
|
+
2. Wait for the call to return.
|
|
80
|
+
3. Load `gtm`, `conversion-tracking`, and `monitoring` in the same way.
|
|
81
|
+
4. Read provider status and stored snapshots.
|
|
82
|
+
5. Read the declared contracts.
|
|
83
|
+
6. Assess each contract with `canonry_conversion_tracking_integrity`.
|
|
84
|
+
7. Report static failures separately from unknown runtime evidence.
|
|
85
|
+
|
|
86
|
+
Use stored evidence first. Before a live provider discovery or sync, get
|
|
87
|
+
explicit approval.
|
|
88
|
+
|
|
89
|
+
Live discovery needs a full-instance key with
|
|
90
|
+
`google-marketing.read-live`. Sync also needs
|
|
91
|
+
`google-marketing.write` and can use a project-scoped key. A sync queues a run.
|
|
92
|
+
Poll `canonry_run_get` with the returned run ID until its status is `completed`,
|
|
93
|
+
`failed`, or `cancelled`. Assess new evidence only after `completed`. If the run
|
|
94
|
+
fails or is cancelled, report that result instead.
|
|
95
|
+
|
|
96
|
+
MCP does not expose OAuth, resource selection, disconnect, or contract writes.
|
|
97
|
+
Those actions remain in the operator dashboard and CLI.
|
|
98
|
+
|
|
99
|
+
If organic performance is in scope, call `canonry_organic_evidence` from the
|
|
100
|
+
loaded `monitoring` toolkit. Use integrity as a measurement-confidence gate.
|
|
101
|
+
Do not attribute one organic question or citation to one paid conversion.
|
|
102
|
+
|
|
103
|
+
## Read authority
|
|
104
|
+
|
|
105
|
+
Stored snapshot reads are local, redacted evidence. They do not call a provider.
|
|
106
|
+
`google-marketing.read-live` is the explicit Canonry capability for a bounded,
|
|
107
|
+
quota-consuming Google Ads or GTM provider read. It does not grant
|
|
108
|
+
`google-marketing.write` authority, but it also does not narrow Google's OAuth
|
|
109
|
+
token: Google Ads has one broad `adwords` scope. Use a Google Ads user with the
|
|
110
|
+
Read-only account role and a developer token approved for Reporting permissible
|
|
111
|
+
use. GTM uses its read-only OAuth scope.
|
|
112
|
+
|
|
113
|
+
Live queries use bounded account, date, row, page, and retry inputs. GTM uses
|
|
114
|
+
GETs; Google Ads also uses the non-mutating SearchStream POST endpoint. Agents do not
|
|
115
|
+
expose OAuth tokens, OAuth client secrets, or the Google Ads developer token.
|
|
116
|
+
OAuth, resource selection, and disconnect remain explicit operator actions.
|
|
117
|
+
OAuth must start and be confirmed in the same signed-in dashboard browser; CLI
|
|
118
|
+
and MCP never print or transfer authorization URLs.
|
|
119
|
+
|
|
120
|
+
The OAuth web client must register the exact callback
|
|
121
|
+
`<canonry-public-url>/api/v1/google-marketing/callback` (default local install:
|
|
122
|
+
`http://localhost:4100/api/v1/google-marketing/callback`). Pending authorization
|
|
123
|
+
is process-local, expires after 15 minutes, and must be restarted after a server
|
|
124
|
+
restart, browser change, or expiry.
|
|
125
|
+
|
|
126
|
+
## Version 1 safety boundary
|
|
127
|
+
|
|
128
|
+
Version 1 has no Google Ads create, update, or delete path. It has no GTM create,
|
|
129
|
+
edit, version, or publish path. A live read never changes either boundary.
|
|
130
|
+
|
|
131
|
+
A selected draft workspace produces a sanitized draft snapshot. The integrity
|
|
132
|
+
assessment uses the live graph and does not assess the draft graph.
|
|
133
|
+
|
|
134
|
+
The manual browser procedure is external evidence. It does not change the
|
|
135
|
+
Canonry status in version 1.
|
|
136
|
+
|
|
137
|
+
## Future write boundary
|
|
138
|
+
|
|
139
|
+
Any future provider write must be proposal-bound. Google Ads needs a reviewable
|
|
140
|
+
plan and explicit approval. GTM workspace edit approval and GTM publish approval
|
|
141
|
+
are separate events. Approval to edit a draft never authorizes publishing it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,u as h,j as e}from"./vendor-tanstack-Dock0eH1.js";import{
|
|
1
|
+
import{r as a,u as h,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cv as L,cw as P,bR as E,k as u,B as x,d as v,T as k}from"./index-BpHZirJJ.js";const o=100;function M(s){if(s==="all")return;const n=Number.parseInt(s,10);return new Date(Date.now()-n*24*60*60*1e3).toISOString()}function O(s){const n=s.userAgent?.toLowerCase()??"";return n.includes("canonry-mcp")?"MCP":n.includes("canonry-cli")?"CLI":n.includes("mozilla/")?"Dashboard":s.actor==="scheduler"?"Scheduler":s.actor==="agent"?"Agent":s.actor==="mcp"?"MCP":s.actor==="cli"?"CLI":s.actor}function B(s){return/delete|remove|revoke|disconnect|failed/.test(s)?"negative":/create|add|connect|completed/.test(s)?"positive":/update|replace|sync|apply/.test(s)?"caution":"neutral"}function D(s){return[s.action,s.actor,s.entityType,s.entityId,s.actorSession,s.userAgent,s.diff==null?"":JSON.stringify(s.diff)].filter(Boolean).join(" ").toLowerCase()}function H({projectName:s}){const[n,l]=a.useState(0),[c,y]=a.useState("90d"),[m,N]=a.useState(""),[p,w]=a.useState(""),S=a.useMemo(()=>M(c),[c]),g={limit:o,offset:n*o,since:S,actor:m||void 0},C=h({...L({client:u,path:{name:s??""},query:g}),enabled:!!s}),A=h({...P({client:u,query:g}),enabled:!s}),f=h({...E({client:u}),enabled:!s}),i=s?C:A,r=i.data??[],I=a.useMemo(()=>new Map((f.data??[]).map(t=>[t.id,t.displayName||t.name])),[f.data]),d=p.trim().toLowerCase(),j=a.useMemo(()=>d?r.filter(t=>D(t).includes(d)):r,[r,d]),b=t=>{l(0),t()};return i.isLoading?e.jsx("p",{className:"supporting-copy mt-6",role:"status",children:"Loading change history…"}):e.jsxs("section",{className:"mt-6","aria-labelledby":"change-history-heading",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Audit trail"}),e.jsx("h2",{id:"change-history-heading",children:"Change history"}),e.jsx("p",{className:"supporting-copy mt-1 max-w-2xl",children:"Configuration and operator actions. Execution outcomes remain in Runs."})]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",onClick:()=>{i.refetch()},disabled:i.isFetching,children:i.isFetching?"Refreshing…":"Refresh"})]}),e.jsxs("div",{className:"mt-4 grid gap-2 sm:grid-cols-3",children:[e.jsx("input",{type:"search","aria-label":"Search change history",value:p,onChange:t=>w(t.target.value),placeholder:"Search actions, entities, or details",className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong placeholder-mono-600 focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600"}),e.jsxs("select",{"aria-label":"Filter change history by actor",value:m,onChange:t=>b(()=>N(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"",children:"All actors"}),e.jsx("option",{value:"api",children:"API and dashboard"}),e.jsx("option",{value:"cli",children:"CLI"}),e.jsx("option",{value:"scheduler",children:"Scheduler"}),e.jsx("option",{value:"agent",children:"Agent"}),e.jsx("option",{value:"mcp",children:"MCP"})]}),e.jsxs("select",{"aria-label":"Filter change history by date range",value:c,onChange:t=>b(()=>y(t.target.value)),className:"min-h-11 rounded-md border border-base bg-bg px-3 text-sm text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-600",children:[e.jsx("option",{value:"30d",children:"Last 30 days"}),e.jsx("option",{value:"90d",children:"Last 90 days"}),e.jsx("option",{value:"365d",children:"Last year"}),e.jsx("option",{value:"all",children:"All retained history"})]})]}),i.isError?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"Change history unavailable"}),e.jsx("p",{children:i.error instanceof Error?i.error.message:"Could not load the audit trail."})]}):j.length===0?e.jsxs(v,{className:"surface-card empty-card mt-4",children:[e.jsx("h3",{children:"No changes match these filters"}),e.jsx("p",{children:"Widen the date range or clear the actor and search filters."})]}):e.jsx("div",{className:"evidence-table-wrap mt-4",children:e.jsxs("table",{className:"evidence-table",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"When"}),s?null:e.jsx("th",{children:"Scope"}),e.jsx("th",{children:"Action"}),e.jsx("th",{children:"Entity"}),e.jsx("th",{children:"Origin"}),e.jsx("th",{children:"Details"})]})}),e.jsx("tbody",{children:j.map(t=>e.jsxs("tr",{children:[e.jsx("td",{className:"whitespace-nowrap tabular-nums text-muted",children:new Date(t.createdAt).toLocaleString()}),s?null:e.jsx("td",{className:"text-secondary",children:t.projectId?I.get(t.projectId)??`Deleted project · ${t.projectId}`:"Instance settings"}),e.jsx("td",{children:e.jsx(k,{tone:B(t.action),children:t.action})}),e.jsxs("td",{children:[e.jsx("span",{className:"text-strong",children:t.entityType}),t.entityId?e.jsx("span",{className:"mt-0.5 block max-w-48 truncate font-mono text-[11px] text-faint",title:t.entityId,children:t.entityId}):null]}),e.jsxs("td",{children:[e.jsx("span",{className:"text-secondary",children:O(t)}),t.actorSession?e.jsxs("span",{className:"mt-0.5 block max-w-40 truncate font-mono text-[11px] text-faint",title:t.actorSession,children:["Session ",t.actorSession]}):null]}),e.jsx("td",{children:t.diff==null?e.jsx("span",{className:"text-faint",children:"No field diff"}):e.jsxs("details",{children:[e.jsx("summary",{className:"min-h-11 cursor-pointer py-3 text-sm text-secondary hover:text-strong",children:"View diff"}),e.jsx("pre",{className:"max-h-72 max-w-xl overflow-auto whitespace-pre-wrap break-words rounded-md bg-bg-elevated p-3 text-xs text-secondary",children:JSON.stringify(t.diff,null,2)})]})})]},t.id))})]})}),!i.isError&&(n>0||r.length===o)?e.jsxs("div",{className:"mt-4 flex items-center justify-between gap-3",children:[e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:n===0,onClick:()=>l(t=>Math.max(0,t-1)),children:"Previous"}),e.jsxs("span",{className:"text-xs tabular-nums text-muted",children:["Page ",n+1]}),e.jsx(x,{type:"button",variant:"outline",size:"sm",disabled:r.length<o,onClick:()=>l(t=>t+1),children:"Next"})]}):null]})}export{H as A};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cr as _,ag as H,cs as U,ct as K,cu as V,d as x,T as j,i as u,B as w,f as F,cv as Y,cw as J,aj as X,cx as Z}from"./index-DyjfMyum.js";import{D as ee,T as se,E as te,P as ae,v as ne,t as re}from"./vendor-lucide-nYKv-BB7.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,$]=n.useState([]),[m,L]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[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,z,O,W]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),K().catch(()=>[]),V().catch(()=>null)]);h(s),i(r),y(z),$(O),L(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){E(!0),l(null),o(null);try{const s=await Y();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function q(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await J(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof X&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function G(s){l(null),o(null);try{await Z(s),o(`Pruned cached release ${s}.`),await f()}catch(r){l(r instanceof Error?r.message:"Failed to prune release")}}const Q=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:A})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:P})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:F(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:()=>{G(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
|
|
1
|
+
import{r as n,j as e}from"./vendor-tanstack-Dock0eH1.js";import{cK as _,ag as H,cL as U,cM as K,cN as V,d as x,T as j,i as u,B as w,f as F,cO as Y,cP as J,aj as X,cQ as Z}from"./index-BpHZirJJ.js";import{D as ee,T as se,E as te,P as ae,v as ne,t as re}from"./vendor-lucide-nYKv-BB7.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function g({children:t,label:h="More info",placement:a="top",className:i}){const d=n.useId(),[y,c]=n.useState(!1);return e.jsxs("span",{className:`relative inline-flex ${i??""}`,children:[e.jsx("button",{type:"button","aria-label":h,"aria-describedby":y?d:void 0,className:"inline-flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-strong focus:text-strong focus:outline-none focus-visible:ring-1 focus-visible:ring-mono-500",onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),onFocus:()=>c(!0),onBlur:()=>c(!1),children:e.jsx(re,{className:"h-3.5 w-3.5","aria-hidden":!0})}),y&&e.jsx("span",{id:d,role:"tooltip",className:`absolute z-50 w-64 rounded border border-strong bg-bg-elevated px-3 py-2 text-xs font-normal leading-relaxed text-strong shadow-lg ${a==="top"?"bottom-full mb-2":"top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-normal`,children:t})]})}const le="https://commoncrawl.org/web-graphs";function S(t){return t==null?"—":t>=1e12?`${(t/1e12).toFixed(1)} TB`:t>=1e9?`${(t/1e9).toFixed(1)} GB`:t>=1e6?`${(t/1e6).toFixed(1)} MB`:t>=1e3?`${(t/1e3).toFixed(1)} KB`:`${t} B`}function b(t){if(!t)return"—";const h=Date.now()-new Date(t).getTime(),a=Math.floor(h/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const i=Math.floor(a/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}function C(t){switch(t){case"ready":return"positive";case"failed":return"negative";case"downloading":case"querying":case"queued":return"caution"}}function me(){const[t,h]=n.useState(null),[a,i]=n.useState(null),[d,y]=n.useState([]),[c,L]=n.useState([]),[m,$]=n.useState(null),[B,D]=n.useState(!0),[R,E]=n.useState(!1),[p,I]=n.useState(!1),[N,v]=n.useState(""),[M,k]=n.useState(!1),[P,l]=n.useState(null),[A,o]=n.useState(null),f=n.useCallback(async()=>{D(!0),l(null);try{const[s,r,O,z,W]=await Promise.all([_(),H().catch(()=>null),U().catch(()=>[]),K().catch(()=>[]),V().catch(()=>null)]);h(s),i(r),y(O),L(z),$(W)}catch(s){l(s instanceof Error?s.message:"Failed to load backlinks status")}finally{D(!1)}},[]);n.useEffect(()=>{f()},[f]);async function T(){E(!0),l(null),o(null);try{const s=await Y();o(s.alreadyPresent?`DuckDB already installed (${s.version}).`:`Installed DuckDB ${s.version}.`),await f()}catch(s){l(s instanceof Error?s.message:"Failed to install DuckDB")}finally{E(!1)}}async function q(){const s=N.trim()||void 0;I(!0),l(null),o(null);try{const r=await J(s);o(s?`Queued sync for ${r.release}. Download + query runs in the background.`:`Queued sync for auto-discovered release ${r.release}. Download + query runs in the background.`),v(""),k(!1),await f()}catch(r){r instanceof X&&r.code==="MISSING_DEPENDENCY"?l("DuckDB is not installed. Install it first."):l(r instanceof Error?r.message:"Failed to trigger sync")}finally{I(!1)}}async function Q(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 G=a?.status==="ready"&&c.every(s=>s.release!==a.release);return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Backlink data"}),e.jsx("p",{className:"page-subtitle",children:"Manage the shared backlink source and review its latest data."})]})}),P&&e.jsx(x,{className:"surface-card p-4 mb-4 border-negative-800/60",children:e.jsx("p",{className:"text-sm text-negative",children:P})}),A&&e.jsx(x,{className:"surface-card p-4 mb-4 border-positive-800/60",children:e.jsx("p",{className:"text-sm text-positive",children:A})}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Readiness"}),e.jsx("h2",{children:"Backlink source"})]}),t?.duckdbInstalled?e.jsx(j,{tone:"positive",children:"Installed"}):e.jsx(j,{tone:"caution",children:"Not installed"})]}),e.jsx(x,{className:"surface-card p-4",children:B?e.jsx("p",{className:"text-sm text-muted",children:"Checking…"}):t?.duckdbInstalled?e.jsx("p",{className:"text-sm text-secondary",children:"Ready to sync a Common Crawl release."}):e.jsxs("div",{className:"flex items-center justify-between gap-4",children:[e.jsx("p",{className:"text-sm text-secondary",children:"Install the local query engine to enable release syncs."}),!u()&&e.jsxs(w,{type:"button",size:"sm",disabled:R,onClick:F(T),children:[e.jsx(ee,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),R?"Installing…":"Install DuckDB"]})]})})]}),e.jsxs("section",{className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Latest sync"}),e.jsxs("h2",{className:"flex items-center gap-2",children:["Release sync",e.jsx(g,{label:"What is a release sync?",children:"A release sync downloads one Common Crawl dump (~16 GB) and extracts backlinks for every project in this workspace in one pass. This is the heavy job — subsequent per-project re-runs skip the download and just re-query the cached files."})]})]}),a&&e.jsx(j,{tone:C(a.status),children:a.status})]}),e.jsxs(x,{className:"surface-card p-5",children:[a?e.jsxs("div",{className:"space-y-2 text-sm",children:[e.jsxs("p",{className:"text-strong",children:["Release ",e.jsx("code",{className:"text-neutral",children:a.release})]}),a.phaseDetail&&e.jsx("p",{className:"text-secondary",children:a.phaseDetail}),e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-4 gap-4 text-xs text-muted pt-2",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Projects"}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.projectsProcessed??"—"})]}),e.jsxs("div",{children:[e.jsxs("p",{className:"text-faint uppercase tracking-wide flex items-center gap-1",children:["Rows",e.jsx(g,{label:"What are rows?",children:"Total number of (project, referring domain) pairs persisted in SQLite from this sync, across every project in the workspace."})]}),e.jsx("p",{className:"text-neutral mt-0.5",children:a.domainsDiscovered??"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Started"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.downloadStartedAt??a.createdAt)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-faint uppercase tracking-wide",children:"Finished"}),e.jsx("p",{className:"text-neutral mt-0.5",children:b(a.queryFinishedAt)})]})]}),a.error&&e.jsx("p",{className:"text-sm text-negative-400 pt-2",children:a.error})]}):e.jsx("p",{className:"text-sm text-muted",children:"No release sync has run in this workspace yet."}),G&&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:()=>{Q(s.release)},children:[e.jsx(ne,{className:"h-4 w-4 mr-1.5","aria-hidden":!0}),"Prune"]}),e.jsx(g,{label:"What does Prune do?",placement:"top",children:"Deletes the ~16 GB cache for this release from disk. Backlink results already in SQLite remain untouched. To re-run extracts against this release, you’d have to sync it again (another ~16 GB download)."})]})})]},s.release)),c.length===0&&e.jsx("tr",{children:e.jsx("td",{className:"px-4 py-4 text-sm text-muted",colSpan:u()?4:5,children:"No cached releases on this machine. If you ran a sync from a different machine (or deleted the cache), the backlink data is still in the database — but you’ll need to re-sync a release to run new extracts."})})]})]})}),e.jsx("p",{className:"mt-3 text-xs text-muted",children:"Common Crawl files are stored locally. DuckDB reads them; Canonry stores results in its normal database."})]})]}),d.length>1&&e.jsxs("section",{className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"History"}),e.jsx("h2",{children:"Past release syncs"})]})}),e.jsx(x,{className:"surface-card overflow-hidden",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-base text-left text-xs uppercase tracking-wide text-faint",children:[e.jsx("th",{className:"px-4 py-2 font-medium",children:"Release"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Projects"}),e.jsx("th",{className:"px-4 py-2 text-right font-medium",children:"Rows"}),e.jsx("th",{className:"px-4 py-2 font-medium",children:"Finished"})]})}),e.jsx("tbody",{children:d.map(s=>e.jsxs("tr",{className:"border-b border-mono-900 last:border-0",children:[e.jsx("td",{className:"px-4 py-2 text-strong",children:e.jsx("code",{children:s.release})}),e.jsx("td",{className:"px-4 py-2",children:e.jsx(j,{tone:C(s.status),children:s.status})}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.projectsProcessed??"—"}),e.jsx("td",{className:"px-4 py-2 text-right text-secondary tabular-nums",children:s.domainsDiscovered??"—"}),e.jsx("td",{className:"px-4 py-2 text-secondary",children:b(s.queryFinishedAt??s.updatedAt)})]},s.id))})]})})]})]})}export{me as BacklinksPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./vendor-tanstack-Dock0eH1.js";import{A as s}from"./AuditHistoryPanel-
|
|
1
|
+
import{j as e}from"./vendor-tanstack-Dock0eH1.js";import{A as s}from"./AuditHistoryPanel-BO_TSFPx.js";import"./index-BpHZirJJ.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-lucide-nYKv-BB7.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";function l(){return e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"History"}),e.jsx("p",{className:"page-subtitle",children:"Changes to projects and settings."})]})}),e.jsx(s,{})]})}export{l as HistoryPage};
|