flet-box 1.1.1 → 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 (156) hide show
  1. package/AGENTS_FOR_CODE_GEN.md +284 -0
  2. package/CONTRIBUTING.md +238 -0
  3. package/README.md +17 -13
  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 +18 -1
  11. package/bin/commands/createBundle.js +20 -8
  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/bundleConfig.js +47 -3
  20. package/bin/utils/contract.js +27 -0
  21. package/bin/utils/dtsProps.js +5 -1
  22. package/bin/utils/migrationGuide.js +146 -0
  23. package/bin/utils/moduleExports.js +9 -5
  24. package/bin/utils/templates.js +248 -30
  25. package/bin/utils/toolkit.js +191 -0
  26. package/docs/README.md +26 -1
  27. package/docs/arquitectura.md +1 -1
  28. package/docs/cli/README.md +201 -11
  29. package/docs/guides/README.md +6 -0
  30. package/docs/guides/accessibility.md +2 -1
  31. package/docs/guides/ai-toolkit.md +166 -0
  32. package/docs/guides/compatibility.md +1 -1
  33. package/docs/guides/first-app.md +6 -7
  34. package/docs/guides/migration.md +92 -0
  35. package/docs/guides/minification-and-protection.md +2 -1
  36. package/docs/guides/mobile-and-platforms.md +1 -1
  37. package/docs/guides/online-updates.md +20 -6
  38. package/docs/server/deployment.md +1 -1
  39. package/docs/tools/styling.md +2 -2
  40. package/docs/tools/theme.md +139 -4
  41. package/docs/widget/Accordion.md +74 -8
  42. package/docs/widget/AdMob.md +68 -3
  43. package/docs/widget/AdSense.md +67 -2
  44. package/docs/widget/AlertDialog.md +1 -1
  45. package/docs/widget/AnimatedBox.md +80 -39
  46. package/docs/widget/AnimatedText.md +74 -18
  47. package/docs/widget/Audio.md +73 -14
  48. package/docs/widget/Avatar.md +69 -4
  49. package/docs/widget/Badge.md +68 -3
  50. package/docs/widget/BottomSheet.md +2 -2
  51. package/docs/widget/Button.md +69 -3
  52. package/docs/widget/Card.md +69 -4
  53. package/docs/widget/Carousel.md +67 -2
  54. package/docs/widget/Chart.md +67 -2
  55. package/docs/widget/Checkbox.md +74 -8
  56. package/docs/widget/Chip.md +68 -3
  57. package/docs/widget/CircularBar.md +74 -15
  58. package/docs/widget/CircularChart.md +71 -10
  59. package/docs/widget/CodeViewer.md +67 -2
  60. package/docs/widget/Column.md +68 -3
  61. package/docs/widget/Container.md +68 -3
  62. package/docs/widget/DataTable.md +67 -2
  63. package/docs/widget/Divider.md +67 -2
  64. package/docs/widget/DraggBox.md +67 -2
  65. package/docs/widget/Dropdown.md +76 -10
  66. package/docs/widget/DroppBox.md +67 -2
  67. package/docs/widget/FloatingActionButton.md +68 -3
  68. package/docs/widget/GridView.md +67 -2
  69. package/docs/widget/Icon.md +71 -5
  70. package/docs/widget/Image.md +68 -3
  71. package/docs/widget/Input.md +68 -3
  72. package/docs/widget/Inspector.md +2 -2
  73. package/docs/widget/InstallButton.md +73 -13
  74. package/docs/widget/ListTile.md +69 -3
  75. package/docs/widget/ListView.md +67 -2
  76. package/docs/widget/Maps.md +68 -3
  77. package/docs/widget/Markdown.md +74 -15
  78. package/docs/widget/MatrixRain.md +72 -11
  79. package/docs/widget/Menu.md +209 -0
  80. package/docs/widget/Modal.md +3 -3
  81. package/docs/widget/Pagination.md +68 -3
  82. package/docs/widget/ParallaxBox.md +72 -12
  83. package/docs/widget/ProgressBar.md +69 -4
  84. package/docs/widget/QRCode.md +67 -2
  85. package/docs/widget/README.md +54 -50
  86. package/docs/widget/Radio.md +76 -10
  87. package/docs/widget/Rating.md +70 -6
  88. package/docs/widget/Row.md +68 -3
  89. package/docs/widget/Skeleton.md +68 -3
  90. package/docs/widget/Slider.md +75 -9
  91. package/docs/widget/SnackBar.md +2 -2
  92. package/docs/widget/Stack.md +68 -3
  93. package/docs/widget/Stepper.md +68 -3
  94. package/docs/widget/Switch.md +74 -8
  95. package/docs/widget/Text.md +71 -4
  96. package/docs/widget/ThemePicker.md +197 -0
  97. package/docs/widget/Tooltip.md +72 -7
  98. package/docs/widget/TreeView.md +70 -10
  99. package/docs/widget/Video.md +71 -10
  100. package/package.json +10 -3
  101. package/skills/fletbox-design/SKILL.md +89 -5
  102. package/skills/fletbox-motion/SKILL.md +1 -1
  103. package/skills/fletbox-rules/SKILL.md +56 -4
  104. package/src/core/runApp.js +12 -0
  105. package/src/index.d.ts +374 -101
  106. package/src/index.js +9 -0
  107. package/src/navigations/AdaptiveScaffold.js +2 -2
  108. package/src/navigations/BottomNavigation.js +1 -1
  109. package/src/navigations/Scaffold.js +18 -6
  110. package/src/tools/useState.js +11 -4
  111. package/src/utils/colors.js +275 -0
  112. package/src/utils/errorBoundary.js +288 -0
  113. package/src/utils/index.js +10 -2
  114. package/src/utils/stackPosition.js +39 -24
  115. package/src/utils/syntaxHighlight.js +4 -1
  116. package/src/utils/themes.js +279 -3
  117. package/src/widget-factory/addChildren.js +17 -10
  118. package/src/widget-factory/assignProps.js +18 -6
  119. package/src/widget-factory/createWidget.js +7 -0
  120. package/src/widget-factory/deprecations.js +77 -0
  121. package/src/widget-factory/translateProps.js +11 -1
  122. package/src/widget-factory/warnings.js +1 -27
  123. package/src/widget-factory/widgetFactory.js +59 -17
  124. package/src/widgets/Accordion.js +21 -7
  125. package/src/widgets/Badge.js +11 -2
  126. package/src/widgets/Button.js +15 -3
  127. package/src/widgets/Carousel.js +12 -4
  128. package/src/widgets/Chart.js +7 -2
  129. package/src/widgets/Checkbox.js +1 -2
  130. package/src/widgets/Chip.js +24 -10
  131. package/src/widgets/CircularBar.js +9 -3
  132. package/src/widgets/CircularChart.js +5 -3
  133. package/src/widgets/Container.js +6 -0
  134. package/src/widgets/Divider.js +3 -1
  135. package/src/widgets/DraggBox.js +9 -0
  136. package/src/widgets/Dropdown.js +3 -3
  137. package/src/widgets/DroppBox.js +5 -0
  138. package/src/widgets/FloatingActionButton.js +1 -1
  139. package/src/widgets/Icon.js +5 -1
  140. package/src/widgets/InstallButton.js +4 -2
  141. package/src/widgets/Markdown.js +171 -103
  142. package/src/widgets/Menu.js +340 -0
  143. package/src/widgets/Modal.js +7 -1
  144. package/src/widgets/Pagination.js +10 -5
  145. package/src/widgets/ProgressBar.js +5 -4
  146. package/src/widgets/QRCode.js +4 -0
  147. package/src/widgets/Radio.js +1 -2
  148. package/src/widgets/Rating.js +9 -0
  149. package/src/widgets/Slider.js +4 -1
  150. package/src/widgets/SnackBar.js +2 -2
  151. package/src/widgets/Switch.js +1 -2
  152. package/src/widgets/Text.js +9 -5
  153. package/src/widgets/ThemePicker.js +234 -0
  154. package/src/widgets/TreeView.js +2 -1
  155. package/src/widgets/index.js +2 -0
  156. package/docs/CONTRIBUTING.md +0 -156
@@ -36,6 +36,23 @@ export const indexHtml = (appName = "FletBox App") => `<!DOCTYPE html>
36
36
  <meta property="og:title" content="${appName}">
37
37
  <meta property="og:description" content="${appName} built with Flet-Box.">
38
38
  <meta property="og:type" content="website">
39
+ <meta property="og:url" content="./">
40
+ <meta property="og:image" content="./src/assets/icon-512.png">
41
+ <meta property="og:image:alt" content="${appName}">
42
+
43
+ <meta name="twitter:card" content="summary">
44
+ <meta name="twitter:title" content="${appName}">
45
+ <meta name="twitter:description" content="${appName} built with Flet-Box.">
46
+ <meta name="twitter:url" content="./">
47
+ <meta name="twitter:image" content="./src/assets/icon-512.png">
48
+ <meta name="twitter:image:alt" content="${appName}">
49
+
50
+ <!-- The share tags stay relative on purpose: this one file runs on :8000,
51
+ through a tunnel and on Pages. 'flet-box build' rewrites og:image,
52
+ og:url, twitter:image and twitter:url to absolute URLs at the site
53
+ origin in the copy it publishes, because a scraper does not resolve
54
+ a relative path the way a browser does. Replace the icon with a real
55
+ cover if you have one: a square icon is what a 'summary' card expects. -->
39
56
 
40
57
  <meta name="theme-color" content="${SHELL_BRAND}">
41
58
 
@@ -344,6 +361,7 @@ import {
344
361
  Slider,
345
362
  Divider,
346
363
  Tooltip,
364
+ ThemePicker,
347
365
  Icon,
348
366
  SnackBar,
349
367
  AlertDialog,
@@ -353,7 +371,8 @@ import {
353
371
  setTheme,
354
372
  getTheme,
355
373
  setBaseFontSize,
356
- getBaseFontSize
374
+ getBaseFontSize,
375
+ listAccents
357
376
  } from 'flet-box';
358
377
 
359
378
  // Key-based RAM keeps a value across re-renders and hot reloads; localStorage is
@@ -451,6 +470,28 @@ export const AboutScreen = () => {
451
470
  activeColor: colors.primary,
452
471
  onPress: (value) => setTheme(value ? 'dark' : 'light')
453
472
  }),
473
+ // The accent is the reader's answer, not the build's. This app opens on
474
+ // neutral (themes.js applies it at boot) and this row is how they leave it:
475
+ // one square per family, split diagonally into what the hue wears on a light
476
+ // page and what it wears on a dark one, with the tick already on the family
477
+ // painted. A tap repaints every widget on screen and stores the choice, so
478
+ // this does not wait for the Save button below — that keeps the profile.
479
+ // Five squares is a decision; \`listAccents()\` has twelve, so the filter is
480
+ // the brand's answer to what this app is willing to look like.
481
+ Column({
482
+ gap: 8,
483
+ children: [
484
+ Text({ text: 'Accent color', size: 16, weight: 'bold', color: colors.text }),
485
+ ThemePicker({
486
+ label: 'Accent color',
487
+ accents: listAccents().filter((family) =>
488
+ ['neutral', 'blue', 'teal', 'rose', 'orange'].includes(family.id)
489
+ ),
490
+ onChange: (id) =>
491
+ SnackBar({ message: 'Accent: ' + id, type: 'success' }).show()
492
+ })
493
+ ]
494
+ }),
454
495
  Column({
455
496
  gap: 8,
456
497
  children: [
@@ -591,12 +632,15 @@ export const themesJs = () => `// database/themes.js
591
632
  // framework's own \`color().contrastRatio()\` against the surface it is written on:
592
633
  // the number sits next to the pair that needs it, because a token that cannot
593
634
  // carry AA text is decoration, not a color.
594
- import { registerPalette } from 'flet-box';
635
+ import { applyAccentPreference, registerPalette } from 'flet-box';
595
636
 
596
637
  export const lightColors = {
597
- // #6366f1 put white on 4.47:1 — a hair under AA. Two units darker measures
598
- // 4.55:1, the same indigo, and every brand button passes.
599
- primary: '#6165ef',
638
+ // The brand a new app shows is neutral: no hue, the page's own ink. White on
639
+ // this fill measures 17.85:1 and the fill as words on this page measures the
640
+ // same, so a button, an active tab and a link all pass in both directions
641
+ // before anyone has chosen a color. The reader changes it on the Preferences
642
+ // screen (\`ThemePicker\`), which paints a hue and remembers it.
643
+ primary: '#0f172a',
600
644
  secondary: '#8b5cf6',
601
645
  success: '#10b981',
602
646
  warning: '#f59e0b',
@@ -629,10 +673,14 @@ export const lightColors = {
629
673
  border: '#e2e8f0',
630
674
  overlay: 'rgba(0, 0, 0, 0.5)',
631
675
  shadow: 'rgba(0, 0, 0, 0.1)',
632
- // What to write on a brand fill, and the soft tints that replace
633
- // \`colors.primary + '15'\`.
676
+ // What to write on a brand fill, and the soft plate that replaces
677
+ // \`colors.primary + '15'\`. The plate is a lifted gray rather than a tint of
678
+ // a hue, because the brand has no hue: the page ink sits on it at 14.56:1, and
679
+ // since the neutral brand *is* that ink a chip written in \`colors.primary\`
680
+ // measures the same. One limit: \`textSecondary\` below (#64748b) on this plate
681
+ // is 3.88:1, so a caption inside a \`primaryBg\` area uses \`colors.text\`.
634
682
  onPrimary: '#ffffff',
635
- primaryBg: '#eef2ff',
683
+ primaryBg: '#e7e8ea',
636
684
  // The translucent surface for a card with \`backdropFilter\`. A token, so the
637
685
  // app never hand-writes an \`rgba()\` the clean-UI gate would reject.
638
686
  glass: 'rgba(255, 255, 255, 0.66)',
@@ -647,7 +695,10 @@ export const lightColors = {
647
695
  };
648
696
 
649
697
  export const darkColors = {
650
- primary: '#818cf8',
698
+ // The other half of the same neutral: here the brand is the page's near-white
699
+ // ink. It reads 12.02:1 on this background and 9.85:1 on a card, so it is
700
+ // legal as words and as a glyph, and it carries its own label below.
701
+ primary: '#cbd5e1',
651
702
  secondary: '#a78bfa',
652
703
  success: '#34d399',
653
704
  warning: '#fbbf24',
@@ -677,9 +728,14 @@ export const darkColors = {
677
728
  border: '#334155',
678
729
  overlay: 'rgba(0, 0, 0, 0.6)',
679
730
  shadow: 'rgba(0, 0, 0, 0.3)',
680
- // #818cf8 is a light fill: white on it is 2.98:1, the page's own ink is 5.98:1.
681
- onPrimary: '#0f172a',
682
- primaryBg: '#1e1b4b',
731
+ // #cbd5e1 is a light fill: white on it is 1.48:1, so its words are the page's
732
+ // own near-black and measure 13.40:1. The plate sits one step off the page
733
+ // (1.18:1) and one very quiet step off a card (1.03:1), which is what a brand
734
+ // with no hue costs: a tinted banner works on the background, and on a card
735
+ // the mark is \`colors.border\` or the ink itself. Ink on the plate: 13.81:1,
736
+ // secondary ink 5.90:1, the brand 10.19:1.
737
+ onPrimary: '#09090b',
738
+ primaryBg: '#252629',
683
739
  glass: 'rgba(30, 41, 59, 0.66)',
684
740
  glassBorder: 'rgba(226, 232, 240, 0.22)',
685
741
  error: '#f87171',
@@ -697,6 +753,21 @@ export const darkColors = {
697
753
  // the reader's answer (\`setTheme\`, \`toggleTheme\`), not the designer's.
698
754
  registerPalette('light', lightColors);
699
755
  registerPalette('dark', darkColors);
756
+
757
+ // The palette above answers what a theme *is*; the accent layer is what the reader
758
+ // picks, and it owns exactly three tokens: \`primary\`, \`onPrimary\` and
759
+ // \`primaryBg\`. A new app opens on \`neutral\` — the same three values the palettes
760
+ // carry, so the two layers agree and nothing repaints a frame after boot.
761
+ //
762
+ // \`applyAccentPreference\` is the app's verb, not the reader's: it paints only when
763
+ // nobody has chosen yet and writes no preference down, so the Preferences screen's
764
+ // \`ThemePicker\` opens with the neutral square marked as the answer, and a hue
765
+ // picked on a previous visit still wins over this line. \`setAccent\` would answer
766
+ // for the reader and overwrite their choice on every load.
767
+ //
768
+ // An app with a brand of its own keeps the three tokens above and changes the id
769
+ // here to a family it registers with \`registerAccent\`.
770
+ applyAccentPreference('neutral');
700
771
  `;
701
772
 
702
773
  export const runSh = () => `#!/bin/bash
@@ -858,10 +929,13 @@ export const packageJson = (name) => `{
858
929
  "version": "1.0.0",
859
930
  "type": "module",
860
931
  "scripts": {
932
+ "predev": "flet-box check-api --emit-api && flet-box unpack --all --md",
861
933
  "dev": "bash run.sh",
934
+ "verify": "flet-box verify",
862
935
  "check": "node scripts/check-app.mjs",
863
936
  "ui": "node scripts/check-ui.mjs",
864
937
  "fix": "flet-box audit-fix",
938
+ "metrics": "flet-box metrics",
865
939
  "api": "flet-box unpack --all --md",
866
940
  "build": "flet-box createBundle www",
867
941
  "start": "npm run dev",
@@ -907,6 +981,22 @@ export const capacitorConfig = (appName, appId) => `${JSON.stringify(
907
981
  // The user replaces TU-USUARIO/TU-REPO with their real public GitHub names.
908
982
  export const OTA_PLACEHOLDER_URL = "https://TU-USUARIO.github.io/TU-REPO/";
909
983
 
984
+ // The pictures a created app keeps in `assets/`. `flet-box create` makes that
985
+ // folder and writes `assets/logo.png` into it, so it is where an app's own
986
+ // images live — but `createBundle` copies only `src/assets` (icons, fonts) by
987
+ // itself. Undeclared, every bundled build served those screens with broken
988
+ // images while `npm run dev`, which serves the project root, looked perfect.
989
+ // One glob per extension: the asset matcher understands `*`, `**` and `?`,
990
+ // not braces. Images only — a source document parked in `assets/` (the PDF a
991
+ // catalogue was written from) must stay out of the bundle.
992
+ const PROJECT_ASSET_PATTERNS = [
993
+ "assets/**/*.png",
994
+ "assets/**/*.jpg",
995
+ "assets/**/*.jpeg",
996
+ "assets/**/*.svg",
997
+ "assets/**/*.webp",
998
+ ];
999
+
910
1000
  // OTA manifest written to every generated project. The loader (www/loader.js)
911
1001
  // reads `update.url` via www/config.json to know where to fetch updates.
912
1002
  export const fletboxConfig = (url = OTA_PLACEHOLDER_URL) => `${JSON.stringify(
@@ -918,6 +1008,7 @@ export const fletboxConfig = (url = OTA_PLACEHOLDER_URL) => `${JSON.stringify(
918
1008
  mandatory: false,
919
1009
  minNativeVersion: "0.0.0",
920
1010
  notes: "",
1011
+ assets: PROJECT_ASSET_PATTERNS,
921
1012
  },
922
1013
  },
923
1014
  null,
@@ -1296,14 +1387,21 @@ export const githubWorkflow = (appName = "FletBox App", siteUrl = null) => {
1296
1387
  const site = /^https?:\/\//i.test(String(siteUrl || ""))
1297
1388
  ? String(siteUrl).replace(/\/?$/, "/")
1298
1389
  : `https://TU-USUARIO.github.io/${slug}/`;
1299
- // Only when the owner is still a guess: the address lives in two files, and a
1300
- // developer who edits one and not the other ships an app that polls a site
1301
- // that is not where the bundle is.
1390
+ // Only when the owner is still a guess. The build no longer trusts the
1391
+ // placeholder in fletbox.config.json (createBundle derives the real Pages
1392
+ // address from this repository), so what is left to correct here is the
1393
+ // address a human reads before reading a single step.
1302
1394
  const todo = site.includes("TU-USUARIO")
1303
1395
  ? `# The owner above is a placeholder: this project was created without \`--repo\`,
1304
- # so the folder name is only a guess at the repo name. Write the real address in
1305
- # both files that state it — this header, and fletbox.config.json (update.url),
1306
- # which is the URL an installed app polls for an update.
1396
+ # so the folder name is only a guess at the repo name. Put the real owner here
1397
+ # once the repository exists.
1398
+ #
1399
+ # \`fletbox.config.json\` may keep its own placeholder: the build recognises it as
1400
+ # "not configured" and derives the address from this repo's repository field or
1401
+ # git remote, so an installed app polls the site this workflow deploys. The URL
1402
+ # that landed in the manifest is printed by the build and by the
1403
+ # \`Where this lives\` step below. Set \`update.url\` by hand only when this app
1404
+ # will live on another host.
1307
1405
  `
1308
1406
  : "";
1309
1407
  return `name: Deploy ${appName}
@@ -1621,6 +1719,7 @@ android/capacitor-cordova-android-plugins/
1621
1719
  package-lock.json
1622
1720
  .DS_Store
1623
1721
  *.log
1722
+ .fletbox/reports/
1624
1723
  `;
1625
1724
 
1626
1725
  export const readme = (name, _template, appId) => `# ${name}
@@ -1665,6 +1764,8 @@ framework from scratch:
1665
1764
  Claude Code and Cursor.
1666
1765
  - \`.fletbox/api/cheatsheet.md\` — every widget and its real props, generated from the
1667
1766
  installed package. This is the lookup an agent reads instead of the framework.
1767
+ A prop marked \`(deprecated → x)\` still works, but it is the name being retired;
1768
+ write \`x\`. The old spelling will not fail — nothing else will tell you.
1668
1769
  - \`.fletbox/api/api.json\` — the same surface as data: per prop its engine target,
1669
1770
  default, aliases and a \`sample\` value the types prove works. Write calls from this.
1670
1771
  - \`.fletbox/memory/\` — notes the agent keeps between sessions, so it does not
@@ -1680,7 +1781,8 @@ npm run ui # clean-UI gate: tokens, scale, tap targets,
1680
1781
  # contrast — each finding cites the rule it broke
1681
1782
  npm run ui -- --tokens # measured contrast of every token, on both surfaces
1682
1783
  npm run ui -- --strict # warnings (a one-off tint, an image with no alt) fail too
1683
- npm run fix # rewrite typos it can prove (imports, prop keys)
1784
+ npm run fix # rewrite what it can prove: imports, prop typos,
1785
+ # renamed props
1684
1786
  npm run api # regenerate .fletbox/api/api.json and api.md
1685
1787
  flet-box unpack Button # each prop, where it really goes, and what is dead
1686
1788
  flet-box unpack Button --example # a paste-ready call using only verified props
@@ -1779,9 +1881,12 @@ Two workflow files ship with the project, and they answer to different triggers:
1779
1881
  The address this project publishes to is written at the top of
1780
1882
  \`.github/workflows/build.yml\`. \`flet-box create <name> --repo <user>/<repo>\`
1781
1883
  fills it in from the repo; without \`--repo\` it stays
1782
- \`https://TU-USUARIO.github.io/<name>/\`, and the header says which two files to
1783
- edit. That is a sub-path, so \`index.html\` uses relative asset paths and works
1784
- without changes.
1884
+ \`https://TU-USUARIO.github.io/<name>/\`, and the header explains what is still a
1885
+ guess. The build ignores that placeholder in \`fletbox.config.json\` and derives
1886
+ the real address from this repository, so the OTA feed is correct either way.
1887
+
1888
+ The Pages address is a sub-path, so \`index.html\` uses relative asset paths and
1889
+ works without changes.
1785
1890
 
1786
1891
  **Why a push does not update anyone's app:** this same address is the OTA feed
1787
1892
  installed apps poll, and the update check compares bundle **hashes**, not version
@@ -1871,9 +1976,10 @@ updates:
1871
1976
  \`\`\`
1872
1977
 
1873
1978
  - The GitHub repository **must be public**.
1874
- - Replace \`TU-USUARIO\` with your GitHub username and \`TU-REPO\` with the
1875
- repository name, or set \`update.url\` to any HTTPS host that serves your
1876
- \`config.json\` + bundle.
1979
+ - \`TU-USUARIO\`/\`TU-REPO\` are a placeholder, not a setting: the build sees them as
1980
+ "not configured" and derives the Pages address from this project's \`repository\`
1981
+ field or git remote, so pushing the folder is enough. Edit \`update.url\` only
1982
+ when the app lives on another HTTPS host that serves your \`config.json\` + bundle.
1877
1983
  - Enable GitHub Pages: **Settings → Pages → Source: GitHub Actions**. Without
1878
1984
  that the deploy step is green and no site exists.
1879
1985
  - Cut a release with \`flet-box release --push\` (bumps the version, rebuilds
@@ -2067,10 +2173,12 @@ export const AppBarComponent = ({ title, isMobile = false, showBack = false, onB
2067
2173
  size: 24,
2068
2174
  height: 40,
2069
2175
  color: colors.text,
2176
+ 'aria-label': 'Open the menu',
2070
2177
  onclick: () => openDrawer()
2071
2178
  })
2072
2179
  }) : null,
2073
- // An icon-only control still needs a name: the Tooltip is what says so.
2180
+ // An icon-only control still needs a name, and a Tooltip is a description
2181
+ // (aria-describedby), not a name: the aria-label is what a screen reader says.
2074
2182
  actions: [
2075
2183
  Tooltip({
2076
2184
  text: isDark ? 'Switch to light' : 'Switch to dark',
@@ -2080,6 +2188,7 @@ export const AppBarComponent = ({ title, isMobile = false, showBack = false, onB
2080
2188
  size: 24,
2081
2189
  height: 40,
2082
2190
  color: colors.text,
2191
+ 'aria-label': isDark ? 'Switch to light' : 'Switch to dark',
2083
2192
  onclick: () => toggleTheme()
2084
2193
  })
2085
2194
  })
@@ -2422,6 +2531,13 @@ export const agentsMd = (appName, template, version) =>
2422
2531
  'fixes every size and color you may use, and the checker in section 5',
2423
2532
  'proves you right or wrong in seconds.',
2424
2533
  '',
2534
+ 'If you are an agent **writing code** in this app rather than reading it,',
2535
+ '[AGENTS_FOR_CODE_GEN.md](AGENTS_FOR_CODE_GEN.md) is the shorter contract:',
2536
+ 'the golden rule (a prop that is not in the cheatsheet does not exist), what',
2537
+ 'may never be written, the ten design rules that survive a review, what gets',
2538
+ 'rejected and why, and the verification protocol. Read that one first; this',
2539
+ 'file is its long form.',
2540
+ '',
2425
2541
  '## 0. Start here — the skills folder',
2426
2542
  '',
2427
2543
  'Everything this framework knows about building a professional screen is grouped',
@@ -2477,7 +2593,7 @@ export const agentsMd = (appName, template, version) =>
2477
2593
  '// Correct',
2478
2594
  "Text({ text: 'Hello', size: 18, color: colors.primary })",
2479
2595
  "Button({ text: 'Save', onPress: save, bgColor: colors.primary })",
2480
- 'Box({ child: Text({ text: count }), padding: 12 })',
2596
+ 'Container({ child: Text({ text: count }), padding: 12 })',
2481
2597
  '```',
2482
2598
  '',
2483
2599
  '```js',
@@ -2495,7 +2611,7 @@ export const agentsMd = (appName, template, version) =>
2495
2611
  ' at runtime and nothing fetches a CDN.',
2496
2612
  '- No `.jsx`, `.tsx`, `.vue` or `.svelte` files. Plain `.js`, ESM imports with a',
2497
2613
  ' explicit `.js` extension.',
2498
- '- Styling is props (`bgColor`, `radius`, `padding`), not class strings or',
2614
+ '- Styling is props (`bgColor`, `borderRadius`, `padding`), not class strings or',
2499
2615
  ' `styled-components`.',
2500
2616
  '- `child` takes one widget; `children` takes an array or string. Swapping them',
2501
2617
  ' is the most common silent bug.',
@@ -2600,6 +2716,11 @@ export const agentsMd = (appName, template, version) =>
2600
2716
  '',
2601
2717
  '- A screen uses **one** brand color. Two accent colors competing on one view',
2602
2718
  ' means one of them is wrong.',
2719
+ '- The brand is an **accent**, not a value on the screen: this app opens on the',
2720
+ ' \`neutral\` family (\`applyAccentPreference\` at the bottom of',
2721
+ ' \`src/database/themes.js\`) and the reader can move it on the Preferences',
2722
+ ' screen, where \`ThemePicker\` lives. To ship a hue, change that id or register',
2723
+ ' a family with \`registerAccent\` — never write the hex into a screen.',
2603
2724
  '- Never type a hex or `rgb()` literal. If a token is missing, add it to a',
2604
2725
  ' `theme.js` in this app that re-exports `colors` — do not scatter the value.',
2605
2726
  '- A frosted surface is `colors.glass` with `colors.glassBorder` plus a',
@@ -2617,7 +2738,7 @@ export const agentsMd = (appName, template, version) =>
2617
2738
  '',
2618
2739
  '**Space and type: an 8-based scale.** Use these numbers and nothing between',
2619
2740
  'them. They are plain props (`padding`, `gap`, `margin`, `width`, `height`,',
2620
- '`size`, `radius`) — the factory takes the number, no unit and no `px` string.',
2741
+ '`size`, `borderRadius`) — the factory takes the number, no unit and no `px` string.',
2621
2742
  '',
2622
2743
  '```',
2623
2744
  'spacing 4 · hairline, icon inset',
@@ -2645,29 +2766,76 @@ export const agentsMd = (appName, template, version) =>
2645
2766
  '- `useState` bindings, `flex` and `gap` do the layout. Reach for an absolute',
2646
2767
  ' offset only for overlays, and use `stackPosition` rather than raw coordinates.',
2647
2768
  '',
2769
+ '**The phone range is the test device — strict.** Every phone is `xs` in the',
2770
+ 'breakpoint table, so nothing in the shell changes between 320 and 430; the',
2771
+ 'layout has to. Compose at `360×640` and prove the screen at `320×568` (the',
2772
+ 'floor), `375×667`, `390×844`, `393×852`, `412×915` and `430×932` (the',
2773
+ 'ceiling), in portrait. At 320 nothing overflows or clips: no horizontal',
2774
+ 'scroll, no label cut because its row ran out of room. A heading whose text',
2775
+ 'the app does not control — a lesson name, a chapter, a user title — is `24`',
2776
+ 'on a phone and `32` only from `atLeast("md")`: at 360 a long name at `32` is',
2777
+ 'four lines and pushes the controls under it off the first screen. Fixed',
2778
+ 'chrome is inside the 320 budget: an app bar title plus its actions, a tab',
2779
+ 'strip, a labeled bottom bar. If one needs more than 320, shorten what it',
2780
+ 'says — never let it scroll sideways.',
2781
+ '',
2648
2782
  '## 5. Verification loop (required)',
2649
2783
  '',
2650
2784
  'After every edit, before you say a task is done:',
2651
2785
  '',
2652
2786
  '```bash',
2787
+ 'npm run verify # parses first, then every gate, then your test scripts',
2788
+ ' # — one report, one exit code; this is the command to',
2789
+ ' # run after every edit',
2790
+ 'npm run verify -- --fix # the same, after the mechanical repair: it rewrites',
2791
+ ' # a mistyped prop or a wrong relative path first, so a',
2792
+ ' # typo never reaches you as work to do',
2653
2793
  'npm run check # static gate: invented widgets/props, positional args,',
2654
2794
  ' # foreign imports, and broken imports between your own',
2655
2795
  ' # modules (a name a file never exports)',
2656
2796
  'npm run ui # clean-UI gate: the tokens and the scale above, 40px',
2657
2797
  ' # targets, labels, contrast — every finding cites the',
2658
2798
  ' # rule number it broke',
2799
+ 'npm run metrics # the north metric: how much code passed the gate with',
2800
+ ' # no correction, and which failures keep coming back',
2801
+ 'flet-box report # lost? every file of this project, its verdict, and',
2802
+ ' # the one next step — read-only, writes no metrics row',
2803
+ 'flet-box tools # the map: which command answers which question',
2659
2804
  'npm run dev # serve on :8000 — check the UI actually renders',
2660
2805
  '```',
2661
2806
  '',
2662
- 'Both gates read the installed package, so they are always in sync with what',
2807
+ '`npm run verify` parses every module first (`node --check`, the parser the',
2808
+ 'runtime uses: a missing `)` comes back as `file:line:column` with a caret',
2809
+ 'under the character and a hint), then runs those two gates, the type gate',
2810
+ '(your own `jsconfig.json` through TypeScript, the same service the editor',
2811
+ 'uses: a widget declared as a bare `HTMLElement` makes `widget.update()` an',
2812
+ 'error here and there), the structural gate (a cycle between two of your own',
2813
+ 'modules, a module outside the project folders, markup written by hand) and',
2814
+ 'the prop contract in parallel, and',
2815
+ 'prints one report: the gate that failed, the file at `line:column`, and what',
2816
+ 'to do about it. A project that declares its own test scripts gets those run',
2817
+ 'too; a created app declares none.',
2818
+ 'All of them read the installed package, so they are always in sync with what',
2663
2819
  'runs. They exit non-zero on a real error: fix the code, not the checker. A',
2664
2820
  'warning is allowed to stay only when you can justify it in one sentence.',
2665
2821
  '',
2822
+ 'Fix what the report names, and nothing else. Every finding carries a file and',
2823
+ 'a line: change that line. Do not rewrite the file to make it pass, and do not',
2824
+ 'reformat or rename a line the report did not name. If the same error survives',
2825
+ 'two attempts, the diagnosis was wrong: revert and hand the report back.',
2826
+ '',
2666
2827
  '`npm run ui` proves what is *written*; the rules it cites are the numbered ones',
2667
2828
  'in [skills/fletbox-design/SKILL.md](skills/fletbox-design/SKILL.md),',
2668
2829
  'and that file is the part a checker cannot do — hierarchy, copy, whether the',
2669
2830
  'screen solves the problem. Read it before writing a screen, not after.',
2670
2831
  '',
2832
+ 'Every `npm run verify` also appends a row to',
2833
+ '`.fletbox/metrics/generations.md` — green or not — and `npm run metrics`',
2834
+ 'reads those rows: the share of code that passed the gate with no correction',
2835
+ 'at all, plus the failures that recur most often. When the same code comes',
2836
+ 'back a third time with no rule behind it, the command fails: that is the',
2837
+ 'signal to write the rule, not to fix the same line again.',
2838
+ '',
2671
2839
  'When the error is a mistyped name, `npm run fix` rewrites it for you — a',
2672
2840
  'relative path that does not resolve, a name the target module never exports,',
2673
2841
  'an identifier used without its `import`, and a prop key the API does not know',
@@ -2675,6 +2843,13 @@ export const agentsMd = (appName, template, version) =>
2675
2843
  'one real name is close enough; anything ambiguous is printed untouched for you',
2676
2844
  'to decide. Run `npm run check` again after it, and read the diff.',
2677
2845
  '',
2846
+ 'When you are lost, do not guess which gate to run: `flet-box report` names',
2847
+ 'every file of this project (clean ones included), its verdict, its widgets,',
2848
+ 'and a ranked next step; `flet-box tools` is the catalogue that maps a',
2849
+ 'question to the command. Both are read-only — no repair, no metrics row —',
2850
+ 'and the only file they can write is the report page, when `--html` asks for',
2851
+ 'it, into `.fletbox/reports/` (generated output, not committed).',
2852
+ '',
2678
2853
  '## 6. Memory — do not relearn the same thing',
2679
2854
  '',
2680
2855
  'The full format lives in [skills/fletbox-memory/SKILL.md](skills/fletbox-memory/SKILL.md).',
@@ -2697,8 +2872,14 @@ export const agentsMd = (appName, template, version) =>
2697
2872
  '| Command | What it does |',
2698
2873
  '| --- | --- |',
2699
2874
  '| `npm run dev` | static dev server on port 8000 |',
2875
+ '| `npm run verify` | parses every module, then every gate, then your test scripts: one report, one exit code |',
2700
2876
  '| `npm run check` | anti-hallucination gate over `src/` |',
2701
2877
  '| `npm run fix` | rewrite mistyped imports and prop names it can prove |',
2878
+ '| `npm run metrics` | the north metric: how much code passed the gate with no correction |',
2879
+ '| `flet-box report` | every file with its verdict, the widget ledger, and a ranked next step (read-only) |',
2880
+ '| `flet-box report --html` | the same report as one self-contained page in `.fletbox/reports/` |',
2881
+ '| `flet-box tools` | the catalogue: which command answers which question (`--emit` writes `.fletbox/api/tools.json`) |',
2882
+ '| `flet-box mcp` | the same read-only tools over MCP stdio, for an assistant with no shell |',
2702
2883
  '| `npm run check -- --props Button` | print the real props of a widget |',
2703
2884
  '| `flet-box unpack Button` | everything Button accepts **and that actually works** |',
2704
2885
  '| `flet-box unpack Button --example` | a paste-ready call using only live props |',
@@ -2817,6 +2998,43 @@ export const memoryStatusMd = (appName, template) => {
2817
2998
  ].join('\n');
2818
2999
  };
2819
3000
 
3001
+ export const metricsLogMd = () =>
3002
+ [
3003
+ '# Generations',
3004
+ '',
3005
+ 'The log of every `npm run verify` run in this app, newest last. One row per',
3006
+ 'run: when it happened, whether the gates were green, which gates failed and',
3007
+ 'which codes they reported.',
3008
+ '',
3009
+ 'A **generation** is a run of consecutive failing rows that ends at the first',
3010
+ 'green one — the rows before it are the corrections that were needed. A green',
3011
+ 'row that follows a green row is a generation that needed none.',
3012
+ '',
3013
+ '**The north metric** is the share of generations whose *first* run was green:',
3014
+ 'the code that passed the gate with no correction at all. It is measured by',
3015
+ '`npm run metrics`, which also prints the three failures that recurred most',
3016
+ 'often and the rule each one should belong to. That list is the loop: a code',
3017
+ 'that keeps coming back and has no rule behind it is a rule that has not been',
3018
+ 'written yet.',
3019
+ '',
3020
+ '## Gate defects',
3021
+ '',
3022
+ 'A code listed here was reported by the gate, not by the code it checked. The',
3023
+ 'window is the first and the last day it accounts for, so the runs inside it',
3024
+ 'count as passed and never feed the rule loop: that is the framework\'s debt,',
3025
+ 'not a correction this app needed. `Fixed` empty (`—`) means the defect is',
3026
+ 'still open. A run after `Fixed` counts again — a defect that comes back is',
3027
+ 'this app\'s finding until the window is reopened. Remove a row to hand its',
3028
+ 'findings back.',
3029
+ '',
3030
+ '| Code | Seen | Fixed | Where |',
3031
+ '| --- | --- | --- | --- |',
3032
+ '',
3033
+ '| When | Project | Result | Gates | Codes |',
3034
+ '| --- | --- | --- | --- | --- |',
3035
+ '',
3036
+ ].join('\n');
3037
+
2820
3038
  export const memoryIndexMd = (appName) =>
2821
3039
  [
2822
3040
  '# Memory index',