{action.title}
+- Success metric: {action.successMetric} -
-{action.title}
-{action.action}
- {action.why.length > 0 && ( -Why
-Evidence
-{action.title}
+- Success metric: {action.successMetric} -
-{action.action}
+Why
+Evidence
++ Win condition: {action.successMetric} +
+ + ) + })}{d.title}
{d.detail}
- {d.evidence.length > 0 && ( -Latest AI visibility check
+{headlineTitle}
+{headlineSubtitle}
+Citation trend
+{trendArrow}
+{citedFragment}
+Mention coverage
+{exec.mentionRate}%
+{mentionedFragment}
+Prioritized actions
+{formatNumber(actionCount)}
+Sorted for agency follow-up.
+{label}
+—
+No prior data
+{label}
++ {delta.current}{valueSuffix} {deltaArrow(delta.direction)} +
++ {sign}{unit === '%' ? delta.deltaAbs.toFixed(1) : delta.deltaAbs}{valueSuffix} vs {delta.prior}{valueSuffix} +
+{label}
+—
+Not enough trend data
+{label}
++ {formatNumber(delta.current)} {deltaArrow(delta.direction)} +
++ {sign}{formatNumber(delta.deltaAbs)} {countLabel} vs prior {WHATS_CHANGED_PERIOD_DAYS} days +
+AI engine movements
+| Engine | +Prior | +Current | +Change | +
|---|---|---|---|
| {m.provider} | +{m.prior}% | +{m.current}% | ++ {sign}{m.deltaAbs.toFixed(1)}% {deltaArrow(m.direction)} + | +
{heading}
+{emptyMessage}
+{heading}
+| Severity | +Title | +Query | +Provider | +
|---|---|---|---|
|
+ |
+ {i.title} | +{i.query} | +{i.provider} | +
Provider citation rate
{competitor.sharePct}% of citations went to tracked competitors ({competitor.count} of {totalCitations}).
)} -Top source domains
- {so.topDomains.length === 0 ? ( -| Domain | -Citations | -Tag | + {so.topDomains.length > 0 && ( +
|---|
| Domain | +Citations | +Tag | +
|---|---|---|
| {d.domain} | +{d.count} | +
+ {d.isCompetitor
+ ? |
| {d.domain} | -{d.count} | -
- {d.isCompetitor
- ? |
-
By source type
- {so.categories.length === 0 ? ( -Query categories
-{c.category}
-{formatNumber(c.clicks)} clicks
-{formatPercent(c.sharePct)} share · {formatNumber(c.impressions)} impressions
-Channel breakdown
-{c.channel}
-{formatNumber(c.sessions)}
-{formatPercent(c.sharePct)}
-By channel
-{c.channelGroup}
-{formatNumber(c.sessions)}
-{formatPercent(c.sharePct)}
-By source
-{s.source}
-{formatNumber(s.sessions)}
-{formatNumber(s.users)} users · {formatPercent(s.sharePct)}
-Coverage breakdown
+Run-by-run breakdown
+Check-by-check breakdown
| Run | +Check | Cited queries | -Per-provider rates | +Per-engine rates |
|---|
| {o.query} | - {Math.round(o.score)} - / 100 - | +{Math.round(o.score)} |
{o.drivers.length > 0
?
${escapeHtml(heading)}+${escapeHtml(emptyMessage)} +${escapeHtml(reportSeverityLabel(i.severity))} |
+ ${escapeHtml(i.title)}${countChip} |
+ ${escapeHtml(i.query)} |
+ ${escapeHtml(i.provider)} |
+ ${escapeHtml(heading)}+
+ ${renderRateDeltaTile('Citation rate', w.citationRate, '%')}
+ ${renderRateDeltaTile('Mention rate', w.mentionRate, '%')}
+ ${renderRateDeltaTile('Cited queries', w.citedQueryCount, 'count')}
+ ${renderTrafficDeltaTile('GSC clicks', w.gscClicksDelta, 'clicks')}
+ ${renderTrafficDeltaTile('AI referral sessions', w.aiReferralsDelta, 'sessions')}
+ `
+ const movements = renderProviderMovements(w.providerMovements)
+ const wins = renderWinsLosses(w.wins, 'Wins', 'No new gains in the latest check.')
+ const regressions = renderWinsLosses(w.regressions, 'Regressions', 'No new regressions in the latest check.')
+ return section(
+ { id: 'whats-changed', eyebrow: 'Section 2', title: "What's Changed", intro: w.headline },
+ `${rateTiles}${movements}${wins}${regressions}`,
+ )
+}
+
function renderProviderBars(rates: ProjectReportDto['citationScorecard']['providerRates']): string {
if (rates.length === 0) return ''
const max = Math.max(...rates.map(r => r.citationRate), 100)
@@ -997,7 +1034,7 @@ function renderProviderBars(rates: ProjectReportDto['citationScorecard']['provid
function renderCitationMatrix(scorecard: ProjectReportDto['citationScorecard']): string {
if (scorecard.queries.length === 0 || scorecard.providers.length === 0) {
- return renderEmpty('Run a visibility sweep to populate the citation matrix.')
+ return renderEmpty('Run a check to populate the citation matrix.')
}
const headers = scorecard.providers.map(p => `${escapeHtml(p)} | `).join('')
const rows = scorecard.queries.map((q, qi) => {
@@ -1036,7 +1073,7 @@ function renderCitationScorecard(report: ProjectReportDto): string {
${renderCitationMatrix(report.citationScorecard)}
`
return section(
- { id: 'citation-scorecard', eyebrow: 'Section 2', title: 'Citation Scorecard', intro: 'Provider-by-provider citation and mention coverage for the latest sweep.' },
+ { id: 'citation-scorecard', eyebrow: 'Section 3', title: 'Citation Scorecard', intro: 'Per-engine citation and mention coverage from the latest check.' },
body,
)
}
@@ -1096,8 +1133,8 @@ function renderCompetitorLandscape(report: ProjectReportDto): string {
const noMentionData = mentionLandscape.competitors.length === 0 && mentionLandscape.projectMentionCount === 0
if (noCitationData && noMentionData) {
return section(
- { id: 'competitor-landscape', eyebrow: 'Section 3', title: 'Competitor Landscape' },
- renderEmpty('No competitor data yet. Add competitors and run a visibility sweep.'),
+ { id: 'competitor-landscape', eyebrow: 'Section 4', title: 'Competitor Landscape' },
+ renderEmpty('No competitor data yet. Add competitors and run a check.'),
)
}
@@ -1138,7 +1175,7 @@ function renderCompetitorLandscape(report: ProjectReportDto): string {
return section(
{
id: 'competitor-landscape',
- eyebrow: 'Section 3',
+ eyebrow: 'Section 4',
title: 'Competitor Landscape',
intro: 'Who AI engines cite and mention instead of the client.',
},
@@ -1218,8 +1255,8 @@ function renderAiSourceOrigin(report: ProjectReportDto): string {
const origin = report.aiSourceOrigin
if (origin.categories.length === 0 && origin.topDomains.length === 0) {
return section(
- { id: 'ai-source-origin', eyebrow: 'Section 4', title: 'AI Citation Sources' },
- renderEmpty('No source data yet. Run a visibility sweep first.'),
+ { id: 'ai-source-origin', eyebrow: 'Section 5', title: 'AI Citation Sources' },
+ renderEmpty('No source data yet. Run a check first.'),
)
}
@@ -1247,9 +1284,9 @@ function renderAiSourceOrigin(report: ProjectReportDto): string {
return section(
{
id: 'ai-source-origin',
- eyebrow: 'Section 4',
+ eyebrow: 'Section 5',
title: 'AI Citation Sources',
- intro: 'External domains AI engines trusted most in the latest sweep.',
+ intro: 'External domains AI engines cited most in the latest check.',
},
`${headlineFragment}${table}${renderCategoryBars(origin.categories)}`,
)
@@ -1295,7 +1332,7 @@ function renderGsc(report: ProjectReportDto): string {
const gsc = report.gsc
if (!gsc) {
return section(
- { id: 'gsc', eyebrow: 'Section 5', title: 'GSC Performance' },
+ { id: 'gsc', eyebrow: 'Section 6', title: 'GSC Performance' },
renderEmpty('Connect Google Search Console to populate this section.'),
)
}
@@ -1344,7 +1381,7 @@ function renderGsc(report: ProjectReportDto): string {
const dateRange = gscDateRange(report)
return section(
- { id: 'gsc', eyebrow: 'Section 5', title: 'GSC Performance', intro: `Search demand signals to compare against AI visibility${dateRange ? ` for ${dateRange}` : ''}.` },
+ { id: 'gsc', eyebrow: 'Section 6', title: 'GSC Performance', intro: `Search demand signals to compare against AI visibility${dateRange ? ` for ${dateRange}` : ''}.` },
`Total clicks ${formatNumber(gsc.totalClicks)} Total impressions ${formatNumber(gsc.totalImpressions)} Total sessions ${formatNumber(ga.totalSessions)} Total users ${formatNumber(ga.totalUsers)} Total sessions ${formatNumber(social.totalSessions)} Organic social ${formatNumber(social.organicSessions)} Total sessions ${formatNumber(ai.totalSessions)} Total users ${formatNumber(ai.totalUsers)} Indexed ${formatNumber(ih.indexed)} Total inspected ${formatNumber(ih.total)} Run-by-run breakdown+Check-by-check breakdown
|