@AGENTS.md
The web dashboard follows a dark, professional analytics aesthetic inspired by Vercel's design system — clean, minimal, high-contrast, and information-dense. Rival tools like Semrush, Ahrefs, and Profound for data richness, but match Vercel for polish: generous whitespace, sharp typography, subtle borders, no visual noise. Follow these conventions for all UI work:
- Sidebar navigation (persistent left,
w-56, hidden on mobile with full-screen overlay fallback). - Compact topbar with breadcrumb, health pills, and primary action button.
- Page container (
max-w-6xl, centered) for all page content. - Pages use a
page-header(title + subtitle + optional actions) followed by sections separated bypage-section-divider.
- Background:
bg-zinc-950. Cards/surfaces:bg-zinc-900/30withborder-zinc-800/60. - Font: Geist Sans (400–800 weights) for UI text, Geist Mono for code/numerics. Globally enabled OpenType features
cv11,ss01,ss03for sharper i/l/I/0 disambiguation. Headings tighten tracking (-0.015em,-0.02emon h1).text-zinc-50primary,text-zinc-400secondary,text-zinc-500/text-zinc-600for labels. - Tone colors: positive = emerald, caution = amber, negative = rose, neutral = zinc.
- No decorative background gradients. Keep it clean and flat.
- AEO performance hero + metric cards: the project overview leads with the AEO performance hero — three paired Mention / Cited / Mention-share rows with linear progress bars (stacking below
480px) — followed by secondary metric cards in asm:grid-cols-2 lg:grid-cols-3grid. Linear bars beat stacked radials when several numbers are read against each other. Keep a single.metric-grid/.metric-carddefinition; a duplicate once overrode the column count. - Score gauges (
ScoreGauge): SVG radial progress rings for a single numeric/text metric (e.g. traffic-source detail). Don't rebuild the overview header as a gauge cluster. - Data tables for evidence, findings, and competitors (not card grids). Tables are more scanable for analysts.
- Insight cards with left-border accent color based on tone (
insight-card-positive,insight-card-caution,insight-card-negative). - Sparklines for inline trend visualization in overview project rows.
- ToneBadge for all status/state indicators. Map tones through helper functions (
toneFromRunStatus,toneFromCitationState, etc.). - Filter chips use
rounded-fullpill style. - Health pills in topbar use
rounded-fullwith tone-colored borders.
- Main nav items use Lucide icons (
LayoutDashboard,Globe,Play,Settings). - Projects section shows each project with a colored dot indicating visibility health tone.
- Resources section at bottom with
Rocketicon for Setup. - Doc links in sidebar footer.
- Prioritize information density. Analysts want to scan, not scroll through cards.
- Use tables for any list of 3+ structured items (evidence, findings, competitors).
- Use cards only for insights/interpretations where narrative matters.
- Keep eyebrow labels (
text-[10px], uppercase, tracking-wide) for section context.
- Heavy text belongs in tooltips, not inline. A data surface shows values, one-line captions, and eyebrow labels — not prose. Multi-sentence explanations (methodology, "what this means", the evidence behind a finding) push the numbers down and break the analyst's scan, so they move into an
InfoTooltip(components/shared/InfoTooltip.tsx) on the relevant heading, label, or row title. The trigger is a real keyboard-reachable button and the copy rides itsaria-label, so nothing is lost for assistive tech or for tests (getByRole('button', { name })). - What may stay inline: the metric value itself, a single-line caption/subtitle, eyebrow section labels, and empty / onboarding states (which must instruct — a "connect this integration" empty state is the only content, not heavy text).
- The test: if a sentence explains or justifies rather than labels or names, it goes in a tooltip. The section heading gets the info icon; the descriptive paragraph under it should not exist.
- Skip-to-content link.
aria-current="page"on active nav items.aria-labelon nav landmarks.- Focus-visible rings on interactive elements.
- Screen-reader-only labels (
.sr-only) where needed.
Recharts is the only charting library. All charts must use it via ChartPrimitives.tsx — never import recharts directly in page/section components and never add Chart.js, Highcharts, D3, Plotly, Nivo, or Victory. ESLint enforces this.
- Import chart components and shared constants from
components/shared/ChartPrimitives.js. - Use
CHART_TOOLTIP_STYLE,CHART_AXIS_TICK,CHART_GRID_STROKE,CHART_AXIS_STROKE, andCHART_SERIES_COLORSfor consistent styling. - Use
formatChartDateLabelfor tooltip labels andformatChartDateTickfor axis ticks. - Custom SVG is allowed only for non-chart visualizations (gauges, sparklines, timelines) where Recharts is overkill.
- If Recharts is missing a feature, extend
ChartPrimitives.tsxrather than adding a second library.
The downloadable HTML report (canonry report / GET /report.html) and the in-app SPA report view must stay perfectly aligned. They are two renderers of the same ProjectReportDto — clients and agencies see one report. Any change to a section, label, headline, chart, tile, or order in apps/web/src/pages/ReportPage.tsx must ship the same change in packages/api-routes/src/report-renderer.ts in the same commit, and vice versa. Tile labels, eyebrows, titles, subtitles, action-card copy, and evidence-card titles must match verbatim across both. Update packages/api-routes/test/report-renderer.test.ts whenever client/agency strings change. See AGENTS.md "Report parity" for the full rule set.
- Don't use hero grids with large descriptive text blocks on the project page. Keep headers compact.
- Don't set multi-sentence explanatory prose inline in a data view — move it to an
InfoTooltipon the heading or row title. (Empty / onboarding states are the exception.) - Don't put evidence or findings in card grids. Use tables.
- Don't add decorative background gradients or glow effects.
- Don't create new component files unless the component is reused across 3+ pages.
- Don't import
rechartsdirectly — useChartPrimitives.js. - Don't add alternative charting libraries (Highcharts, Chart.js, D3, etc.).
- Don't change report copy or sections in only one of the SPA / HTML surfaces — they must move together.
The repo ships two Claude skills under skills/, both bundled into the published @ainyc/canonry package and installable into any user's project via canonry skills install:
| Skill | Audience | Purpose |
|---|---|---|
skills/canonry/ |
External users (their Claude Code / Codex) | Operator playbook: how to install canonry, run sweeps, audit indexing, fix integrations |
skills/aero/ |
Aero (canonry's built-in analyst) AND external users | Analyst playbook: regression diagnosis, orchestration, memory patterns, reporting |
Keep both skills in sync with the codebase. Both are co-equal — the analyst playbook ships alongside the operator playbook in every install (see feedback_analyst_is_core memory).
Each skill is a directory tree:
skills/<name>/
SKILL.md # tight entry point (≤ ~100 lines): when to use, top-level capabilities, references TOC
references/ # deep playbooks the agent reads on demand
*.md
SKILL.md is the only file always pulled into agent context when the skill is invoked. References lazy-load — the agent Reads them only when the task matches. Keep SKILL.md lean and push detail into references/.
- New CLI command → add it to
skills/canonry/references/canonry-cli.md - New provider → update the provider list in
SKILL.mdandcanonry-cli.md - New integration (Google/Bing/CDP feature) → update the relevant reference file in
skills/canonry/references/ - Changed troubleshooting patterns → update the troubleshooting table in
SKILL.md - New analytics feature → update
references/aeo-analysis.md - New analyst workflow / reporting template → update
skills/aero/references/
packages/canonry/scripts/copy-agent-assets.tsmirrorsskills/<name>/intopackages/canonry/assets/agent-workspace/skills/<name>/at build time so the trees ship in the published package.canonry skills install [--dir <path>] [--client claude|codex|all] [--force]writes the bundled trees into<dir>/.claude/skills/<name>/and (for codex) creates a relative symlink at<dir>/.codex/skills/<name>pointing back at the Claude path. Default scope: all skills, both clients.canonry initauto-runsinstallSkills()when the cwd looks like a project (has.git,canonry.yaml, orpackage.json); otherwise prints a tip. Pass--skip-skillsto opt out or--skills-dir <path>to override the target.
- Internal implementation details, file paths, or architecture
- Anything that changes every release (version numbers, changelog)
- Dev-only workflows (testing, CI, building from source beyond basic install)