flet-box 1.0.6 → 1.0.8

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 (162) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +90 -11
  3. package/bin/commands/auditFix.js +682 -0
  4. package/bin/commands/checkApi.js +20 -6
  5. package/bin/commands/checkUi.js +1369 -0
  6. package/bin/commands/create.js +86 -12
  7. package/bin/commands/createBundle.js +75 -1
  8. package/bin/commands/images.js +349 -0
  9. package/bin/commands/pruneAssets.js +170 -0
  10. package/bin/commands/release.js +34 -1
  11. package/bin/commands/runCloudflare.js +47 -9
  12. package/bin/commands/runServer.js +91 -25
  13. package/bin/commands/toPng.js +346 -0
  14. package/bin/commands/unpack.js +1173 -0
  15. package/bin/ota/loader.js +90 -0
  16. package/bin/utils/appSources.js +101 -0
  17. package/bin/utils/bundleConfig.js +166 -0
  18. package/bin/utils/dtsProps.js +147 -0
  19. package/bin/utils/imageTools.js +271 -0
  20. package/bin/utils/images/bmp.js +99 -0
  21. package/bin/utils/images/gif.js +244 -0
  22. package/bin/utils/images/index.js +163 -0
  23. package/bin/utils/images/jpeg.js +643 -0
  24. package/bin/utils/images/png.js +486 -0
  25. package/bin/utils/images/unsupported.js +29 -0
  26. package/bin/utils/neutralise.js +109 -6
  27. package/bin/utils/pwaShell.js +137 -0
  28. package/bin/utils/templates.js +1653 -471
  29. package/docs/README.md +2 -0
  30. package/docs/cli/README.md +144 -1
  31. package/docs/guides/README.md +2 -0
  32. package/docs/guides/clean-ui.md +211 -0
  33. package/docs/guides/minification-and-protection.md +1 -1
  34. package/docs/guides/online-updates.md +105 -9
  35. package/docs/tools/README.md +1 -1
  36. package/docs/tools/animation.md +1 -1
  37. package/docs/tools/markdown.md +3 -3
  38. package/docs/tools/theme.md +41 -1
  39. package/docs/widget/Accordion.md +4 -0
  40. package/docs/widget/AdMob.md +5 -1
  41. package/docs/widget/AdSense.md +4 -0
  42. package/docs/widget/AnimatedBox.md +7 -3
  43. package/docs/widget/AnimatedText.md +4 -0
  44. package/docs/widget/Audio.md +5 -1
  45. package/docs/widget/Avatar.md +15 -3
  46. package/docs/widget/Badge.md +4 -0
  47. package/docs/widget/Button.md +4 -0
  48. package/docs/widget/COMMON_PROPS.md +52 -0
  49. package/docs/widget/Card.md +4 -0
  50. package/docs/widget/Carousel.md +4 -0
  51. package/docs/widget/Chart.md +4 -0
  52. package/docs/widget/Checkbox.md +4 -0
  53. package/docs/widget/Chip.md +4 -0
  54. package/docs/widget/CircularBar.md +4 -0
  55. package/docs/widget/CircularChart.md +4 -0
  56. package/docs/widget/CodeViewer.md +4 -0
  57. package/docs/widget/Column.md +4 -0
  58. package/docs/widget/Container.md +4 -0
  59. package/docs/widget/DataTable.md +4 -0
  60. package/docs/widget/Divider.md +4 -0
  61. package/docs/widget/DraggBox.md +6 -2
  62. package/docs/widget/Dropdown.md +6 -0
  63. package/docs/widget/DroppBox.md +5 -1
  64. package/docs/widget/FloatingActionButton.md +4 -0
  65. package/docs/widget/GridView.md +4 -0
  66. package/docs/widget/Icon.md +4 -0
  67. package/docs/widget/Image.md +14 -3
  68. package/docs/widget/Input.md +4 -0
  69. package/docs/widget/Inspector.md +2 -2
  70. package/docs/widget/InstallButton.md +4 -0
  71. package/docs/widget/ListTile.md +4 -0
  72. package/docs/widget/ListView.md +4 -0
  73. package/docs/widget/Maps.md +191 -0
  74. package/docs/widget/Markdown.md +4 -0
  75. package/docs/widget/MatrixRain.md +4 -0
  76. package/docs/widget/Pagination.md +4 -0
  77. package/docs/widget/ParallaxBox.md +4 -0
  78. package/docs/widget/ProgressBar.md +4 -0
  79. package/docs/widget/QRCode.md +4 -0
  80. package/docs/widget/README.md +12 -10
  81. package/docs/widget/Radio.md +5 -1
  82. package/docs/widget/Rating.md +4 -0
  83. package/docs/widget/Row.md +4 -0
  84. package/docs/widget/Skeleton.md +4 -0
  85. package/docs/widget/Slider.md +4 -0
  86. package/docs/widget/Stack.md +4 -0
  87. package/docs/widget/Stepper.md +4 -0
  88. package/docs/widget/Switch.md +4 -0
  89. package/docs/widget/Tabs.md +5 -5
  90. package/docs/widget/Text.md +4 -0
  91. package/docs/widget/Tooltip.md +14 -13
  92. package/docs/widget/TreeView.md +4 -0
  93. package/docs/widget/Video.md +6 -2
  94. package/package.json +2 -2
  95. package/src/animations/AnimatedBox.js +4 -1
  96. package/src/animations/MatrixRain.js +3 -3
  97. package/src/core/hmr-client.js +129 -34
  98. package/src/index.d.ts +195 -7
  99. package/src/index.js +2 -0
  100. package/src/navigations/AppBar.js +22 -16
  101. package/src/navigations/BottomNavigation.js +8 -13
  102. package/src/navigations/CollapsibleSideBar.js +30 -8
  103. package/src/navigations/Drawer.js +25 -15
  104. package/src/navigations/DrawerItem.js +3 -1
  105. package/src/navigations/Scaffold.js +26 -12
  106. package/src/navigations/Tabs.js +104 -59
  107. package/src/tools/color.js +54 -0
  108. package/src/tools/index.js +9 -1
  109. package/src/tools/scale.js +41 -0
  110. package/src/tools/shadow.js +20 -0
  111. package/src/utils/index.js +1 -0
  112. package/src/utils/readableInk.js +19 -0
  113. package/src/utils/syntaxHighlight.js +78 -10
  114. package/src/utils/themes.js +217 -54
  115. package/src/utils/units.js +33 -11
  116. package/src/widget-factory/effects.js +56 -5
  117. package/src/widget-factory/processProps.js +10 -1
  118. package/src/widget-factory/remTool.js +15 -29
  119. package/src/widget-factory/stackPosition.js +18 -3
  120. package/src/widget-factory/translateProps.js +60 -1
  121. package/src/widgets/Accordion.js +20 -23
  122. package/src/widgets/AdMob.js +7 -2
  123. package/src/widgets/Avatar.js +236 -133
  124. package/src/widgets/Badge.js +3 -2
  125. package/src/widgets/BottomSheet.js +14 -13
  126. package/src/widgets/Button.js +69 -30
  127. package/src/widgets/Card.js +6 -2
  128. package/src/widgets/Carousel.js +12 -10
  129. package/src/widgets/Chart.js +28 -5
  130. package/src/widgets/Checkbox.js +76 -18
  131. package/src/widgets/Chip.js +31 -10
  132. package/src/widgets/CircularBar.js +7 -7
  133. package/src/widgets/CircularChart.js +26 -8
  134. package/src/widgets/CodeViewer.js +93 -20
  135. package/src/widgets/Container.js +13 -0
  136. package/src/widgets/DataTable.js +17 -11
  137. package/src/widgets/DraggBox.js +2 -1
  138. package/src/widgets/Dropdown.js +59 -14
  139. package/src/widgets/DroppBox.js +4 -2
  140. package/src/widgets/FloatingActionButton.js +13 -7
  141. package/src/widgets/Icon.js +3 -1
  142. package/src/widgets/Image.js +102 -3
  143. package/src/widgets/Input.js +30 -23
  144. package/src/widgets/Inspector.js +54 -1
  145. package/src/widgets/InstallButton.js +6 -4
  146. package/src/widgets/ListTile.js +4 -3
  147. package/src/widgets/ListView.js +30 -12
  148. package/src/widgets/Maps.js +1495 -0
  149. package/src/widgets/Markdown.js +37 -26
  150. package/src/widgets/Modal.js +14 -11
  151. package/src/widgets/Pagination.js +16 -11
  152. package/src/widgets/ProgressBar.js +6 -4
  153. package/src/widgets/Radio.js +73 -15
  154. package/src/widgets/Rating.js +24 -6
  155. package/src/widgets/Skeleton.js +31 -31
  156. package/src/widgets/Slider.js +29 -18
  157. package/src/widgets/SnackBar.js +35 -39
  158. package/src/widgets/Stepper.js +13 -9
  159. package/src/widgets/Switch.js +59 -18
  160. package/src/widgets/Tooltip.js +24 -12
  161. package/src/widgets/TreeView.js +27 -15
  162. package/src/widgets/index.js +1 -0
package/docs/README.md CHANGED
@@ -14,6 +14,7 @@ This documentation is one continuous book. Start at **Chapter 0** and follow the
14
14
  | 9 | [State, Router & Services](guides/state.md) | Shared state, navigation routes, storage, and HTTP | Intermediate |
15
15
  | 10 | [Tools & Utilities](tools/README.md) | The helper functions that ship with the framework | Intermediate |
16
16
  | 11 | [The CLI](cli/README.md) | Create, run, and build projects with `flet-box` | Intermediate |
17
+ | 12 | [Clean UI](guides/clean-ui.md) | The design system every app ships with and the gate that enforces it | Intermediate |
17
18
  | A | [Appendices](guides/minification-and-protection.md) | Minification, mobile, compatibility, embedding, online updates (OTA) | Advanced |
18
19
 
19
20
  > **Levels.** *Beginner:* start here and read in order. *Intermediate:* continue in order — you are comfortable with widgets. *Advanced:* for when you ship, skip on a first read.
@@ -50,6 +51,7 @@ Front cover (this page)
50
51
  → Animation helpers · tools/animation.md
51
52
  → Utilities guide · guides/utilities.md
52
53
  → The CLI · cli/README.md
54
+ → Clean UI · guides/clean-ui.md
53
55
  → Appendices (minification → mobile → compatibility → embedding → online updates)
54
56
  → The end of the book
55
57
  ```
@@ -62,6 +62,33 @@ http://localhost:8000
62
62
 
63
63
  The default project includes screens, an app bar, a drawer, routing, and the files needed to start experimenting.
64
64
 
65
+ ### Pointing the project at its repository
66
+
67
+ `flet-box create` writes a GitHub Pages workflow into every project. Pass the
68
+ repository with `--repo` and the same address is filled in everywhere it is
69
+ needed:
70
+
71
+ ```bash
72
+ flet-box create my-app --repo kuko53348/my-app
73
+ ```
74
+
75
+ That sets `update.url` in `fletbox.config.json` (what an installed app polls for
76
+ an over-the-air update), names the site at the top of
77
+ `.github/workflows/build.yml`, and prints the address in the closing summary:
78
+
79
+ ```text
80
+ ⬆️ OTA updates: https://kuko53348.github.io/my-app/
81
+ CI: .github/workflows/build.yml publishes the web to GitHub Pages on every push.
82
+ It goes live at: https://kuko53348.github.io/my-app/
83
+ Enable it once: Settings → Pages → Source: GitHub Actions. The job prints the URL.
84
+ ```
85
+
86
+ The first positional argument is always the project name; a flag that arrives
87
+ before it is read as the name. Without `--repo` nothing is invented — the
88
+ placeholder stays in both files and the workflow says which two to edit. The
89
+ reason the workflow log, not the header, is the authority on the live address is
90
+ explained in [The Pages address of a generated app](../guides/online-updates.md).
91
+
65
92
  ## Project templates
66
93
 
67
94
  FletBox provides four project templates.
@@ -423,8 +450,116 @@ Capacitor template with different slots keeps working. It:
423
450
  The script is idempotent: running it twice leaves identical files. The brand
424
451
  color `#1a1a2e` is defined at the top of the script.
425
452
 
453
+ ## Convert asset images to PNG
454
+
455
+ Screens need PNG, and a `logo.jpg` dropped into `assets/` still works — until an
456
+ Android or iOS build asks for a format the source photo cannot give it.
457
+ `flet-box to-png` converts a folder of images to PNG using nothing but Node
458
+ itself, so it works on a machine where no image library is installed.
459
+
460
+ ```bash
461
+ flet-box to-png # converts ./assets, ./public/assets or ./src/assets
462
+ flet-box to-png assets # converts one folder
463
+ flet-box to-png assets --dry-run
464
+ ```
465
+
466
+ It recognises a file by its leading bytes, never by its extension, so a
467
+ `logo.jpg` that is really a PNG is treated as the PNG it is. What it converts:
468
+
469
+ | Format | Notes |
470
+ | --- | --- |
471
+ | JPEG | Baseline and extended sequential, greyscale or colour, any sampling factor (4:4:4, 4:2:2, 4:2:0, 4:1:1), restart intervals. Progressive, arithmetic-coded, lossless, 12-bit and CMYK files are refused with the reason. |
472
+ | PNG | Colour types grey/RGB/palette/grey+alpha/RGBA at 1–16 bits, all five row filters, `tRNS` transparency. Already a PNG, so it is left alone unless you ask with `--optimize` or `--force`. Interlaced (Adam7) files are refused. |
473
+ | GIF | First frame, with or without interlacing and transparency. An animated file converts with a note saying how many frames were dropped. |
474
+ | BMP | Uncompressed 1/4/8/24/32-bit, top-down or bottom-up. Run-length (RLE) files are refused. |
475
+
476
+ These need a codec or a rasteriser that a zero-dependency package cannot carry,
477
+ so they are reported as declined rather than quietly skipped: WebP, AVIF/HEIF,
478
+ TIFF, ICO and SVG.
479
+
480
+ Nothing is overwritten. `assets/logo.jpg` becomes `assets/logo.png` beside it and
481
+ the source stays; if `logo.png` already exists the file is kept and reported as a
482
+ name clash, because that collision usually means someone already converted it by
483
+ hand. `--replace` deletes each source, and only after the PNG it replaces has been
484
+ read back and checked:
485
+
486
+ ```bash
487
+ flet-box to-png assets --out build/img # write the PNGs somewhere else
488
+ flet-box to-png assets --force # replace an existing logo.png
489
+ flet-box to-png assets --replace # delete the source once its PNG is verified
490
+ flet-box to-png assets --optimize # re-pack the PNGs already in the folder
491
+ flet-box to-png assets --flat # top level only, no sub-folders
492
+ ```
493
+
494
+ The summary counts the outcomes apart — converted, declined, already a PNG, name
495
+ clashes, and with `--optimize` also optimized, already optimal and the bytes saved
496
+ — and only a file whose bytes could not be decoded counts as a failure, which is
497
+ the one case that exits non-zero.
498
+
499
+ ### The PNG it writes is the smallest one that holds the picture
500
+
501
+ A converted file is not written in one fixed layout. The writer looks at the
502
+ pixels first and keeps whichever arrangement is smallest: a palette when the
503
+ frame really does hold 256 colours or fewer (an exact address per pixel, not a
504
+ quantisation), greyscale when every channel already agrees, three bytes per pixel
505
+ when no pixel uses alpha. It then deflates the five row filters and keeps the
506
+ shortest result. Nothing is approximated, so the file reads back as the same
507
+ picture — and the command checks that before it calls the conversion a success.
508
+
509
+ `--optimize` asks the same question about files that already are PNG, which is
510
+ how a hand-made asset gets its turn:
511
+
512
+ ```bash
513
+ flet-box to-png assets --optimize
514
+ ```
515
+
516
+ It replaces a PNG only when the new bytes are smaller *and* every pixel matches,
517
+ so running it twice is free: the second pass reports each file as already optimal
518
+ and touches nothing. The repo's own icons lose 13 KB on the 1024 maskable and 3 KB
519
+ on the logo, and the other four come back as already optimal. A 16-bit PNG is left
520
+ alone either way — rewriting it would trade someone else's bit depth for a smaller
521
+ file, and "smaller without losing quality" does not cover that.
522
+
523
+ What this cannot do is make a photograph small. PNG stores every pixel, and a
524
+ camera JPEG has already thrown most of them away, so the PNG of a photo comes out
525
+ three to four times the file it came from (a 3.5 MB picture writes as 12 MB). The
526
+ command converts it anyway and says so in the report: keep the JPEG — or a WebP
527
+ from a tool that carries a codec — for that picture, and let `to-png` do the flat
528
+ art, the logos and the icons.
529
+
530
+ ## Check the UI
531
+
532
+ `flet-box check-ui` reads your app's own `src/` and reports the design mistakes a
533
+ static pass can prove: a hand-typed color literal instead of a token, a size that
534
+ is not on the 8-based scale, a tap target below the Fitts floor, a form field with
535
+ no label, and text whose contrast against the fill it sits on is below WCAG AA.
536
+
537
+ ```bash
538
+ flet-box check-ui # scan ./src
539
+ flet-box check-ui --root ../my-app # scan another project
540
+ flet-box check-ui --tokens # print the measured token contrast table
541
+ flet-box check-ui --strict # contrast warnings fail too
542
+ flet-box check-ui --json # machine-readable findings
543
+ ```
544
+
545
+ Aliases: `flet-box ui`, `flet-box checkUi`. The command exits `1` when it finds an
546
+ error, or when it finds a warning with `--strict`, so it works as a CI gate.
547
+
548
+ Every prop name the checker uses comes from the installed package's own prop
549
+ registry, so it cannot invent an API while judging yours. The scale, the radii and
550
+ the tap minimum are the framework's own numbers, read from its `scale` module —
551
+ the same table the widget defaults are drawn from.
552
+
553
+ What it deliberately does not judge: taste, hierarchy, and whether the screen
554
+ solves the user's problem. `flet-box create` writes the rules as a skill file
555
+ alongside the app, and that file says so.
556
+
557
+ The full contract — the 21 rules, every finding code, and how to fix one — is
558
+ [Chapter 12, Clean UI](../guides/clean-ui.md).
559
+
426
560
  ## Command reference
427
561
 
562
+
428
563
  | Command | What it does |
429
564
  | --- | --- |
430
565
  | `flet-box create <name>` | Creates a default project. |
@@ -432,6 +567,7 @@ color `#1a1a2e` is defined at the top of the script.
432
567
  | `flet-box create <name> --full` | Creates a project with drawer and bottom navigation. |
433
568
  | `flet-box create <name> --sidebar` | Creates a desktop sidebar project. |
434
569
  | `flet-box create <name> --adaptive` | Creates a mobile and desktop project. |
570
+ | `flet-box create <name> --repo <user>/<repo>` | Sets the GitHub Pages address, the OTA URL and the workflow header from the repo. |
435
571
  | `flet-box screen <name>` | Creates one screen. |
436
572
  | `flet-box screen <number>` | Creates up to ten screens. |
437
573
  | `flet-box component <name>` | Creates one component. |
@@ -447,6 +583,13 @@ color `#1a1a2e` is defined at the top of the script.
447
583
  | `flet-box run-native <android\|ios>` | Compiles and launches the app on an emulator/simulator or connected device. |
448
584
  | `flet-box run-native <android\|ios> --device` | Forces a physical device instead of the emulator. |
449
585
  | `flet-box run-native <android\|ios> --livereload` | Launches the app with hot reload on the device. |
586
+ | `flet-box to-png [dir]` | Converts JPEG, GIF, BMP and non-PNG-precision images in a folder to PNG. |
587
+ | `flet-box to-png --dry-run` | Reports what would happen, writes nothing. |
588
+ | `flet-box to-png --replace` | Deletes each source after its PNG was written and read back. |
589
+ | `flet-box to-png --optimize` | Re-packs the PNGs already in the folder, and only when that makes them smaller. |
590
+ | `flet-box check-ui` | Runs the clean-UI gate over `src/`: tokens, scale, tap targets, labels, contrast. |
591
+ | `flet-box check-ui --tokens` | Prints the measured contrast of every color token, on the background and on the surface. |
592
+ | `flet-box check-ui --strict` | Makes the contrast warnings fail the run, not just the errors. |
450
593
  | `flet-box pkg` | Opens the package manager. |
451
594
  | `flet-box --version` | Shows the CLI version. |
452
595
  | `flet-box --help` | Shows the command list. |
@@ -536,7 +679,7 @@ npm install --save-dev esbuild
536
679
  ## Continue reading
537
680
 
538
681
  - **Previous:** [FletBox utilities](../guides/utilities.md)
539
- - **Next:** [Minify and protect a FletBox build](../guides/minification-and-protection.md) — Appendix A
682
+ - **Next:** [Clean UI](../guides/clean-ui.md) — Chapter 12
540
683
  - **Index:** [Guides index](../guides/README.md) · [The FletBox Book](../README.md)
541
684
 
542
685
  You are reading **Chapter 11 · The CLI**.
@@ -9,6 +9,8 @@ This directory is the **conceptual index** of FletBox: how to build apps, set up
9
9
  - **[Routing with FletBox](router.md)** — Chapter 9 · fixed paths, dynamic parameters, queries, history, and route subscriptions.
10
10
  - **[Frontend services](frontend-services.md)** — Chapter 9 · the practical walkthrough of the three storage layers (`RamStore`, `Session`, `Storage`) and the HTTP client.
11
11
  - **[FletBox utilities](utilities.md)** — Chapter 10 · layout/theme/format helpers and validator tools.
12
+ - **[The CLI](../cli/README.md)** — Chapter 11 · create, run, and build projects with `flet-box`.
13
+ - **[Clean UI](clean-ui.md)** — Chapter 12 · the token scale, the 21 rules, and the `flet-box check-ui` gate that enforces them.
12
14
  - **[Widget structure](widget-structure.md)** — the canonical, modular shape every widget follows (and how new ones are added).
13
15
 
14
16
  ## Appendices (Advanced — for shipping)
@@ -0,0 +1,211 @@
1
+ # Clean UI — the system contract
2
+
3
+ A FletBox screen is not decorated, it is extended: every app copies the widget
4
+ defaults, and every later screen copies the screen before it. This page is the
5
+ contract that makes the copy look like one product — the numbers, the tokens,
6
+ and the two commands that prove a screen obeys them.
7
+
8
+ It is the published version of the rule file that ships inside every generated
9
+ app, so the app's agent and the framework's documentation cannot disagree.
10
+
11
+ ## The two gates
12
+
13
+ A generated project has two checks, and they answer different questions:
14
+
15
+ ```bash
16
+ npm run check # is the API real? widgets, props, imports
17
+ npm run ui # does the screen follow these rules?
18
+ npm run ui -- --tokens # measured contrast of every token, on both surfaces
19
+ npm run dev # then look at it: the checker has no eyes
20
+ ```
21
+
22
+ `npm run check` (`flet-box check-api`) is about correctness — an invented prop
23
+ fails the build. `npm run ui` (`flet-box check-ui`) is about the system — a
24
+ `padding: 21` is valid JavaScript and a design defect at the same time. Neither
25
+ one replaces opening the page.
26
+
27
+ ## Where the rules live
28
+
29
+ | Home | What it holds |
30
+ | --- | --- |
31
+ | `.claude/skills/fletbox-clean-ui/SKILL.md` | The 21 numbered rules below, written for the agent that builds the screen. Mirrored to `.qoder/skills/…` and `.cursor/rules/fletbox-clean-ui.mdc`. |
32
+ | `AGENTS.md` §4 of the app | The short version: one brand color per screen, no hex literals, spacing and radius only from the scale. |
33
+ | `src/tools/scale.js` in the framework | The tables themselves — `SPACING_SCALE`, `RADIUS_SCALE`, `TYPE_SCALE`, `TAP_MINIMUM`, `PILL`, `LABEL_TEXT`. |
34
+ | `bin/commands/checkUi.js` | The gate. It re-exports those same tables instead of keeping its own copy. |
35
+
36
+ That last line matters more than it looks. A gate that carries its own numbers
37
+ drifts from the widgets, and the app then gets told to be clean by values the
38
+ components it uses do not follow. The defaults and the checker read one table.
39
+
40
+ ## The scale
41
+
42
+ | Dimension | Allowed values |
43
+ | --- | --- |
44
+ | Spacing | `0 · 4 · 8 · 12 · 16 · 18 · 24 · 32 · 40 · 48 · 64 · 96` |
45
+ | Radius | `0 · 4 · 8 · 12 · 16 · 999` — `999` is a pill, `0` a flush edge; both are decisions |
46
+ | Type | `12 · 14 · 16 · 18 · 24 · 32 · 40 · 48` |
47
+ | Tap minimum | `40` — Fitts: the smallest pressable box a finger reliably hits |
48
+ | Label text | `14` — the reading size a control's own label may not fall under |
49
+
50
+ Type steps are two apart on purpose: a 15 next to a 16 reads as a mistake, not
51
+ as hierarchy. The same logic gives the scale its gaps — a value between two
52
+ steps is a shape no other element in the app has.
53
+
54
+ ## Every number is a framework unit
55
+
56
+ A number on a size prop is not a CSS pixel. It goes through `toREM`
57
+ (`src/utils/units.js`) and reaches the page as `value / 16` rem, so `40` is 40px
58
+ at the default base and 60px for a reader who raised their browser font to 24.
59
+ That is what makes one build fit a phone, a tablet and a desktop, and it is why
60
+ the Fitts floor here is 40 rem-units rather than the 44px the guidelines quote:
61
+ a target written in rem obeys the accessibility setting, a fixed pixel shrinks
62
+ in relative terms the moment the type grows.
63
+
64
+ `toREM` also rewrites a length someone spelled out in pixels — `"8px 12px"`
65
+ becomes `"0.5rem 0.75rem"` — while anything that is not made only of pixel
66
+ tokens (`"50%"`, `"calc(100% - 8px)"`, `"2px solid red"`) passes through
67
+ untouched.
68
+
69
+ Pixels stay pixels where a pixel is the honest unit:
70
+
71
+ - canvas backing stores (`QRCode`, `Chart`, `CircularBar`, `CircularChart`),
72
+ - Maps' projected tile and pin space,
73
+ - anything measured from the DOM (`offsetWidth`, `getBoundingClientRect`, scroll offsets),
74
+ - the hairline `borderWidth`, and shadow depth (`SHADOW_LEVELS`), because a
75
+ shadow that scales with the font stops matching the surface it lifts,
76
+ - a virtualised row's scroll math — `ListView` converts `itemSize + gap` through
77
+ the live root font size so the slot a row gets and the row's own box agree.
78
+
79
+ The framework keeps this honest with its own gate, `scripts/check-widget-scale.mjs`
80
+ (part of `npm test`): a length written straight to `el.style.*` that is neither
81
+ `toREM(...)` nor measured geometry fails the build.
82
+
83
+ ## The 21 rules
84
+
85
+ When two of them conflict, take them in this order: **consistency · clarity ·
86
+ accessibility · usability · scalability · maintainability · conversion**.
87
+
88
+ | # | Rule | What it forbids |
89
+ | --- | --- | --- |
90
+ | 00 | The two gates | Shipping a screen neither `check` nor `ui` has run against. |
91
+ | 01 | System before component | Deciding a value case by case; define the token, then the component, then the screen. |
92
+ | 02 | Consistency is sacred | An arbitrary difference — a second card radius, a second button height. |
93
+ | 03 | Design tokens only | A hex literal, a `px` string, a value off the scale. |
94
+ | 04 | Hierarchy in three seconds | More than one primary action competing on a screen. |
95
+ | 05 | One purpose per screen | A screen that needs "and" to describe it. |
96
+ | 06 | Reduce cognitive load | Choices with no default, fields that could be inferred, copy that explains the interface. |
97
+ | 07 | Spacing communicates relationship | Related things sitting farther apart than unrelated ones. |
98
+ | 08 | Typography is structure | More than one size per element, body copy centered, a line that runs past ~75 characters. |
99
+ | 09 | Color carries meaning | Brand color used for decoration, or a destructive action in the same fill as a safe one. |
100
+ | 10 | Depth is a scale | An `elevation` number on a widget that never reads it, or a hand-typed `boxShadow`. |
101
+ | 11 | Accessibility is a floor | Text under WCAG AA contrast, a tap target under 40, an image with no `alt` decision. |
102
+ | 12 | Every interaction has four states | A control or list with no loading, empty, error or success path. |
103
+ | 13 | Forms prevent the error | Validation that only reports after a failed submit; an unlabeled field or toggle. |
104
+ | 14 | Destructive actions designed last | A delete that is the closest, fastest, most colorful thing on the screen. |
105
+ | 15 | Reduce interaction cost | Taps, scrolls and keystrokes that carry no information. |
106
+ | 16 | Respect the platform | Fighting the browser's own conventions — back, scroll, text selection. |
107
+ | 17 | The review | Shipping before reading your own diff as the user. |
108
+ | 18 | Where these books are wrong | Following a guideline that measurement disagrees with. |
109
+ | 19 | What no checker can see | Taste, hierarchy in the wild, whether the screen solves the problem. |
110
+ | 20 | The loop | Fixing the checker instead of the code, or leaving a rule you had to break undocumented. |
111
+
112
+ ## What `flet-box check-ui` proves
113
+
114
+ The gate reads your `src/` and reports what a static pass can prove. Prop names
115
+ come from the installed package's own registry, so it cannot invent an API while
116
+ judging yours. Each finding carries a code, and the code maps to a rule above.
117
+
118
+ ### Errors (exit 1)
119
+
120
+ | Code | Rule | What it catches |
121
+ | --- | --- | --- |
122
+ | `RAW_COLOR` | 03 | A color literal on a widget outside `src/database/themes.js`. |
123
+ | `OFF_SCALE_SPACING` | 07 | A padding, margin, gap or size off the spacing scale — including inside a `"12px 20px"` shorthand. |
124
+ | `OFF_SCALE_RADIUS` | 03 | A corner radius that is not a scale step. |
125
+ | `OFF_SCALE_TYPE` | 08 | A font size off the type scale. |
126
+ | `SMALL_TAP_TARGET` | 11 | A pressable widget under the Fitts floor, including a bare `Icon` with `onPress`. |
127
+ | `LOW_CONTRAST` | 11 | Text measured below AA against the fill it actually sits on. |
128
+ | `UNLABELED_FIELD` | 13 | An `Input` or `Dropdown` with neither a `label` nor a `placeholder`. |
129
+ | `UNLABELED_TOGGLE` | 13 | A `Switch`, `Checkbox` or `Radio` with nothing to say what it toggles. |
130
+ | `DESTRUCTIVE_COLOR` | 14 | A delete/remove verb rendered in the brand fill instead of the danger one. |
131
+ | `UNREADABLE` | — | A file the scanner could not parse, reported rather than skipped. |
132
+
133
+ ### Warnings (fail with `--strict`)
134
+
135
+ | Code | Rule | What it catches |
136
+ | --- | --- | --- |
137
+ | `LOW_CONTRAST_SOFT` | 11 | Text that clears AA only because of its size. |
138
+ | `LOW_CONTRAST_UI` | 11 | A graphic that carries meaning below 3:1. |
139
+ | `LOW_CONTRAST_ON_SURFACE` | 11 | Text measured against `background` and `surface` at once, when the fill is inherited. |
140
+ | `HAND_MIXED_COLOR` | 03 | `colors.primary + "22"` — a one-off shade no other screen can reuse. |
141
+ | `DEAD_ELEVATION` | 10 | A numeric `elevation` on a widget that never reads it: CSS drops the bare number and the box stays flat in silence. |
142
+ | `IMAGE_NO_ALT` | 11 | An `Image` with no `alt` — `alt: ""` is a decision, a missing prop is an oversight. |
143
+ | `ICON_ONLY_CONTROL` | 15 | A pressable glyph with no name; wrap it in `Tooltip({ text, child })`. |
144
+ | `CENTERED_BODY_TEXT` | 08 | A paragraph centered; a paragraph needs a left edge to find the next line. |
145
+ | `GENERIC_ACTION_LABEL` | 06 | "OK", "Submit", "Click here" — a label that says nothing about what pressing it does. |
146
+ | `CONFIRM_ORDER` | 14 | A destructive button placed before Cancel in the same row. |
147
+ | `UNCONSTRAINED_FORM_WIDTH` | 08 | A form block whose fields run to the full width of a desktop window. |
148
+
149
+ ### Informational
150
+
151
+ | Code | Rule | What it says |
152
+ | --- | --- | --- |
153
+ | `SMALL_PRESET` | 11 | `size: "small"` on a control that is the screen's main action (~30px). |
154
+ | `CROWDED_FORM` | 06 | Many fields in one file: group them, or split the task. |
155
+ | `LIST_WITHOUT_STATE` | 12 | A list with no loading, empty or failed state in this file. |
156
+ | `MULTIPLE_PRIMARY_ACTIONS` | 04 | Several filled buttons on one screen. |
157
+
158
+ ## Running the gate
159
+
160
+ ```bash
161
+ flet-box check-ui # scan ./src
162
+ flet-box check-ui --root ../my-app # scan another project
163
+ flet-box check-ui --tokens # print the measured token contrast table
164
+ flet-box check-ui --strict # warnings fail too
165
+ flet-box check-ui --json # machine-readable findings
166
+ ```
167
+
168
+ Aliases: `flet-box ui`, `flet-box checkUi`. Exit code is `1` when an error is
169
+ present (or a warning, with `--strict`), so it fits in CI as the app's
170
+ `npm run ui`.
171
+
172
+ `--tokens` is the one worth reading before you pick colors. It prints the
173
+ measured ratio of every token against `background` and `surface` — for the
174
+ palette in use, so switch the theme and read it again if you ship both. A brand
175
+ color that looks fine and measures 2.15:1 (`colors.warning`, decoration only)
176
+ becomes a number instead of an argument, and the table already tells you which
177
+ tokens clear AA body text, which are large-text-or-UI only, and on which fill
178
+ writing text is a mistake.
179
+
180
+ ## Fixing a finding
181
+
182
+ Fix the code, not the checker. A finding you keep needs one sentence of
183
+ justification — in the commit or in the file — because the next person, human or
184
+ agent, will otherwise copy the exception as if it were the rule.
185
+
186
+ And a rule you had to break is information about the rule: if the exception was
187
+ right twice, it is a defect in the scale, not in the app.
188
+
189
+ ## What no checker can see
190
+
191
+ `check-ui` has no eyes. It cannot tell you that the screen is ugly, that the
192
+ hierarchy reads wrong at a glance, or that the flow does not solve the user's
193
+ problem. Those are rules 17 and 19, and they are the ones that need a person.
194
+ Run the gate, then open the page.
195
+
196
+ ## Related pages
197
+
198
+ - [FletBox utilities](utilities.md) — the theme and layout helpers the tokens come from.
199
+ - [Themes](../tools/theme.md) — `registerPalette`, the call that gets your brand onto the page.
200
+ - [The CLI](../cli/README.md) — every command, including `check-api` and `unpack`.
201
+ - [Widget structure](widget-structure.md) — how a widget is built, and where its defaults live.
202
+
203
+ ---
204
+
205
+ ## Continue reading
206
+
207
+ - **Previous:** [The CLI](../cli/README.md) — Chapter 11
208
+ - **Next:** [Minify and protect a FletBox build](minification-and-protection.md) — Appendix A
209
+ - **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
210
+
211
+ You are reading **Chapter 12 · Clean UI**.
@@ -255,6 +255,6 @@ Minification reduces the web payload inside the native application. It does not
255
255
 
256
256
  ## Continue reading
257
257
 
258
- - **Previous:** [The FletBox CLI](../cli/README.md)
258
+ - **Previous:** [Clean UI](clean-ui.md) — Chapter 12
259
259
  - **Next:** [Build FletBox for mobile and other platforms](mobile-and-platforms.md) — Appendix B
260
260
  - **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
@@ -15,7 +15,11 @@ you edit source code
15
15
  v
16
16
  git tag v1.0.1 pushed to GitHub
17
17
  |
18
- | GitHub Actions (on: tags v*)
18
+ | GitHub Actions — the web job deploys www/ on every push to main,
19
+ | and keeps www/config.json only when that commit carries a v* tag.
20
+ | A run started BY THE TAG cannot deploy (the github-pages environment
21
+ | accepts the default branch only), so publish the feed with one
22
+ | manual run on main: Actions → Deploy → Run workflow.
19
23
  v
20
24
  www/ deployed to GitHub Pages <- the ONLY place an update is published
21
25
  |
@@ -33,7 +37,7 @@ loader.js fetches <update.url>/config.json
33
37
 
34
38
  Three invariants make this safe:
35
39
 
36
- 1. **Updates are published only by release tags.** Ordinary `git push` runs CI (so breakage is caught) but never publishes, so a half-finished test build can never reach your users.
40
+ 1. **Only a release tag can be offered as an update.** The web workflow publishes your site on every push to main, so the preview is never stale — but it deletes `www/config.json` from any build whose commit carries no `v*` tag. With no manifest to read, an installed app logs `no remote config (offline or not deployed yet)` and keeps running the release it already has. So the site is live for reading while the update feed stays on tags, and a half-finished test build can never reach your users.
37
41
  2. **The app verifies integrity.** A downloaded bundle is executed only if its sha256 matches the hash in `config.json`.
38
42
  3. **The app always launches.** Offline, a wrong URL, a hash mismatch, or a declined dialog all fall back to the bundle already on the device. OTA can never brick the app.
39
43
 
@@ -54,7 +58,7 @@ flet-box release --push # also push the commit and the tag to origin
54
58
  2. Bumps the version in `package.json` using the odometer scheme below.
55
59
  3. Rebuilds the production bundle (`www/`), which regenerates `www/config.json` with the new version and the sha256 of the new bundle.
56
60
  4. Commits the version bump and creates the annotated tag `vX.Y.Z`.
57
- 5. Optionally pushes (`--push`), which triggers the CI deploy.
61
+ 5. Optionally pushes (`--push`), which deploys the site. The tag is what turns the update feed on, and a tag run cannot deploy by itself — after pushing, start the workflow once more from Actions (Run workflow, on main) and that build keeps `www/config.json`.
58
62
 
59
63
  If the build fails, the version bump is rolled back so a failed release leaves no trace.
60
64
 
@@ -79,6 +83,10 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
79
83
  "builtAt": "2026-10-11T19:53:24.524Z",
80
84
  "bundle": "src/app.js",
81
85
  "size": 36132,
86
+ "assets": [
87
+ { "path": "docs/widget/Maps.md", "hash": "b0f2…", "size": 4213 },
88
+ { "path": "data/catalog.json", "hash": "7ac9…", "size": 980 }
89
+ ],
82
90
  "minNativeVersion": "0.0.0",
83
91
  "update": {
84
92
  "url": "https://kuko53348.github.io/my-app/",
@@ -96,6 +104,7 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
96
104
  | `hash` | sha256 of the bundle. **This is what drives the update decision**, not the version. |
97
105
  | `bundle` | Path of the bundle relative to the update URL. |
98
106
  | `size` | Bundle size in bytes (shown in the dialog as KB). |
107
+ | `assets` | Extra files this release publishes (see [Shipping extra files](#shipping-extra-files-release-assets)). Each entry is verified by its own `hash`; the bundle `hash` never covers them. |
99
108
  | `minNativeVersion` | Oldest native shell this bundle supports. Older shells are told to update from the store instead. |
100
109
  | `update.url` | Where `config.json` and the bundle are published (your GitHub Pages site). |
101
110
  | `update.mode` | `prompt` (ask the user, default) or `auto` (download silently). |
@@ -127,6 +136,92 @@ To configure it by hand, create `fletbox.config.json`:
127
136
  }
128
137
  ```
129
138
 
139
+ ## The Pages address of a generated app
140
+
141
+ The URL you paste into `update.url` is not a guess: `flet-box create` knows the
142
+ address the app will be published to and prints it when it finishes.
143
+
144
+ ```bash
145
+ flet-box create my-app --repo kuko53348/my-app
146
+ ```
147
+
148
+ That `--repo` (or the same value typed at the interactive prompt) does three
149
+ things at once:
150
+
151
+ - sets `update.url` in `fletbox.config.json` to `https://kuko53348.github.io/my-app/`;
152
+ - writes that address into the header of the generated `.github/workflows/build.yml`;
153
+ - prints it in the closing summary, with the one-time setting Pages needs.
154
+
155
+ Without `--repo` nothing is invented. `fletbox.config.json` keeps the generic
156
+ placeholder `https://TU-USUARIO.github.io/TU-REPO/`, the workflow header names a
157
+ guess built from the folder name, and the workflow says so in its own comment:
158
+ write the real address in both files that state it.
159
+
160
+ **The workflow log is the authority.** The header names the address you typed at
161
+ scaffold time; the `Where this lives` step prints the address the run *actually*
162
+ belongs to, read from GitHub itself:
163
+
164
+ ```text
165
+ site: https://kuko53348.github.io/my-app/ <- from the running repo
166
+ header: https://TU-USUARIO.github.io/my-app/ <- from the file
167
+ version: 1.0.3
168
+ ```
169
+
170
+ When those two disagree, trust `site:`. They disagree when the repository was
171
+ created with a different name than the folder, or when the app was never pushed.
172
+ Note the owner is lower-cased: GitHub Pages serves the site under the
173
+ lower-cased login, so `Kuko53348` in your remote is `kuko53348` in the URL.
174
+
175
+ **A green run that 404s has one cause.** The deploy job can only write to Pages
176
+ after you enable the destination once: **Settings → Pages → Build and deployment
177
+ → Source: GitHub Actions**. Until you do, every run succeeds and no site exists —
178
+ which is exactly what the `Where this lives` step prints in the log, so a failed
179
+ release is never mistaken for a broken bundle.
180
+
181
+ The address is a **sub-path** (`/<repo>/`), and that is why the bundle uses
182
+ relative asset paths: the same `www/` a phone polls for updates is the one served
183
+ under any path, with no rebuild for a custom domain or a rename. If you do move to
184
+ a custom domain, change `update.url` in `fletbox.config.json` and rebuild — the
185
+ native shell keeps the old bundle until the manifest tells it otherwise.
186
+
187
+ ## Shipping extra files (release assets)
188
+
189
+ OTA swaps **one file**: `src/app.js`. Anything your app reads from disk at runtime — markdown pages, JSON data, per-tenant config — keeps the copy baked into the build the device installed. So an app installed three releases ago can run a bundle that was updated over the air and still be missing files the current catalog refers to. The symptom is not a crash, it is a "this page does not exist" for content that clearly exists online.
190
+
191
+ Declare those files in `fletbox.config.json` and they become part of every release:
192
+
193
+ ```json
194
+ {
195
+ "update": {
196
+ "assets": ["docs/**/*.md", "data/catalog.json"]
197
+ }
198
+ }
199
+ ```
200
+
201
+ Globs are relative to the project root and support `**`, `*` and `?`. At build time `flet-box createBundle` copies each match into `www/` at the same path, hashes it, and lists it in the manifest's `assets` array — which is also what your CI publishes to GitHub Pages.
202
+
203
+ The app then reads them through the loader instead of `fetch`:
204
+
205
+ ```js
206
+ const asset = await globalThis.__FLETBOX_OTA_ASSET__?.("docs/widget/Maps.md");
207
+ const markdown = asset && asset.ok ? await asset.text() : await (await fetch("./docs/widget/Maps.md")).text();
208
+ ```
209
+
210
+ It returns a `Response`, or `null` when the file exists in neither place. For each path it tries, in order:
211
+
212
+ 1. **The OTA cache** — the published copy already downloaded on a previous launch, so repeat reads and offline reads are free.
213
+ 2. **The published release** at `update.url`. The bytes are verified against the manifest hash; a mismatch is logged and ignored rather than trusted.
214
+ 3. **The copy inside this build**, exactly as `fetch` would return it.
215
+
216
+ Because step 3 is always available, the helper can never leave you with less than you had before: an unpublished file, an offline device, or a tampered download all serve the shipped copy.
217
+
218
+ Two rules keep this honest:
219
+
220
+ - **A file only updates OTA if it is listed.** An empty or missing `assets` list means the manifest publishes nothing but the bundle.
221
+ - **Bundle and assets are verified separately.** `hash` is the bundle; each asset entry carries its own hash, so one corrupted file cannot serve unverified content — and a bad asset never blocks a good bundle update.
222
+
223
+ **Old shells have no helper.** `loader.js` is baked into the native build and OTA replaces the bundle, never the loader — so a store build made before this feature cannot fetch published assets, however many updates it accepts. That is what the `fetch` fallback in the snippet is for, and it closes only when you ship a new store build.
224
+
130
225
  ## What the app does on launch
131
226
 
132
227
  `www/loader.js` (wired in automatically as the module entry of every production bundle) runs **before** your app:
@@ -141,17 +236,17 @@ The dialog is plain HTML/CSS rendered by the loader itself, so it appears before
141
236
 
142
237
  ## What OTA can and cannot change
143
238
 
144
- OTA replaces the **web layer only** — the files inside `www/`.
239
+ OTA replaces the **web layer only**: the `src/app.js` bundle plus whatever you list in `update.assets`. Everything else on the device is what the store build shipped.
145
240
 
146
241
  | Updates over the air | Needs a new store submission |
147
242
  | --- | --- |
148
243
  | UI, screens, widgets, styles | New native plugins / Capacitor version |
149
244
  | App logic and routing | New Android/iOS permissions |
150
- | Text, images, fonts, assets in `www/` | Changes to `AndroidManifest.xml` / `Info.plist` |
245
+ | Release assets declared in `update.assets` (markdown, JSON, images) | Changes to `AndroidManifest.xml` / `Info.plist` |
151
246
  | Bug fixes and new JS features | Native code or signing changes |
152
247
  | Anything inside the bundle | Anything that changes the app's identity or purpose |
153
248
 
154
- Rule of thumb: if the change lives in `src/` (your FletBox code) it ships OTA; if it lives in the native shell it goes through the store. The `minNativeVersion` field exists for the boundary case where a new web bundle needs a newer shell: older shells skip the OTA and you direct users to the store update.
249
+ Rule of thumb: if the change lives in `src/` (your FletBox code) it ships OTA; if it lives in the native shell it goes through the store. A data or content file ships OTA only when it is listed in `update.assets` — otherwise the device keeps the copy its store build baked in. The `minNativeVersion` field exists for the boundary case where a new web bundle needs a newer shell: older shells skip the OTA and you direct users to the store update.
155
250
 
156
251
  ## Store policy (read this before relying on OTA)
157
252
 
@@ -170,19 +265,20 @@ The generated service worker uses a hybrid strategy so OTA and offline coexist:
170
265
  - **Static assets** (fonts, icons, images, css): cache-first, since they are immutable per release.
171
266
  - **Cross-origin requests** (your remote `config.json` and bundle) are left to the network and are never cached by the worker.
172
267
 
173
- Downloaded OTA bundles live in a **separate** cache (`fletbox-ota`) owned by the loader, so a service-worker update never wipes an installed OTA bundle.
268
+ Downloaded OTA bundles **and** release assets live in a **separate** cache (`fletbox-ota`) owned by the loader, so a service-worker update never wipes an installed OTA bundle. The loader fetches published assets with `cache: no-store`, so a stale service-worker copy of a file in `www/` cannot shadow the published one.
174
269
 
175
270
  ## Requirements and troubleshooting
176
271
 
177
- Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings → Pages → Source = "GitHub Actions"), and the workflow from `flet-box create` (it already deploys `www/` on `v*` tags). GitHub Pages and Actions are free for public repos.
272
+ Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings → Pages → Source = "GitHub Actions"), and the workflow from `flet-box create` (`.github/workflows/build.yml` publishes `www/` on every push, and publishes the OTA manifest only for a `v*` tag; Android and iOS live in `build-native.yml`, which starts by hand). GitHub Pages and Actions are free for public repos.
178
273
 
179
274
  | Symptom | Likely cause |
180
275
  | --- | --- |
181
276
  | Build prints "THIS APP DOES NOT SUPPORT OTA UPDATES" | No public GitHub repo is reachable from the project (no `origin` remote, a non-GitHub remote, or nothing configured). The app still works — it runs the bundle it shipped with. Create the repo, push it, and rebuild. |
182
- | No dialog, app runs old code | `update.url` missing/incorrect, or the tag was never pushed (CI never published). |
277
+ | No dialog, app runs old code | `update.url` missing/incorrect, or no `v*` tag was published — a normal push updates the site but deliberately leaves the manifest out, so there is nothing to compare against. |
183
278
  | `hash mismatch` in console | The published `config.json` and bundle are from different builds. Re-run `flet-box release` so both are published together. |
184
279
  | Dialog appears but download fails | The Pages site is not reachable/CORS-blocked from the app origin. Pages serves with permissive headers; check you are not behind a proxy that strips them. |
185
280
  | Update applies but reverts on next launch | Cache Storage unavailable (private browsing). The bundle runs once but cannot persist. |
281
+ | A data/content file is missing on the device but published online | It is not listed in `update.assets`, so OTA only ever replaced the bundle — or the installed shell predates release assets, in which case the app must fall back to its own copy. |
186
282
  | Want to force a clean state | Clear site data for the app origin (this removes the OTA cache and localStorage entry). |
187
283
 
188
284
  Because the decision is hash-based, two builds of identical source produce the same hash and no spurious update is offered; only a real content change triggers the dialog.
@@ -98,7 +98,7 @@ Helpers for shaping collections and generating data.
98
98
 
99
99
  - `parseMarkdown(md)`, `parseInlineMarkdown(md)` — reserved placeholders that pass text through unchanged (real rendering is `markdownToWidgets`/`parseInlineToWidgets`).
100
100
  - `markdownToWidgets(md)` / `parseMarkdownToWidgets(md)` — markdown string → widgets (alias names for the same function).
101
- - `tokenize(code)`, `generateHighlightedHtml(code)`, `highlightColors` — syntax highlighting.
101
+ - `tokenize(code)`, `generateHighlightedHtml(code, palette?)`, `highlightColors` — syntax highlighting (light and dark token maps; the highlighter picks one from the active theme).
102
102
  - `escapeHtml(text)` — escape untrusted text before inserting it as HTML.
103
103
 
104
104
  ## Visual effects
@@ -31,7 +31,7 @@ one style would otherwise fight, and the older animation would resume after the
31
31
 
32
32
  ## Widgets
33
33
 
34
- - `AnimatedBox({ animations, timing, delay, fillMode, child, top, right, bottom, left })` — animates a child widget. `animations` is an array of `{ effect, from, to, duration }` entries; effects include `backgroundColor`, `color`, `borderRadius`, `opacity`, `scale`, `translate`, `rotate`, `width`, `height`, spacing, `boxShadow`, and gradients.
34
+ - `AnimatedBox({ animations, timing, delay, fillMode, child, top, right, bottom, left })` — animates a child widget. `animations` is an array of `{ effect, from, to, duration }` entries; each plays once and holds `to` at the end (add `reverse: true` to cycle back, and `loop: true` to repeat); effects include `backgroundColor`, `color`, `borderRadius`, `opacity`, `scale`, `translate`, `rotate`, `width`, `height`, spacing, `boxShadow`, and gradients.
35
35
  - `AnimatedText({ child, animations, sameTime, delayBetween, orientation })` — animates a `Text` widget (letters split per animation).
36
36
  - `MatrixRain({ chars, fontSize, speed, fadeAmount, resetProbability, useDynamicColor, position, zIndex })` — the falling-characters canvas effect (returns a `canvas`).
37
37
  - `ParallaxBox({ type, speed, direction, maxOffset, reverse, child })` — content that moves; `type` is `"scroll"`, `"mouse"`, or `"hover"`, `direction` is `"vertical"`, `"horizontal"`, or `"both"`.