diff --git a/runner/apps/authoring/src/App.tsx b/runner/apps/authoring/src/App.tsx index 89a56e8f97..35e1542cf3 100644 --- a/runner/apps/authoring/src/App.tsx +++ b/runner/apps/authoring/src/App.tsx @@ -94,7 +94,6 @@ import { } from "./telemetry/metrics.js"; import { consumeForkMarker, - browserCountsSave, exampleActionAttrs, exampleOpenAttrs, exampleOpenKey, @@ -3136,8 +3135,6 @@ function Authoring({ }); await assertApiOk(res, `save failed (${res.status})`); if (filesRef.current === sentFiles) clearDirty(); - // Fallback for an API without the marker; remove once every deployed API sends it. - if (browserCountsSave(await res.json().catch(() => null))) noteExampleAction("example.saved"); } catch (e) { // Losing a save is the worst outcome in the app — the user's edits are only // in this tab's memory until the PATCH lands. Which is exactly why an diff --git a/runner/apps/authoring/src/exampleAnalytics.ts b/runner/apps/authoring/src/exampleAnalytics.ts index e2a5dbf4f9..5075e80403 100644 --- a/runner/apps/authoring/src/exampleAnalytics.ts +++ b/runner/apps/authoring/src/exampleAnalytics.ts @@ -150,10 +150,3 @@ export function consumeForkMarker(search: string): { isFork: boolean; search: st export function exampleOpenKey(lineage: string, version: string): string { return `${lineage}\u0000${version}`; } - -/** Whether the browser counts an editor Save itself: only when the Save - * response lacks the API's `exampleSaved` marker, i.e. an API that does not - * write `example.saved` (contract §5). Remove once every deployed API sends it. */ -export function browserCountsSave(saveResponse: unknown): boolean { - return !(typeof saveResponse === "object" && saveResponse !== null && "exampleSaved" in saveResponse); -} diff --git a/runner/docs/observability-contract.md b/runner/docs/observability-contract.md index 6b8cd2d2be..e0d0568786 100644 --- a/runner/docs/observability-contract.md +++ b/runner/docs/observability-contract.md @@ -366,9 +366,7 @@ What gates the count is a successful rebuild whose request carries a valid field only while its own telemetry gate is open (§10). The rebuild and the point are registered with `ctx.waitUntil`, so a client disconnect within the 30 s grace does not cancel them. Every rebuild response carries `exampleSaved` (whether the point was -written). The editor emits the browser `example.saved` only when a response lacks that -key, i.e. an API that does not count saves; that fallback can be removed once every -deployed API sends the marker. +written). The API is the only emitter of `example.saved`; the editor never emits it. A build that fails on the demo's own input is client input (`isUserBuildError` in `workers/api/src/share.ts`), on any route that builds inline: `POST /api/demos`, `PATCH diff --git a/runner/e2e/example-analytics.spec.ts b/runner/e2e/example-analytics.spec.ts index 9ce2d02364..f341340523 100644 --- a/runner/e2e/example-analytics.spec.ts +++ b/runner/e2e/example-analytics.spec.ts @@ -385,8 +385,7 @@ test("landing on /edit/:id?fork=1 (onFork's own destination) fires example.open }); // `example.saved` is the API worker's (contract §5): the browser hands it the -// open example's `ht_major` in the Save request, and counts the Save itself -// only when the response lacks the API's `exampleSaved` marker. +// open example's `ht_major` in the Save request and never emits the event. const SAVED_DEMO_ID = "e2esave01"; /** Opens a stubbed saved demo, edits and Saves it against a PATCH answering @@ -433,7 +432,7 @@ async function saveOnce(page: Page, saveResponse: Record) { return { patches, events, open }; } -test("a Save sends the open example's ht_major to the API and, with the API's marker, emits no example.saved itself", async ({ page }) => { +test("a Save sends the open example's ht_major to the API and emits no example.saved itself", async ({ page }) => { const { patches, events, open } = await saveOnce(page, { ok: true, htVersion: "18.0.0", exampleSaved: true }); expect(patches).toHaveLength(1); expect(patches[0]).toHaveProperty("files"); @@ -442,13 +441,10 @@ test("a Save sends the open example's ht_major to the API and, with the API's ma expect(events.filter((e) => e.name === "example.saved")).toHaveLength(0); }); -test("a Save answered without the exampleSaved marker (an API that does not count it) emits one browser example.saved", async ({ page }) => { - const { events } = await saveOnce(page, { ok: true, htVersion: "18.0.0" }); - const saved = events.filter((e) => e.name === "example.saved"); - expect(saved).toHaveLength(1); - expect(saved[0].attributes?.["hot.metric_kind"]).toBe("saved"); - expect(saved[0].attributes?.["hot.ref"]).toBe(SAVED_DEMO_ID); - expect(saved[0].attributes?.["hot.ht_major"]).toBe("18"); +test("a Save answered without an exampleSaved marker emits no browser example.saved", async ({ page }) => { + const { patches, events } = await saveOnce(page, { ok: true, htVersion: "18.0.0" }); + expect(patches).toHaveLength(1); + expect(events.filter((e) => e.name === "example.saved")).toHaveLength(0); }); // A starter picked from the picker is a real, later `example.open` — never diff --git a/runner/pipeline/example-analytics-taxonomy.test.mjs b/runner/pipeline/example-analytics-taxonomy.test.mjs index c4f3b361e4..eac569880b 100644 --- a/runner/pipeline/example-analytics-taxonomy.test.mjs +++ b/runner/pipeline/example-analytics-taxonomy.test.mjs @@ -8,7 +8,6 @@ import test from "node:test"; import assert from "node:assert/strict"; import { - browserCountsSave, consumeForkMarker, exampleActionAttrs, exampleOpenAttrs, @@ -167,15 +166,3 @@ test("consumeForkMarker: one-shot -- reading the stripped search a second time n assert.equal(first.isFork, true); assert.equal(second.isFork, false, "a manual reload of the same (already-stripped) URL must not re-count as a fork"); }); - -// ---- browserCountsSave --------------------------------------------------------- - -test("browserCountsSave: a Save response carrying the API's exampleSaved marker is never counted by the browser", () => { - assert.equal(browserCountsSave({ ok: true, htVersion: "18.0.0", exampleSaved: true }), false); - assert.equal(browserCountsSave({ ok: true, htVersion: "18.0.0", exampleSaved: false }), false); -}); - -test("browserCountsSave: a Save response without the marker (an API that does not count saves) is counted once by the browser", () => { - assert.equal(browserCountsSave({ ok: true, htVersion: "18.0.0" }), true); - assert.equal(browserCountsSave(null), true); -});