@ainyc/canonry 5.5.0 → 5.6.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 (38) hide show
  1. package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +1 -1
  2. package/assets/agent-workspace/skills/canonry/references/aeo-analysis.md +10 -0
  3. package/assets/assets/{AuditHistoryPanel-BMGGxHpw.js → AuditHistoryPanel-DVUbjx1M.js} +1 -1
  4. package/assets/assets/{BacklinksPage-CS3wJSWe.js → BacklinksPage-D01VTDAp.js} +1 -1
  5. package/assets/assets/ChartPrimitives-BQC2VH0O.js +1 -0
  6. package/assets/assets/{HistoryPage-Ba9ezeuP.js → HistoryPage-Bk1gsXe5.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-Y9eseEv0.js → MeasurementPropertyPage-D7p5vpY_.js} +1 -1
  8. package/assets/assets/ProjectPage-Dv3USEIE.js +8 -0
  9. package/assets/assets/{RunRow-DbUtgNUI.js → RunRow-Dpa0kAip.js} +1 -1
  10. package/assets/assets/{RunsPage-BBuFIYuS.js → RunsPage-B59z7DQ_.js} +1 -1
  11. package/assets/assets/{SettingsPage-DNnR33D-.js → SettingsPage-HJDgmQi9.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-Cf4KHWp0.js → SiteHealthSection-BnJA-zvQ.js} +3 -3
  13. package/assets/assets/{TrafficPage-DSQjaZM9.js → TrafficPage-C8na59q_.js} +5 -5
  14. package/assets/assets/{TrafficSourceDetailPage-C-Pg69TW.js → TrafficSourceDetailPage-B2fO8bDc.js} +1 -1
  15. package/assets/assets/{extract-error-message-DNLmL0ID.js → extract-error-message-670MySRM.js} +1 -1
  16. package/assets/assets/index-DTrkuKOh.js +86 -0
  17. package/assets/assets/index-PT4UiPTM.css +1 -0
  18. package/assets/assets/{react-sigma_core.esm.min-DkItaBPY.js → react-sigma_core.esm.min-DOhuYLQj.js} +1 -1
  19. package/assets/assets/v2-overview-adapter-CQyDNkXa.js +1 -0
  20. package/assets/assets/{vendor-recharts-D-cUuLZo.js → vendor-recharts-llkUFBGR.js} +1 -1
  21. package/assets/index.html +3 -3
  22. package/dist/{chunk-75ECQ77U.js → chunk-C4KBR4H6.js} +6 -6
  23. package/dist/{chunk-REQEWYNF.js → chunk-D3DKDTXP.js} +1027 -70
  24. package/dist/{chunk-DX4CZNNU.js → chunk-GHAUT7XJ.js} +2 -2
  25. package/dist/{chunk-KKDXJWDK.js → chunk-I4AHG7K3.js} +3 -3
  26. package/dist/{chunk-LQAOGFFK.js → chunk-KZYOJ4F5.js} +2 -2
  27. package/dist/{chunk-BL3FMPSM.js → chunk-TM2NQMJA.js} +803 -777
  28. package/dist/cli.js +10 -9
  29. package/dist/{demo-server-BSH6SSWD.js → demo-server-JEGVGOPQ.js} +7 -3
  30. package/dist/index.js +5 -5
  31. package/dist/{intelligence-service-CU7W5AVC.js → intelligence-service-5P62KKG6.js} +2 -2
  32. package/dist/mcp.js +3 -3
  33. package/package.json +11 -11
  34. package/assets/assets/ChartPrimitives-Bxa_vFmq.js +0 -1
  35. package/assets/assets/ProjectPage-Cl1N9Dcw.js +0 -8
  36. package/assets/assets/index-BCCrbno5.js +0 -86
  37. package/assets/assets/index-gQj7x9RX.css +0 -1
  38. package/assets/assets/v2-overview-adapter-DKBMcYBh.js +0 -1
@@ -3276,6 +3276,55 @@ function absolutizeProjectUrl(url, canonicalDomain) {
3276
3276
  if (trimmed.startsWith("/")) return `https://${host}${trimmed}`;
3277
3277
  return `https://${host}/${trimmed}`;
3278
3278
  }
3279
+ function safeLinkHref(value) {
3280
+ const trimmed = (value ?? "").trim();
3281
+ if (!trimmed) return "#";
3282
+ if (trimmed.startsWith("/")) return trimmed;
3283
+ if (/^https?:\/\//i.test(trimmed)) return trimmed;
3284
+ if (/^mailto:/i.test(trimmed)) return trimmed;
3285
+ return "#";
3286
+ }
3287
+ function describeLandingPage(raw) {
3288
+ const value = raw ?? "";
3289
+ const queryIndex = value.indexOf("?");
3290
+ const path = queryIndex === -1 ? value : value.slice(0, queryIndex);
3291
+ const query = queryIndex === -1 ? "" : value.slice(queryIndex + 1);
3292
+ const displayPath = path || "/";
3293
+ if (!query) return { path: displayPath, querySummary: null, raw: value };
3294
+ let summary = "";
3295
+ try {
3296
+ summary = summarizeQueryParams(new URLSearchParams(query));
3297
+ } catch {
3298
+ summary = "tracking params";
3299
+ }
3300
+ return { path: displayPath, querySummary: summary || null, raw: value };
3301
+ }
3302
+ function summarizeQueryParams(params) {
3303
+ const keys = Array.from(params.keys());
3304
+ const total = keys.length;
3305
+ if (total === 0) return "";
3306
+ const noun = total === 1 ? "param" : "params";
3307
+ const tag = inferAdSource(params);
3308
+ return tag ? `${tag} \xB7 ${total} ${noun}` : `${total} tracking ${noun}`;
3309
+ }
3310
+ function inferAdSource(params) {
3311
+ if (params.has("fbclid")) return "Facebook Ad";
3312
+ if (params.has("gclid") || params.has("gbraid") || params.has("wbraid")) return "Google Ad";
3313
+ if (params.has("msclkid")) return "Microsoft Ad";
3314
+ if (params.has("ttclid")) return "TikTok Ad";
3315
+ if (params.has("li_fat_id")) return "LinkedIn Ad";
3316
+ if (params.has("twclid")) return "X / Twitter Ad";
3317
+ if (params.has("epik")) return "Pinterest Ad";
3318
+ for (const k of params.keys()) {
3319
+ if (k.startsWith("hsa_")) return "Search Ad";
3320
+ }
3321
+ const src = params.get("utm_source");
3322
+ const med = params.get("utm_medium");
3323
+ if (src && med) return `${src} / ${med}`;
3324
+ if (src) return `Source: ${src}`;
3325
+ if (med) return `Medium: ${med}`;
3326
+ return null;
3327
+ }
3279
3328
  function hostOf(value) {
3280
3329
  if (value == null) return null;
3281
3330
  const trimmed = value.trim();
@@ -6360,10 +6409,58 @@ function visibilityReportPageSchema(itemSchema) {
6360
6409
  total: z15.number().int().nonnegative()
6361
6410
  }).strict();
6362
6411
  }
6412
+ var visibilityReportRateChangeUnavailableReasonSchema = z15.enum([
6413
+ "current-unavailable",
6414
+ "previous-unavailable",
6415
+ "not-applicable"
6416
+ ]);
6417
+ var VisibilityReportRateChangeUnavailableReasons = visibilityReportRateChangeUnavailableReasonSchema.enum;
6418
+ var visibilityReportRateChangeSchema = z15.discriminatedUnion("state", [
6419
+ z15.object({
6420
+ state: z15.literal("available"),
6421
+ previous: visibilityReportRateSchema,
6422
+ delta: z15.number().min(-1).max(1)
6423
+ }).strict(),
6424
+ z15.object({
6425
+ state: z15.literal("unavailable"),
6426
+ reason: visibilityReportRateChangeUnavailableReasonSchema
6427
+ }).strict()
6428
+ ]);
6429
+ var visibilityReportComparedRunSchema = z15.object({
6430
+ id: nonBlankIdSchema,
6431
+ createdAt: dateTimeSchema,
6432
+ completedAt: dateTimeSchema.nullable()
6433
+ }).strict();
6434
+ var visibilityReportComparisonUnavailableReasonSchema = z15.enum([
6435
+ "no-selected-run",
6436
+ "no-previous-run",
6437
+ "scoped-run",
6438
+ "partial-run",
6439
+ "definition-changed",
6440
+ "model-changed",
6441
+ "legacy-unknown"
6442
+ ]);
6443
+ var VisibilityReportComparisonUnavailableReasons = visibilityReportComparisonUnavailableReasonSchema.enum;
6444
+ var visibilityReportComparisonSchema = z15.discriminatedUnion("state", [
6445
+ z15.object({
6446
+ state: z15.literal("available"),
6447
+ previousRun: visibilityReportComparedRunSchema,
6448
+ mentionCoverage: visibilityReportRateChangeSchema,
6449
+ citationCoverage: visibilityReportRateChangeSchema,
6450
+ propertyReach: visibilityReportRateChangeSchema
6451
+ }).strict(),
6452
+ z15.object({
6453
+ state: z15.literal("unavailable"),
6454
+ reason: visibilityReportComparisonUnavailableReasonSchema,
6455
+ previousRun: visibilityReportComparedRunSchema.nullable()
6456
+ }).strict()
6457
+ ]);
6363
6458
  var visibilityReportPopulationSchema = z15.object({
6364
6459
  queryClass: visibilityReportPopulationClassSchema,
6365
6460
  summary: visibilityReportSummarySchema,
6366
6461
  trend: z15.array(visibilityReportTrendPointSchema),
6462
+ /** Optional: report builds and servers that predate the field omit it. */
6463
+ comparison: visibilityReportComparisonSchema.optional(),
6367
6464
  queries: visibilityReportPageSchema(visibilityReportQueryRowSchema),
6368
6465
  evidence: visibilityReportPageSchema(visibilityReportEvidenceRowSchema),
6369
6466
  /** `items: []` alone means no measured competitors only when this is available. */
@@ -6423,6 +6520,44 @@ var visibilityReportResponseSchema = z15.object({
6423
6520
  message: "Populations must exactly match the selected query class order"
6424
6521
  });
6425
6522
  }
6523
+ for (const [populationIndex, population] of value.populations.entries()) {
6524
+ const comparison = population.comparison;
6525
+ if (comparison?.state !== "available") continue;
6526
+ for (const key of ["mentionCoverage", "citationCoverage", "propertyReach"]) {
6527
+ const change = comparison[key];
6528
+ if (change.state !== "available") continue;
6529
+ const path = ["populations", populationIndex, "comparison", key];
6530
+ const current = population.summary[key].rate;
6531
+ const previous = change.previous.rate;
6532
+ if (current === null) {
6533
+ ctx.addIssue({ code: z15.ZodIssueCode.custom, path, message: "Available changes require a current rate" });
6534
+ }
6535
+ if (previous === null) {
6536
+ ctx.addIssue({
6537
+ code: z15.ZodIssueCode.custom,
6538
+ path: [...path, "previous", "rate"],
6539
+ message: "Available changes require a previous rate"
6540
+ });
6541
+ }
6542
+ if (current !== null && previous !== null && Math.abs(change.delta - (current - previous)) > Number.EPSILON) {
6543
+ ctx.addIssue({ code: z15.ZodIssueCode.custom, path: [...path, "delta"], message: "Delta must equal current minus previous" });
6544
+ }
6545
+ if (value.selection.run.id === null) {
6546
+ ctx.addIssue({ code: z15.ZodIssueCode.custom, path, message: "Available changes require a selected run" });
6547
+ }
6548
+ }
6549
+ }
6550
+ });
6551
+ var visibilityReportScopeErrorReasonSchema = z15.enum(["retired-scope", "retired-market"]);
6552
+ var VisibilityReportScopeErrorReasons = visibilityReportScopeErrorReasonSchema.enum;
6553
+ var visibilityReportScopeErrorDetailsSchema = z15.object({
6554
+ reason: visibilityReportScopeErrorReasonSchema,
6555
+ kind: visibilityReportScopeKindSchema.exclude(["project"]),
6556
+ key: nonBlankIdSchema
6557
+ }).strict().superRefine((details, ctx) => {
6558
+ if (details.reason === VisibilityReportScopeErrorReasons["retired-market"] && details.kind !== "market") {
6559
+ ctx.addIssue({ code: z15.ZodIssueCode.custom, path: ["kind"], message: "retired-market details must name a market" });
6560
+ }
6426
6561
  });
6427
6562
 
6428
6563
  // ../contracts/src/query-tracking.ts
@@ -6590,6 +6725,11 @@ var queryTrackingWorkspaceResponseSchema = z16.object({
6590
6725
  targets: z16.array(queryTrackingTargetSchema),
6591
6726
  groups: z16.array(queryTrackingGroupSchema),
6592
6727
  markets: z16.array(queryTrackingMarketSchema),
6728
+ /**
6729
+ * Server-built scope choices for this workspace's Groups, markets, and
6730
+ * Properties. Optional so a client tolerates a server that predates it.
6731
+ */
6732
+ scopeOptions: z16.array(visibilityReportScopeOptionSchema).optional(),
6593
6733
  tracked: z16.array(queryTrackingTrackedRowSchema),
6594
6734
  savedSources: z16.object({
6595
6735
  research: z16.array(queryTrackingResearchCandidateSchema),
@@ -8040,6 +8180,10 @@ function formatRatio(value) {
8040
8180
  if (!Number.isFinite(value) || value === 0) return "0%";
8041
8181
  return `${(value * 100).toFixed(1)}%`;
8042
8182
  }
8183
+ function formatWholePercent(ratio) {
8184
+ if (!Number.isFinite(ratio)) return "0%";
8185
+ return `${Math.round(Number((ratio * 100).toFixed(6)))}%`;
8186
+ }
8043
8187
  function formatNumber(value) {
8044
8188
  if (!Number.isFinite(value)) return "\u2014";
8045
8189
  if (Math.abs(value) >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
@@ -8525,14 +8669,14 @@ function roundSignificant(value, sig = 6) {
8525
8669
  const factor = 10 ** (sig - magnitude);
8526
8670
  return (Math.round(value * factor) + 0) / factor;
8527
8671
  }
8528
- function wilsonInterval(successes, n, z62 = 1.96) {
8672
+ function wilsonInterval(successes, n, z63 = 1.96) {
8529
8673
  if (!Number.isFinite(n) || n <= 0) return null;
8530
8674
  const s = Math.max(0, Math.min(successes, n));
8531
8675
  const p = s / n;
8532
- const z210 = z62 * z62;
8676
+ const z210 = z63 * z63;
8533
8677
  const denom = 1 + z210 / n;
8534
8678
  const center = (p + z210 / (2 * n)) / denom;
8535
- const margin = z62 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
8679
+ const margin = z63 / denom * Math.sqrt(p * (1 - p) / n + z210 / (4 * n * n));
8536
8680
  return {
8537
8681
  low: round(Math.max(0, center - margin)),
8538
8682
  high: round(Math.min(1, center + margin))
@@ -15177,9 +15321,11 @@ var citationsTrendPointSchema = z52.object({
15177
15321
  mentionRate: z52.number()
15178
15322
  }))
15179
15323
  });
15324
+ var reportInsightTypeSchema = z52.enum(["regression", "gain", "opportunity"]);
15325
+ var ReportInsightTypes = reportInsightTypeSchema.enum;
15180
15326
  var reportInsightSchema = z52.object({
15181
15327
  id: z52.string(),
15182
- type: z52.enum(["regression", "gain", "opportunity"]),
15328
+ type: reportInsightTypeSchema,
15183
15329
  severity: z52.enum(["critical", "high", "medium", "low"]),
15184
15330
  title: z52.string(),
15185
15331
  query: z52.string(),
@@ -15362,6 +15508,38 @@ function reportActionTone(action) {
15362
15508
  if (action.confidence === "low") return "neutral";
15363
15509
  return "caution";
15364
15510
  }
15511
+ function reportPressureTone(label) {
15512
+ if (label === "High") return "negative";
15513
+ if (label === "Moderate") return "caution";
15514
+ if (label === "Low") return "positive";
15515
+ return "neutral";
15516
+ }
15517
+ function reportSeverityTone(severity) {
15518
+ switch (severity) {
15519
+ case "critical":
15520
+ return "negative";
15521
+ case "high":
15522
+ return "negative";
15523
+ case "medium":
15524
+ return "caution";
15525
+ case "low":
15526
+ return "neutral";
15527
+ }
15528
+ }
15529
+ function reportInsightTone(insight) {
15530
+ return insight.type === ReportInsightTypes.gain ? "positive" : reportSeverityTone(insight.severity);
15531
+ }
15532
+ function reportSourceCategoryTone(category) {
15533
+ switch (category) {
15534
+ case SourceCategories.competitor:
15535
+ return "negative";
15536
+ case SourceCategories.directory:
15537
+ case SourceCategories.forum:
15538
+ return "caution";
15539
+ default:
15540
+ return "neutral";
15541
+ }
15542
+ }
15365
15543
  function reportSeverityLabel(severity) {
15366
15544
  switch (severity) {
15367
15545
  case "critical":
@@ -17871,80 +18049,799 @@ function isTrendBaseline(points) {
17871
18049
  return points.length < MIN_TREND_POINTS;
17872
18050
  }
17873
18051
 
17874
- // ../contracts/src/operational-logs.ts
18052
+ // ../contracts/src/report-sections.ts
17875
18053
  import { z as z61 } from "zod";
17876
- var operationalLogLevelSchema = z61.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
17877
- var diagnosticContextSchema = z61.object({
17878
- runId: z61.string().min(1).max(256).optional(),
17879
- projectId: z61.string().min(1).max(256).optional(),
17880
- requestId: z61.string().min(1).max(256).optional(),
17881
- actor: z61.string().min(1).max(512).optional(),
17882
- credentialId: z61.string().min(1).max(512).optional(),
17883
- userAgent: z61.string().min(1).max(512).optional(),
17884
- actorSession: z61.string().min(1).max(512).optional(),
17885
- method: z61.string().min(1).max(16).optional(),
17886
- route: z61.string().min(1).max(256).optional(),
17887
- operationId: z61.string().min(1).max(256).optional(),
17888
- jobId: z61.string().min(1).max(256).optional(),
17889
- taskId: z61.string().min(1).max(256).optional(),
17890
- traceId: z61.string().min(1).max(256).optional(),
17891
- errorCode: z61.string().min(1).max(128).optional(),
17892
- attempt: z61.number().finite().optional(),
17893
- count: z61.number().finite().optional(),
17894
- total: z61.number().finite().optional(),
17895
- progress: z61.number().finite().optional(),
17896
- httpStatus: z61.number().finite().optional(),
17897
- statusCode: z61.number().finite().optional(),
17898
- durationMs: z61.number().finite().optional(),
17899
- bytes: z61.number().finite().optional(),
17900
- retryAfterMs: z61.number().finite().optional(),
17901
- retriable: z61.boolean().optional(),
17902
- retrying: z61.boolean().optional(),
17903
- cancelled: z61.boolean().optional(),
17904
- success: z61.boolean().optional()
17905
- }).strict();
17906
- var operationalLogEntryDtoSchema = z61.object({
17907
- cursor: z61.string().min(1).max(512),
17908
- ts: z61.string().datetime(),
18054
+ var CLIENT_AUDIENCE = reportAudienceSchema.enum.client;
18055
+ var BOTH_AUDIENCES = reportActionAudienceSchema.enum.both;
18056
+ var ADVANCED_MODE = visibilityReportResolvedModeSchema.enum.advanced;
18057
+ function pluralize(count, singular, plural = `${singular}s`) {
18058
+ return count === 1 ? singular : plural;
18059
+ }
18060
+ var reportSectionIdSchema = z61.enum([
18061
+ "client-summary",
18062
+ "executive-summary",
18063
+ "share-of-voice",
18064
+ "whats-changed",
18065
+ "client-action-plan",
18066
+ "agency-action-plan",
18067
+ "agency-diagnostics",
18068
+ "citation-scorecard",
18069
+ "competitor-landscape",
18070
+ "ai-source-origin",
18071
+ "gsc",
18072
+ "ga",
18073
+ "social-referrals",
18074
+ "ai-referrals",
18075
+ "server-activity",
18076
+ "indexing-health",
18077
+ "citations-trend",
18078
+ "insights",
18079
+ "content-opportunities",
18080
+ "content-gaps",
18081
+ "recommended-next-steps",
18082
+ "client-evidence-summary"
18083
+ ]);
18084
+ var ReportSectionIds = reportSectionIdSchema.enum;
18085
+ var PROVIDER_DISPLAY_NAMES = {
18086
+ gemini: "Gemini",
18087
+ openai: "ChatGPT",
18088
+ claude: "Claude",
18089
+ perplexity: "Perplexity",
18090
+ local: "Local model",
18091
+ "cdp:chatgpt": "ChatGPT (browser)"
18092
+ };
18093
+ function reportProviderDisplayName(provider) {
18094
+ return PROVIDER_DISPLAY_NAMES[provider] ?? provider.charAt(0).toUpperCase() + provider.slice(1);
18095
+ }
18096
+ function reportClientHorizonLabel(horizon) {
18097
+ switch (horizon) {
18098
+ case "immediate":
18099
+ return "Do now";
18100
+ case "short-term":
18101
+ return "This month";
18102
+ case "medium-term":
18103
+ return "Next quarter";
18104
+ }
18105
+ }
18106
+ function reportClientConfidenceLabel(confidence) {
18107
+ switch (confidence) {
18108
+ case "high":
18109
+ return "Strong evidence";
18110
+ case "medium":
18111
+ return "Some evidence";
18112
+ case "low":
18113
+ return "Worth trying";
18114
+ }
18115
+ }
18116
+ function reportLocationDisplay(location) {
18117
+ if (!location) return "";
18118
+ const place = [location.city, location.region, location.country].filter(Boolean).join(", ");
18119
+ return place ? `${location.label} (${place})` : location.label;
18120
+ }
18121
+ var REPORT_HEADER_COPY = {
18122
+ eyebrow: "AI Visibility Report",
18123
+ market: "Market",
18124
+ noMarket: "No market set",
18125
+ generated: "Generated"
18126
+ };
18127
+ function reportHeaderMarketLabel(location) {
18128
+ return location ? `${REPORT_HEADER_COPY.market}: ${reportLocationDisplay(location)}` : REPORT_HEADER_COPY.noMarket;
18129
+ }
18130
+ function reportHeaderPeriodLabel(periodDays) {
18131
+ return `Last ${periodDays} days`;
18132
+ }
18133
+ function reportCompactList(items, limit = 3) {
18134
+ const visible = items.slice(0, limit);
18135
+ const more = items.length - visible.length;
18136
+ return `${visible.join(", ")}${more > 0 ? `, +${more} more` : ""}`;
18137
+ }
18138
+ function reportTruncatedList(items, limit) {
18139
+ return `${items.slice(0, limit).join(", ")}${items.length > limit ? "\u2026" : ""}`;
18140
+ }
18141
+ function reportMoreChipLabel(count) {
18142
+ return `+${count} more`;
18143
+ }
18144
+ function reportInstanceCountLabel(count) {
18145
+ return `\xD7 ${count}`;
18146
+ }
18147
+ function reportBarChartLabel(title) {
18148
+ return `${title} bar chart`;
18149
+ }
18150
+ function reportLineChartLabel(title) {
18151
+ return `${title} line chart`;
18152
+ }
18153
+ function reportDeltaArrow(direction) {
18154
+ if (direction === "up") return "\u2191";
18155
+ if (direction === "down") return "\u2193";
18156
+ return "\u2192";
18157
+ }
18158
+ function reportDirectionTone(direction) {
18159
+ if (direction === "up") return "positive";
18160
+ if (direction === "down") return "negative";
18161
+ return "neutral";
18162
+ }
18163
+ function reportRateDeltaCopy(delta, unit) {
18164
+ return unit === "%" ? `${delta.deltaAbs > 0 ? "+" : ""}${delta.deltaAbs.toFixed(1)}% vs ${delta.prior}%` : formatAverageDelta(delta);
18165
+ }
18166
+ function reportMovementChangeCopy(movement) {
18167
+ return `${movement.deltaAbs > 0 ? "+" : ""}${movement.deltaAbs.toFixed(1)}% ${reportDeltaArrow(movement.direction)}`;
18168
+ }
18169
+ function reportPriorWindowLabel(days) {
18170
+ return `vs prior ${days} days`;
18171
+ }
18172
+ function reportClientTrendCopy(delta) {
18173
+ if (!delta) return null;
18174
+ const checks = delta.window ?? 1;
18175
+ const compare = checks >= 2 ? `vs prior ${checks} checks (avg ${delta.prior}%)` : `since last check (was ${delta.prior}%)`;
18176
+ const arrow = reportDeltaArrow(delta.direction);
18177
+ if (delta.direction === "up") return { text: `Up ${delta.deltaAbs.toFixed(1)} points ${compare}`, tone: "positive", arrow };
18178
+ if (delta.direction === "down") return { text: `Down ${Math.abs(delta.deltaAbs).toFixed(1)} points ${compare}`, tone: "negative", arrow };
18179
+ return { text: `Holding steady ${compare}`, tone: "neutral", arrow };
18180
+ }
18181
+ var SHARED_SECTION_COPY = {
18182
+ "client-summary": {
18183
+ heroEyebrow: "Overview",
18184
+ heroEmpty: "No AI check has been run yet. Run a check to see how AI tools answer customer queries about your business.",
18185
+ tiles: {
18186
+ mentioned: "AI mentions your name",
18187
+ cited: "AI links to your website",
18188
+ providers: "AI tools tested"
18189
+ },
18190
+ noData: "No data yet",
18191
+ explainer: {
18192
+ lead: "Mentions and links are different.",
18193
+ mention: { article: "A", term: "mention", definition: "is when AI says your name out loud in its answer." },
18194
+ link: { article: "A", term: "link", definition: "is when AI lists your website as a source it used." },
18195
+ closing: "AI can do either, both, or neither \u2014 that's why we track both."
18196
+ },
18197
+ queriesHeading: "Customer queries we tested",
18198
+ providerBarsHeading: "How often each AI tool mentions you",
18199
+ providerBarsSubtitle: "Higher is better. Each bar shows the share of customer queries where the AI named you in the answer."
18200
+ },
18201
+ "whats-changed": {
18202
+ client: {
18203
+ eyebrow: "Since last check",
18204
+ title: "What's different since last check",
18205
+ empty: "No comparison yet \u2014 trends will appear after a few more checks.",
18206
+ tiles: {
18207
+ mentionRate: "AI mentions your name",
18208
+ citationRate: "AI links to your website",
18209
+ mentionedQueryCount: "Queries AI mentioned you in",
18210
+ gscClicks: "Visitors from Google",
18211
+ aiReferrals: "Visitors from AI tools"
18212
+ },
18213
+ gscCountLabel: "visits",
18214
+ aiReferralsCountLabel: "visits",
18215
+ movementsHeading: "How each AI tool changed",
18216
+ movementHeaders: ["AI tool", "Was", "Now", "Change"],
18217
+ winsHeading: "What got better",
18218
+ winsEmpty: "No new wins this period.",
18219
+ regressionsHeading: "What got worse",
18220
+ regressionsEmpty: "Nothing got worse this period.",
18221
+ insightHeaders: ["What changed", "Customer query", "AI tool"]
18222
+ },
18223
+ agency: {
18224
+ eyebrow: "Section 2",
18225
+ title: "What's Changed",
18226
+ empty: "Trends will appear after a few more checks.",
18227
+ tiles: {
18228
+ citationRate: "Citation rate",
18229
+ mentionRate: "Mention rate",
18230
+ citedQueryCount: "Cited queries",
18231
+ gscClicks: "GSC clicks",
18232
+ aiReferrals: "AI referral sessions"
18233
+ },
18234
+ gscCountLabel: "clicks",
18235
+ aiReferralsCountLabel: "sessions",
18236
+ movementsHeading: "AI engine movements",
18237
+ movementHeaders: ["Engine", "Prior", "Current", "Change"],
18238
+ winsHeading: "Wins",
18239
+ winsEmpty: "No new gains in the latest check.",
18240
+ regressionsHeading: "Regressions",
18241
+ regressionsEmpty: "No new regressions in the latest check.",
18242
+ insightHeaders: ["Severity", "Title", "Query", "Provider"]
18243
+ },
18244
+ rateTileEmpty: "No prior data",
18245
+ trafficTileEmpty: "Not enough trend data"
18246
+ },
18247
+ "client-action-plan": {
18248
+ eyebrow: "Action plan",
18249
+ title: "What to do next",
18250
+ intro: "Approve these in order. They are sorted by what will move the needle fastest.",
18251
+ empty: "No recommendations yet \u2014 run an AI check to populate this.",
18252
+ rankTitle: "Priority \u2014 1 will move the needle fastest",
18253
+ detailsSummary: "See the data behind this",
18254
+ whyLabel: "Why this matters",
18255
+ evidenceLabel: "What we saw",
18256
+ successLabel: "What success looks like:"
18257
+ },
18258
+ "agency-action-plan": {
18259
+ eyebrow: "Agency actions",
18260
+ title: "Agency Action Plan",
18261
+ intro: "The highest-leverage work, sorted by urgency and evidence strength.",
18262
+ empty: "No prioritized actions yet.",
18263
+ rankTitle: "Impact rank \u2014 1 is the highest-leverage action",
18264
+ detailsSummary: "Evidence details",
18265
+ whyLabel: "Why",
18266
+ evidenceLabel: "Evidence",
18267
+ successLabel: "Win condition:"
18268
+ },
18269
+ "client-evidence-summary": {
18270
+ eyebrow: "What we based this on",
18271
+ title: "The signals behind this plan",
18272
+ intro: "The data behind the recommendations above. Switch to Agency for the full breakdowns.",
18273
+ empty: "No supporting evidence yet \u2014 this fills in after the first AI check.",
18274
+ sources: {
18275
+ heading: "Where AI gets its answers",
18276
+ subtitle: "The websites AI tools cited most often when answering customer queries about your industry.",
18277
+ competitorTag: "(competitor)"
18278
+ },
18279
+ indexing: {
18280
+ heading: "Pages Google can find on your site",
18281
+ subtitle: "Google indexing your site increases the chances of it appearing in AI search (especially Gemini)."
18282
+ },
18283
+ search: {
18284
+ heading: "What people search Google for",
18285
+ subtitleLead: "You appeared in",
18286
+ subtitleMiddle: "Google searches and got",
18287
+ subtitleTail: "this period."
18288
+ },
18289
+ opportunities: {
18290
+ heading: "Topics where you could improve",
18291
+ subtitle: "Customer queries where better content on your site would help AI cite you.",
18292
+ cededTag: "Ceded surface"
18293
+ }
18294
+ }
18295
+ };
18296
+ function reportClientHeroSentence(totalQueries, mentionedQueries) {
18297
+ if (!(totalQueries > 0)) return SHARED_SECTION_COPY["client-summary"].heroEmpty;
18298
+ return `When customers asked AI ${totalQueries} ${pluralize(totalQueries, "query", "queries")} about your industry, AI mentioned you in ${mentionedQueries} of ${totalQueries === 1 ? "them" : "those queries"}.`;
18299
+ }
18300
+ function reportClientMentionedSubtitle(mentionedQueries, totalQueries) {
18301
+ return totalQueries > 0 ? `Says your name in ${mentionedQueries} of ${totalQueries} ${pluralize(totalQueries, "query", "queries")}` : SHARED_SECTION_COPY["client-summary"].noData;
18302
+ }
18303
+ function reportClientCitedSubtitle(citedQueries, totalQueries) {
18304
+ return totalQueries > 0 ? `Cites your site as a source in ${citedQueries} of ${totalQueries} ${pluralize(totalQueries, "query", "queries")}` : SHARED_SECTION_COPY["client-summary"].noData;
18305
+ }
18306
+ function reportClientProvidersSubtitle(providers, queryCount) {
18307
+ return providers.length > 0 ? providers.map((provider) => reportProviderDisplayName(provider)).join(", ") : `${formatNumber(queryCount)} ${pluralize(queryCount, "query", "queries")} tested`;
18308
+ }
18309
+ function reportClientQueriesSubtitle(queryCount) {
18310
+ return `These are the ${queryCount} ${pluralize(queryCount, "query we asked", "queries we asked")} every AI tool. The numbers above measure how often you came up.`;
18311
+ }
18312
+ function reportAudienceActions(report, audience) {
18313
+ const actions = audience === CLIENT_AUDIENCE ? report.clientSummary.actionItems : report.agencyDiagnostics.priorities.length > 0 ? report.agencyDiagnostics.priorities : report.actionPlan.filter((action) => action.audience === BOTH_AUDIENCES || action.audience === audience);
18314
+ return dedupeReportActions(report, actions);
18315
+ }
18316
+ function reportActionHorizonBadge(audience, horizon) {
18317
+ return audience === CLIENT_AUDIENCE ? reportClientHorizonLabel(horizon) : reportHorizonLabel(horizon);
18318
+ }
18319
+ function reportActionConfidenceBadge(audience, confidence) {
18320
+ return audience === CLIENT_AUDIENCE ? reportClientConfidenceLabel(confidence) : `${reportConfidenceLabel(confidence)} confidence`;
18321
+ }
18322
+ function reportClientSourceCount(count) {
18323
+ return `${formatNumber(count)}\xD7`;
18324
+ }
18325
+ function reportClientIndexedPages(indexed, total) {
18326
+ return `${formatNumber(indexed)} of ${formatNumber(total)} pages indexed`;
18327
+ }
18328
+ function reportClientNotIndexedTail(notIndexed) {
18329
+ return `${pluralize(notIndexed, "page is", "pages are")} not indexed yet.`;
18330
+ }
18331
+ function reportClientClicksNoun(clicks) {
18332
+ return pluralize(clicks, "click");
18333
+ }
18334
+ function reportClientSearchCount(impressions) {
18335
+ return `${formatNumber(impressions)} ${pluralize(impressions, "search", "searches")}`;
18336
+ }
18337
+ function reportClientIndexingTone(indexedPct) {
18338
+ if (indexedPct >= 90) return "positive";
18339
+ if (indexedPct >= 70) return "caution";
18340
+ return "negative";
18341
+ }
18342
+ var AGENCY_OVERVIEW_COPY = {
18343
+ "executive-summary": {
18344
+ eyebrow: "Section 1",
18345
+ title: "Executive Summary",
18346
+ intro: "Citation = source list. Mention = answer text. They are independent signals.",
18347
+ heroKicker: "Latest AI visibility check",
18348
+ emptyTitle: "No AI citation data yet",
18349
+ emptySubtitle: "Run a check to populate the first citation and mention baseline.",
18350
+ noQueries: "no queries",
18351
+ proofTiles: {
18352
+ citationTrend: "Citation trend",
18353
+ mentionCoverage: "Mention coverage",
18354
+ prioritizedActions: "Prioritized actions"
18355
+ },
18356
+ prioritizedActionsCopy: "Sorted for agency follow-up.",
18357
+ tiles: {
18358
+ citationRate: "Citation rate",
18359
+ mentionRate: "Mention rate",
18360
+ queriesTracked: "Queries tracked",
18361
+ gscClicks: "GSC clicks",
18362
+ gaSessions: "GA sessions"
18363
+ },
18364
+ trendLabels: { up: "\u2191 Up", down: "\u2193 Down", flat: "\u2192 Flat", unknown: "\u2014" },
18365
+ marketScope: {
18366
+ heading: "Market Scope",
18367
+ currentLabel: "Current check",
18368
+ currentCopy: "All findings below are scoped to this run.",
18369
+ notIncludedLabel: "Not included",
18370
+ providerLabel: "Provider context",
18371
+ noOtherMarkets: "None",
18372
+ noProviders: "\u2014",
18373
+ singleMarketCopy: "Single-market report; findings can be read as the current market view.",
18374
+ noMarketCopy: "No geographic hint was attached to this check; read findings as default-market or national results.",
18375
+ noProviderMetadataCopy: "No provider-level location metadata is available for this report.",
18376
+ warningTitle: "Location handling needs review",
18377
+ warningDetail: "used weak or indirect market handling. Treat provider-level differences cautiously."
18378
+ }
18379
+ },
18380
+ "agency-diagnostics": {
18381
+ eyebrow: "Agency diagnostics",
18382
+ title: "Technical Diagnostics",
18383
+ intro: "Fast-read operator flags behind the action plan.",
18384
+ empty: "No agency diagnostics available yet.",
18385
+ /** Legacy diagnostics with this title are hidden: the market scope card covers them. */
18386
+ hiddenTitle: "Location caveat"
18387
+ },
18388
+ "recommended-next-steps": {
18389
+ eyebrow: "Section 16",
18390
+ title: "Recommended Next Steps",
18391
+ intro: "Action items bucketed by timing.",
18392
+ empty: "No outstanding actions."
18393
+ }
18394
+ };
18395
+ function reportExecutiveHeadline(report) {
18396
+ const copy = AGENCY_OVERVIEW_COPY["executive-summary"];
18397
+ const summary = report.executiveSummary;
18398
+ const trend = summary.trend;
18399
+ const trendLabel = trend === "up" ? copy.trendLabels.up : trend === "down" ? copy.trendLabels.down : trend === "flat" ? copy.trendLabels.flat : copy.trendLabels.unknown;
18400
+ const trendTone = trend === "up" ? "positive" : trend === "down" ? "negative" : "neutral";
18401
+ const hasQueries = (summary.totalQueryCount ?? 0) > 0;
18402
+ const queryNoun = summary.totalQueryCount === 1 ? "query" : "queries";
18403
+ const priorities = report.agencyDiagnostics.priorities.length > 0 ? report.agencyDiagnostics.priorities : report.actionPlan;
18404
+ const gscDateRange = summary.gsc ? reportGscDateRange(report) : "";
18405
+ return {
18406
+ trendLabel,
18407
+ trendTone,
18408
+ title: hasQueries ? `${summary.citedQueryCount} of ${summary.totalQueryCount} tracked ${queryNoun} cite ${report.meta.project.displayName}` : copy.emptyTitle,
18409
+ subtitle: hasQueries ? `${summary.citationRate}% citation coverage and ${summary.mentionRate}% mention coverage across ${summary.providerCount} ${pluralize(summary.providerCount, "provider")}.` : copy.emptySubtitle,
18410
+ citedFragment: hasQueries ? `${summary.citedQueryCount}/${summary.totalQueryCount} ${queryNoun} cited` : copy.noQueries,
18411
+ mentionedFragment: hasQueries ? `${summary.mentionedQueryCount}/${summary.totalQueryCount} ${queryNoun} mentioned` : copy.noQueries,
18412
+ prioritizedActionCount: dedupeReportActions(report, priorities).length,
18413
+ providerCountLabel: `${summary.providerCount} provider${summary.providerCount === 1 ? "" : "s"}`,
18414
+ competitorCountLabel: `${summary.competitorCount} competitor${summary.competitorCount === 1 ? "" : "s"} tracked`,
18415
+ gscDelta: summary.gsc ? `${formatNumber(summary.gsc.impressions)} imp \xB7 ${formatRatio(summary.gsc.ctr)} CTR${gscDateRange ? ` \xB7 ${gscDateRange}` : ""}` : null,
18416
+ gaDelta: summary.ga ? `${formatNumber(summary.ga.users)} users \xB7 ${formatDate(summary.ga.periodStart)} \u2192 ${formatDate(summary.ga.periodEnd)}` : null
18417
+ };
18418
+ }
18419
+ function reportMarketScope(report) {
18420
+ const copy = AGENCY_OVERVIEW_COPY["executive-summary"].marketScope;
18421
+ const location = report.meta.location;
18422
+ const handling = report.meta.providerLocationHandling;
18423
+ if (!location && handling.length === 0) return null;
18424
+ const otherLocations = location?.otherConfiguredLabels ?? [];
18425
+ const weak = handling.filter((entry) => entry.treatment === "ignored" || entry.treatment === "browser-geo").map((entry) => entry.provider);
18426
+ return {
18427
+ currentValue: location ? reportLocationDisplay(location) : REPORT_HEADER_COPY.noMarket,
18428
+ notIncludedValue: otherLocations.length > 0 ? reportCompactList(otherLocations, 4) : copy.noOtherMarkets,
18429
+ notIncludedCopy: location ? otherLocations.length > 0 ? `${otherLocations.length} configured ${pluralize(otherLocations.length, "market")} still ${otherLocations.length === 1 ? "needs" : "need"} a matching check before cross-market recommendations.` : copy.singleMarketCopy : copy.noMarketCopy,
18430
+ providerValue: handling.length > 0 ? formatNumber(handling.length) : copy.noProviders,
18431
+ providerCopy: handling.length > 0 ? weak.length > 0 ? `${weak.length} ${pluralize(weak.length, "provider")} need a closer location check.` : `${handling.length} ${pluralize(handling.length, "provider")} received the market context.` : copy.noProviderMetadataCopy,
18432
+ weakProviders: weak.length > 0 ? reportCompactList(weak, 4) : null
18433
+ };
18434
+ }
18435
+ var COMPETITIVE_EVIDENCE_COPY = {
18436
+ "citation-scorecard": {
18437
+ eyebrow: "Section 3",
18438
+ title: "Citation Scorecard",
18439
+ intro: "Per-engine citation and mention coverage from the latest check.",
18440
+ providerChartTitle: "Provider citation rate",
18441
+ empty: "Run a check to populate the citation matrix.",
18442
+ queryHeader: "Query",
18443
+ /** Two-glyph matrix cells: citation first, then mention. */
18444
+ glyphs: { cited: "C", notCited: "c", mentioned: "M", notMentioned: "m", pending: "\u2013", missingCell: "\u2014 \u2014" },
18445
+ legend: {
18446
+ lead: "Legend:",
18447
+ citedMeaning: "= cited/not,",
18448
+ mentionedMeaning: "= mentioned/not,",
18449
+ pendingMeaning: "= no data."
18450
+ }
18451
+ },
18452
+ "competitor-landscape": {
18453
+ eyebrow: "Section 4",
18454
+ title: "Competitor Landscape",
18455
+ intro: "Who AI engines cite and mention instead of the client.",
18456
+ empty: "No competitor data yet. Add competitors and run a check.",
18457
+ noCompetitors: "No competitors configured.",
18458
+ citationsChartTitle: "Citations per domain",
18459
+ brandedChartTitle: "Mentions per domain \xB7 branded queries",
18460
+ headers: {
18461
+ domain: "Domain",
18462
+ pressure: "Pressure",
18463
+ citations: "Citations",
18464
+ citationShare: "Citation share",
18465
+ citedQueries: "Cited queries"
18466
+ },
18467
+ citationShareTooltip: "Citation share \u2014 % of cited-source slots that went to this competitor across tracked queries. Distinct from Mention Share."
18468
+ },
18469
+ "ai-source-origin": {
18470
+ eyebrow: "Section 5",
18471
+ title: "AI Citation Sources",
18472
+ intro: "External domains AI engines cited most in the latest check.",
18473
+ empty: "No source data yet. Run a check first.",
18474
+ topSourcesHeading: "Top sources",
18475
+ topSourceHeaders: ["Domain", "Citations", "Tag"],
18476
+ trackedCompetitorTag: "Tracked competitor",
18477
+ externalTag: "External",
18478
+ categoriesHeading: "By source type"
18479
+ }
18480
+ };
18481
+ function reportProviderRateLabel(rate) {
18482
+ return `${rate.citationRate}% (${rate.citedCount}/${rate.totalCount})`;
18483
+ }
18484
+ var MENTION_SCOPE_LABELS = {
18485
+ "non-brand": "non-brand queries",
18486
+ pooled: "pooled queries \xB7 classification unavailable"
18487
+ };
18488
+ function reportMentionScopeLabel(scope) {
18489
+ return MENTION_SCOPE_LABELS[scope] ?? "pooled queries \xB7 classification unavailable";
18490
+ }
18491
+ function reportCompetitorMentionCopy(mentionLandscape) {
18492
+ const scopeLabel = reportMentionScopeLabel(mentionLandscape.scope);
18493
+ const branded = mentionLandscape.branded;
18494
+ return {
18495
+ scopeLabel,
18496
+ mentionsHeader: `Mentions (${scopeLabel})`,
18497
+ mentionsTooltip: mentionLandscape.scope === "non-brand" ? `Mentions on ${scopeLabel}. Branded queries are counted separately \u2014 the client is named on nearly all of them and a competitor cannot be, so pooling the two would rank the client on its own brand recall.` : `Mentions on ${scopeLabel}. The project has no usable brand identity for a branded/non-brand split, so all tracked queries remain pooled and this is not a competitive category read.`,
18498
+ mentionsChartTitle: `Mentions per domain \xB7 ${scopeLabel}`,
18499
+ mentionShareUnavailable: `Mention share unavailable for ${scopeLabel}: no tracked brand was named, so the denominator is 0.`,
18500
+ brandedNote: `Branded queries contain the client's own name. The client is named on nearly all of them and a competitor structurally cannot be, so these are kept out of the competitive figure above. Read them as brand recall: ${branded?.projectMentionCount ?? 0} of ${branded?.totalAnswerSnapshots ?? 0} branded answers named the client.`
18501
+ };
18502
+ }
18503
+ function reportCitedUrlCount(count) {
18504
+ return `${count} cited URL${count > 1 ? "s" : ""}`;
18505
+ }
18506
+ function reportSourceOriginHeadline(categories) {
18507
+ const competitor = categories.find((category) => category.category === SourceCategories.competitor);
18508
+ if (!competitor) return null;
18509
+ const total = categories.reduce((sum, category) => sum + category.count, 0);
18510
+ return { share: `${competitor.sharePct}%`, detail: `of citations went to tracked competitors (${competitor.count} of ${total}).` };
18511
+ }
18512
+ function reportSourceCategoryShareLabel(sharePct) {
18513
+ return `(${sharePct}%)`;
18514
+ }
18515
+ var SEARCH_TRAFFIC_COPY = {
18516
+ gsc: {
18517
+ eyebrow: "Section 6",
18518
+ title: "GSC Performance",
18519
+ empty: "Connect Google Search Console to populate this section.",
18520
+ tiles: {
18521
+ clicks: "Total clicks",
18522
+ impressions: "Total impressions",
18523
+ ctr: "Avg CTR",
18524
+ position: "Avg position"
18525
+ },
18526
+ trendTitle: "Clicks over time",
18527
+ topQueriesHeading: "Top queries",
18528
+ topQueryHeaders: ["Query", "Clicks", "Imp.", "CTR", "Pos.", "Category"],
18529
+ intentHeading: "Search demand by intent",
18530
+ intentCountLabel: "clicks",
18531
+ untrackedDemand: {
18532
+ heading: "AEO queries without search demand",
18533
+ subtitle: "Review whether these still belong in the tracking set."
18534
+ },
18535
+ suggestedQueries: {
18536
+ heading: "Search queries you should track",
18537
+ subtitle: "High-impression candidates to add to AEO tracking."
18538
+ }
18539
+ },
18540
+ ga: {
18541
+ eyebrow: "Section 7",
18542
+ title: "GA4 Traffic",
18543
+ empty: "Connect Google Analytics 4 to populate this section.",
18544
+ tiles: {
18545
+ sessions: "Total sessions",
18546
+ users: "Total users",
18547
+ organicSessions: "Organic sessions"
18548
+ },
18549
+ topPagesHeading: "Top landing pages",
18550
+ topPageHeaders: ["Page", "Sessions", "Organic"],
18551
+ channelsHeading: "Channel mix",
18552
+ channelsCountLabel: "sessions"
18553
+ },
18554
+ "social-referrals": {
18555
+ eyebrow: "Section 8",
18556
+ title: "Social Referrals",
18557
+ intro: "Social traffic split by channel and campaign.",
18558
+ empty: "No social referral data yet.",
18559
+ tiles: {
18560
+ sessions: "Total sessions",
18561
+ organic: "Organic social",
18562
+ paid: "Paid social"
18563
+ },
18564
+ channelsHeading: "Social channel mix",
18565
+ channelsCountLabel: "sessions",
18566
+ campaignsHeading: "Top campaigns",
18567
+ campaignHeaders: ["Source", "Medium", "Sessions"]
18568
+ },
18569
+ "ai-referrals": {
18570
+ eyebrow: "Section 9",
18571
+ title: "AI Referral Traffic",
18572
+ intro: "Traffic arriving from AI answer engines.",
18573
+ empty: "No AI referral traffic detected yet.",
18574
+ tiles: {
18575
+ sessions: "Total sessions"
18576
+ },
18577
+ trendTitle: "AI referral sessions over time",
18578
+ sourcesHeading: "AI sessions by source",
18579
+ sourcesCountLabel: "sessions",
18580
+ topPagesHeading: "Top AI landing pages",
18581
+ topPageHeaders: ["Page", "Sessions"]
18582
+ }
18583
+ };
18584
+ function reportGscDateRange(report) {
18585
+ const summary = report.executiveSummary.gsc;
18586
+ const gsc = report.gsc;
18587
+ const start = summary?.periodStart || gsc?.periodStart || gsc?.trend.at(0)?.date || "";
18588
+ const end = summary?.periodEnd || gsc?.periodEnd || gsc?.trend.at(-1)?.date || "";
18589
+ return formatDateRange(start, end);
18590
+ }
18591
+ function reportGscIntro(report) {
18592
+ const dateRange = reportGscDateRange(report);
18593
+ return `Search demand signals to compare against AI visibility${dateRange ? ` for ${dateRange}` : ""}.`;
18594
+ }
18595
+ function reportGaIntro(ga) {
18596
+ return `Site traffic from ${formatDate(ga.periodStart)} to ${formatDate(ga.periodEnd)}.`;
18597
+ }
18598
+ function reportShareBarShareLabel(countLabel, sharePct) {
18599
+ return `${countLabel} \xB7 ${sharePct}%`;
18600
+ }
18601
+ var SERVER_TRENDS_COPY = {
18602
+ "server-activity": {
18603
+ title: "AI Visibility \u2014 Server-Side",
18604
+ client: {
18605
+ eyebrow: "AI engine attention",
18606
+ introNoData: "Live telemetry from your server logs.",
18607
+ empty: "Your server-side traffic source is connected. Numbers will appear after the next sync.",
18608
+ tiles: {
18609
+ botRequests: "AI bot requests observed",
18610
+ userFetches: "AI user-fetch requests",
18611
+ referralSessions: "AI referral sessions"
18612
+ },
18613
+ userFetchFallback: "ChatGPT-User, Perplexity-User, MistralAI-User",
18614
+ operatorsHeading: "By AI tool",
18615
+ operatorsFootnote: "Bot requests are bulk crawl (GPTBot, PerplexityBot, \u2026). User fetches are on-demand reads triggered by real users inside an AI surface (ChatGPT-User, Perplexity-User, \u2026). Verified means the request came from an IP the operator publishes as its own; unverified means the user-agent matched but the IP is not in a published range. User-fetch totals count both, since many genuine user fetches come from outside any published range."
18616
+ },
18617
+ agency: {
18618
+ eyebrow: "Section 10",
18619
+ intro: "What AI engines actually do in your server logs \u2014 direct evidence, complementary to citations (which measure what they say).",
18620
+ emptyNotConnected: "Connect a server-side traffic source to surface what AI engines do directly in your server logs \u2014 distinct from GA4 click-throughs.",
18621
+ empty: "Source connected \u2014 collecting your first data. Numbers will appear after the next sync.",
18622
+ operatorsHeading: "Per AI operator",
18623
+ operatorsNote: "Verified means the request's source IP falls inside the operator's published range. Unverified bots claim the user-agent but the IP is not in a published range, so it could be the real bot or an imitator. User fetches are on-demand reads from an AI surface on behalf of a real user (ChatGPT-User, Perplexity-User, \u2026), disjoint from bulk crawl and counted whether or not the IP can be verified.",
18624
+ noDelta: "\u2014",
18625
+ crawledPathsHeading: "Top crawled paths",
18626
+ crawledPathHeaders: ["Path", "Hits", "Verified", "Distinct operators"],
18627
+ referralProductsHeading: "AI-referral sessions by product",
18628
+ referralProductsNote: "Where humans landed coming from each AI product (chatgpt.com, claude.ai, \u2026).",
18629
+ referralProductHeaders: ["Product", "Sessions", "Distinct landing paths"],
18630
+ referralLandingHeading: "Top AI-referral landing paths",
18631
+ referralLandingHeaders: ["Path", "Sessions", "Distinct products"]
18632
+ },
18633
+ /** The noun `formatDeltaCopy` puts after the prior-window count. */
18634
+ countNouns: {
18635
+ requests: "requests",
18636
+ hits: "hits",
18637
+ sessions: "sessions"
18638
+ }
18639
+ },
18640
+ "indexing-health": {
18641
+ eyebrow: "Section 11",
18642
+ title: "Indexing Health",
18643
+ empty: "Connect Google Search Console or Bing Webmaster Tools and run a sitemap inspection.",
18644
+ tiles: {
18645
+ indexed: "Indexed",
18646
+ total: "Total inspected",
18647
+ share: "Indexed share"
18648
+ },
18649
+ coverageHeading: "Coverage breakdown",
18650
+ coverageLabel: "Coverage stacked bar",
18651
+ segments: {
18652
+ indexed: "Indexed",
18653
+ notIndexed: "Not indexed",
18654
+ deindexed: "Deindexed",
18655
+ unknown: "Unknown"
18656
+ }
18657
+ },
18658
+ "citations-trend": {
18659
+ eyebrow: "Section 12",
18660
+ title: "Citations Over Time",
18661
+ intro: "Citation coverage across recent checks.",
18662
+ empty: "Run multiple checks to see a trend.",
18663
+ chartTitle: "Overall citation rate",
18664
+ breakdownHeading: "Check-by-check breakdown",
18665
+ breakdownHeaders: ["Check", "Cited queries", "Per-engine rates"]
18666
+ }
18667
+ };
18668
+ function reportServerActivityHeading(audience, hasData, windowDays) {
18669
+ const copy = SERVER_TRENDS_COPY["server-activity"];
18670
+ const isClient = audience === CLIENT_AUDIENCE;
18671
+ return {
18672
+ id: ReportSectionIds["server-activity"],
18673
+ eyebrow: isClient ? copy.client.eyebrow : copy.agency.eyebrow,
18674
+ title: copy.title,
18675
+ intro: isClient ? hasData ? `What AI engines actually do in your server logs over the last ${windowDays} days \u2014 the other half of citations.` : copy.client.introNoData : copy.agency.intro
18676
+ };
18677
+ }
18678
+ function reportServerActivityWindowLabel(windowDays) {
18679
+ return `${windowDays}d`;
18680
+ }
18681
+ function reportServerActivityClientOperatorHeaders(windowDays) {
18682
+ const window = reportServerActivityWindowLabel(windowDays);
18683
+ return ["AI tool", `Bot requests (${window})`, `User fetches (${window})`, "Referral sessions"];
18684
+ }
18685
+ function reportServerActivityAgencyTiles(windowDays) {
18686
+ const window = reportServerActivityWindowLabel(windowDays);
18687
+ return {
18688
+ verified: `Verified crawler hits (${window})`,
18689
+ unverified: `Unverified crawler hits (${window})`,
18690
+ userFetches: `AI user-fetch hits (${window})`,
18691
+ referralSessions: `AI-referral sessions (${window})`
18692
+ };
18693
+ }
18694
+ function reportServerActivityAgencyOperatorHeaders(windowDays) {
18695
+ return ["Operator", "Verified hits", "Unverified", "User fetches", "Referral sessions", `${reportServerActivityWindowLabel(windowDays)} delta`];
18696
+ }
18697
+ function reportServerActivityTrendTitle(windowDays) {
18698
+ return `Verified crawler hits over time (last ${windowDays} days)`;
18699
+ }
18700
+ function reportServerActivityCrawledPathsNote(windowDays) {
18701
+ return `Pages AI bots fetched most often (verified only, last ${reportServerActivityWindowLabel(windowDays)}).`;
18702
+ }
18703
+ function reportServerActivityOperatorDelta(deltaPct) {
18704
+ if (deltaPct === null) return SERVER_TRENDS_COPY["server-activity"].agency.noDelta;
18705
+ return `${deltaPct > 0 ? "+" : ""}${deltaPct}%`;
18706
+ }
18707
+ function reportServerActivityPathHits(path) {
18708
+ return path.verifiedHits + path.unverifiedHits;
18709
+ }
18710
+ function reportCrawlerTrustSummary(verified, unverified) {
18711
+ return `${formatNumber(verified)} verified \xB7 ${formatNumber(unverified)} unverified`;
18712
+ }
18713
+ function reportReferralRedirectNote(redirects) {
18714
+ return redirects > 0 ? `${formatNumber(redirects)} blocked by redirects` : "";
18715
+ }
18716
+ function reportIndexingIntro(provider) {
18717
+ return `Pages absent from ${provider === "google" ? "Google" : "Bing"} are harder for AI engines to retrieve.`;
18718
+ }
18719
+ function reportIndexingLegendLabel(label, count) {
18720
+ return `${label}: ${count}`;
18721
+ }
18722
+ function reportCitationsTrendBaseline(pointCount) {
18723
+ return `Building baseline (${pointCount} of ${MIN_TREND_POINTS} checks completed). Trend will appear once more checks are recorded.`;
18724
+ }
18725
+ function reportTrendProviderRates(rates) {
18726
+ return rates.map((rate) => `${rate.provider}: ${rate.citationRate}%`).join(" \xB7 ");
18727
+ }
18728
+ var INSIGHTS_CONTENT_COPY = {
18729
+ insights: {
18730
+ eyebrow: "Section 13",
18731
+ title: "Insights & Alerts",
18732
+ intro: "Regressions, gains, and recurring alerts ordered by severity.",
18733
+ empty: "No insights yet \u2014 run a check to generate alerts.",
18734
+ headers: ["Severity", "Title", "Query", "Provider", "Recommendation"],
18735
+ noRecommendation: "\u2014"
18736
+ },
18737
+ "content-opportunities": {
18738
+ eyebrow: "Section 14",
18739
+ title: "Content Opportunities",
18740
+ intro: "Queries where content work has the clearest path to more AI citations. Opportunity score is 0\u2013100, higher = stronger. Winnability flags whether the cited surface is ownable or ceded to aggregators/editorial.",
18741
+ headers: ["Query", "Action", "Winnability", "Score", "Why", "Our page", "Winning competitor", "Confidence"],
18742
+ scoreSuffix: "/100",
18743
+ scoreCardTooltip: "Opportunity score (0\u2013100, higher = stronger)",
18744
+ scoreHeaderTooltip: "Opportunity score (0\u2013100)",
18745
+ noDriverSignal: "No driver signal yet",
18746
+ noPage: "No page yet",
18747
+ noWinningCompetitor: "\u2014"
18748
+ },
18749
+ "content-gaps": {
18750
+ eyebrow: "Section 15",
18751
+ title: "Content Gaps",
18752
+ intro: "Tracked queries where competitors are cited and the client is missing.",
18753
+ headers: ["Query", "Competitors cited", "Domains", "Miss rate"]
18754
+ }
18755
+ };
18756
+ function reportOpportunityActionLine(opportunity) {
18757
+ return `${contentActionLabel(opportunity.action)} \xB7 ${actionConfidenceLabel(opportunity.actionConfidence)} confidence`;
18758
+ }
18759
+ function reportMissRateLabel(missRate) {
18760
+ return formatWholePercent(missRate);
18761
+ }
18762
+ var REPORT_SECTION_COPY = {
18763
+ ...SHARED_SECTION_COPY,
18764
+ ...AGENCY_OVERVIEW_COPY,
18765
+ ...COMPETITIVE_EVIDENCE_COPY,
18766
+ ...SEARCH_TRAFFIC_COPY,
18767
+ ...SERVER_TRENDS_COPY,
18768
+ ...INSIGHTS_CONTENT_COPY
18769
+ };
18770
+
18771
+ // ../contracts/src/operational-logs.ts
18772
+ import { z as z62 } from "zod";
18773
+ var operationalLogLevelSchema = z62.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
18774
+ var diagnosticContextSchema = z62.object({
18775
+ runId: z62.string().min(1).max(256).optional(),
18776
+ projectId: z62.string().min(1).max(256).optional(),
18777
+ requestId: z62.string().min(1).max(256).optional(),
18778
+ actor: z62.string().min(1).max(512).optional(),
18779
+ credentialId: z62.string().min(1).max(512).optional(),
18780
+ userAgent: z62.string().min(1).max(512).optional(),
18781
+ actorSession: z62.string().min(1).max(512).optional(),
18782
+ method: z62.string().min(1).max(16).optional(),
18783
+ route: z62.string().min(1).max(256).optional(),
18784
+ operationId: z62.string().min(1).max(256).optional(),
18785
+ jobId: z62.string().min(1).max(256).optional(),
18786
+ taskId: z62.string().min(1).max(256).optional(),
18787
+ traceId: z62.string().min(1).max(256).optional(),
18788
+ errorCode: z62.string().min(1).max(128).optional(),
18789
+ attempt: z62.number().finite().optional(),
18790
+ count: z62.number().finite().optional(),
18791
+ total: z62.number().finite().optional(),
18792
+ progress: z62.number().finite().optional(),
18793
+ httpStatus: z62.number().finite().optional(),
18794
+ statusCode: z62.number().finite().optional(),
18795
+ durationMs: z62.number().finite().optional(),
18796
+ bytes: z62.number().finite().optional(),
18797
+ retryAfterMs: z62.number().finite().optional(),
18798
+ retriable: z62.boolean().optional(),
18799
+ retrying: z62.boolean().optional(),
18800
+ cancelled: z62.boolean().optional(),
18801
+ success: z62.boolean().optional()
18802
+ }).strict();
18803
+ var operationalLogEntryDtoSchema = z62.object({
18804
+ cursor: z62.string().min(1).max(512),
18805
+ ts: z62.string().datetime(),
17909
18806
  level: operationalLogLevelSchema,
17910
- module: z61.string().min(1).max(256),
17911
- action: z61.string().min(1).max(256),
17912
- message: z61.string().max(4096).optional(),
17913
- runId: z61.string().min(1).max(256).optional(),
17914
- projectId: z61.string().min(1).max(256).optional(),
18807
+ module: z62.string().min(1).max(256),
18808
+ action: z62.string().min(1).max(256),
18809
+ message: z62.string().max(4096).optional(),
18810
+ runId: z62.string().min(1).max(256).optional(),
18811
+ projectId: z62.string().min(1).max(256).optional(),
17915
18812
  context: diagnosticContextSchema
17916
18813
  }).strict();
17917
- var logQuerySchema = z61.object({
18814
+ var logQuerySchema = z62.object({
17918
18815
  level: operationalLogLevelSchema.optional(),
17919
- module: z61.string().trim().min(1).max(256).optional(),
17920
- runId: z61.string().trim().min(1).max(256).optional(),
17921
- projectId: z61.string().trim().min(1).max(256).optional(),
17922
- actor: z61.string().trim().min(1).max(512).optional(),
17923
- requestId: z61.string().trim().min(1).max(256).optional(),
17924
- since: z61.string().datetime().optional(),
17925
- until: z61.string().datetime().optional(),
17926
- limit: z61.coerce.number().int().min(1).max(200).default(100),
17927
- cursor: z61.string().trim().min(1).max(512).optional()
18816
+ module: z62.string().trim().min(1).max(256).optional(),
18817
+ runId: z62.string().trim().min(1).max(256).optional(),
18818
+ projectId: z62.string().trim().min(1).max(256).optional(),
18819
+ actor: z62.string().trim().min(1).max(512).optional(),
18820
+ requestId: z62.string().trim().min(1).max(256).optional(),
18821
+ since: z62.string().datetime().optional(),
18822
+ until: z62.string().datetime().optional(),
18823
+ limit: z62.coerce.number().int().min(1).max(200).default(100),
18824
+ cursor: z62.string().trim().min(1).max(512).optional()
17928
18825
  }).strict().superRefine((query, ctx) => {
17929
18826
  if (query.since && query.until && Date.parse(query.since) > Date.parse(query.until)) {
17930
- ctx.addIssue({ code: z61.ZodIssueCode.custom, path: ["until"], message: '"until" must be on or after "since".' });
18827
+ ctx.addIssue({ code: z62.ZodIssueCode.custom, path: ["until"], message: '"until" must be on or after "since".' });
17931
18828
  }
17932
18829
  });
17933
- var operationalLogListDtoSchema = z61.object({
17934
- entries: z61.array(operationalLogEntryDtoSchema),
17935
- nextCursor: z61.string().min(1).max(512).nullable(),
18830
+ var operationalLogListDtoSchema = z62.object({
18831
+ entries: z62.array(operationalLogEntryDtoSchema),
18832
+ nextCursor: z62.string().min(1).max(512).nullable(),
17936
18833
  /** Matching entries left after this page. */
17937
- truncated: z61.number().int().nonnegative(),
18834
+ truncated: z62.number().int().nonnegative(),
17938
18835
  /** Entries evicted by the active retention policy. */
17939
- dropped: z61.number().int().nonnegative(),
17940
- retention: z61.enum(["process", "durable"]),
17941
- retentionPolicy: z61.object({
17942
- maxEntries: z61.number().int().positive(),
17943
- maxAgeSeconds: z61.number().int().positive()
18836
+ dropped: z62.number().int().nonnegative(),
18837
+ retention: z62.enum(["process", "durable"]),
18838
+ retentionPolicy: z62.object({
18839
+ maxEntries: z62.number().int().positive(),
18840
+ maxAgeSeconds: z62.number().int().positive()
17944
18841
  }).strict().optional(),
17945
18842
  /** Best-effort persistence failures observed by this store. */
17946
- captureErrors: z61.number().int().nonnegative().optional(),
17947
- observedAt: z61.string().datetime()
18843
+ captureErrors: z62.number().int().nonnegative().optional(),
18844
+ observedAt: z62.string().datetime()
17948
18845
  }).strict();
17949
18846
 
17950
18847
  // ../contracts/src/log-redaction.ts
@@ -18212,6 +19109,8 @@ export {
18212
19109
  gaMeasurementHostScopeSchema,
18213
19110
  gaMeasurementAnalysisDtoSchema,
18214
19111
  absolutizeProjectUrl,
19112
+ safeLinkHref,
19113
+ describeLandingPage,
18215
19114
  hostOf,
18216
19115
  registrableDomain,
18217
19116
  brandLabelFromDomain,
@@ -18303,6 +19202,7 @@ export {
18303
19202
  visibilityReportRequestSchema,
18304
19203
  visibilityReportQuerySchema,
18305
19204
  visibilityReportResponseSchema,
19205
+ VisibilityReportScopeErrorReasons,
18306
19206
  expandQueryTemplate,
18307
19207
  queryTrackingProvenanceSchema,
18308
19208
  queryTrackingPreviewRequestSchema,
@@ -18399,6 +19299,7 @@ export {
18399
19299
  measurementDraftApplyGroupMembershipRequestSchema,
18400
19300
  measurementDraftApplyGroupMembershipResponseSchema,
18401
19301
  formatRatio,
19302
+ formatWholePercent,
18402
19303
  formatNumber,
18403
19304
  formatDate,
18404
19305
  formatIsoDate,
@@ -18408,13 +19309,11 @@ export {
18408
19309
  shiftIsoCalendarDate,
18409
19310
  isoDateDaysBeforeInTimeZone,
18410
19311
  startOfDayHourInTimeZone,
18411
- formatDateRange,
18412
19312
  parseInclusiveEndMs,
18413
19313
  relativeChangeRatio,
18414
19314
  deltaPercent,
18415
19315
  deltaTone,
18416
19316
  formatDeltaCopy,
18417
- formatAverageDelta,
18418
19317
  formatWindowCountDelta,
18419
19318
  compactDateToIso,
18420
19319
  parseBoundedRate,
@@ -18767,14 +19666,14 @@ export {
18767
19666
  parseReportPeriodDays,
18768
19667
  reportComparisonWindowDays,
18769
19668
  reportActionTone,
19669
+ reportPressureTone,
19670
+ reportInsightTone,
19671
+ reportSourceCategoryTone,
18770
19672
  reportSeverityLabel,
18771
- reportHorizonLabel,
18772
19673
  reportActionCategoryLabel,
18773
- reportConfidenceLabel,
18774
19674
  projectReportDtoSchema,
18775
19675
  organicEvidencePeriodSchema,
18776
19676
  organicEvidenceDtoSchema,
18777
- dedupeReportActions,
18778
19677
  dedupeReportOpportunities,
18779
19678
  CodingAgents,
18780
19679
  agentPluginClientLabel,
@@ -18946,6 +19845,64 @@ export {
18946
19845
  detectAgentRuntime,
18947
19846
  MIN_TREND_POINTS,
18948
19847
  isTrendBaseline,
19848
+ ReportSectionIds,
19849
+ reportProviderDisplayName,
19850
+ REPORT_HEADER_COPY,
19851
+ reportHeaderMarketLabel,
19852
+ reportHeaderPeriodLabel,
19853
+ reportCompactList,
19854
+ reportTruncatedList,
19855
+ reportMoreChipLabel,
19856
+ reportInstanceCountLabel,
19857
+ reportBarChartLabel,
19858
+ reportLineChartLabel,
19859
+ reportDeltaArrow,
19860
+ reportDirectionTone,
19861
+ reportRateDeltaCopy,
19862
+ reportMovementChangeCopy,
19863
+ reportPriorWindowLabel,
19864
+ reportClientTrendCopy,
19865
+ reportClientHeroSentence,
19866
+ reportClientMentionedSubtitle,
19867
+ reportClientCitedSubtitle,
19868
+ reportClientProvidersSubtitle,
19869
+ reportClientQueriesSubtitle,
19870
+ reportAudienceActions,
19871
+ reportActionHorizonBadge,
19872
+ reportActionConfidenceBadge,
19873
+ reportClientSourceCount,
19874
+ reportClientIndexedPages,
19875
+ reportClientNotIndexedTail,
19876
+ reportClientClicksNoun,
19877
+ reportClientSearchCount,
19878
+ reportClientIndexingTone,
19879
+ reportExecutiveHeadline,
19880
+ reportMarketScope,
19881
+ reportProviderRateLabel,
19882
+ reportCompetitorMentionCopy,
19883
+ reportCitedUrlCount,
19884
+ reportSourceOriginHeadline,
19885
+ reportSourceCategoryShareLabel,
19886
+ reportGscIntro,
19887
+ reportGaIntro,
19888
+ reportShareBarShareLabel,
19889
+ reportServerActivityHeading,
19890
+ reportServerActivityClientOperatorHeaders,
19891
+ reportServerActivityAgencyTiles,
19892
+ reportServerActivityAgencyOperatorHeaders,
19893
+ reportServerActivityTrendTitle,
19894
+ reportServerActivityCrawledPathsNote,
19895
+ reportServerActivityOperatorDelta,
19896
+ reportServerActivityPathHits,
19897
+ reportCrawlerTrustSummary,
19898
+ reportReferralRedirectNote,
19899
+ reportIndexingIntro,
19900
+ reportIndexingLegendLabel,
19901
+ reportCitationsTrendBaseline,
19902
+ reportTrendProviderRates,
19903
+ reportOpportunityActionLine,
19904
+ reportMissRateLabel,
19905
+ REPORT_SECTION_COPY,
18949
19906
  operationalLogEntryDtoSchema,
18950
19907
  logQuerySchema,
18951
19908
  operationalLogListDtoSchema,