Skip to content

Commit 0776d9a

Browse files
committed
Add AI telemetry/provenance and prerender SEO
Introduce M1 AI surfaces and improve docs prerendering: - Add conversation-arc telemetry (src/components/ai/conversationArc.ts) — opt-in module-scoped ring buffer store with enable/disable, record/flush/getEvents, subscribe/unsubscribe, capacity handling and tests (conversationArc.test.ts). - Add annotation provenance/lifecycle type surface and helper (src/components/ai/annotationProvenance.ts) with accompanying tests to validate non-mutating withProvenance and documented shapes. - Add variant-discovery stubs and tests (src/components/ai/variantDiscovery.ts + variantDiscovery.test.ts) for future proposers/evaluators. - Enhance prerender tooling (scripts/prerender.mjs and its types) to support per-route ROUTE_META, blog OG card copying, blog-aware generatePage signature, meta injection anchored at <body> (works with minified shells), sitemap.xml and robots.txt generation, and related tests updates for prerender behavior. - Update documentation (CLAUDE.md) to describe the new AI APIs: conversation arc, annotation provenance/lifecycle, and variant discovery. These changes add type-safe surfaces and test coverage for AI telemetry and provenance (M1), and improve SEO/static build correctness for blog and section pages.
1 parent ac76930 commit 0776d9a

12 files changed

Lines changed: 1550 additions & 45 deletions

CLAUDE.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,50 @@ function SuggestedChart({ data, intent }) {
325325
}
326326
```
327327

328+
### Conversation-arc telemetry (`semiotic/ai`)
329+
Opt-in event store that records the arc of an AI-assisted session: `suggestion-shown → suggestion-chosen → audience-set → chart-rendered → chart-edited → chart-replaced → chart-exported | chart-abandoned`. Module-scoped, no React provider needed. Default surface is a no-op — call `enableConversationArc()` to start recording.
330+
- **`enableConversationArc({ capacity?, sessionId? })`** → enables recording. Bounded ring buffer (default 1000 events). Safe to call multiple times; reuses the existing session unless `sessionId` is overridden.
331+
- **`disableConversationArc()`** → stops recording without dropping buffered events.
332+
- **`getConversationArcStore()`** → returns `{ enabled, sessionId, capacity, record(input), flush(), getEvents(), subscribe(listener), clear(), reset() }`. Methods are safe to call when disabled (no-op).
333+
- **Events**: `ConversationArcEvent` discriminated union with `type`, `timestamp`, `sessionId`, optional `arcId` + `meta`. Each variant carries its own payload (e.g. `SuggestionShownEvent` has `components`, `intent`, `topScore`, `audience`).
334+
335+
```ts
336+
import { enableConversationArc, getConversationArcStore } from "semiotic/ai"
337+
338+
enableConversationArc()
339+
const store = getConversationArcStore()
340+
const unsub = store.subscribe((event) => console.log(event.type, event))
341+
store.record({ type: "suggestion-shown", components: ["LineChart"], intent: "trend" })
342+
```
343+
344+
### Annotation provenance + lifecycle (`semiotic/ai`, types also re-exported from `semiotic`)
345+
Type surface for "where did this annotation come from?" and "is it stale?" Optional blocks attached to any annotation — existing arrays keep working unchanged.
346+
- **`provenance`**: `{ author?, source?, confidence?, created_at?, stable_id? }`. `source` is an open string union (`"user" | "ai" | "agent" | "import" | "computed" | "system" | (string & {})`).
347+
- **`lifecycle`**: `{ freshness?, ttl_hint?, anchor? }`. `freshness` is `"fresh" | "aging" | "stale" | "expired"`. `anchor` is `"fixed" | "latest" | "sticky" | "semantic"`. `ttl_hint` accepts an ISO 8601 duration string (`"P30D"`) or milliseconds.
348+
- **`withProvenance(annotation, { provenance?, lifecycle? })`** → returns a new annotation with the blocks attached. Pure, SSR-safe.
349+
- **`Annotated<T>`** type alias: `T & { provenance?, lifecycle? }`. Use for explicit typing.
350+
- Type surface only at this stage. Freshness computation, default visual treatment, and stable-id anchor resolution land later.
351+
352+
```ts
353+
import { withProvenance } from "semiotic/ai"
354+
355+
const ann = withProvenance(
356+
{ type: "y-threshold", value: 100, label: "SLA breach" },
357+
{
358+
provenance: { author: "alice", source: "user", created_at: "2026-05-20T14:00:00Z" },
359+
lifecycle: { ttl_hint: "P30D", anchor: "semantic" },
360+
},
361+
)
362+
```
363+
364+
### Variant discovery (`semiotic/ai`)
365+
Interface for proposing and scoring chart variants beyond the hand-curated `capability.variants`. Heuristic and model-based proposers plug in through `registerVariantDiscovery`. M1 ships the type surface + stub implementations; behavior arrives in subsequent milestones.
366+
- **`VariantProposal`**: `{ id, base_component, intent_deltas?, rubric_deltas?, buildProps?, rationale?, source: "manual" | "heuristic" | "model", variant_key?, tags? }`.
367+
- **`VariantScore`**: `{ proposal_id, fit (0–5), novelty (0–1), risk (0–1), reasons }`. `fit` mixes with `suggestCharts` composite scores in unified rankings.
368+
- **`proposeVariant(component, capability, context)`**`VariantProposal[]`. M1 stub returns `[]`.
369+
- **`evaluateVariantProposal(proposal, profile, audience?)`**`VariantScore`. M1 stub returns a neutral baseline with a reason pointing back at the design doc.
370+
- **`registerVariantDiscovery(fn)`** → registers an external proposer, returns an unregister callback. Pair with `getRegisteredVariantDiscovery()` / `clearVariantDiscovery()` for inspection and teardown.
371+
- Full design + sequencing in `docs/strategy/variant-discovery.md`.
328372

329373
## AI Behavior Contracts
330374

scripts/prerender.d.mts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ export function extractRoutesFromSource(source: string): string[]
22

33
export function copyDocsApiAssets(publicApiDir?: string, buildDir?: string): string[]
44

5-
export function generatePage(shellHtml: string, routePath: string): string
5+
export function copyBlogOgCards(publicOgDir?: string, buildDir?: string): string[]
6+
7+
export interface BlogEntryMeta {
8+
slug: string
9+
title: string
10+
subtitle?: string
11+
excerpt?: string
12+
author: string
13+
date: string
14+
tags?: string[]
15+
}
16+
17+
export function generatePage(
18+
shellHtml: string,
19+
routePath: string,
20+
blogMeta?: BlogEntryMeta | null
21+
): string
622

723
export function prerender(): void

0 commit comments

Comments
 (0)