Skip to content

feat: opt-in feedback command and invite - #75

Merged
theDakshJaitly merged 2 commits into
mainfrom
feat/feedback
Jun 9, 2026
Merged

feat: opt-in feedback command and invite#75
theDakshJaitly merged 2 commits into
mainfrom
feat/feedback

Conversation

@theDakshJaitly

Copy link
Copy Markdown
Collaborator

The third and final piece of the telemetry + feedback handoff. Branches off main (E1 #73 and telemetry #74 are merged).

What

  • mex feedback opens a hosted form (FEEDBACK_FORM_URL → Tally) in the browser so users can opt in to maintainer user-research calls.
  • A quiet, dismissible one-line invite appears after a successful check/sync and in the bare-mex TUI.

Trust properties

  • The CLI never collects, prompts for, or transmits an email — its job ends at opening the URL. The email is entered by the user on the web form only.
  • Fully separate from telemetry: the feedback module does not import the telemetry module (or vice versa), and no feedback value ever enters the telemetry payload.
  • Doesn't nag: the invite is TTY-gated (never in pipes/CI), printed to stderr (so it can't corrupt check --json), shown a few times then stops on its own. mex feedback or mex config set feedback off dismisses it for good; on re-enables.

Also folds in the E1 (#73) Copilot review nits

  • ensureScaffoldIdentity/getScaffoldIdentity now backfill an empty scaffold_name without regenerating the id.
  • Reworded ".mex/config.json" → "the scaffold's config.json" in the ScaffoldIdentity JSDoc and CHANGELOG (the context/ layout stores config at the repo root).

Tests

229 pass, typecheck clean. New test/feedback.test.ts covers: form URL, opener seam + error swallowing, engage-dismisses-invite, TTY gating, the show-cap, stderr-not-stdout, and re-enable. Plus a regression test for the scaffold_name backfill.

- `mex feedback` opens a hosted form (FEEDBACK_FORM_URL) in the browser so
  users can opt in to maintainer user-research calls. The CLI never collects,
  prompts for, or transmits an email — its job ends at opening the URL.
- A quiet, dismissible one-line invite appears after a successful `check`/`sync`
  (to stderr, so it never corrupts `--json`) and in the bare-`mex` TUI. It's
  TTY-gated, shown a few times then stops on its own, and `mex feedback` or
  `mex config set feedback off` dismisses it for good (`on` re-enables).
- Kept fully separate from telemetry: the feedback module does not import the
  telemetry module (or vice versa), and no feedback value enters the telemetry
  payload.

Also folds in the Copilot review nits from the E1 PR (#73):
- ensureScaffoldIdentity/getScaffoldIdentity now backfill an empty
  scaffold_name without regenerating the id.
- Reword "`.mex/config.json`" to "the scaffold's config.json" in the
  ScaffoldIdentity JSDoc and CHANGELOG (the context/ layout stores it at the
  repo root).
Copilot AI review requested due to automatic review settings June 9, 2026 06:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in feedback flow to the mex CLI while keeping it isolated from telemetry, and folds in a small scaffold-identity robustness improvement (backfilling missing scaffold_name without regenerating IDs). This fits alongside the previously merged scaffold identity + telemetry handoff by providing a separate, user-initiated channel for maintainer research contact.

Changes:

  • Introduces mex feedback to open a hosted feedback form URL and permanently dismiss future invites on engagement.
  • Adds a quiet invite surface after check/sync and in the Ink TUI, with global-config persistence for dismissal/show-count.
  • Improves scaffold identity handling to backfill a missing/empty scaffold_name without changing an existing scaffold_id, with regression coverage.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/feedback.test.ts Adds unit tests for feedback URL opening, TTY gating, invite show-cap, stderr behavior, and enable/disable flows.
test/config.test.ts Adds regression coverage for scaffold identity backfill without regenerating scaffold_id.
src/types.ts Updates ScaffoldIdentity JSDoc wording to match current scaffold config location semantics.
src/tui.ts Displays the feedback invite in the TUI (subject to invite state).
src/global-config.ts Extends global config schema with feedback invite dismissal and show-count keys.
src/feedback/index.ts Implements feedback command behavior, browser opener seam, invite gating, persistence, and invite printing.
src/config.ts Updates scaffold identity ensure/get logic to backfill missing scaffold_name while preserving scaffold_id.
src/cli.ts Wires in feedback command and invite surfacing; extends mex config set with `feedback on
CHANGELOG.md Documents the new feedback command/invite and updates scaffold identity wording.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli.ts Outdated
Comment on lines 165 to 168
// Warm moment — the user just got a drift result. Quietly invite feedback.
maybeShowInvite();

if (hasErrors) process.exit(1);
Comment thread src/feedback/index.ts
Comment on lines +112 to +127
/**
* Print the invite to **stderr** (so it never corrupts machine-readable stdout
* like `check --json`) and record that it was shown. No-op when the invite
* should not show. Returns true if it was printed.
*/
export function maybeShowInvite(): boolean {
if (!shouldShowInvite()) return false;
try {
process.stderr.write(`\n ${INVITE_TEXT}\n (hide: mex config set feedback off)\n\n`);
const count = readGlobalConfig().feedbackInviteCount;
setGlobalConfigKey("feedbackInviteCount", (typeof count === "number" ? count : 0) + 1);
return true;
} catch {
return false;
}
}
Comment thread src/tui.ts Outdated
import { runDriftCheck } from "./drift/index.js";
import { checkHeartbeat, type HeartbeatResult } from "./heartbeat.js";
import { appendEvent, readEvents, type EventEntry, type EventKind } from "./events.js";
import { isInviteDismissed, INVITE_TEXT } from "./feedback/index.js";
Comment thread src/tui.ts Outdated
Comment on lines +199 to +203
),
h(Box, { marginTop: 1 }, h(Text, { dimColor: true }, "↑/↓ choose · enter run · r refresh · l log · esc dashboard · q quit")),
isInviteDismissed()
? null
: h(Box, { marginTop: 1 }, h(Text, { color: COLORS.crab }, `✨ ${INVITE_TEXT}`)),
- The check invite now fires only on a clean check (after the error-exit),
  not right before `process.exit(1)` on a drift-failing check — matches
  "after a successful check".
- The TUI invite now respects the same gating as the CLI nudge: it uses
  shouldShowInvite() (so the INVITE_MAX_SHOWS cap and dismissal both apply)
  and records one show per TUI session via a useEffect. Extracted a
  recordInviteShown() helper so the count is bumped consistently from both
  surfaces without printing.
@theDakshJaitly
theDakshJaitly merged commit 43af616 into main Jun 9, 2026
2 checks passed
@theDakshJaitly
theDakshJaitly deleted the feat/feedback branch June 9, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants