Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions runner/apps/authoring/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ import {
} from "./telemetry/metrics.js";
import {
consumeForkMarker,
browserCountsSave,
exampleActionAttrs,
exampleOpenAttrs,
exampleOpenKey,
Expand Down Expand Up @@ -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
Expand Down
7 changes: 0 additions & 7 deletions runner/apps/authoring/src/exampleAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 1 addition & 3 deletions runner/docs/observability-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 6 additions & 10 deletions runner/e2e/example-analytics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -433,7 +432,7 @@ async function saveOnce(page: Page, saveResponse: Record<string, unknown>) {
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");
Expand All @@ -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
Expand Down
13 changes: 0 additions & 13 deletions runner/pipeline/example-analytics-taxonomy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
browserCountsSave,
consumeForkMarker,
exampleActionAttrs,
exampleOpenAttrs,
Expand Down Expand Up @@ -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);
});
Loading