flet-box 1.1.2 → 1.1.3

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 (154) hide show
  1. package/AGENTS_FOR_CODE_GEN.md +284 -0
  2. package/CONTRIBUTING.md +238 -0
  3. package/README.md +2 -2
  4. package/bin/cli.js +88 -1
  5. package/bin/commands/auditFix.js +181 -27
  6. package/bin/commands/checkApi.js +14 -3
  7. package/bin/commands/checkStructure.js +368 -0
  8. package/bin/commands/checkSyntax.js +293 -0
  9. package/bin/commands/checkUi.js +666 -14
  10. package/bin/commands/create.js +14 -0
  11. package/bin/commands/createBundle.js +2 -2
  12. package/bin/commands/metrics.js +448 -0
  13. package/bin/commands/release.js +18 -0
  14. package/bin/commands/report.js +849 -0
  15. package/bin/commands/tools.js +76 -0
  16. package/bin/commands/unpack.js +39 -11
  17. package/bin/commands/verify.js +879 -0
  18. package/bin/mcp.js +280 -0
  19. package/bin/utils/contract.js +27 -0
  20. package/bin/utils/dtsProps.js +5 -1
  21. package/bin/utils/migrationGuide.js +146 -0
  22. package/bin/utils/moduleExports.js +9 -5
  23. package/bin/utils/templates.js +225 -18
  24. package/bin/utils/toolkit.js +191 -0
  25. package/docs/README.md +5 -1
  26. package/docs/arquitectura.md +1 -1
  27. package/docs/cli/README.md +168 -2
  28. package/docs/guides/README.md +6 -0
  29. package/docs/guides/accessibility.md +2 -1
  30. package/docs/guides/ai-toolkit.md +166 -0
  31. package/docs/guides/compatibility.md +1 -1
  32. package/docs/guides/migration.md +92 -0
  33. package/docs/guides/minification-and-protection.md +2 -1
  34. package/docs/guides/mobile-and-platforms.md +1 -1
  35. package/docs/guides/online-updates.md +2 -0
  36. package/docs/server/deployment.md +1 -1
  37. package/docs/tools/styling.md +2 -2
  38. package/docs/tools/theme.md +139 -4
  39. package/docs/widget/Accordion.md +74 -8
  40. package/docs/widget/AdMob.md +68 -3
  41. package/docs/widget/AdSense.md +67 -2
  42. package/docs/widget/AlertDialog.md +1 -1
  43. package/docs/widget/AnimatedBox.md +80 -39
  44. package/docs/widget/AnimatedText.md +74 -18
  45. package/docs/widget/Audio.md +73 -14
  46. package/docs/widget/Avatar.md +69 -4
  47. package/docs/widget/Badge.md +68 -3
  48. package/docs/widget/BottomSheet.md +2 -2
  49. package/docs/widget/Button.md +69 -3
  50. package/docs/widget/Card.md +69 -4
  51. package/docs/widget/Carousel.md +67 -2
  52. package/docs/widget/Chart.md +67 -2
  53. package/docs/widget/Checkbox.md +74 -8
  54. package/docs/widget/Chip.md +68 -3
  55. package/docs/widget/CircularBar.md +74 -15
  56. package/docs/widget/CircularChart.md +71 -10
  57. package/docs/widget/CodeViewer.md +67 -2
  58. package/docs/widget/Column.md +68 -3
  59. package/docs/widget/Container.md +68 -3
  60. package/docs/widget/DataTable.md +67 -2
  61. package/docs/widget/Divider.md +67 -2
  62. package/docs/widget/DraggBox.md +67 -2
  63. package/docs/widget/Dropdown.md +76 -10
  64. package/docs/widget/DroppBox.md +67 -2
  65. package/docs/widget/FloatingActionButton.md +68 -3
  66. package/docs/widget/GridView.md +67 -2
  67. package/docs/widget/Icon.md +71 -5
  68. package/docs/widget/Image.md +68 -3
  69. package/docs/widget/Input.md +68 -3
  70. package/docs/widget/Inspector.md +2 -2
  71. package/docs/widget/InstallButton.md +73 -13
  72. package/docs/widget/ListTile.md +69 -3
  73. package/docs/widget/ListView.md +67 -2
  74. package/docs/widget/Maps.md +68 -3
  75. package/docs/widget/Markdown.md +74 -15
  76. package/docs/widget/MatrixRain.md +72 -11
  77. package/docs/widget/Menu.md +209 -0
  78. package/docs/widget/Modal.md +3 -3
  79. package/docs/widget/Pagination.md +68 -3
  80. package/docs/widget/ParallaxBox.md +72 -12
  81. package/docs/widget/ProgressBar.md +69 -4
  82. package/docs/widget/QRCode.md +67 -2
  83. package/docs/widget/README.md +54 -50
  84. package/docs/widget/Radio.md +76 -10
  85. package/docs/widget/Rating.md +70 -6
  86. package/docs/widget/Row.md +68 -3
  87. package/docs/widget/Skeleton.md +68 -3
  88. package/docs/widget/Slider.md +75 -9
  89. package/docs/widget/SnackBar.md +2 -2
  90. package/docs/widget/Stack.md +68 -3
  91. package/docs/widget/Stepper.md +68 -3
  92. package/docs/widget/Switch.md +74 -8
  93. package/docs/widget/Text.md +71 -4
  94. package/docs/widget/ThemePicker.md +197 -0
  95. package/docs/widget/Tooltip.md +72 -7
  96. package/docs/widget/TreeView.md +70 -10
  97. package/docs/widget/Video.md +71 -10
  98. package/package.json +10 -3
  99. package/skills/fletbox-design/SKILL.md +89 -5
  100. package/skills/fletbox-motion/SKILL.md +1 -1
  101. package/skills/fletbox-rules/SKILL.md +56 -4
  102. package/src/core/runApp.js +12 -0
  103. package/src/index.d.ts +374 -101
  104. package/src/index.js +9 -0
  105. package/src/navigations/AdaptiveScaffold.js +2 -2
  106. package/src/navigations/BottomNavigation.js +1 -1
  107. package/src/navigations/Scaffold.js +18 -6
  108. package/src/tools/useState.js +11 -4
  109. package/src/utils/colors.js +275 -0
  110. package/src/utils/errorBoundary.js +288 -0
  111. package/src/utils/index.js +10 -2
  112. package/src/utils/stackPosition.js +39 -24
  113. package/src/utils/syntaxHighlight.js +4 -1
  114. package/src/utils/themes.js +279 -3
  115. package/src/widget-factory/addChildren.js +17 -10
  116. package/src/widget-factory/assignProps.js +18 -6
  117. package/src/widget-factory/createWidget.js +7 -0
  118. package/src/widget-factory/deprecations.js +77 -0
  119. package/src/widget-factory/translateProps.js +11 -1
  120. package/src/widget-factory/warnings.js +1 -27
  121. package/src/widget-factory/widgetFactory.js +59 -17
  122. package/src/widgets/Accordion.js +21 -7
  123. package/src/widgets/Badge.js +11 -2
  124. package/src/widgets/Button.js +15 -3
  125. package/src/widgets/Carousel.js +12 -4
  126. package/src/widgets/Chart.js +7 -2
  127. package/src/widgets/Checkbox.js +1 -2
  128. package/src/widgets/Chip.js +24 -10
  129. package/src/widgets/CircularBar.js +9 -3
  130. package/src/widgets/CircularChart.js +5 -3
  131. package/src/widgets/Container.js +6 -0
  132. package/src/widgets/Divider.js +3 -1
  133. package/src/widgets/DraggBox.js +9 -0
  134. package/src/widgets/Dropdown.js +3 -3
  135. package/src/widgets/DroppBox.js +5 -0
  136. package/src/widgets/FloatingActionButton.js +1 -1
  137. package/src/widgets/Icon.js +5 -1
  138. package/src/widgets/InstallButton.js +4 -2
  139. package/src/widgets/Markdown.js +171 -103
  140. package/src/widgets/Menu.js +340 -0
  141. package/src/widgets/Modal.js +7 -1
  142. package/src/widgets/Pagination.js +10 -5
  143. package/src/widgets/ProgressBar.js +5 -4
  144. package/src/widgets/QRCode.js +4 -0
  145. package/src/widgets/Radio.js +1 -2
  146. package/src/widgets/Rating.js +9 -0
  147. package/src/widgets/Slider.js +4 -1
  148. package/src/widgets/SnackBar.js +2 -2
  149. package/src/widgets/Switch.js +1 -2
  150. package/src/widgets/Text.js +9 -5
  151. package/src/widgets/ThemePicker.js +234 -0
  152. package/src/widgets/TreeView.js +2 -1
  153. package/src/widgets/index.js +2 -0
  154. package/docs/CONTRIBUTING.md +0 -156
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * `flet-box audit-fix` — write what `check-api` only reports.
3
3
  *
4
- * Two repairs, both anchored to spans the gate already knows how to find:
4
+ * Three repairs, all anchored to spans the gate already knows how to find:
5
5
  *
6
- * imports a relative path that does not resolve, a name the target module
7
- * never exports, a default import from a module without a default,
8
- * and identifiers used with no import at all (a flet-box export, or
9
- * a Node builtin used as `path.join(…)`).
10
- * props a key inside `Widget({ … })` that neither the widget's declared
11
- * props, the structural props, nor the engine registry knows —
12
- * `chilldren` → `children`, `postion` → `position`.
6
+ * imports a relative path that does not resolve, a name the target module
7
+ * never exports, a default import from a module without a default,
8
+ * and identifiers used with no import at all (a flet-box export, or
9
+ * a Node builtin used as `path.join(…)`).
10
+ * props a key inside `Widget({ … })` that neither the widget's declared
11
+ * props, the structural props, nor the engine registry knows —
12
+ * `chilldren` → `children`, `postion` → `position`.
13
+ * migrations a key inside `Widget({ … })` that the widget still honours but no
14
+ * longer recommends — `Radio({ onSelect })` → `Radio({ onPress })`.
13
15
  *
14
16
  * A rewrite only happens when the intended name is unambiguous: one candidate,
15
17
  * edit distance small enough that no real name could collide with it. Anything
@@ -23,6 +25,7 @@ import { c, banner, section } from "../utils/colors.js";
23
25
  import { neutralise } from "../utils/neutralise.js";
24
26
  import { resolveLocalModule, readModuleExports } from "../utils/moduleExports.js";
25
27
  import { resolveInterfaceProps } from "../utils/dtsProps.js";
28
+ import { deprecationFor } from "../../src/widget-factory/deprecations.js";
26
29
  import {
27
30
  STRUCTURAL_PROPS,
28
31
  collectSources,
@@ -252,9 +255,13 @@ const strictMatch = (wanted, candidates) => {
252
255
  /**
253
256
  * Top-level keys of an object literal, with their spans.
254
257
  *
258
+ * `entryEnd` is where the whole entry ends — the key *and* its value — which
259
+ * is what a removal has to take, since deleting a key without its value would
260
+ * leave the value behind.
261
+ *
255
262
  * @param {string} code comment/string-free source
256
263
  * @param {number} open index of `{`
257
- * @returns {{ keys: Array<{ name: string, start: number, end: number }>, opaque: boolean }}
264
+ * @returns {{ keys: Array<{ name: string, start: number, end: number, entryEnd: number }>, opaque: boolean }}
258
265
  */
259
266
  const literalKeySpans = (code, open) => {
260
267
  const keys = [];
@@ -268,6 +275,7 @@ const literalKeySpans = (code, open) => {
268
275
  const at = entryStart + (text ? lead : 0);
269
276
  current = "";
270
277
  if (!text) return;
278
+ const entryEnd = at + text.length;
271
279
  if (text.startsWith("...")) {
272
280
  opaque = true;
273
281
  return;
@@ -275,9 +283,9 @@ const literalKeySpans = (code, open) => {
275
283
  const key = /^(?:async\s+)?['"]?([A-Za-z_$][\w$]*)['"]?\s*:/.exec(text);
276
284
  if (key) {
277
285
  const start = at + text.indexOf(key[1]);
278
- keys.push({ name: key[1], start, end: start + key[1].length });
286
+ keys.push({ name: key[1], start, end: start + key[1].length, entryEnd });
279
287
  } else if (/^[A-Za-z_$][\w$]*$/.test(text)) {
280
- keys.push({ name: text, start: at, end: at + text.length });
288
+ keys.push({ name: text, start: at, end: at + text.length, entryEnd });
281
289
  }
282
290
  };
283
291
  for (let i = open; i < code.length; i++) {
@@ -518,20 +526,15 @@ const planImports = (file, code, api, allSources) => {
518
526
  };
519
527
 
520
528
  /**
521
- * Plan the prop-key rewrites for one file.
529
+ * The widgets an app imported from flet-box, as `local name → exported name`.
522
530
  *
523
- * @param {string} file
524
- * @param {string} code
525
- * @param {object} api
526
- * @returns {{ edits: Edit[], skipped: string[] }}
531
+ * Only capitalised exports are widgets; the rest of the barrel is functions
532
+ * and tools, and a call to one of those never carries a props object.
533
+ *
534
+ * @param {string} withStrings Neutralised source, strings kept.
535
+ * @returns {Map<string, string>}
527
536
  */
528
- const planProps = (file, code, api) => {
529
- const edits = [];
530
- const skipped = [];
531
- const rel = path.relative(api.root, file);
532
- const withStrings = neutralise(code, { keepStrings: true });
533
- const body = neutralise(code);
534
-
537
+ const fletWidgetImports = (withStrings) => {
535
538
  const localToWidget = new Map();
536
539
  for (const match of withStrings.matchAll(IMPORT_RE)) {
537
540
  if (match[2] !== "flet-box") continue;
@@ -546,6 +549,54 @@ const planProps = (file, code, api) => {
546
549
  if (/^[A-Z]/.test(exported)) localToWidget.set(local, exported);
547
550
  }
548
551
  }
552
+ return localToWidget;
553
+ };
554
+
555
+ /**
556
+ * The value written after a key of an object literal, or null.
557
+ *
558
+ * @param {string} code Source with the strings kept — a neutralised string
559
+ * keeps its quotes but loses its content, and the content is the value.
560
+ * @param {{ end: number }} key The key's span.
561
+ * @returns {string|null}
562
+ */
563
+ const keyValue = (code, key) => {
564
+ const match = /^\s*:\s*['"]?([^'"\s,}]+)/.exec(code.slice(key.end, key.end + 80));
565
+ return match ? match[1] : null;
566
+ };
567
+
568
+ /**
569
+ * The span that removes one entry of an object literal, comma included.
570
+ *
571
+ * @param {Array<{ start: number, end: number, entryEnd: number }>} keys
572
+ * @param {number} index The entry to remove.
573
+ * @returns {{ start: number, end: number }}
574
+ */
575
+ const removalSpan = (keys, index) => {
576
+ const key = keys[index];
577
+ const next = keys[index + 1];
578
+ if (next) return { start: key.start, end: next.start };
579
+ const prev = keys[index - 1];
580
+ if (prev) return { start: prev.entryEnd, end: key.entryEnd };
581
+ return { start: key.start, end: key.entryEnd };
582
+ };
583
+
584
+ /**
585
+ * Plan the prop-key rewrites for one file.
586
+ *
587
+ * @param {string} file
588
+ * @param {string} code
589
+ * @param {object} api
590
+ * @returns {{ edits: Edit[], skipped: string[] }}
591
+ */
592
+ const planProps = (file, code, api) => {
593
+ const edits = [];
594
+ const skipped = [];
595
+ const rel = path.relative(api.root, file);
596
+ const withStrings = neutralise(code, { keepStrings: true });
597
+ const body = neutralise(code);
598
+
599
+ const localToWidget = fletWidgetImports(withStrings);
549
600
 
550
601
  for (const [local, exported] of localToWidget) {
551
602
  const iface = `${exported}Props`;
@@ -579,6 +630,103 @@ const planProps = (file, code, api) => {
579
630
  return { edits, skipped };
580
631
  };
581
632
 
633
+ /**
634
+ * Plan the renames an app can make without touching a line by hand.
635
+ *
636
+ * A prop the framework still honours but no longer recommends is not broken
637
+ * code, so `check-api` has nothing to say about it — but every screen that
638
+ * keeps the old name keeps an old spelling forever. The table in
639
+ * `src/widget-factory/deprecations.js` is the list, and it is per widget
640
+ * because the same name means different things in different widgets.
641
+ *
642
+ * The rewrite only happens when it is provably the same call: the widget has to
643
+ * declare the old name, it has to declare the new one, and the call must not
644
+ * already carry the new one (two callbacks for one event is a decision, not a
645
+ * rename).
646
+ *
647
+ * A row with no target (`to: null`) is a prop nobody reads any more: the key
648
+ * is removed, together with the comma that held it.
649
+ *
650
+ * @param {string} file
651
+ * @param {string} code
652
+ * @param {object} api
653
+ * @returns {{ edits: Edit[], skipped: string[] }}
654
+ */
655
+ const planMigrations = (file, code, api) => {
656
+ const edits = [];
657
+ const skipped = [];
658
+ const rel = path.relative(api.root, file);
659
+ const withStrings = neutralise(code, { keepStrings: true });
660
+ const body = neutralise(code);
661
+
662
+ for (const [local, exported] of fletWidgetImports(withStrings)) {
663
+ const iface = `${exported}Props`;
664
+ if (!api.interfaces.has(iface)) continue;
665
+ const declared = resolveInterfaceProps(api.interfaces, iface);
666
+ const callRe = new RegExp(`\\b${escapeRe(local)}\\s*\\(\\s*\\{`, "g");
667
+ let call;
668
+ while ((call = callRe.exec(body))) {
669
+ const open = call.index + call[0].length - 1;
670
+ const { keys, opaque } = literalKeySpans(body, open);
671
+ if (opaque) continue;
672
+ const byName = new Map(keys.map((entry) => [entry.name, entry]));
673
+
674
+ for (const key of keys) {
675
+ const rule = deprecationFor(exported, key.name);
676
+ if (!rule || !rule.to) continue;
677
+ if (!declared.has(rule.from) || !declared.has(rule.to)) {
678
+ skipped.push(`${rel}: ${exported}({ ${key.name} }) — the widget does not declare the pair`);
679
+ continue;
680
+ }
681
+ const inert = (rule.guard || []).find((name) => byName.has(name));
682
+ if (inert) {
683
+ skipped.push(
684
+ `${rel}: ${exported}({ ${key.name} }) — ${inert} is already there, so the old name does nothing`,
685
+ );
686
+ continue;
687
+ }
688
+ let target = rule.to;
689
+ if (rule.alternate) {
690
+ const side = byName.get(rule.alternate.prop);
691
+ if (side && keyValue(withStrings, side) === rule.alternate.value) {
692
+ target = rule.alternate.to;
693
+ }
694
+ }
695
+ if (byName.has(target)) {
696
+ skipped.push(
697
+ `${rel}: ${exported}({ ${key.name} }) — ${target} is already there; two callbacks for one event is your call`,
698
+ );
699
+ continue;
700
+ }
701
+ edits.push({
702
+ start: key.start,
703
+ end: key.end,
704
+ text: target,
705
+ note: `${exported}({ ${c("brightRed", key.name)} }) → ${c("brightGreen", target)}`,
706
+ kind: "migration",
707
+ });
708
+ }
709
+
710
+ // The removals run after the renames: a prop that only meant something
711
+ // next to another one goes away with it.
712
+ for (let index = 0; index < keys.length; index++) {
713
+ const key = keys[index];
714
+ const rule = deprecationFor(exported, key.name);
715
+ if (!rule || rule.to) continue;
716
+ if (!declared.has(rule.from)) continue;
717
+ edits.push({
718
+ ...removalSpan(keys, index),
719
+ text: "",
720
+ note: `${exported}({ ${c("brightRed", key.name)} }) → ${c("brightGreen", "removed")}`,
721
+ kind: "migration",
722
+ });
723
+ }
724
+ }
725
+ }
726
+
727
+ return { edits, skipped };
728
+ };
729
+
582
730
  /**
583
731
  * Apply planned edits to one file.
584
732
  *
@@ -615,13 +763,14 @@ export const auditFix = async (argv = [], io = {}) => {
615
763
  return i >= 0 && argv[i + 1] ? path.resolve(argv[i + 1]) : process.cwd();
616
764
  })();
617
765
  const onlyFlag = argv.includes("--only") ? argv[argv.indexOf("--only") + 1] : null;
618
- if (onlyFlag && !["imports", "props"].includes(onlyFlag)) {
619
- print(c("red", `--only expects "imports" or "props", got "${onlyFlag}"`));
766
+ if (onlyFlag && !["imports", "props", "migrations"].includes(onlyFlag)) {
767
+ print(c("red", `--only expects "imports", "props" or "migrations", got "${onlyFlag}"`));
620
768
  return { exitCode: 1, changed: [], untouched: [] };
621
769
  }
622
770
  const only = {
623
771
  imports: !onlyFlag || onlyFlag === "imports",
624
772
  props: !onlyFlag || onlyFlag === "props",
773
+ migrations: !onlyFlag || onlyFlag === "migrations",
625
774
  };
626
775
 
627
776
  let api;
@@ -637,7 +786,11 @@ export const auditFix = async (argv = [], io = {}) => {
637
786
  const files = collectSources(root);
638
787
  const changed = [];
639
788
  const untouched = [];
640
- const passes = [only.imports && "imports", only.props && "props"].filter(Boolean);
789
+ const passes = [
790
+ only.imports && "imports",
791
+ only.props && "props",
792
+ only.migrations && "migrations",
793
+ ].filter(Boolean);
641
794
 
642
795
  print(banner("🔧 FletBox audit fix", `${files.length} file(s) · ${passes.join(" + ")}`));
643
796
 
@@ -651,6 +804,7 @@ export const auditFix = async (argv = [], io = {}) => {
651
804
  const runs = [];
652
805
  if (only.imports) runs.push(() => planImports(file, text, api, files));
653
806
  if (only.props) runs.push(() => planProps(file, text, api));
807
+ if (only.migrations) runs.push(() => planMigrations(file, text, api));
654
808
  for (const run of runs) {
655
809
  const plan = run();
656
810
  untouched.push(...plan.skipped);
@@ -673,7 +827,7 @@ export const auditFix = async (argv = [], io = {}) => {
673
827
 
674
828
  print(
675
829
  changed.length
676
- ? `\n${c("brightGreen", `${changed.length} file(s) rewritten`)} ${c("gray", "— run npm run check to confirm, and git diff to review.")}`
830
+ ? `\n${c("brightGreen", `${changed.length} file(s) rewritten`)} ${c("gray", "— run npm run verify to confirm, and git diff to review.")}`
677
831
  : c("gray", "\nNothing to rewrite — every import resolves and every prop is known."),
678
832
  );
679
833
  return { exitCode: 0, changed, untouched };
@@ -37,6 +37,7 @@ import {
37
37
  import { c, banner, section } from "../utils/colors.js";
38
38
  import { neutralise } from "../utils/neutralise.js";
39
39
  import { resolveLocalModule, readModuleExports } from "../utils/moduleExports.js";
40
+ import { deprecationFor } from "../../src/widget-factory/deprecations.js";
40
41
 
41
42
  const require = createRequire(import.meta.url);
42
43
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -538,7 +539,7 @@ const ERROR_CODES = new Set([
538
539
  "MISSING_EXPORT",
539
540
  ]);
540
541
 
541
- const SEVERITY = {
542
+ export const SEVERITY = {
542
543
  WRONG_STACK: "error",
543
544
  UNKNOWN_EXPORT: "error",
544
545
  UNKNOWN_PROP: "error",
@@ -600,7 +601,7 @@ const entry = (prop, info, api) => {
600
601
  * @param {string} projectRoot
601
602
  * @returns {string} written path
602
603
  */
603
- const emitCheatsheet = (api, projectRoot) => {
604
+ export const emitCheatsheet = (api, projectRoot) => {
604
605
  const widgetNames = api.exports
605
606
  .filter((name) => /^[A-Z]/.test(name) && api.interfaces.has(`${name}Props`))
606
607
  .sort();
@@ -614,6 +615,10 @@ const emitCheatsheet = (api, projectRoot) => {
614
615
  `Regenerate with \`flet-box check-api --emit-api\`.`,
615
616
  );
616
617
  out.push(`Widgets: ${widgetNames.length} · engine props: ${api.ALL_PROPS.length}`);
618
+ out.push(
619
+ `A prop marked \`(deprecated → x)\` still works and is not the name to write; ` +
620
+ `\`npm run fix\` renames the call it can prove.`,
621
+ );
617
622
  out.push(``);
618
623
  out.push(`## Common props (every widget)`);
619
624
  out.push(
@@ -629,7 +634,13 @@ const emitCheatsheet = (api, projectRoot) => {
629
634
  [...own]
630
635
  .map(([prop, info]) => {
631
636
  const type = info.type.length > 46 ? `${info.type.slice(0, 43)}…` : info.type;
632
- return `${prop}${info.optional ? "?" : ""}: ${type}`;
637
+ // The cheatsheet is the first file an agent reads, so a renamed prop has
638
+ // to say so here. Without the marker `onPress` and `onToggle` look like
639
+ // two equally good names, and half the calls built from this file carry
640
+ // the one the framework is trying to retire.
641
+ const rule = deprecationFor(name, prop);
642
+ const mark = rule ? ` (deprecated → ${rule.to ?? "removed"})` : "";
643
+ return `${prop}${info.optional ? "?" : ""}: ${type}${mark}`;
633
644
  })
634
645
  .join(" · "),
635
646
  );