Skip to content
Open
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
13 changes: 13 additions & 0 deletions packages/cli/src/telemetry/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
trackFigmaImport,
trackRenderFeedback,
trackRenderPreflightRejected,
trackWebmAlphaDropped,
trackAuthLoginStarted,
trackAuthLoginCompleted,
trackAuthLoginFailed,
Expand Down Expand Up @@ -199,6 +200,18 @@ describe("trackCommandFailure", () => {
});
});

describe("trackWebmAlphaDropped", () => {
beforeEach(() => {
trackEvent.mockClear();
});

it("emits a minimal dropped-alpha event", () => {
trackWebmAlphaDropped();

expect(trackEvent).toHaveBeenCalledWith("webm_alpha_dropped", {});
});
});

describe("trackFigmaImport", () => {
beforeEach(() => {
trackEvent.mockClear();
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/telemetry/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ export function trackTranscribeUnavailable(props: { optional: boolean }): void {
trackEvent("transcribe_unavailable", { optional: props.optional });
}

// Track dropped WebM alpha as an environment signal, so fleet telemetry can
// reveal ffmpeg/libvpx-vp9 builds and OS combinations that need MOV fallback.
export function trackWebmAlphaDropped(): void {
trackEvent("webm_alpha_dropped", {});
}

// A skills install was skipped because a required prerequisite binary is
// absent from PATH (e.g. git on a fresh Windows box). Best-effort callers
// (init) skip cleanly rather than crash, so the skip is otherwise invisible;
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/utils/reject-unknown-flags.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import type { ArgsDef, CommandDef } from "citty";
import addCmd from "../commands/add.js";
import { assertKnownFlags } from "./reject-unknown-flags.js";

const cmd = {
Expand Down Expand Up @@ -39,6 +40,10 @@ describe("assertKnownFlags", () => {
expect(ok(["--no-docker"])).not.toThrow();
});

it("accepts add command --no-clipboard negation", () => {
expect(() => assertKnownFlags(addCmd, ["shader-wipe", "--no-clipboard"])).not.toThrow();
});

it("accepts global flags and stops at --", () => {
expect(ok(["--help"])).not.toThrow();
expect(ok(["--json"])).not.toThrow();
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/reject-unknown-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function unknownFlagIn(tok: string, known: Set<string>): string | null {
* + the global set). Only dash-prefixed tokens are inspected, so positionals and
* flag values pass through untouched. Stops at `--`.
*/
export function assertKnownFlags(cmd: CommandDef<ArgsDef>, rawArgs: string[]): void {
export function assertKnownFlags<T extends ArgsDef>(cmd: CommandDef<T>, rawArgs: string[]): void {
if (!Array.isArray(rawArgs)) return;
// citty types `args` as Resolvable<ArgsDef> (it may be a fn/promise); every
// hyperframes command uses a static object, so treat anything else as "no
Expand Down
51 changes: 49 additions & 2 deletions packages/cli/src/utils/webmAlphaCheck.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
import { describe, expect, it } from "vitest";
import { webmAlphaAdvisory } from "./webmAlphaCheck.js";
import { beforeEach, describe, expect, it, vi } from "vitest";

const execFileSync = vi.fn();
const findFFprobe = vi.fn();
const trackWebmAlphaDropped = vi.fn();

vi.mock("node:child_process", () => ({
execFileSync: (...args: unknown[]) => execFileSync(...args),
}));

vi.mock("../browser/ffmpeg.js", () => ({
findFFprobe: () => findFFprobe(),
}));

vi.mock("../telemetry/events.js", () => ({
trackWebmAlphaDropped: () => trackWebmAlphaDropped(),
}));

const { webmAlphaAdvisory, warnIfWebmAlphaDropped } = await import("./webmAlphaCheck.js");

beforeEach(() => {
execFileSync.mockReset();
findFFprobe.mockReset();
trackWebmAlphaDropped.mockReset();
});

describe("webmAlphaAdvisory", () => {
it("warns when a probed webm lacks the ALPHA_MODE sidecar tag", () => {
Expand Down Expand Up @@ -27,3 +50,27 @@ describe("webmAlphaAdvisory", () => {
expect(webmAlphaAdvisory("mov", { probed: true, alphaMode: false })).toBeUndefined();
});
});

describe("warnIfWebmAlphaDropped", () => {
it("tracks telemetry when a completed WebM lost alpha", () => {
findFFprobe.mockReturnValue("/usr/bin/ffprobe");
execFileSync.mockReturnValue(JSON.stringify({ streams: [{ codec_name: "vp9", tags: {} }] }));
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});

try {
warnIfWebmAlphaDropped("out.webm", "webm", false);
} finally {
warn.mockRestore();
}

expect(trackWebmAlphaDropped).toHaveBeenCalledTimes(1);
});

it("does not track telemetry when quiet or not rendering WebM", () => {
warnIfWebmAlphaDropped("out.webm", "webm", true);
warnIfWebmAlphaDropped("out.mp4", "mp4", false);

expect(trackWebmAlphaDropped).not.toHaveBeenCalled();
expect(findFFprobe).not.toHaveBeenCalled();
});
});
2 changes: 2 additions & 0 deletions packages/cli/src/utils/webmAlphaCheck.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { execFileSync } from "node:child_process";
import { findFFprobe } from "../browser/ffmpeg.js";
import { trackWebmAlphaDropped } from "../telemetry/events.js";
import { c } from "../ui/colors.js";

/**
Expand Down Expand Up @@ -96,6 +97,7 @@ export function warnIfWebmAlphaDropped(outputPath: string, format: string, quiet
if (quiet || format !== "webm") return;
const advisory = webmAlphaAdvisory(format, probeWebmAlpha(outputPath));
if (!advisory) return;
trackWebmAlphaDropped();
console.warn(`\n${c.warn("⚠")} ${c.bold("Transparency not preserved")}`);
console.warn(` ${c.dim(advisory)}\n`);
}
5 changes: 5 additions & 0 deletions packages/producer/src/services/htmlCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,11 @@ export async function discoverMediaFromBrowser(page: Page): Promise<BrowserMedia
if (!id) return;

const src = htmlEl.src || htmlEl.getAttribute("src") || "";
// These parseFloat calls assume the runtime start resolver has already
// numericized data-start before this browser evaluate() runs. If a
// dynamic-src regression lets a nonnumeric data-start reach here,
// parseFloat silently yields NaN and drops timing. See #2062 and
// packages/core/src/runtime/startResolver.ts for the invariant.
const start = parseFloat(htmlEl.getAttribute("data-start") || "0");
const end = parseFloat(htmlEl.getAttribute("data-end") || "0");
const duration = parseFloat(htmlEl.getAttribute("data-duration") || "0");
Expand Down
Loading