feat: post-render and Studio feedback collection via PostHog surveys (#1101)

* feat(cli): prompt for render satisfaction after successful renders

* feat: add text feedback, doctor context, and Studio render feedback UI

* feat(studio): replace render feedback with session-based Studio experience bar

Move the feedback prompt out of RenderQueueItem (where it triggered every
5th render) into a standalone StudioFeedbackBar mounted at the bottom of
the preview area. The new bar is session-gated (shows after the 5th studio
session), auto-dismisses after 20s, and respects a 30-day cooldown once
dismissed or submitted. Renames telemetry to trackStudioFeedback with a
"studio_experience" survey ID to reflect the broader scope.

* feat(studio): attach browser doctor summary to feedback events

* fix(studio): use recurring interval for feedback instead of one-time cooldown

* fix(cli): skip feedback prompt when an agent runtime is detected

* feat(cli): add hyperframes feedback command and agent render hint

- New `hyperframes feedback --rating <1-5> --comment "..."` command
  for submitting anonymous render satisfaction feedback via telemetry.
- When an AI agent runtime is detected after a render, print a dimmed
  hint to stdout so the agent can optionally call the command instead
  of silently skipping the readline prompt.
- Export getDoctorSummary from telemetry/feedback.ts to share the
  system-info collector between the interactive prompt and the CLI command.
- Register the command in cli.ts and help.ts under Settings.

* fix(studio): align feedback interval to every 15 sessions

* fix: show CLI feedback on first render, Studio every 10 sessions

* feat: add env flags to disable feedback prompts

* feat: env flags to configure feedback prompt frequency

* fix: address review — agent hint reachability, cadence gate, session debounce, deprecated API
This commit is contained in:
Miguel Ángel
2026-05-28 12:17:47 -04:00
committed by GitHub
parent b7b855845a
commit d625dc8509
11 changed files with 552 additions and 99 deletions
+55
View File
@@ -0,0 +1,55 @@
import { defineCommand } from "citty";
import type { Example } from "./_examples.js";
import { trackRenderFeedback } from "../telemetry/events.js";
import { shouldTrack, flush } from "../telemetry/client.js";
import { getDoctorSummary } from "../telemetry/feedback.js";
import { c } from "../ui/colors.js";
export const examples: Example[] = [
["Submit render feedback", 'hyperframes feedback --rating 4 --comment "fast but font missing"'],
["Quick rating only", "hyperframes feedback --rating 5"],
];
function parseRating(raw: string): number | null {
const n = parseInt(raw, 10);
return n >= 1 && n <= 5 && Number.isFinite(n) ? n : null;
}
export default defineCommand({
meta: { name: "feedback", description: "Submit anonymous feedback about your experience" },
args: {
rating: {
type: "string",
description: "Satisfaction rating (1=poor, 5=great)",
required: true,
},
comment: {
type: "string",
description: "Optional details about your experience",
},
},
async run({ args }) {
const rating = parseRating(args.rating);
if (rating === null) {
console.error(c.error("Rating must be between 1 and 5"));
process.exit(1);
}
if (!shouldTrack()) {
console.log(c.dim("Telemetry is disabled. Feedback not sent."));
return;
}
const doctorSummary = await getDoctorSummary();
trackRenderFeedback({
rating,
renderDurationMs: 0,
comment: args.comment || undefined,
doctorSummary,
});
await flush();
console.log(c.dim("Thanks for the feedback!"));
},
});
+5
View File
@@ -45,6 +45,7 @@ import { c } from "../ui/colors.js";
import { formatBytes, formatDuration, errorBox } from "../ui/format.js";
import { renderProgress } from "../ui/progress.js";
import { trackRenderComplete, trackRenderError } from "../telemetry/events.js";
import { maybePromptRenderFeedback } from "../telemetry/feedback.js";
import { bytesToMb } from "../telemetry/system.js";
import { VERSION } from "../version.js";
import { isDevMode } from "../utils/env.js";
@@ -827,6 +828,10 @@ export async function renderLocal(
const elapsed = Date.now() - startTime;
trackRenderMetrics(job, elapsed, options, false);
printRenderComplete(outputPath, elapsed, options.quiet);
await maybePromptRenderFeedback({
renderDurationMs: elapsed,
quiet: options.quiet,
});
if (options.exitAfterComplete) scheduleRenderProcessExit();
}