@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
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { getCssTokens, fillFitRule, FORBIDDEN_IMPORT_SPECIFIERS } from '@ggui-ai
6
6
  import { classifyUi, MODEL_REGISTRY, listContractGadgets, HOOK_NAME_RE, modelRefOfRoute, isGeneratorId, DANGEROUS_PATTERNS as DANGEROUS_PATTERNS$1, 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
@@ -3166,17 +3166,17 @@ Only list ACTUAL composition problems. Use 'fail' only for a fundamentally incoh
3166
3166
  }
3167
3167
  });
3168
3168
  function stripComments(code) {
3169
- const scanner = ts7.createScanner(
3170
- ts7.ScriptTarget.Latest,
3169
+ const scanner = ts4.createScanner(
3170
+ ts4.ScriptTarget.Latest,
3171
3171
  /* skipTrivia */
3172
3172
  false,
3173
- ts7.LanguageVariant.JSX,
3173
+ ts4.LanguageVariant.JSX,
3174
3174
  code
3175
3175
  );
3176
3176
  let out = "";
3177
- for (let token = scanner.scan(); token !== ts7.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
3177
+ for (let token = scanner.scan(); token !== ts4.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
3178
3178
  const text = scanner.getTokenText();
3179
- out += token === ts7.SyntaxKind.SingleLineCommentTrivia || token === ts7.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
3179
+ out += token === ts4.SyntaxKind.SingleLineCommentTrivia || token === ts4.SyntaxKind.MultiLineCommentTrivia ? text.replace(/[^\n]/g, " ") : text;
3180
3180
  }
3181
3181
  return out;
3182
3182
  }
@@ -3199,20 +3199,20 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3199
3199
  "Link"
3200
3200
  ]);
3201
3201
  const HANDLER_ATTRS = ["onClick", "onChange", "onPress", "onSelect"];
3202
- const sf = ts7.createSourceFile(
3202
+ const sf = ts4.createSourceFile(
3203
3203
  "source.tsx",
3204
3204
  sourceCode,
3205
- ts7.ScriptTarget.Latest,
3205
+ ts4.ScriptTarget.Latest,
3206
3206
  /* setParentNodes */
3207
3207
  true,
3208
- ts7.ScriptKind.TSX
3208
+ ts4.ScriptKind.TSX
3209
3209
  );
3210
3210
  function tagNameText(node) {
3211
- return ts7.isIdentifier(node.tagName) ? node.tagName.text : void 0;
3211
+ return ts4.isIdentifier(node.tagName) ? node.tagName.text : void 0;
3212
3212
  }
3213
3213
  function attrExpression(node, attrName) {
3214
3214
  for (const attr of node.attributes.properties) {
3215
- if (ts7.isJsxAttribute(attr) && ts7.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts7.isJsxExpression(attr.initializer)) {
3215
+ if (ts4.isJsxAttribute(attr) && ts4.isIdentifier(attr.name) && attr.name.text === attrName && attr.initializer !== void 0 && ts4.isJsxExpression(attr.initializer)) {
3216
3216
  return attr.initializer.expression;
3217
3217
  }
3218
3218
  }
@@ -3224,19 +3224,19 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3224
3224
  if (anyElementIsInteractive) return true;
3225
3225
  if (!TRAIT_HOST_TAGS.has(tag)) return false;
3226
3226
  const asExpr = attrExpression(node, "as");
3227
- return asExpr !== void 0 && ts7.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
3227
+ return asExpr !== void 0 && ts4.isIdentifier(asExpr) && TRAITS_THAT_FIRE.has(asExpr.text);
3228
3228
  }
3229
3229
  function extractCalleeName(expr) {
3230
3230
  if (expr === void 0) return void 0;
3231
- if (ts7.isIdentifier(expr)) return expr.text;
3232
- if (ts7.isArrowFunction(expr) || ts7.isFunctionExpression(expr)) {
3231
+ if (ts4.isIdentifier(expr)) return expr.text;
3232
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) {
3233
3233
  const body = expr.body;
3234
- if (ts7.isCallExpression(body) && ts7.isIdentifier(body.expression)) {
3234
+ if (ts4.isCallExpression(body) && ts4.isIdentifier(body.expression)) {
3235
3235
  return body.expression.text;
3236
3236
  }
3237
- if (ts7.isBlock(body)) {
3237
+ if (ts4.isBlock(body)) {
3238
3238
  for (const stmt of body.statements) {
3239
- if (ts7.isExpressionStatement(stmt) && ts7.isCallExpression(stmt.expression) && ts7.isIdentifier(stmt.expression.expression)) {
3239
+ if (ts4.isExpressionStatement(stmt) && ts4.isCallExpression(stmt.expression) && ts4.isIdentifier(stmt.expression.expression)) {
3240
3240
  return stmt.expression.expression.text;
3241
3241
  }
3242
3242
  }
@@ -3248,7 +3248,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3248
3248
  let found;
3249
3249
  function walk(node) {
3250
3250
  if (found !== void 0) return;
3251
- const opening = ts7.isJsxElement(node) ? node.openingElement : ts7.isJsxSelfClosingElement(node) ? node : void 0;
3251
+ const opening = ts4.isJsxElement(node) ? node.openingElement : ts4.isJsxSelfClosingElement(node) ? node : void 0;
3252
3252
  if (opening !== void 0) {
3253
3253
  const tag = tagNameText(opening);
3254
3254
  if (tag !== void 0 && (anyElementIsInteractive || INTERACTIVE_DESCENDANTS.has(tag))) {
@@ -3264,13 +3264,13 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3264
3264
  }
3265
3265
  }
3266
3266
  }
3267
- ts7.forEachChild(node, walk);
3267
+ ts4.forEachChild(node, walk);
3268
3268
  }
3269
- ts7.forEachChild(outer, walk);
3269
+ ts4.forEachChild(outer, walk);
3270
3270
  return found;
3271
3271
  }
3272
3272
  function visit(node) {
3273
- if (ts7.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
3273
+ if (ts4.isJsxElement(node) && isTraitHostWithFiringAs(node.openingElement)) {
3274
3274
  const outerCallee = extractCalleeName(attrExpression(node.openingElement, "onClick")) ?? extractCalleeName(attrExpression(node.openingElement, "onPress"));
3275
3275
  if (outerCallee !== void 0 && bindings.has(outerCallee)) {
3276
3276
  const match = findMatchingInteractiveDescendant(node, outerCallee);
@@ -3291,7 +3291,7 @@ function detectCertainDoubleWiredActions(sourceCode, actionBindings, designMode
3291
3291
  }
3292
3292
  }
3293
3293
  }
3294
- ts7.forEachChild(node, visit);
3294
+ ts4.forEachChild(node, visit);
3295
3295
  }
3296
3296
  visit(sf);
3297
3297
  return issues;
@@ -3310,6 +3310,63 @@ function isGadgetExportImported(source, pkg, exportName) {
3310
3310
  }
3311
3311
  return false;
3312
3312
  }
3313
+ function detectComponentNeverReturns(sourceCode) {
3314
+ const sf = ts4.createSourceFile(
3315
+ "source.tsx",
3316
+ sourceCode,
3317
+ ts4.ScriptTarget.Latest,
3318
+ /* setParentNodes */
3319
+ true,
3320
+ ts4.ScriptKind.TSX
3321
+ );
3322
+ const isFunctionLike = (n) => ts4.isFunctionDeclaration(n) || ts4.isFunctionExpression(n) || ts4.isArrowFunction(n) || ts4.isMethodDeclaration(n) || ts4.isClassDeclaration(n) || ts4.isClassExpression(n);
3323
+ function resolveTopLevel(name) {
3324
+ for (const st of sf.statements) {
3325
+ if (ts4.isFunctionDeclaration(st) && st.name?.text === name) return st;
3326
+ if (ts4.isVariableStatement(st)) {
3327
+ for (const decl of st.declarationList.declarations) {
3328
+ if (ts4.isIdentifier(decl.name) && decl.name.text === name && decl.initializer !== void 0 && (ts4.isArrowFunction(decl.initializer) || ts4.isFunctionExpression(decl.initializer))) {
3329
+ return decl.initializer;
3330
+ }
3331
+ }
3332
+ }
3333
+ }
3334
+ return void 0;
3335
+ }
3336
+ let component;
3337
+ for (const st of sf.statements) {
3338
+ if (ts4.isFunctionDeclaration(st) && st.modifiers?.some((m) => m.kind === ts4.SyntaxKind.DefaultKeyword)) {
3339
+ component = st;
3340
+ break;
3341
+ }
3342
+ if (ts4.isExportAssignment(st) && st.isExportEquals !== true) {
3343
+ const expr = st.expression;
3344
+ if (ts4.isArrowFunction(expr) || ts4.isFunctionExpression(expr)) component = expr;
3345
+ else if (ts4.isIdentifier(expr)) component = resolveTopLevel(expr.text);
3346
+ break;
3347
+ }
3348
+ }
3349
+ if (component === void 0) return null;
3350
+ const body = component.body;
3351
+ if (body === void 0 || !ts4.isBlock(body)) return null;
3352
+ let returnsValue = false;
3353
+ let jsxStatementLine;
3354
+ const visit = (node) => {
3355
+ if (returnsValue) return;
3356
+ if (ts4.isReturnStatement(node)) {
3357
+ if (node.expression !== void 0) returnsValue = true;
3358
+ return;
3359
+ }
3360
+ if (jsxStatementLine === void 0 && ts4.isExpressionStatement(node) && (ts4.isJsxElement(node.expression) || ts4.isJsxFragment(node.expression) || ts4.isJsxSelfClosingElement(node.expression))) {
3361
+ jsxStatementLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
3362
+ }
3363
+ node.forEachChild((child) => {
3364
+ if (!isFunctionLike(child)) visit(child);
3365
+ });
3366
+ };
3367
+ for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
3368
+ return returnsValue ? null : { jsxStatementLine };
3369
+ }
3313
3370
  async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
3314
3371
  const issues = [];
3315
3372
  const lines = sourceCode.split("\n");
@@ -3729,6 +3786,19 @@ ${errorDetail}`,
3729
3786
  fix: "Add export default function Component(props: Props) { ... }"
3730
3787
  });
3731
3788
  }
3789
+ const neverReturns = detectComponentNeverReturns(sourceCode);
3790
+ if (neverReturns !== null) {
3791
+ issues.push({
3792
+ tier: 0,
3793
+ result: "fail",
3794
+ category: "compile",
3795
+ subcategory: "renders-nothing",
3796
+ severity: "critical",
3797
+ 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`,
3798
+ fix: "Wrap the tree in the return the boilerplate gave you: `return (` before the root element and `);` after it.",
3799
+ ...neverReturns.jsxStatementLine === void 0 ? {} : { line: neverReturns.jsxStatementLine }
3800
+ });
3801
+ }
3732
3802
  if (contract) {
3733
3803
  try {
3734
3804
  const contractIssues = validateAllContracts(sourceCode, contract);
@@ -11987,18 +12057,18 @@ ${context}
11987
12057
  }
11988
12058
  var LABELED_COMPONENTS = /* @__PURE__ */ new Set(["Input", "TextArea", "Select"]);
11989
12059
  function checkReactRules(code) {
11990
- const sf = ts7.createSourceFile("component.tsx", code, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TSX);
12060
+ const sf = ts4.createSourceFile("component.tsx", code, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
11991
12061
  const issues = [];
11992
12062
  function visit(node) {
11993
- if (ts7.isJsxSelfClosingElement(node) || ts7.isJsxOpeningElement(node)) {
12063
+ if (ts4.isJsxSelfClosingElement(node) || ts4.isJsxOpeningElement(node)) {
11994
12064
  const tagName = node.tagName.getText(sf);
11995
12065
  if (LABELED_COMPONENTS.has(tagName)) {
11996
12066
  const propNames = /* @__PURE__ */ new Set();
11997
12067
  let hasSpread = false;
11998
12068
  for (const attr of node.attributes.properties) {
11999
- if (ts7.isJsxAttribute(attr)) {
12069
+ if (ts4.isJsxAttribute(attr)) {
12000
12070
  propNames.add(attr.name.getText(sf));
12001
- } else if (ts7.isJsxSpreadAttribute(attr)) {
12071
+ } else if (ts4.isJsxSpreadAttribute(attr)) {
12002
12072
  hasSpread = true;
12003
12073
  }
12004
12074
  }
@@ -12013,7 +12083,7 @@ function checkReactRules(code) {
12013
12083
  }
12014
12084
  }
12015
12085
  }
12016
- ts7.forEachChild(node, visit);
12086
+ ts4.forEachChild(node, visit);
12017
12087
  }
12018
12088
  visit(sf);
12019
12089
  return issues;
@@ -13182,6 +13252,50 @@ function runViewportSized(input) {
13182
13252
  )
13183
13253
  ];
13184
13254
  }
13255
+ var TERMINAL_WORDS = /* @__PURE__ */ new Set([
13256
+ "submit",
13257
+ "confirm",
13258
+ "approve",
13259
+ "schedule",
13260
+ "reserve",
13261
+ "book",
13262
+ "pay",
13263
+ "checkout",
13264
+ "purchase",
13265
+ "order"
13266
+ ]);
13267
+ var TERMINAL_LABEL_RX = /\b(submit|confirm|approve|schedule|reserve|book|pay|checkout|purchase|place\s+order)\b/i;
13268
+ var GUARD_RX = /\b(disabled|aria-disabled)\s*=/;
13269
+ function nameWords(name) {
13270
+ return name.split(/(?=[A-Z])|[_\-\s]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
13271
+ }
13272
+ function findUnguardedTerminalControls(sourceCode) {
13273
+ if (GUARD_RX.test(sourceCode)) return [];
13274
+ const names = /* @__PURE__ */ new Set();
13275
+ for (const m of sourceCode.matchAll(/useAction(?:<[^>]*>)?\(\s*['"`]([^'"`]+)['"`]/g)) {
13276
+ const name = m[1] ?? "";
13277
+ if (nameWords(name).some((w) => TERMINAL_WORDS.has(w))) names.add(name);
13278
+ }
13279
+ for (const m of sourceCode.matchAll(/>([^<>{}]{1,60})<\/Button>/g)) {
13280
+ const label = (m[1] ?? "").replace(/\s+/g, " ").trim();
13281
+ if (label.length > 0 && TERMINAL_LABEL_RX.test(label)) names.add(label.slice(0, 40));
13282
+ }
13283
+ return [...names];
13284
+ }
13285
+ function runTerminalActionUnguarded(input) {
13286
+ if (input.compiledCode === null) return [];
13287
+ const found = findUnguardedTerminalControls(input.sourceCode);
13288
+ if (found.length === 0) return [];
13289
+ const list = found.map((t) => `"${t}"`).join(", ");
13290
+ return [
13291
+ mkIssue(
13292
+ "universal.terminal_action_unguarded",
13293
+ `${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.`,
13294
+ '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.',
13295
+ "warn"
13296
+ )
13297
+ ];
13298
+ }
13185
13299
  var UNIVERSAL_CHECKS = [
13186
13300
  {
13187
13301
  id: "universal.icon_name_known",
@@ -13201,6 +13315,12 @@ var UNIVERSAL_CHECKS = [
13201
13315
  values: ALL_RENDER_VALUES,
13202
13316
  run: runViewportSized
13203
13317
  },
13318
+ {
13319
+ id: "universal.terminal_action_unguarded",
13320
+ axis: "render",
13321
+ values: ALL_RENDER_VALUES,
13322
+ run: runTerminalActionUnguarded
13323
+ },
13204
13324
  {
13205
13325
  id: "universal.accent_text_ink",
13206
13326
  axis: "render",
@@ -15066,7 +15186,8 @@ type ${typeName} = ${tsType};`
15066
15186
  );
15067
15187
  const callSig = tsType === "void" ? "() => void \u2014 fire and forget" : `(data: ${tsType}) => void`;
15068
15188
  const toolHint = tool ? ` \u2192 nextStep: ${tool}` : "";
15069
- actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}`);
15189
+ const onceHint = " \u2014 if this is meant ONCE, disable its control after it fires";
15190
+ actionHookCalls.push(` const ${key} = useAction<${typeName}>('${key}'); // ${callSig}${toolHint}${onceHint}`);
15070
15191
  }
15071
15192
  const streamChannels = contract?.streamSpec ?? {};
15072
15193
  const streamChannelEntries = Object.entries(streamChannels);
@@ -15412,12 +15533,33 @@ Rules:
15412
15533
  3. Default values come from the current request \u2014 so the component renders correctly standalone.
15413
15534
  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.
15414
15535
  5. Props interface must be typed and exported.`;
15415
- var FRAME_SIZING = `## Sizing \u2014 the frame owns the height
15536
+ var TERMINAL_ACTIONS = `## Actions the user means once
15537
+
15538
+ 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.
15539
+
15540
+ 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".
15541
+
15542
+ **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.
15543
+
15544
+ When you do guard one:
15545
+ - Hold a small piece of state for it and set it in the handler: \`const [submitted, setSubmitted] = useState(false)\`.
15546
+ - \`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.
15547
+ - A card re-rendered with new props starts fresh; the state above lives with the component, not with the request.
15548
+
15549
+ 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.`;
15550
+ var FRAME_SIZING = `## Sizing \u2014 the frame owns the height and the width
15416
15551
 
15417
15552
  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:
15418
15553
  - 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.
15419
15554
  - 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.
15420
- - 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.`;
15555
+ - 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.
15556
+
15557
+ 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.**
15558
+
15559
+ - 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.
15560
+ - 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.
15561
+ - Centre that inner column with the container's own centring, not by shrinking the root.
15562
+ - **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.`;
15421
15563
  var COMPONENT_STRUCTURE = `## Component Structure
15422
15564
 
15423
15565
  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.
@@ -15629,6 +15771,7 @@ ${axisSection}
15629
15771
  CONTRACT_SURFACE,
15630
15772
  DEFENSIVE_CODING,
15631
15773
  GESTURE_ROUTING,
15774
+ TERMINAL_ACTIONS,
15632
15775
  FREE_GESTURE_SURFACE,
15633
15776
  ANTI_PATTERNS,
15634
15777
  CROSS_REFERENCE_RULES,
@@ -15659,11 +15802,11 @@ ${inputs.primitivesDoc}`
15659
15802
  }
15660
15803
  var VIRTUAL_DTS_PATH = "/__gadget__.d.ts";
15661
15804
  var EXTRACTOR_COMPILER_OPTIONS = {
15662
- target: ts7.ScriptTarget.ESNext,
15663
- module: ts7.ModuleKind.ESNext,
15664
- moduleResolution: ts7.ModuleResolutionKind.Bundler,
15805
+ target: ts4.ScriptTarget.ESNext,
15806
+ module: ts4.ModuleKind.ESNext,
15807
+ moduleResolution: ts4.ModuleResolutionKind.Bundler,
15665
15808
  allowJs: false,
15666
- jsx: ts7.JsxEmit.ReactJSX,
15809
+ jsx: ts4.JsxEmit.ReactJSX,
15667
15810
  noEmit: true,
15668
15811
  esModuleInterop: true,
15669
15812
  // skipLibCheck so an unresolvable wrapper-internal import (the
@@ -15675,31 +15818,31 @@ var EXTRACTOR_COMPILER_OPTIONS = {
15675
15818
  var callSignatureCache = /* @__PURE__ */ new Map();
15676
15819
  var componentPropsCache = /* @__PURE__ */ new Map();
15677
15820
  function buildExtractorContext(dtsContent, names) {
15678
- const sourceFile = ts7.createSourceFile(
15821
+ const sourceFile = ts4.createSourceFile(
15679
15822
  VIRTUAL_DTS_PATH,
15680
15823
  dtsContent,
15681
- ts7.ScriptTarget.ESNext,
15824
+ ts4.ScriptTarget.ESNext,
15682
15825
  /* setParentNodes */
15683
15826
  true,
15684
- ts7.ScriptKind.TS
15827
+ ts4.ScriptKind.TS
15685
15828
  );
15686
- const defaultLibName = ts7.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
15829
+ const defaultLibName = ts4.getDefaultLibFileName(EXTRACTOR_COMPILER_OPTIONS);
15687
15830
  const host = {
15688
15831
  getSourceFile(fileName) {
15689
15832
  if (fileName === VIRTUAL_DTS_PATH) return sourceFile;
15690
- const libContent = ts7.sys.readFile(
15691
- ts7.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
15833
+ const libContent = ts4.sys.readFile(
15834
+ ts4.getDefaultLibFilePath(EXTRACTOR_COMPILER_OPTIONS).replace(
15692
15835
  /[^/\\]+$/,
15693
15836
  fileName
15694
15837
  )
15695
15838
  );
15696
15839
  if (libContent !== void 0) {
15697
- return ts7.createSourceFile(
15840
+ return ts4.createSourceFile(
15698
15841
  fileName,
15699
15842
  libContent,
15700
- ts7.ScriptTarget.ESNext,
15843
+ ts4.ScriptTarget.ESNext,
15701
15844
  true,
15702
- ts7.ScriptKind.TS
15845
+ ts4.ScriptKind.TS
15703
15846
  );
15704
15847
  }
15705
15848
  return void 0;
@@ -15723,14 +15866,14 @@ function buildExtractorContext(dtsContent, names) {
15723
15866
  },
15724
15867
  fileExists(f) {
15725
15868
  if (f === VIRTUAL_DTS_PATH) return true;
15726
- return ts7.sys.fileExists(f);
15869
+ return ts4.sys.fileExists(f);
15727
15870
  },
15728
15871
  readFile(f) {
15729
15872
  if (f === VIRTUAL_DTS_PATH) return dtsContent;
15730
- return ts7.sys.readFile(f);
15873
+ return ts4.sys.readFile(f);
15731
15874
  }
15732
15875
  };
15733
- const program = ts7.createProgram(
15876
+ const program = ts4.createProgram(
15734
15877
  [VIRTUAL_DTS_PATH],
15735
15878
  EXTRACTOR_COMPILER_OPTIONS,
15736
15879
  host
@@ -15748,9 +15891,9 @@ function buildExtractorContext(dtsContent, names) {
15748
15891
  }
15749
15892
  const MAX_DEPTH2 = 4;
15750
15893
  function renderType(t, depth) {
15751
- const plain = () => checker.typeToString(t, void 0, ts7.TypeFormatFlags.NoTruncation);
15894
+ const plain = () => checker.typeToString(t, void 0, ts4.TypeFormatFlags.NoTruncation);
15752
15895
  if (depth >= MAX_DEPTH2) return plain();
15753
- if ((t.getFlags() & ts7.TypeFlags.Boolean) !== 0) return "boolean";
15896
+ if ((t.getFlags() & ts4.TypeFlags.Boolean) !== 0) return "boolean";
15754
15897
  if (t.isUnion()) {
15755
15898
  return t.types.map((member) => renderType(member, depth)).join(" | ");
15756
15899
  }
@@ -15764,13 +15907,13 @@ function buildExtractorContext(dtsContent, names) {
15764
15907
  const params = cs.getParameters().map((p) => {
15765
15908
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15766
15909
  const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
15767
- const optional = decl !== void 0 && ts7.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15910
+ const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15768
15911
  return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
15769
15912
  }).join(", ");
15770
15913
  const ret = renderType(cs.getReturnType(), depth + 1);
15771
15914
  return `(${params}) => ${ret}`;
15772
15915
  }
15773
- const isObject = (t.getFlags() & ts7.TypeFlags.Object) !== 0;
15916
+ const isObject = (t.getFlags() & ts4.TypeFlags.Object) !== 0;
15774
15917
  const isArrayOrTuple = checker.isArrayType(t) || checker.isTupleType(t);
15775
15918
  const typeArgs = t.typeArguments;
15776
15919
  if (symbol !== void 0 && isObject && !isArrayOrTuple && !isWrapperLocal(symbol) && typeArgs !== void 0 && typeArgs.length > 0) {
@@ -15783,7 +15926,7 @@ function buildExtractorContext(dtsContent, names) {
15783
15926
  const body = props.map((p) => {
15784
15927
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15785
15928
  const pType = decl !== void 0 ? checker.getTypeOfSymbolAtLocation(p, decl) : checker.getDeclaredTypeOfSymbol(p);
15786
- const optional = (p.getFlags() & ts7.SymbolFlags.Optional) !== 0;
15929
+ const optional = (p.getFlags() & ts4.SymbolFlags.Optional) !== 0;
15787
15930
  return `${p.getName()}${optional ? "?" : ""}: ${renderType(pType, depth + 1)}`;
15788
15931
  }).join("; ");
15789
15932
  return `{ ${body} }`;
@@ -15798,7 +15941,7 @@ function buildExtractorContext(dtsContent, names) {
15798
15941
  const name = symbol.getName();
15799
15942
  if (!requested.has(name)) continue;
15800
15943
  if (signaturesByName.has(name)) continue;
15801
- const resolved2 = (symbol.flags & ts7.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
15944
+ const resolved2 = (symbol.flags & ts4.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
15802
15945
  const declarations = resolved2.getDeclarations();
15803
15946
  if (declarations === void 0 || declarations.length === 0) continue;
15804
15947
  const declaration = declarations[0];
@@ -15813,7 +15956,7 @@ function printCallSignature(sig, ctx) {
15813
15956
  const params = sig.getParameters().map((p) => {
15814
15957
  const decl = p.valueDeclaration ?? p.declarations?.[0];
15815
15958
  const pType = decl !== void 0 ? ctx.checker.getTypeOfSymbolAtLocation(p, decl) : ctx.checker.getDeclaredTypeOfSymbol(p);
15816
- const optional = decl !== void 0 && ts7.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15959
+ const optional = decl !== void 0 && ts4.isParameter(decl) && (decl.questionToken !== void 0 || decl.initializer !== void 0);
15817
15960
  return `${p.getName()}${optional ? "?" : ""}: ${ctx.renderType(pType, 0)}`;
15818
15961
  }).join(", ");
15819
15962
  const ret = ctx.renderType(sig.getReturnType(), 0);
@@ -16027,6 +16170,7 @@ ${axisSection}
16027
16170
  CONTRACT_SURFACE,
16028
16171
  DEFENSIVE_CODING,
16029
16172
  GESTURE_ROUTING,
16173
+ TERMINAL_ACTIONS,
16030
16174
  INTERACTIVE_TRAITS,
16031
16175
  ANTI_PATTERNS,
16032
16176
  CROSS_REFERENCE_RULES,
@@ -17085,7 +17229,7 @@ var HOST_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a", "div", "span", "li
17085
17229
  var HOST_CHANGE_TAGS = /* @__PURE__ */ new Set(["select", "input", "textarea"]);
17086
17230
  function findWiring(input) {
17087
17231
  const { sourceCode, hookName, hookArg } = input;
17088
- const sf = ts7.createSourceFile("Component.tsx", sourceCode, ts7.ScriptTarget.Latest, true, ts7.ScriptKind.TSX);
17232
+ const sf = ts4.createSourceFile("Component.tsx", sourceCode, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
17089
17233
  const hookBindings = findHookBindings(sf, hookName, hookArg);
17090
17234
  if (hookBindings.length === 0) {
17091
17235
  return {
@@ -17108,7 +17252,7 @@ function findWiring(input) {
17108
17252
  const customPropElements = [];
17109
17253
  const submitButton = { found: false };
17110
17254
  function visit(node) {
17111
- if (ts7.isJsxAttribute(node) && node.initializer) {
17255
+ if (ts4.isJsxAttribute(node) && node.initializer) {
17112
17256
  const attrName = node.name.getText(sf);
17113
17257
  const initRefs = findReferencedNames(node.initializer, callableNames);
17114
17258
  if (initRefs.size > 0) {
@@ -17146,19 +17290,19 @@ function findWiring(input) {
17146
17290
  }
17147
17291
  }
17148
17292
  }
17149
- if (ts7.isJsxOpeningLikeElement(node)) {
17293
+ if (ts4.isJsxOpeningLikeElement(node)) {
17150
17294
  const tag = getTagName(node);
17151
17295
  if ((tag ?? "").toLowerCase() === "button") {
17152
17296
  const typeAttr = node.attributes.properties.find(
17153
- (p) => ts7.isJsxAttribute(p) && p.name.getText(sf) === "type"
17297
+ (p) => ts4.isJsxAttribute(p) && p.name.getText(sf) === "type"
17154
17298
  );
17155
- if (typeAttr && ts7.isJsxAttribute(typeAttr) && typeAttr.initializer) {
17299
+ if (typeAttr && ts4.isJsxAttribute(typeAttr) && typeAttr.initializer) {
17156
17300
  const v = typeAttr.initializer.getText(sf).toLowerCase();
17157
17301
  if (v.includes("submit")) submitButton.found = true;
17158
17302
  }
17159
17303
  }
17160
17304
  }
17161
- ts7.forEachChild(node, visit);
17305
+ ts4.forEachChild(node, visit);
17162
17306
  }
17163
17307
  visit(sf);
17164
17308
  if (sawClickOnHost) {
@@ -17195,32 +17339,32 @@ function findWiring(input) {
17195
17339
  function findHookBindings(sf, hookName, hookArg) {
17196
17340
  const bindings = [];
17197
17341
  function visit(node) {
17198
- if (ts7.isVariableDeclaration(node) && node.initializer && ts7.isCallExpression(node.initializer)) {
17342
+ if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isCallExpression(node.initializer)) {
17199
17343
  const call = node.initializer;
17200
17344
  const callee = call.expression.getText(sf);
17201
17345
  if (callee !== hookName) {
17202
- ts7.forEachChild(node, visit);
17346
+ ts4.forEachChild(node, visit);
17203
17347
  return;
17204
17348
  }
17205
17349
  const firstArg = call.arguments[0];
17206
- if (!firstArg || !ts7.isStringLiteral(firstArg)) {
17207
- ts7.forEachChild(node, visit);
17350
+ if (!firstArg || !ts4.isStringLiteral(firstArg)) {
17351
+ ts4.forEachChild(node, visit);
17208
17352
  return;
17209
17353
  }
17210
17354
  if (firstArg.text !== hookArg) {
17211
- ts7.forEachChild(node, visit);
17355
+ ts4.forEachChild(node, visit);
17212
17356
  return;
17213
17357
  }
17214
- if (ts7.isIdentifier(node.name)) {
17358
+ if (ts4.isIdentifier(node.name)) {
17215
17359
  bindings.push(node.name.text);
17216
- } else if (ts7.isObjectBindingPattern(node.name)) {
17360
+ } else if (ts4.isObjectBindingPattern(node.name)) {
17217
17361
  for (const elem of node.name.elements) {
17218
17362
  const target = elem.name;
17219
- if (ts7.isIdentifier(target)) bindings.push(target.text);
17363
+ if (ts4.isIdentifier(target)) bindings.push(target.text);
17220
17364
  }
17221
17365
  }
17222
17366
  }
17223
- ts7.forEachChild(node, visit);
17367
+ ts4.forEachChild(node, visit);
17224
17368
  }
17225
17369
  visit(sf);
17226
17370
  return bindings;
@@ -17228,24 +17372,24 @@ function findHookBindings(sf, hookName, hookArg) {
17228
17372
  function expandAliases(sf, bindings) {
17229
17373
  const all = new Set(bindings);
17230
17374
  function visit(node) {
17231
- if (ts7.isVariableDeclaration(node) && node.initializer && ts7.isIdentifier(node.name)) {
17375
+ if (ts4.isVariableDeclaration(node) && node.initializer && ts4.isIdentifier(node.name)) {
17232
17376
  const newName = node.name.text;
17233
17377
  const init = node.initializer;
17234
- if (ts7.isIdentifier(init) && all.has(init.text)) {
17378
+ if (ts4.isIdentifier(init) && all.has(init.text)) {
17235
17379
  all.add(newName);
17236
17380
  }
17237
- if (ts7.isArrowFunction(init) || ts7.isFunctionExpression(init)) {
17381
+ if (ts4.isArrowFunction(init) || ts4.isFunctionExpression(init)) {
17238
17382
  if (bodyReferences(init.body, all)) {
17239
17383
  all.add(newName);
17240
17384
  }
17241
17385
  }
17242
- if (ts7.isCallExpression(init)) {
17386
+ if (ts4.isCallExpression(init)) {
17243
17387
  if (bodyReferences(init, all)) {
17244
17388
  all.add(newName);
17245
17389
  }
17246
17390
  }
17247
17391
  }
17248
- ts7.forEachChild(node, visit);
17392
+ ts4.forEachChild(node, visit);
17249
17393
  }
17250
17394
  visit(sf);
17251
17395
  return all;
@@ -17254,11 +17398,11 @@ function bodyReferences(body, names) {
17254
17398
  let found = false;
17255
17399
  function walk(n) {
17256
17400
  if (found) return;
17257
- if (ts7.isIdentifier(n) && names.has(n.text)) {
17401
+ if (ts4.isIdentifier(n) && names.has(n.text)) {
17258
17402
  found = true;
17259
17403
  return;
17260
17404
  }
17261
- ts7.forEachChild(n, walk);
17405
+ ts4.forEachChild(n, walk);
17262
17406
  }
17263
17407
  walk(body);
17264
17408
  return found;
@@ -17266,16 +17410,16 @@ function bodyReferences(body, names) {
17266
17410
  function findReferencedNames(node, names) {
17267
17411
  const found = /* @__PURE__ */ new Set();
17268
17412
  function walk(n) {
17269
- if (ts7.isIdentifier(n) && names.has(n.text)) {
17413
+ if (ts4.isIdentifier(n) && names.has(n.text)) {
17270
17414
  found.add(n.text);
17271
17415
  }
17272
- ts7.forEachChild(n, walk);
17416
+ ts4.forEachChild(n, walk);
17273
17417
  }
17274
17418
  walk(node);
17275
17419
  return found;
17276
17420
  }
17277
17421
  function getTagName(el) {
17278
- if (ts7.isJsxOpeningLikeElement(el)) {
17422
+ if (ts4.isJsxOpeningLikeElement(el)) {
17279
17423
  return el.tagName.getText();
17280
17424
  }
17281
17425
  return null;
@@ -21019,6 +21163,10 @@ interface Props ${tsInterface}
21019
21163
  if (entry.nextStep) {
21020
21164
  line += `
21021
21165
  Next-step hint: agent intends to call \`${entry.nextStep}\``;
21166
+ }
21167
+ if (entry.confirm === true) {
21168
+ line += `
21169
+ Author flag: \`confirm\` \u2014 the author marked this action grave enough to confirm before it fires, so a second press is a mistake.`;
21022
21170
  }
21023
21171
  return line;
21024
21172
  }).join("\n");