@ggui-ai/ui-gen 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/adapters/generation-dispatch.js +252 -108
  2. package/dist/adapters/generation-dispatch.js.map +1 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/advanced/index.js +261 -113
  5. package/dist/advanced/index.js.map +1 -1
  6. package/dist/boilerplate.js +27 -3
  7. package/dist/boilerplate.js.map +1 -1
  8. package/dist/check/index.js +70 -0
  9. package/dist/check/index.js.map +1 -1
  10. package/dist/evaluation/axis-checks/index.js +50 -0
  11. package/dist/evaluation/axis-checks/index.js.map +1 -1
  12. package/dist/evaluation/axis-checks/registry.js +50 -0
  13. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  14. package/dist/evaluation/index.js +50 -0
  15. package/dist/evaluation/index.js.map +1 -1
  16. package/dist/harness/index.js +27 -3
  17. package/dist/harness/index.js.map +1 -1
  18. package/dist/harness/prompts.js +4 -0
  19. package/dist/harness/prompts.js.map +1 -1
  20. package/dist/harness/runtime.js +27 -3
  21. package/dist/harness/runtime.js.map +1 -1
  22. package/dist/harness/types-public.js +27 -3
  23. package/dist/harness/types-public.js.map +1 -1
  24. package/dist/index.js +261 -113
  25. package/dist/index.js.map +1 -1
  26. package/package.json +12 -12
  27. package/src/boilerplate/free-design-prompt.ts +2 -0
  28. package/src/boilerplate/generate.ts +8 -1
  29. package/src/boilerplate/hard-sections.ts +31 -2
  30. package/src/boilerplate/system-prompt.ts +2 -0
  31. package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +5 -3
  32. package/src/boilerplate/templates/layouts/fullscreen-mobile.tsx.tmpl +3 -3
  33. package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +5 -3
  34. package/src/check/run-tier0.ts +152 -0
  35. package/src/contract-context.ts +13 -0
  36. package/src/evaluation/axis-checks/checks/universal.ts +64 -0
@@ -6,7 +6,7 @@ import { getCssTokens, fillFitRule, FORBIDDEN_IMPORT_SPECIFIERS } from '@ggui-ai
6
6
  import { classifyUi, MODEL_REGISTRY, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, listContractGadgets, HOOK_NAME_RE, modelRefOfRoute, isGeneratorId, APP_GENERATION_PROFILE_BOUNDS, STDLIB_GADGETS_PACKAGE, resolveAppGadgets } from '@ggui-ai/protocol';
7
7
  import fs, { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync, rm, createReadStream, createWriteStream, readdirSync, statSync } from 'fs';
8
8
  import { contentHash } from '@ggui-ai/protocol/content-hash';
9
- import ts7 from 'typescript';
9
+ import ts4 from 'typescript';
10
10
  import path, { dirname, resolve, join, basename } from 'path';
11
11
  import { createRequire } from 'module';
12
12
  import { Linter } from 'eslint';
@@ -1935,53 +1935,53 @@ var init_validation = __esm({
1935
1935
  }
1936
1936
  });
1937
1937
  function extractWireCallSites(code) {
1938
- const sf = ts7.createSourceFile(
1938
+ const sf = ts4.createSourceFile(
1939
1939
  "component.tsx",
1940
1940
  code,
1941
- ts7.ScriptTarget.Latest,
1941
+ ts4.ScriptTarget.Latest,
1942
1942
  /*setParentNodes*/
1943
1943
  true,
1944
- ts7.ScriptKind.TSX
1944
+ ts4.ScriptKind.TSX
1945
1945
  );
1946
1946
  const sites = [];
1947
1947
  function visit(node) {
1948
- if (ts7.isCallExpression(node)) {
1948
+ if (ts4.isCallExpression(node)) {
1949
1949
  const callee = node.expression;
1950
- if (ts7.isIdentifier(callee)) {
1950
+ if (ts4.isIdentifier(callee)) {
1951
1951
  const kind = HOOK_TO_KIND[callee.text];
1952
1952
  if (kind) {
1953
1953
  const firstArg = node.arguments[0];
1954
- if (firstArg && ts7.isStringLiteral(firstArg)) {
1954
+ if (firstArg && ts4.isStringLiteral(firstArg)) {
1955
1955
  sites.push({ kind, name: firstArg.text });
1956
1956
  }
1957
1957
  }
1958
1958
  }
1959
1959
  }
1960
- ts7.forEachChild(node, visit);
1960
+ ts4.forEachChild(node, visit);
1961
1961
  }
1962
1962
  visit(sf);
1963
1963
  return sites;
1964
1964
  }
1965
1965
  function extractWireImports(code) {
1966
- const sf = ts7.createSourceFile(
1966
+ const sf = ts4.createSourceFile(
1967
1967
  "component.tsx",
1968
1968
  code,
1969
- ts7.ScriptTarget.Latest,
1969
+ ts4.ScriptTarget.Latest,
1970
1970
  /*setParentNodes*/
1971
1971
  true,
1972
- ts7.ScriptKind.TSX
1972
+ ts4.ScriptKind.TSX
1973
1973
  );
1974
1974
  const imported = /* @__PURE__ */ new Set();
1975
1975
  for (const stmt of sf.statements) {
1976
- if (!ts7.isImportDeclaration(stmt)) continue;
1976
+ if (!ts4.isImportDeclaration(stmt)) continue;
1977
1977
  const moduleSpecifier = stmt.moduleSpecifier;
1978
- if (!ts7.isStringLiteral(moduleSpecifier)) continue;
1978
+ if (!ts4.isStringLiteral(moduleSpecifier)) continue;
1979
1979
  if (moduleSpecifier.text !== "@ggui-ai/wire") continue;
1980
1980
  const clause = stmt.importClause;
1981
1981
  if (!clause) continue;
1982
1982
  const bindings = clause.namedBindings;
1983
1983
  if (!bindings) continue;
1984
- if (!ts7.isNamedImports(bindings)) continue;
1984
+ if (!ts4.isNamedImports(bindings)) continue;
1985
1985
  for (const el of bindings.elements) {
1986
1986
  imported.add(el.name.text);
1987
1987
  }
@@ -2133,11 +2133,11 @@ var init_json_schema_ts = __esm({
2133
2133
  }
2134
2134
  });
2135
2135
  function extractPropsInterface(code) {
2136
- const sf = ts7.createSourceFile("component.tsx", code, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TSX);
2136
+ const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
2137
2137
  const props = [];
2138
2138
  function visitMembers(members) {
2139
2139
  for (const member of members) {
2140
- if (ts7.isPropertySignature(member) && member.name) {
2140
+ if (ts4.isPropertySignature(member) && member.name) {
2141
2141
  const name = member.name.getText(sf);
2142
2142
  const type = member.type ? member.type.getText(sf) : "unknown";
2143
2143
  const optional = !!member.questionToken;
@@ -2146,17 +2146,17 @@ function extractPropsInterface(code) {
2146
2146
  }
2147
2147
  }
2148
2148
  function visit(node) {
2149
- if (ts7.isInterfaceDeclaration(node) && node.name.text === "Props") {
2149
+ if (ts4.isInterfaceDeclaration(node) && node.name.text === "Props") {
2150
2150
  visitMembers(node.members);
2151
2151
  return;
2152
2152
  }
2153
- if (ts7.isTypeAliasDeclaration(node) && node.name.text === "Props") {
2154
- if (ts7.isTypeLiteralNode(node.type)) {
2153
+ if (ts4.isTypeAliasDeclaration(node) && node.name.text === "Props") {
2154
+ if (ts4.isTypeLiteralNode(node.type)) {
2155
2155
  visitMembers(node.type.members);
2156
2156
  }
2157
2157
  return;
2158
2158
  }
2159
- ts7.forEachChild(node, visit);
2159
+ ts4.forEachChild(node, visit);
2160
2160
  }
2161
2161
  visit(sf);
2162
2162
  return props.length > 0 ? props : null;
@@ -2369,12 +2369,12 @@ function generateFix(code, message, sourceLine) {
2369
2369
  }
2370
2370
  }
2371
2371
  function parseAndStore(vfs, virtualPath, content) {
2372
- const sourceFile = ts7.createSourceFile(
2372
+ const sourceFile = ts4.createSourceFile(
2373
2373
  virtualPath,
2374
2374
  content,
2375
- ts7.ScriptTarget.ES2020,
2375
+ ts4.ScriptTarget.ES2020,
2376
2376
  true,
2377
- virtualPath.endsWith(".tsx") ? ts7.ScriptKind.TSX : ts7.ScriptKind.TS
2377
+ virtualPath.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS
2378
2378
  );
2379
2379
  vfs.set(virtualPath, { content, sourceFile });
2380
2380
  }
@@ -2516,12 +2516,12 @@ function resolveModuleName(vfs, name, containingFile) {
2516
2516
  return { resolvedModule: void 0 };
2517
2517
  }
2518
2518
  function createVfsHost(vfs, componentCode) {
2519
- const componentFile = ts7.createSourceFile(
2519
+ const componentFile = ts4.createSourceFile(
2520
2520
  "Component.tsx",
2521
2521
  componentCode,
2522
- ts7.ScriptTarget.ES2020,
2522
+ ts4.ScriptTarget.ES2020,
2523
2523
  true,
2524
- ts7.ScriptKind.TSX
2524
+ ts4.ScriptKind.TSX
2525
2525
  );
2526
2526
  const host = {
2527
2527
  getSourceFile(fileName) {
@@ -2576,7 +2576,7 @@ function resolved(resolvedFileName) {
2576
2576
  resolvedModule: {
2577
2577
  resolvedFileName,
2578
2578
  isExternalLibraryImport: true,
2579
- extension: ts7.Extension.Dts
2579
+ extension: ts4.Extension.Dts
2580
2580
  }
2581
2581
  };
2582
2582
  }
@@ -2598,18 +2598,18 @@ async function typecheck(code, dtsMap) {
2598
2598
  const prefixedCode = SYNTHETIC_PREFIX + code;
2599
2599
  const lineOffset = SYNTHETIC_PREFIX.split("\n").length - 1;
2600
2600
  const host = createVfsHost(effectiveVfs, prefixedCode);
2601
- const program = ts7.createProgram(["Component.tsx"], COMPILER_OPTIONS, host);
2602
- const diagnostics = ts7.getPreEmitDiagnostics(program);
2601
+ const program = ts4.createProgram(["Component.tsx"], COMPILER_OPTIONS, host);
2602
+ const diagnostics = ts4.getPreEmitDiagnostics(program);
2603
2603
  const errors = [];
2604
2604
  const warnings = [];
2605
2605
  for (const diag of diagnostics) {
2606
2606
  if (diag.file && diag.file.fileName !== "Component.tsx") continue;
2607
2607
  if (!diag.file) continue;
2608
- const diagLine = ts7.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;
2608
+ const diagLine = ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;
2609
2609
  if (diagLine < lineOffset) continue;
2610
- const rawLine = diag.file ? ts7.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1 : 0;
2610
+ const rawLine = diag.file ? ts4.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1 : 0;
2611
2611
  const line = Math.max(1, rawLine - lineOffset);
2612
- const message = ts7.flattenDiagnosticMessageText(diag.messageText, "\n");
2612
+ const message = ts4.flattenDiagnosticMessageText(diag.messageText, "\n");
2613
2613
  const code_ = diag.code;
2614
2614
  const sourceLines = diag.file.text.split("\n");
2615
2615
  const sourceLine = rawLine >= 1 && rawLine <= sourceLines.length ? sourceLines[rawLine - 1]?.trim() ?? "" : "";
@@ -2656,8 +2656,8 @@ var init_type_checker = __esm({
2656
2656
  ]);
2657
2657
  vfsCache = null;
2658
2658
  COMPILER_OPTIONS = {
2659
- target: ts7.ScriptTarget.ES2020,
2660
- module: ts7.ModuleKind.ESNext,
2659
+ target: ts4.ScriptTarget.ES2020,
2660
+ module: ts4.ModuleKind.ESNext,
2661
2661
  // Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)
2662
2662
  // supplies `import React from 'react'` plus a `declare global` JSX
2663
2663
  // namespace shim: `@types/react` v19 removed the *global* `JSX`
@@ -2665,8 +2665,8 @@ var init_type_checker = __esm({
2665
2665
  // for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without
2666
2666
  // the shim, `<div>` degrades to `any` and every typed component
2667
2667
  // falsely rejects the intrinsic `key` prop.
2668
- jsx: ts7.JsxEmit.React,
2669
- moduleResolution: ts7.ModuleResolutionKind.Bundler,
2668
+ jsx: ts4.JsxEmit.React,
2669
+ moduleResolution: ts4.ModuleResolutionKind.Bundler,
2670
2670
  strict: false,
2671
2671
  strictNullChecks: true,
2672
2672
  // Catch undefined.foo() errors that cause runtime crashes
@@ -3158,17 +3158,17 @@ Only list ACTUAL composition problems. Use 'fail' only for a fundamentally incoh
3158
3158
  }
3159
3159
  });
3160
3160
  function stripComments(code) {
3161
- const scanner = ts7.createScanner(
3162
- ts7.ScriptTarget.Latest,
3161
+ const scanner = ts4.createScanner(
3162
+ ts4.ScriptTarget.Latest,
3163
3163
  /* skipTrivia */
3164
3164
  false,
3165
- ts7.LanguageVariant.JSX,
3165
+ ts4.LanguageVariant.JSX,
3166
3166
  code
3167
3167
  );
3168
3168
  let out = "";
3169
- for (let token = scanner.scan(); token !== ts7.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
3169
+ for (let token = scanner.scan(); token !== ts4.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
3170
3170
  const text = scanner.getTokenText();
3171
- out += token === ts7.SyntaxKind.SingleLineCommentTrivia || token === ts7.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
3171
+ out += token === ts4.SyntaxKind.SingleLineCommentTrivia || token === ts4.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
3172
3172
  }
3173
3173
  return out;
3174
3174
  }
@@ -3191,20 +3191,20 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3191
3191
  "Link"
3192
3192
  ]);
3193
3193
  const HANDLER_ATTRS = ["onClick", "onChange", "onPress", "onSelect"];
3194
- const sf = ts7.createSourceFile(
3194
+ const sf = ts4.createSourceFile(
3195
3195
  "source.tsx",
3196
3196
  sourceCode,
3197
- ts7.ScriptTarget.Latest,
3197
+ ts4.ScriptTarget.Latest,
3198
3198
  /* setParentNodes */
3199
3199
  true,
3200
- ts7.ScriptKind.TSX
3200
+ ts4.ScriptKind.TSX
3201
3201
  );
3202
3202
  function tagNameText(node) {
3203
- return ts7.isIdentifier(node.tagName) ? node.tagName.text : void 0;
3203
+ return ts4.isIdentifier(node.tagName) ? node.tagName.text : void 0;
3204
3204
  }
3205
3205
  function attrExpression(node, attrName) {
3206
3206
  for (const attr of node.attributes.properties) {
3207
- if (ts7.isJsxAttribute(attr) && ts7.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts7.isJsxExpression(attr.initializer)) {
3207
+ if (ts4.isJsxAttribute(attr) && ts4.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts4.isJsxExpression(attr.initializer)) {
3208
3208
  return attr.initializer.expression;
3209
3209
  }
3210
3210
  }
@@ -3216,19 +3216,19 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3216
3216
  if (anyElementIsInteractive) return true;
3217
3217
  if (!TRAIT_HOST_TAGS.has(tag)) return false;
3218
3218
  const asExpr = attrExpression(node, "as");
3219
- return asExpr !== void 0 && ts7.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
3219
+ return asExpr !== void 0 && ts4.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
3220
3220
  }
3221
3221
  function extractCalleeName(expr) {
3222
3222
  if (expr === void 0) return void 0;
3223
- if (ts7.isIdentifier(expr)) return expr.text;
3224
- if (ts7.isArrowFunction(expr) || ts7.isFunctionExpression(expr)) {
3223
+ if (ts4.isIdentifier(expr)) return expr.text;
3224
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) {
3225
3225
  const body = expr.body;
3226
- if (ts7.isCallExpression(body) && ts7.isIdentifier(body.expression)) {
3226
+ if (ts4.isCallExpression(body) && ts4.isIdentifier(body.expression)) {
3227
3227
  return body.expression.text;
3228
3228
  }
3229
- if (ts7.isBlock(body)) {
3229
+ if (ts4.isBlock(body)) {
3230
3230
  for (const stmt of body.statements) {
3231
- if (ts7.isExpressionStatement(stmt) && ts7.isCallExpression(stmt.expression) && ts7.isIdentifier(stmt.expression.expression)) {
3231
+ if (ts4.isExpressionStatement(stmt) && ts4.isCallExpression(stmt.expression) && ts4.isIdentifier(stmt.expression.expression)) {
3232
3232
  return stmt.expression.expression.text;
3233
3233
  }
3234
3234
  }
@@ -3240,7 +3240,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3240
3240
  let found;
3241
3241
  function walk(node) {
3242
3242
  if (found !== void 0) return;
3243
- const opening = ts7.isJsxElement(node) ? node.openingElement : ts7.isJsxSelfClosingElement(node) ? node : void 0;
3243
+ const opening = ts4.isJsxElement(node) ? node.openingElement : ts4.isJsxSelfClosingElement(node) ? node : void 0;
3244
3244
  if (opening !== void 0) {
3245
3245
  const tag = tagNameText(opening);
3246
3246
  if (tag !== void 0 && (anyElementIsInteractive || INTERACTIVE_DESCENDANTS.has(tag))) {
@@ -3256,13 +3256,13 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3256
3256
  }
3257
3257
  }
3258
3258
  }
3259
- ts7.forEachChild(node, walk);
3259
+ ts4.forEachChild(node, walk);
3260
3260
  }
3261
- ts7.forEachChild(outer, walk);
3261
+ ts4.forEachChild(outer, walk);
3262
3262
  return found;
3263
3263
  }
3264
3264
  function visit(node) {
3265
- if (ts7.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
3265
+ if (ts4.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
3266
3266
  const outerCallee = extractCalleeName(attrExpression(node.openingElement, "onClick")) ?? extractCalleeName(attrExpression(node.openingElement, "onPress"));
3267
3267
  if (outerCallee !== void 0 && bindings.has(outerCallee)) {
3268
3268
  const match = findMatchingInteractiveDescendant(node, outerCallee);
@@ -3283,7 +3283,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3283
3283
  }
3284
3284
  }
3285
3285
  }
3286
- ts7.forEachChild(node, visit);
3286
+ ts4.forEachChild(node, visit);
3287
3287
  }
3288
3288
  visit(sf);
3289
3289
  return issues;
@@ -3302,6 +3302,63 @@ function isGadgetExportImported(source, pkg, exportName) {
3302
3302
  }
3303
3303
  return false;
3304
3304
  }
3305
+ function detectComponentNeverReturns(sourceCode) {
3306
+ const sf = ts4.createSourceFile(
3307
+ "source.tsx",
3308
+ sourceCode,
3309
+ ts4.ScriptTarget.Latest,
3310
+ /* setParentNodes */
3311
+ true,
3312
+ ts4.ScriptKind.TSX
3313
+ );
3314
+ const isFunctionLike = (n) => ts4.isFunctionDeclaration(n) || ts4.isFunctionExpression(n) || ts4.isArrowFunction(n) || ts4.isMethodDeclaration(n) || ts4.isClassDeclaration(n) || ts4.isClassExpression(n);
3315
+ function resolveTopLevel(name) {
3316
+ for (const st of sf.statements) {
3317
+ if (ts4.isFunctionDeclaration(st) && st.name?.text === name) return st;
3318
+ if (ts4.isVariableStatement(st)) {
3319
+ for (const decl of st.declarationList.declarations) {
3320
+ if (ts4.isIdentifier(decl.name) && decl.name.text === name && decl.initializer !== void 0 && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer))) {
3321
+ return decl.initializer;
3322
+ }
3323
+ }
3324
+ }
3325
+ }
3326
+ return void 0;
3327
+ }
3328
+ let component;
3329
+ for (const st of sf.statements) {
3330
+ if (ts4.isFunctionDeclaration(st) && st.modifiers?.some((m) => m.kind === ts4.SyntaxKind.DefaultKeyword)) {
3331
+ component = st;
3332
+ break;
3333
+ }
3334
+ if (ts4.isExportAssignment(st) && st.isExportEquals !== true) {
3335
+ const expr = st.expression;
3336
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) component = expr;
3337
+ else if (ts4.isIdentifier(expr)) component = resolveTopLevel(expr.text);
3338
+ break;
3339
+ }
3340
+ }
3341
+ if (component === void 0) return null;
3342
+ const body = component.body;
3343
+ if (body === void 0 || !ts4.isBlock(body)) return null;
3344
+ let returnsValue = false;
3345
+ let jsxStatementLine;
3346
+ const visit = (node) => {
3347
+ if (returnsValue) return;
3348
+ if (ts4.isReturnStatement(node)) {
3349
+ if (node.expression !== void 0) returnsValue = true;
3350
+ return;
3351
+ }
3352
+ if (jsxStatementLine === void 0 && ts4.isExpressionStatement(node) && (ts4.isJsxElement(node.expression) || ts4.isJsxFragment(node.expression) || ts4.isJsxSelfClosingElement(node.expression))) {
3353
+ jsxStatementLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
3354
+ }
3355
+ node.forEachChild((child) => {
3356
+ if (!isFunctionLike(child)) visit(child);
3357
+ });
3358
+ };
3359
+ for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
3360
+ return returnsValue ? null : { jsxStatementLine };
3361
+ }
3305
3362
  async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
3306
3363
  const issues = [];
3307
3364
  const lines = sourceCode.split("\n");
@@ -3721,6 +3778,19 @@ ${errorDetail}`,
3721
3778
  fix: "Add export default function Component(props: Props) { ... }"
3722
3779
  });
3723
3780
  }
3781
+ const neverReturns = detectComponentNeverReturns(sourceCode);
3782
+ if (neverReturns !== null) {
3783
+ issues.push({
3784
+ tier: 0,
3785
+ result: "fail",
3786
+ category: "compile",
3787
+ subcategory: "renders-nothing",
3788
+ severity: "critical",
3789
+ description: neverReturns.jsxStatementLine === void 0 ? "The component never returns its tree \u2014 it renders a blank frame" : `The component never returns its tree: the JSX at line ${neverReturns.jsxStatementLine} sits at statement position, so it is built and thrown away and the frame renders blank`,
3790
+ fix: "Wrap the tree in the return the boilerplate gave you: `return (` before the root element and `);` after it.",
3791
+ ...neverReturns.jsxStatementLine === void 0 ? {} : { line: neverReturns.jsxStatementLine }
3792
+ });
3793
+ }
3724
3794
  if (contract) {
3725
3795
  try {
3726
3796
  const contractIssues = validateAllContracts(sourceCode, contract);
@@ -11979,18 +12049,18 @@ ${context}
11979
12049
  }
11980
12050
  var LABELED_COMPONENTS = /* @__PURE__ */ new Set(["Input", "TextArea", "Select"]);
11981
12051
  function checkReactRules(code) {
11982
- const sf = ts7.createSourceFile("component.tsx", code, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TSX);
12052
+ const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
11983
12053
  const issues = [];
11984
12054
  function visit(node) {
11985
- if (ts7.isJsxSelfClosingElement(node) || ts7.isJsxOpeningElement(node)) {
12055
+ if (ts4.isJsxSelfClosingElement(node) || ts4.isJsxOpeningElement(node)) {
11986
12056
  const tagName = node.tagName.getText(sf);
11987
12057
  if (LABELED_COMPONENTS.has(tagName)) {
11988
12058
  const propNames = /* @__PURE__ */ new Set();
11989
12059
  let hasSpread = false;
11990
12060
  for (const attr of node.attributes.properties) {
11991
- if (ts7.isJsxAttribute(attr)) {
12061
+ if (ts4.isJsxAttribute(attr)) {
11992
12062
  propNames.add(attr.name.getText(sf));
11993
- } else if (ts7.isJsxSpreadAttribute(attr)) {
12063
+ } else if (ts4.isJsxSpreadAttribute(attr)) {
11994
12064
  hasSpread = true;
11995
12065
  }
11996
12066
  }
@@ -12005,7 +12075,7 @@ function checkReactRules(code) {
12005
12075
  }
12006
12076
  }
12007
12077
  }
12008
- ts7.forEachChild(node, visit);
12078
+ ts4.forEachChild(node, visit);
12009
12079
  }
12010
12080
  visit(sf);
12011
12081
  return issues;
@@ -13174,6 +13244,50 @@ function runViewportSized(input) {
13174
13244
  )
13175
13245
  ];
13176
13246
  }
13247
+ var TERMINAL_WORDS = /* @__PURE__ */ new Set([
13248
+ "submit",
13249
+ "confirm",
13250
+ "approve",
13251
+ "schedule",
13252
+ "reserve",
13253
+ "book",
13254
+ "pay",
13255
+ "checkout",
13256
+ "purchase",
13257
+ "order"
13258
+ ]);
13259
+ var TERMINAL_LABEL_RX = /\b(submit|confirm|approve|schedule|reserve|book|pay|checkout|purchase|place\s+order)\b/i;
13260
+ var GUARD_RX = /\b(disabled|aria-disabled)\s*=/;
13261
+ function nameWords(name) {
13262
+ return name.split(/(?=[A-Z])|[_\-\s]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
13263
+ }
13264
+ function findUnguardedTerminalControls(sourceCode) {
13265
+ if (GUARD_RX.test(sourceCode)) return [];
13266
+ const names = /* @__PURE__ */ new Set();
13267
+ for (const m of sourceCode.matchAll(/useAction(?:<[^>]*>)?\(\s*['"`]([^'"`]+)['"`]/g)) {
13268
+ const name = m[1] ?? "";
13269
+ if (nameWords(name).some((w) => TERMINAL_WORDS.has(w))) names.add(name);
13270
+ }
13271
+ for (const m of sourceCode.matchAll(/>([^<>{}]{1,60})<\/Button>/g)) {
13272
+ const label = (m[1] ?? "").replace(/\s+/g, " ").trim();
13273
+ if (label.length > 0 && TERMINAL_LABEL_RX.test(label)) names.add(label.slice(0, 40));
13274
+ }
13275
+ return [...names];
13276
+ }
13277
+ function runTerminalActionUnguarded(input) {
13278
+ if (input.compiledCode === null) return [];
13279
+ const found = findUnguardedTerminalControls(input.sourceCode);
13280
+ if (found.length === 0) return [];
13281
+ const list = found.map((t) => `"${t}"`).join(", ");
13282
+ return [
13283
+ mkIssue(
13284
+ "universal.terminal_action_unguarded",
13285
+ `${list} reads as an action the user means once, and nothing in this component disables the control after it fires \u2014 a double-click sends it twice.`,
13286
+ 'Hold the fired state (`const [submitted, setSubmitted] = useState(false)`), set it in the handler, and pass `disabled={submitted}` to that control with a word that says so ("Submitted"). If the action IS meant to repeat \u2014 sending a message, adding an item \u2014 leave it armed and ignore this.',
13287
+ "warn"
13288
+ )
13289
+ ];
13290
+ }
13177
13291
  var UNIVERSAL_CHECKS = [
13178
13292
  {
13179
13293
  id: "universal.icon_name_known",
@@ -13193,6 +13307,12 @@ var UNIVERSAL_CHECKS = [
13193
13307
  values: ALL_RENDER_VALUES,
13194
13308
  run: runViewportSized
13195
13309
  },
13310
+ {
13311
+ id: "universal.terminal_action_unguarded",
13312
+ axis: "render",
13313
+ values: ALL_RENDER_VALUES,
13314
+ run: runTerminalActionUnguarded
13315
+ },
13196
13316
  {
13197
13317
  id: "universal.accent_text_ink",
13198
13318
  axis: "render",
@@ -15058,7 +15178,8 @@ type ${typeName} = ${tsType};`
15058
15178
  );
15059
15179
  const callSig = tsType === "void" ? "() => void \u2014 fire and forget" : `(data: ${tsType}) => void`;
15060
15180
  const toolHint = tool ? ` \u2192 nextStep: ${tool}` : "";
15061
- actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}`);
15181
+ const onceHint = " \u2014 if this is meant ONCE, disable its control after it fires";
15182
+ actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
15062
15183
  }
15063
15184
  const streamChannels = contract?.streamSpec ?? {};
15064
15185
  const streamChannelEntries = Object.entries(streamChannels);
@@ -15404,12 +15525,33 @@ Rules:
15404
15525
  3. Default values come from the current request \u2014 so the component renders correctly standalone.
15405
15526
  4. Copy comes from the props. NEVER invent display text the contract does not carry \u2014 no eyebrow / kicker / overline (a \`<Text caps>\` label), no helper line, no status text, no tagline. A caps label exists only when a prop supplies its text (\`<Text caps>{props.section}</Text>\`); a heading's fallback is the request's own words, never a slogan of your own. Text the agent cannot change is chatter on every render.
15406
15527
  5. Props interface must be typed and exported.`;
15407
- var FRAME_SIZING = `## Sizing \u2014 the frame owns the height
15528
+ var TERMINAL_ACTIONS = `## Actions the user means once
15529
+
15530
+ A second press is a mistake only when it would make a SECOND THING HAPPEN IN THE WORLD \u2014 a second booking, a second charge, a second order, a second application sent. If pressing again would only write the SAME STATE AGAIN \u2014 saving, updating, renaming, setting a preference, toggling \u2014 nothing is duplicated and the control stays armed.
15531
+
15532
+ Guard a control only where you have evidence, not a feeling about the verb: the request itself names a one-time commitment \u2014 "submit the application", "place the order", "confirm the booking".
15533
+
15534
+ **When in doubt, leave it armed.** A control the user cannot press a second time has lost them something that worked \u2014 save, edit, save again is ordinary. A control left armed is only what every interface does by default.
15535
+
15536
+ When you do guard one:
15537
+ - Hold a small piece of state for it and set it in the handler: \`const [submitted, setSubmitted] = useState(false)\`.
15538
+ - \`disabled={submitted}\` on the control that fires it, and say so in the label or beside it ("Submitted", "Request sent") \u2014 a control that is disabled without explanation reads as broken.
15539
+ - A card re-rendered with new props starts fresh; the state above lives with the component, not with the request.
15540
+
15541
+ Nothing in the wire DECLARES one-shot-ness yet, which is why the test is what a second press would DO \u2014 not how final the word sounds.`;
15542
+ var FRAME_SIZING = `## Sizing \u2014 the frame owns the height and the width
15408
15543
 
15409
15544
  The host sizes the iframe; the component never sizes itself to the viewport. NEVER write \`100vh\` / \`100dvh\` / \`100svh\` (or an \`h-screen\` / \`min-h-screen\` class) as a height on any element:
15410
15545
  - An inline card takes its natural height. A content-sized frame is measured FROM the content, so a \`100vh\` root can never shrink and grows the frame on every re-measure.
15411
15546
  - Under fullscreen the frame already stretches your root to its full height. Centre inside it with flex (\`display: flex; flex-direction: column; justify-content: center\`), never with a viewport height.
15412
- - To fill a parent that has a height use \`height: 100%\` or \`flex: 1\`; a viewport unit is only ever a \`max-height\` cap on a scroll region.`;
15547
+ - To fill a parent that has a height use \`height: 100%\` or \`flex: 1\`; a viewport unit is only ever a \`max-height\` cap on a scroll region.
15548
+
15549
+ The width works the same way, and the mistake is the mirror image: **your outermost element FILLS the frame; a width cap belongs INSIDE it, on the column that holds text.**
15550
+
15551
+ - Never put \`maxWidth\` (or a \`max-w-*\` class) on the element you return at the top. On a wide canvas that turns your card into a narrow strip with the frame's ground on either side of it, and on a narrow one it does nothing \u2014 the same composition reading differently at two sizes.
15552
+ - DO cap the reading measure inside: a column of prose is easiest to read at 60\u201370 characters, so wrap the TEXT in a \`<Container maxWidth="sm">\` (or your own \`max-width\`) and leave the root filling. A hero band, a toolbar, a table or an image then still uses the whole width, which is what makes a wide card look composed rather than padded.
15553
+ - Centre that inner column with the container's own centring, not by shrinking the root.
15554
+ - **They are two different elements.** Even when the whole card is one column of text, the root still fills and the column sits INSIDE it \u2014 \`<Box padding="lg"><Container maxWidth="sm">\u2026</Container></Box>\`, never a root that is itself the container. A centred card is centring inside a filled frame, not a shrunken frame.`;
15413
15555
  var COMPONENT_STRUCTURE = `## Component Structure
15414
15556
 
15415
15557
  Keep JSX nesting depth to 3\u20135 levels. When deeper, extract repeated/complex sections into helper components \u2014 named functions defined above the main Component in the same file. Helpers take data + callbacks via props; they don't own state.
@@ -15621,6 +15763,7 @@ ${axisSection}
15621
15763
  CONTRACT_SURFACE,
15622
15764
  DEFENSIVE_CODING,
15623
15765
  GESTURE_ROUTING,
15766
+ TERMINAL_ACTIONS,
15624
15767
  FREE_GESTURE_SURFACE,
15625
15768
  ANTI_PATTERNS,
15626
15769
  CROSS_REFERENCE_RULES,
@@ -15651,11 +15794,11 @@ ${inputs.primitivesDoc}`
15651
15794
  }
15652
15795
  var VIRTUAL_DTS_PATH = "/__gadget__.d.ts";
15653
15796
  var EXTRACTOR_COMPILER_OPTIONS = {
15654
- target: ts7.ScriptTarget.ESNext,
15655
- module: ts7.ModuleKind.ESNext,
15656
- moduleResolution: ts7.ModuleResolutionKind.Bundler,
15797
+ target: ts4.ScriptTarget.ESNext,
15798
+ module: ts4.ModuleKind.ESNext,
15799
+ moduleResolution: ts4.ModuleResolutionKind.Bundler,
15657
15800
  allowJs: false,
15658
- jsx: ts7.JsxEmit.ReactJSX,
15801
+ jsx: ts4.JsxEmit.ReactJSX,
15659
15802
  noEmit: true,
15660
15803
  esModuleInterop: true,
15661
15804
  // skipLibCheck so an unresolvable wrapper-internal import (the
@@ -15667,31 +15810,31 @@ var EXTRACTOR_COMPILER_OPTIONS = {
15667
15810
  var callSignatureCache = /* @__PURE__ */ new Map();
15668
15811
  var componentPropsCache = /* @__PURE__ */ new Map();
15669
15812
  function buildExtractorContext(dtsContent, names) {
15670
- const sourceFile = ts7.createSourceFile(
15813
+ const sourceFile = ts4.createSourceFile(
15671
15814
  VIRTUAL_DTS_PATH,
15672
15815
  dtsContent,
15673
- ts7.ScriptTarget.ESNext,
15816
+ ts4.ScriptTarget.ESNext,
15674
15817
  /* setParentNodes */
15675
15818
  true,
15676
- ts7.ScriptKind.TS
15819
+ ts4.ScriptKind.TS
15677
15820
  );
15678
- const defaultLibName = ts7.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
15821
+ const defaultLibName = ts4.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
15679
15822
  const host = {
15680
15823
  getSourceFile(fileName) {
15681
15824
  if (fileName === VIRTUAL_DTS_PATH) return sourceFile;
15682
- const libContent = ts7.sys.readFile(
15683
- ts7.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
15825
+ const libContent = ts4.sys.readFile(
15826
+ ts4.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
15684
15827
  /[^/\\]+$/,
15685
15828
  fileName
15686
15829
  )
15687
15830
  );
15688
15831
  if (libContent !== void 0) {
15689
- return ts7.createSourceFile(
15832
+ return ts4.createSourceFile(
15690
15833
  fileName,
15691
15834
  libContent,
15692
- ts7.ScriptTarget.ESNext,
15835
+ ts4.ScriptTarget.ESNext,
15693
15836
  true,
15694
- ts7.ScriptKind.TS
15837
+ ts4.ScriptKind.TS
15695
15838
  );
15696
15839
  }
15697
15840
  return void 0;
@@ -15715,14 +15858,14 @@ function buildExtractorContext(dtsContent, names) {
15715
15858
  },
15716
15859
  fileExists(f) {
15717
15860
  if (f === VIRTUAL_DTS_PATH) return true;
15718
- return ts7.sys.fileExists(f);
15861
+ return ts4.sys.fileExists(f);
15719
15862
  },
15720
15863
  readFile(f) {
15721
15864
  if (f === VIRTUAL_DTS_PATH) return dtsContent;
15722
- return ts7.sys.readFile(f);
15865
+ return ts4.sys.readFile(f);
15723
15866
  }
15724
15867
  };
15725
- const program = ts7.createProgram(
15868
+ const program = ts4.createProgram(
15726
15869
  [VIRTUAL_DTS_PATH],
15727
15870
  EXTRACTOR_COMPILER_OPTIONS,
15728
15871
  host
@@ -15740,9 +15883,9 @@ function buildExtractorContext(dtsContent, names) {
15740
15883
  }
15741
15884
  const MAX_DEPTH2 = 4;
15742
15885
  function renderType(t, depth) {
15743
- const plain = () => checker.typeToString(t, void 0, ts7.TypeFormatFlags.NoTruncation);
15886
+ const plain = () => checker.typeToString(t, void 0, ts4.TypeFormatFlags.NoTruncation);
15744
15887
  if (depth >= MAX_DEPTH2) return plain();
15745
- if ((t.getFlags() & ts7.TypeFlags.Boolean) !== 0) return "boolean";
15888
+ if ((t.getFlags() & ts4.TypeFlags.Boolean) !== 0) return "boolean";
15746
15889
  if (t.isUnion()) {
15747
15890
  return t.types.map((member) => renderType(member, depth)).join(" | ");
15748
15891
  }
@@ -15756,13 +15899,13 @@ function buildExtractorContext(dtsContent, names) {
15756
15899
  const params = cs.getParameters().map((p) => {
15757
15900
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15758
15901
  const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
15759
- const optional = decl !== void 0 && ts7.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15902
+ const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15760
15903
  return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
15761
15904
  }).join(", ");
15762
15905
  const ret = renderType(cs.getReturnType(), depth + 1);
15763
15906
  return `(${params}) => ${ret}`;
15764
15907
  }
15765
- const isObject = (t.getFlags() & ts7.TypeFlags.Object) !== 0;
15908
+ const isObject = (t.getFlags() & ts4.TypeFlags.Object) !== 0;
15766
15909
  const isArrayOrTuple = checker.isArrayType(t) || checker.isTupleType(t);
15767
15910
  const typeArgs = t.typeArguments;
15768
15911
  if (symbol !== void 0 && isObject && !isArrayOrTuple && !isWrapperLocal(symbol) && typeArgs !== void 0 && typeArgs.length > 0) {
@@ -15775,7 +15918,7 @@ function buildExtractorContext(dtsContent, names) {
15775
15918
  const body = props.map((p) => {
15776
15919
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15777
15920
  const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
15778
- const optional = (p.getFlags() & ts7.SymbolFlags.Optional) !== 0;
15921
+ const optional = (p.getFlags() & ts4.SymbolFlags.Optional) !== 0;
15779
15922
  return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
15780
15923
  }).join("; ");
15781
15924
  return `{ ${body} }`;
@@ -15790,7 +15933,7 @@ function buildExtractorContext(dtsContent, names) {
15790
15933
  const name = symbol.getName();
15791
15934
  if (!requested.has(name)) continue;
15792
15935
  if (signaturesByName.has(name)) continue;
15793
- const resolved2 = (symbol.flags & ts7.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
15936
+ const resolved2 = (symbol.flags & ts4.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
15794
15937
  const declarations = resolved2.getDeclarations();
15795
15938
  if (declarations === void 0 || declarations.length === 0) continue;
15796
15939
  const declaration = declarations[0];
@@ -15805,7 +15948,7 @@ function printCallSignature(sig, ctx) {
15805
15948
  const params = sig.getParameters().map((p) => {
15806
15949
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15807
15950
  const pType = decl !== void 0 ? ctx.checker.getTypeOfSymbolAtLocation(p, decl) : ctx.checker.getDeclaredTypeOfSymbol(p);
15808
- const optional = decl !== void 0 && ts7.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15951
+ const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15809
15952
  return `${p.getName()}${optional ? "?" : ""}: ${ctx.renderType(pType, 0)}`;
15810
15953
  }).join(", ");
15811
15954
  const ret = ctx.renderType(sig.getReturnType(), 0);
@@ -16019,6 +16162,7 @@ ${axisSection}
16019
16162
  CONTRACT_SURFACE,
16020
16163
  DEFENSIVE_CODING,
16021
16164
  GESTURE_ROUTING,
16165
+ TERMINAL_ACTIONS,
16022
16166
  INTERACTIVE_TRAITS,
16023
16167
  ANTI_PATTERNS,
16024
16168
  CROSS_REFERENCE_RULES,
@@ -17077,7 +17221,7 @@ var HOST_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a", "div", "span", "li
17077
17221
  var HOST_CHANGE_TAGS = /* @__PURE__ */ new Set(["select", "input", "textarea"]);
17078
17222
  function findWiring(input) {
17079
17223
  const { sourceCode, hookName, hookArg } = input;
17080
- const sf = ts7.createSourceFile("Component.tsx", sourceCode, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TSX);
17224
+ const sf = ts4.createSourceFile("Component.tsx", sourceCode, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
17081
17225
  const hookBindings = findHookBindings(sf, hookName, hookArg);
17082
17226
  if (hookBindings.length === 0) {
17083
17227
  return {
@@ -17100,7 +17244,7 @@ function findWiring(input) {
17100
17244
  const customPropElements = [];
17101
17245
  const submitButton = { found: false };
17102
17246
  function visit(node) {
17103
- if (ts7.isJsxAttribute(node) && node.initializer) {
17247
+ if (ts4.isJsxAttribute(node) && node.initializer) {
17104
17248
  const attrName = node.name.getText(sf);
17105
17249
  const initRefs = findReferencedNames(node.initializer, callableNames);
17106
17250
  if (initRefs.size > 0) {
@@ -17138,19 +17282,19 @@ function findWiring(input) {
17138
17282
  }
17139
17283
  }
17140
17284
  }
17141
- if (ts7.isJsxOpeningLikeElement(node)) {
17285
+ if (ts4.isJsxOpeningLikeElement(node)) {
17142
17286
  const tag = getTagName(node);
17143
17287
  if ((tag ?? "").toLowerCase() === "button") {
17144
17288
  const typeAttr = node.attributes.properties.find(
17145
- (p) => ts7.isJsxAttribute(p) && p.name.getText(sf) === "type"
17289
+ (p) => ts4.isJsxAttribute(p) && p.name.getText(sf) === "type"
17146
17290
  );
17147
- if (typeAttr && ts7.isJsxAttribute(typeAttr) && typeAttr.initializer) {
17291
+ if (typeAttr && ts4.isJsxAttribute(typeAttr) && typeAttr.initializer) {
17148
17292
  const v = typeAttr.initializer.getText(sf).toLowerCase();
17149
17293
  if (v.includes("submit")) submitButton.found = true;
17150
17294
  }
17151
17295
  }
17152
17296
  }
17153
- ts7.forEachChild(node, visit);
17297
+ ts4.forEachChild(node, visit);
17154
17298
  }
17155
17299
  visit(sf);
17156
17300
  if (sawClickOnHost) {
@@ -17187,32 +17331,32 @@ function findWiring(input) {
17187
17331
  function findHookBindings(sf, hookName, hookArg) {
17188
17332
  const bindings = [];
17189
17333
  function visit(node) {
17190
- if (ts7.isVariableDeclaration(node) && node.initializer && ts7.isCallExpression(node.initializer)) {
17334
+ if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isCallExpression(node.initializer)) {
17191
17335
  const call = node.initializer;
17192
17336
  const callee = call.expression.getText(sf);
17193
17337
  if (callee !== hookName) {
17194
- ts7.forEachChild(node, visit);
17338
+ ts4.forEachChild(node, visit);
17195
17339
  return;
17196
17340
  }
17197
17341
  const firstArg = call.arguments[0];
17198
- if (!firstArg || !ts7.isStringLiteral(firstArg)) {
17199
- ts7.forEachChild(node, visit);
17342
+ if (!firstArg || !ts4.isStringLiteral(firstArg)) {
17343
+ ts4.forEachChild(node, visit);
17200
17344
  return;
17201
17345
  }
17202
17346
  if (firstArg.text !== hookArg) {
17203
- ts7.forEachChild(node, visit);
17347
+ ts4.forEachChild(node, visit);
17204
17348
  return;
17205
17349
  }
17206
- if (ts7.isIdentifier(node.name)) {
17350
+ if (ts4.isIdentifier(node.name)) {
17207
17351
  bindings.push(node.name.text);
17208
- } else if (ts7.isObjectBindingPattern(node.name)) {
17352
+ } else if (ts4.isObjectBindingPattern(node.name)) {
17209
17353
  for (const elem of node.name.elements) {
17210
17354
  const target = elem.name;
17211
- if (ts7.isIdentifier(target)) bindings.push(target.text);
17355
+ if (ts4.isIdentifier(target)) bindings.push(target.text);
17212
17356
  }
17213
17357
  }
17214
17358
  }
17215
- ts7.forEachChild(node, visit);
17359
+ ts4.forEachChild(node, visit);
17216
17360
  }
17217
17361
  visit(sf);
17218
17362
  return bindings;
@@ -17220,24 +17364,24 @@ function findHookBindings(sf, hookName, hookArg) {
17220
17364
  function expandAliases(sf, bindings) {
17221
17365
  const all = new Set(bindings);
17222
17366
  function visit(node) {
17223
- if (ts7.isVariableDeclaration(node) && node.initializer && ts7.isIdentifier(node.name)) {
17367
+ if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isIdentifier(node.name)) {
17224
17368
  const newName = node.name.text;
17225
17369
  const init = node.initializer;
17226
- if (ts7.isIdentifier(init) && all.has(init.text)) {
17370
+ if (ts4.isIdentifier(init) && all.has(init.text)) {
17227
17371
  all.add(newName);
17228
17372
  }
17229
- if (ts7.isArrowFunction(init) || ts7.isFunctionExpression(init)) {
17373
+ if (ts4.isArrowFunction(init) || ts4.isFunctionExpression(init)) {
17230
17374
  if (bodyReferences(init.body, all)) {
17231
17375
  all.add(newName);
17232
17376
  }
17233
17377
  }
17234
- if (ts7.isCallExpression(init)) {
17378
+ if (ts4.isCallExpression(init)) {
17235
17379
  if (bodyReferences(init, all)) {
17236
17380
  all.add(newName);
17237
17381
  }
17238
17382
  }
17239
17383
  }
17240
- ts7.forEachChild(node, visit);
17384
+ ts4.forEachChild(node, visit);
17241
17385
  }
17242
17386
  visit(sf);
17243
17387
  return all;
@@ -17246,11 +17390,11 @@ function bodyReferences(body, names) {
17246
17390
  let found = false;
17247
17391
  function walk(n) {
17248
17392
  if (found) return;
17249
- if (ts7.isIdentifier(n) && names.has(n.text)) {
17393
+ if (ts4.isIdentifier(n) && names.has(n.text)) {
17250
17394
  found = true;
17251
17395
  return;
17252
17396
  }
17253
- ts7.forEachChild(n, walk);
17397
+ ts4.forEachChild(n, walk);
17254
17398
  }
17255
17399
  walk(body);
17256
17400
  return found;
@@ -17258,16 +17402,16 @@ function bodyReferences(body, names) {
17258
17402
  function findReferencedNames(node, names) {
17259
17403
  const found = /* @__PURE__ */ new Set();
17260
17404
  function walk(n) {
17261
- if (ts7.isIdentifier(n) && names.has(n.text)) {
17405
+ if (ts4.isIdentifier(n) && names.has(n.text)) {
17262
17406
  found.add(n.text);
17263
17407
  }
17264
- ts7.forEachChild(n, walk);
17408
+ ts4.forEachChild(n, walk);
17265
17409
  }
17266
17410
  walk(node);
17267
17411
  return found;
17268
17412
  }
17269
17413
  function getTagName(el) {
17270
- if (ts7.isJsxOpeningLikeElement(el)) {
17414
+ if (ts4.isJsxOpeningLikeElement(el)) {
17271
17415
  return el.tagName.getText();
17272
17416
  }
17273
17417
  return null;
@@ -21010,6 +21154,10 @@ interface Props ${tsInterface}
21010
21154
  if (entry.nextStep) {
21011
21155
  line += `
21012
21156
  Next-step hint: agent intends to call \`${entry.nextStep}\``;
21157
+ }
21158
+ if (entry.confirm === true) {
21159
+ line += `
21160
+ Author flag: \`confirm\` \u2014 the author marked this action grave enough to confirm before it fires, so a second press is a mistake.`;
21013
21161
  }
21014
21162
  return line;
21015
21163
  }).join("\n");