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.
- package/AGENTS_FOR_CODE_GEN.md +284 -0
- package/CONTRIBUTING.md +238 -0
- package/README.md +2 -2
- package/bin/cli.js +88 -1
- package/bin/commands/auditFix.js +181 -27
- package/bin/commands/checkApi.js +14 -3
- package/bin/commands/checkStructure.js +368 -0
- package/bin/commands/checkSyntax.js +293 -0
- package/bin/commands/checkUi.js +666 -14
- package/bin/commands/create.js +14 -0
- package/bin/commands/createBundle.js +2 -2
- package/bin/commands/metrics.js +448 -0
- package/bin/commands/release.js +18 -0
- package/bin/commands/report.js +849 -0
- package/bin/commands/tools.js +76 -0
- package/bin/commands/unpack.js +39 -11
- package/bin/commands/verify.js +879 -0
- package/bin/mcp.js +280 -0
- package/bin/utils/contract.js +27 -0
- package/bin/utils/dtsProps.js +5 -1
- package/bin/utils/migrationGuide.js +146 -0
- package/bin/utils/moduleExports.js +9 -5
- package/bin/utils/templates.js +225 -18
- package/bin/utils/toolkit.js +191 -0
- package/docs/README.md +5 -1
- package/docs/arquitectura.md +1 -1
- package/docs/cli/README.md +168 -2
- package/docs/guides/README.md +6 -0
- package/docs/guides/accessibility.md +2 -1
- package/docs/guides/ai-toolkit.md +166 -0
- package/docs/guides/compatibility.md +1 -1
- package/docs/guides/migration.md +92 -0
- package/docs/guides/minification-and-protection.md +2 -1
- package/docs/guides/mobile-and-platforms.md +1 -1
- package/docs/guides/online-updates.md +2 -0
- package/docs/server/deployment.md +1 -1
- package/docs/tools/styling.md +2 -2
- package/docs/tools/theme.md +139 -4
- package/docs/widget/Accordion.md +74 -8
- package/docs/widget/AdMob.md +68 -3
- package/docs/widget/AdSense.md +67 -2
- package/docs/widget/AlertDialog.md +1 -1
- package/docs/widget/AnimatedBox.md +80 -39
- package/docs/widget/AnimatedText.md +74 -18
- package/docs/widget/Audio.md +73 -14
- package/docs/widget/Avatar.md +69 -4
- package/docs/widget/Badge.md +68 -3
- package/docs/widget/BottomSheet.md +2 -2
- package/docs/widget/Button.md +69 -3
- package/docs/widget/Card.md +69 -4
- package/docs/widget/Carousel.md +67 -2
- package/docs/widget/Chart.md +67 -2
- package/docs/widget/Checkbox.md +74 -8
- package/docs/widget/Chip.md +68 -3
- package/docs/widget/CircularBar.md +74 -15
- package/docs/widget/CircularChart.md +71 -10
- package/docs/widget/CodeViewer.md +67 -2
- package/docs/widget/Column.md +68 -3
- package/docs/widget/Container.md +68 -3
- package/docs/widget/DataTable.md +67 -2
- package/docs/widget/Divider.md +67 -2
- package/docs/widget/DraggBox.md +67 -2
- package/docs/widget/Dropdown.md +76 -10
- package/docs/widget/DroppBox.md +67 -2
- package/docs/widget/FloatingActionButton.md +68 -3
- package/docs/widget/GridView.md +67 -2
- package/docs/widget/Icon.md +71 -5
- package/docs/widget/Image.md +68 -3
- package/docs/widget/Input.md +68 -3
- package/docs/widget/Inspector.md +2 -2
- package/docs/widget/InstallButton.md +73 -13
- package/docs/widget/ListTile.md +69 -3
- package/docs/widget/ListView.md +67 -2
- package/docs/widget/Maps.md +68 -3
- package/docs/widget/Markdown.md +74 -15
- package/docs/widget/MatrixRain.md +72 -11
- package/docs/widget/Menu.md +209 -0
- package/docs/widget/Modal.md +3 -3
- package/docs/widget/Pagination.md +68 -3
- package/docs/widget/ParallaxBox.md +72 -12
- package/docs/widget/ProgressBar.md +69 -4
- package/docs/widget/QRCode.md +67 -2
- package/docs/widget/README.md +54 -50
- package/docs/widget/Radio.md +76 -10
- package/docs/widget/Rating.md +70 -6
- package/docs/widget/Row.md +68 -3
- package/docs/widget/Skeleton.md +68 -3
- package/docs/widget/Slider.md +75 -9
- package/docs/widget/SnackBar.md +2 -2
- package/docs/widget/Stack.md +68 -3
- package/docs/widget/Stepper.md +68 -3
- package/docs/widget/Switch.md +74 -8
- package/docs/widget/Text.md +71 -4
- package/docs/widget/ThemePicker.md +197 -0
- package/docs/widget/Tooltip.md +72 -7
- package/docs/widget/TreeView.md +70 -10
- package/docs/widget/Video.md +71 -10
- package/package.json +10 -3
- package/skills/fletbox-design/SKILL.md +89 -5
- package/skills/fletbox-motion/SKILL.md +1 -1
- package/skills/fletbox-rules/SKILL.md +56 -4
- package/src/core/runApp.js +12 -0
- package/src/index.d.ts +374 -101
- package/src/index.js +9 -0
- package/src/navigations/AdaptiveScaffold.js +2 -2
- package/src/navigations/BottomNavigation.js +1 -1
- package/src/navigations/Scaffold.js +18 -6
- package/src/tools/useState.js +11 -4
- package/src/utils/colors.js +275 -0
- package/src/utils/errorBoundary.js +288 -0
- package/src/utils/index.js +10 -2
- package/src/utils/stackPosition.js +39 -24
- package/src/utils/syntaxHighlight.js +4 -1
- package/src/utils/themes.js +279 -3
- package/src/widget-factory/addChildren.js +17 -10
- package/src/widget-factory/assignProps.js +18 -6
- package/src/widget-factory/createWidget.js +7 -0
- package/src/widget-factory/deprecations.js +77 -0
- package/src/widget-factory/translateProps.js +11 -1
- package/src/widget-factory/warnings.js +1 -27
- package/src/widget-factory/widgetFactory.js +59 -17
- package/src/widgets/Accordion.js +21 -7
- package/src/widgets/Badge.js +11 -2
- package/src/widgets/Button.js +15 -3
- package/src/widgets/Carousel.js +12 -4
- package/src/widgets/Chart.js +7 -2
- package/src/widgets/Checkbox.js +1 -2
- package/src/widgets/Chip.js +24 -10
- package/src/widgets/CircularBar.js +9 -3
- package/src/widgets/CircularChart.js +5 -3
- package/src/widgets/Container.js +6 -0
- package/src/widgets/Divider.js +3 -1
- package/src/widgets/DraggBox.js +9 -0
- package/src/widgets/Dropdown.js +3 -3
- package/src/widgets/DroppBox.js +5 -0
- package/src/widgets/FloatingActionButton.js +1 -1
- package/src/widgets/Icon.js +5 -1
- package/src/widgets/InstallButton.js +4 -2
- package/src/widgets/Markdown.js +171 -103
- package/src/widgets/Menu.js +340 -0
- package/src/widgets/Modal.js +7 -1
- package/src/widgets/Pagination.js +10 -5
- package/src/widgets/ProgressBar.js +5 -4
- package/src/widgets/QRCode.js +4 -0
- package/src/widgets/Radio.js +1 -2
- package/src/widgets/Rating.js +9 -0
- package/src/widgets/Slider.js +4 -1
- package/src/widgets/SnackBar.js +2 -2
- package/src/widgets/Switch.js +1 -2
- package/src/widgets/Text.js +9 -5
- package/src/widgets/ThemePicker.js +234 -0
- package/src/widgets/TreeView.js +2 -1
- package/src/widgets/index.js +2 -0
- package/docs/CONTRIBUTING.md +0 -156
|
@@ -417,6 +417,35 @@ waiting, remembering (p.099).
|
|
|
417
417
|
- Break a convention only when the measurement says the convention is wrong here,
|
|
418
418
|
and write that measurement down.
|
|
419
419
|
|
|
420
|
+
### The phone range is the test device — strict
|
|
421
|
+
|
|
422
|
+
"Fine on my phone" is not a measurement. Every phone is `xs` in the breakpoint
|
|
423
|
+
table, so nothing in the shell changes between 320 and 430; the layout has to.
|
|
424
|
+
Compose at 360 and prove the screen at each width below, in portrait:
|
|
425
|
+
|
|
426
|
+
| Width × height | Device | What that width judges |
|
|
427
|
+
| --- | --- | --- |
|
|
428
|
+
| 320 × 568 | iPhone SE 1 / small Android | **the floor** — nothing overflows or clips |
|
|
429
|
+
| 360 × 640 | Android baseline | **compose here first**, the cramped common case |
|
|
430
|
+
| 375 × 667 | iPhone SE 2/3 | narrow and tall |
|
|
431
|
+
| 390 × 844 | iPhone 12/13/14 | the modern default |
|
|
432
|
+
| 393 × 852 | iPhone 14 Pro / 15 / 16 | notch and home indicator |
|
|
433
|
+
| 412 × 915 | Pixel 7 | large Android |
|
|
434
|
+
| 430 × 932 | iPhone 14/15 Pro Max | **the ceiling** — no measure so wide it loses the eye |
|
|
435
|
+
|
|
436
|
+
- At 320 nothing overflows or clips: `scrollWidth <= clientWidth` on the page
|
|
437
|
+
and on every bar, and no label ellipsized because its row ran out of room.
|
|
438
|
+
- A heading whose text the app does not control — a lesson name, a chapter, a
|
|
439
|
+
user's title — is `24` on a phone and `32` only where the layout is wide
|
|
440
|
+
(`atLeast("md")`). At 360 a 55-character name at `32` is four lines, and the
|
|
441
|
+
tab strip under it leaves the first screen. The size steps down; the copy is
|
|
442
|
+
never cut to fit it.
|
|
443
|
+
- Fixed chrome is inside the 320 budget, not outside it: an app bar title plus
|
|
444
|
+
its actions, a tab strip, a labeled bottom bar. If a bar needs more than 320,
|
|
445
|
+
shorten what it says — never let it scroll sideways.
|
|
446
|
+
- Verify by resizing the viewport at each width and reading the layout that
|
|
447
|
+
results, not by scaling a 1440 composition down.
|
|
448
|
+
|
|
420
449
|
## 17 Composition starts as a sentence
|
|
421
450
|
|
|
422
451
|
Before the first widget, write four lines: what this screen decides for the
|
|
@@ -465,9 +494,9 @@ broken" report, and none of them is something `npm run ui` can see:
|
|
|
465
494
|
(long-press, a native back gesture), not a layout one. A screen built from two
|
|
466
495
|
hard-coded layouts is two screens to maintain; a screen with one per-step value
|
|
467
496
|
where the shape truly changes is one.
|
|
468
|
-
- Overlays go in a `Stack` with `stackPosition`,
|
|
469
|
-
`
|
|
470
|
-
change; the second is a pixel bomb in any language longer than English.
|
|
497
|
+
- Overlays go in a `Stack` with `stackPosition`, never in absolute coordinates
|
|
498
|
+
(`pos: "absolute"`, `top`, `left`) on a plain `Container`. The first survives a
|
|
499
|
+
content change; the second is a pixel bomb in any language longer than English.
|
|
471
500
|
|
|
472
501
|
## 19 The dated tells
|
|
473
502
|
|
|
@@ -588,7 +617,7 @@ Both are worth more read than copied. The parts that do not survive measurement:
|
|
|
588
617
|
```bash
|
|
589
618
|
npm run check
|
|
590
619
|
npm run ui
|
|
591
|
-
npm run dev # look at the screen
|
|
620
|
+
npm run dev # look at the screen at 320, 430 and 1440px
|
|
592
621
|
```
|
|
593
622
|
|
|
594
623
|
Fix the code, not the checker. Then make the rule reusable: if the fix was right
|
|
@@ -701,6 +730,28 @@ that made it findable.
|
|
|
701
730
|
bloom over a light page is indistinguishable from a smudge:
|
|
702
731
|
`CircularBar({ glow: true, glowColor: colors.primary })`, or a tinted `shadow`
|
|
703
732
|
string on the CTA. One glowing element per screen.
|
|
733
|
+
- A reader's choice does not raise the budget. `ThemePicker` lets them pick the
|
|
734
|
+
hue; the three placements stay the only places it is spent. The picker marks
|
|
735
|
+
the chosen square with the page's ink and a tick, never with the accent — a
|
|
736
|
+
swatch selected in its own color says nothing the square does not already say,
|
|
737
|
+
and rule 28 forbids spending the accent on the control that hands it out.
|
|
738
|
+
- A hue may also be worn as a **theme**, which is a different thing from spending
|
|
739
|
+
it: `ThemePicker({ surfaces: true })` and `setAccent("teal", { surfaces: true })`
|
|
740
|
+
derive the page, the panels, the cards and the hairlines from the same ramp, so
|
|
741
|
+
the screen is tinted teal while the accent itself stays on its three placements.
|
|
742
|
+
Only those five surface tokens move — the ink scale, `primaryBg` and every
|
|
743
|
+
semantic signal keep the palette's values, which is what lets a tinted theme
|
|
744
|
+
still say "this failed" in red — and `getAccentSurfaces()` answers which of the
|
|
745
|
+
two the reader is wearing. Offer it when 27's paper archetype is the app's look;
|
|
746
|
+
offer brand-only when the page is meant to stay neutral.
|
|
747
|
+
- An app that ships a brand color sets it at boot with
|
|
748
|
+
`applyAccentPreference("teal")`, never with `setAccent`: the first paints a
|
|
749
|
+
default without writing a preference down, so a reader who has already chosen
|
|
750
|
+
still opens on their own hue; the second answers for them and stores it, which
|
|
751
|
+
is the app overwriting a person on every load. A created app ships `neutral` —
|
|
752
|
+
no hue, the page's own ink — so the picker opens with that square ticked and
|
|
753
|
+
the reader leaves it, rather than choosing between twelve colors on a first
|
|
754
|
+
visit.
|
|
704
755
|
|
|
705
756
|
## 29 The number is the hero
|
|
706
757
|
|
|
@@ -741,6 +792,14 @@ Column({ gap: 4, children: [
|
|
|
741
792
|
|
|
742
793
|
- Controls are pills: `borderRadius` = half the height. Cards are 16-24. Inner
|
|
743
794
|
thumbnails and tiles are 12-16, one step below their card.
|
|
795
|
+
- **A pill is a one-line shape.** `borderRadius: 999` reads as a control only
|
|
796
|
+
while its height is about one line of its label: a label that wraps turns the
|
|
797
|
+
fill into an egg and the capsule's rounded ends cut into the words. `Button`
|
|
798
|
+
holds its label on one line and ellipsizes what does not fit, so a CTA that
|
|
799
|
+
needs three lines is a copy bug wearing a pill. Keep the label a verb and its
|
|
800
|
+
- **Follow Mission 1** — and leave the lesson title to the heading
|
|
801
|
+
(rule 04). If the action genuinely needs a sentence, the control is a card or
|
|
802
|
+
a list tile, not a button (rule 32).
|
|
744
803
|
- **The nesting rule**, as arithmetic: `outer = inner + padding`. A 16-radius
|
|
745
804
|
tile inside 8 of padding needs a 24-radius card. Equal radii at both levels is
|
|
746
805
|
the failure — the inner corner and the outer corner curve on the same radius,
|
|
@@ -807,6 +866,7 @@ Stack({ alignment: "bottom-left", height: 420, children: [
|
|
|
807
866
|
| switch 2 views of one object | segmented pill with a sliding plate | `Tabs({ variant: "pills", tabs, activeIndex, activeTextColor, buttonColor })` |
|
|
808
867
|
| choose 1 of N with a preview | selection **tile**, not a radio list | `ListTile({ selected, selectedBgColor })`, or a 2-up `Row` of `Card`s |
|
|
809
868
|
| pick a color/size | dot row, selected gets a ring | `Avatar({ shape: "circle", size: 28 })` row, ring via `borderColor` + `borderWidth` |
|
|
869
|
+
| pick the app's accent | two-tone square per hue family | `ThemePicker({ size: 48, showLabels: true })` — it repaints the screen and remembers the choice; hand-built swatches do neither |
|
|
810
870
|
| count then buy | stepper merged into the CTA | one `Row` of `Button`s inside the add-to-cart bar, not two actions |
|
|
811
871
|
| navigate the app | bottom bar, active = accent + label | `BottomNavigation({ items, currentIndex, selectedColor, unselectedColor, showLabels: true })` |
|
|
812
872
|
| a badge on an icon | dot, never a word | `Badge({ value, size, offset, borderColor: colors.background })` |
|
|
@@ -878,7 +938,31 @@ ink white or near-white, and its opacity tiers
|
|
|
878
938
|
- **Ramp, not swatch.** A family is 5-7 stops (base → tint → accent → shadow).
|
|
879
939
|
Build it as a ramp so a card, a tile and a plate can be three steps of one hue
|
|
880
940
|
instead of three hues — which is the only way 27's surface discipline is
|
|
881
|
-
possible without breaking 25's one-accent law.
|
|
941
|
+
possible without breaking 25's one-accent law. The framework already does this
|
|
942
|
+
arithmetic for a reader who asks for a colored app: `setAccent(id,
|
|
943
|
+
{ surfaces: true })` takes the five stops it ships and writes `background`,
|
|
944
|
+
`surface`, `card`, `border` and `divider` off them (palest → wash → white card →
|
|
945
|
+
wash darkened, light; the deep stop scaled toward black in four steps, dark), so
|
|
946
|
+
a tinted theme is a call, not a hand-mixed set of hexes.
|
|
947
|
+
- **A preset beats a mid-tone.** An app that has no ramp of its own does not pick
|
|
948
|
+
a blue and hope: it takes one of the twelve families the framework ships
|
|
949
|
+
(`listAccents()`, `listAccentRamps()` to see the stops) and paints a **stop** of
|
|
950
|
+
that ramp, so `colors.primary` and `colors.primaryBg` are two steps of one
|
|
951
|
+
gradient and never a hue plus a guess. Each pair is bought with seven measured
|
|
952
|
+
numbers per theme: the accent carries its own label at 4.5:1, reads as text on
|
|
953
|
+
the page at 4.5:1 and as a glyph on a card at 3:1, its wash holds page ink,
|
|
954
|
+
secondary ink and the accent at 4.5:1, and the wash lifts off the background by
|
|
955
|
+
1.04-1.5. That is why `#3b82f6` is not a brand color — white words on it
|
|
956
|
+
measure 3.15:1 — and why a hue the list lacks is registered instead of
|
|
957
|
+
invented: `registerAccent` takes an `id`, a `label` and a `light`/`dark` pair —
|
|
958
|
+
one hex each, or a five-stop ramp, whose stop 0 (light) and stop 1 (dark) are
|
|
959
|
+
the ones painted and whose wash is a stop further along the same direction.
|
|
960
|
+
Run a ramp the other way and the plate lands *under* the accent instead of just
|
|
961
|
+
off the page. It belongs in `src/database/themes.js` beside the palette it
|
|
962
|
+
feeds (rule 03), and a registered pair answers to the same veto as a hand-built
|
|
963
|
+
one. What `flet-box create` boots is the `neutral` family — the accent is the
|
|
964
|
+
page's own ink, so an unfinished app reads as paper and type rather than as
|
|
965
|
+
somebody else's brand — and the first change a designer makes is that id.
|
|
882
966
|
- **The light/dark pair rule**: for the same hue in both palettes, lift the
|
|
883
967
|
lightness and drop the saturation going dark. That is why this framework's dark
|
|
884
968
|
`dangerStrong` is `#fca5a5` where the light one is `#b91c1c` — the readable red
|
|
@@ -232,7 +232,7 @@ const chooseMotion = (on) => {
|
|
|
232
232
|
};
|
|
233
233
|
|
|
234
234
|
// A settings screen hands the decision to the reader, and the engine applies it.
|
|
235
|
-
Switch({ label: 'Reduce motion', value: getMotionMode() === 'reduce',
|
|
235
|
+
Switch({ label: 'Reduce motion', value: getMotionMode() === 'reduce', onPress: chooseMotion });
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
Build the screen so it reads with zero animation anyway. Motion is a decision you
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fletbox-rules
|
|
3
|
-
description: The working contract for a FletBox app - FletBox syntax only, never a widget or prop from React/Vue/Svelte/Flutter, look an API up before using it, the rem unit contract, prop aliases, key-based state, resource cleanup, one home for every shared helper, and the
|
|
3
|
+
description: The working contract for a FletBox app - FletBox syntax only, never a widget or prop from React/Vue/Svelte/Flutter, look an API up before using it, the rem unit contract, prop aliases, key-based state, resource cleanup, one home for every shared helper, and the gates that prove the work. Read first, before any other skill, and before writing or editing a single file in src/.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rules — how to work inside this framework
|
|
@@ -101,18 +101,47 @@ reactivity). It is the only renderer. A custom component is still a function cal
|
|
|
101
101
|
returning widgets; never add a parallel way to paint, and never monkey-patch
|
|
102
102
|
`container.update` — compose the update.
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
That includes the DOM itself. App code never writes `innerHTML`: a hand-written
|
|
105
|
+
element skips the theme, the scale and the accessibility every widget already
|
|
106
|
+
has, and it is how a screen ends up speaking two visual languages at once.
|
|
107
|
+
Building an element with `document.createElement` is the same mistake one layer
|
|
108
|
+
down — the gate reports it as a warning, and it is usually a sign that a widget
|
|
109
|
+
is missing.
|
|
110
|
+
|
|
111
|
+
## R8 The gates, after every edit
|
|
105
112
|
|
|
106
113
|
```bash
|
|
114
|
+
npm run verify # every gate at once — one report, one exit code. This is the
|
|
115
|
+
# command to run after every edit
|
|
116
|
+
npm run verify -- --fix # the same, after the mechanical repair: a typo is the
|
|
117
|
+
# tool's job, not yours
|
|
107
118
|
npm run check # does this API exist? exits 1 on an invented widget, prop,
|
|
108
119
|
# positional call, foreign import or broken module import
|
|
109
120
|
npm run ui # is this screen clean? tokens, the scale, tap targets, labels,
|
|
110
121
|
# contrast — and it cites the rule it broke
|
|
111
122
|
npm run fix # rewrites what the checker can prove: a wrong path, a default
|
|
112
123
|
# import of a named export, a missing import line, a typo in a
|
|
113
|
-
# prop key. Anything ambiguous is
|
|
124
|
+
# prop key, a prop the framework renamed. Anything ambiguous is
|
|
125
|
+
# printed, never touched
|
|
126
|
+
npm run metrics # the north metric: how much code passed the gate with no
|
|
127
|
+
# correction at all, and which failures keep coming back
|
|
114
128
|
```
|
|
115
129
|
|
|
130
|
+
`npm run verify` parses every module first — `node --check`, the parser the
|
|
131
|
+
runtime uses, so a missing `)` comes back as `file:line:column` with the
|
|
132
|
+
offending line, a caret under the character and a hint — then runs the API check,
|
|
133
|
+
the type check (the same `jsconfig.json` the editor reads, through the same
|
|
134
|
+
TypeScript service: a widget typed as a bare `HTMLElement` makes
|
|
135
|
+
`widget.update()` an error here and there), the design check, the structural
|
|
136
|
+
check (two of your modules importing each other
|
|
137
|
+
in a circle, a module outside the project folders, markup written by hand) and
|
|
138
|
+
the prop contract, and then any test scripts the project declares. It prints one
|
|
139
|
+
report: the gate that failed, the file at `line:column`, and what to do about it.
|
|
140
|
+
|
|
141
|
+
Fix the line it names. Do not rewrite the file to make it pass, and do not
|
|
142
|
+
reformat a line the report did not name — a diff you cannot justify is a diff
|
|
143
|
+
you should not make.
|
|
144
|
+
|
|
116
145
|
Then look at the screen at 375px and at 1440px, in both themes: **the checker has no
|
|
117
146
|
eyes.** A clean `npm run ui` says the values are legal, not that the screen is
|
|
118
147
|
understood.
|
|
@@ -121,14 +150,37 @@ A finding you keep needs one sentence of justification, written where the next
|
|
|
121
150
|
session will read it (R10). "The design wants it" is not a justification — it is a
|
|
122
151
|
rule that has not been written down yet.
|
|
123
152
|
|
|
153
|
+
Every `npm run verify` writes a row into `.fletbox/metrics/generations.md`, green
|
|
154
|
+
or not, so `npm run metrics` can say how much code passed the gate with no
|
|
155
|
+
correction at all. The same command prints the failures that recur most often
|
|
156
|
+
and the rule each one should belong to — and it **fails** when a code comes back
|
|
157
|
+
three times with no rule behind it. That is the loop: the same mistake is a rule
|
|
158
|
+
you did not write, and you are the one who writes it. A code that comes back
|
|
159
|
+
*with* a rule behind it is a different problem, and the command says so: the
|
|
160
|
+
rule is not landing.
|
|
161
|
+
|
|
162
|
+
But a recurring code can also be the gate's own defect. When you prove that,
|
|
163
|
+
write it into the log's **Gate defects** table with the window of runs it
|
|
164
|
+
explains and where the fix lives — do not rename exports in the app to dodge
|
|
165
|
+
it, and do not hard-code an exception inside the checker: the window is scoped,
|
|
166
|
+
so the same code counts against the project again after the fix day.
|
|
167
|
+
|
|
124
168
|
## R9 Do not trust your memory of this framework
|
|
125
169
|
|
|
126
170
|
FletBox has no `.d.ts` you have read in full, the prop set grows between versions,
|
|
127
|
-
and the installed version is the truth for this app.
|
|
171
|
+
and the installed version is the truth for this app. Four habits follow:
|
|
128
172
|
|
|
129
173
|
- Look up before using, even for `Text`.
|
|
130
174
|
- After a dependency bump, re-run `npm run check` across the app — it also reports
|
|
131
175
|
names the package stopped exporting.
|
|
176
|
+
- **A renamed prop never breaks the call, so nothing will tell you.** The old
|
|
177
|
+
spelling keeps working, marked `@deprecated`, until the next major; the gate
|
|
178
|
+
stays green either way, and an example you copied from three versions ago still
|
|
179
|
+
runs. The cheatsheet marks it — `onToggle?: (value: boolean) => void
|
|
180
|
+
(deprecated → onPress)` — and the prop after the arrow is the one to write.
|
|
181
|
+
`npm run fix` renames the calls it can prove (imports, prop typos and
|
|
182
|
+
deprecations in one pass) and prints the ones it cannot, so run it after every
|
|
183
|
+
upgrade and read the diff — a codemod that guesses is worse than one that stops.
|
|
132
184
|
- What is deployed is built from the **published** package, not from your working
|
|
133
185
|
tree. A fix you made in a local checkout does not exist for an installed app
|
|
134
186
|
until it is released.
|
package/src/core/runApp.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { Container } from "../widgets/Container.js";
|
|
10
10
|
import { setGlobalRender } from "../tools/useState.js";
|
|
11
11
|
import { beginRenderPass, endRenderPass } from "../widget-factory/reactiveValue.js";
|
|
12
|
+
import { renderFailure } from "../utils/errorBoundary.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Mounts a FletBox application into the DOM and manages its full lifecycle.
|
|
@@ -184,12 +185,23 @@ export const runApp = (
|
|
|
184
185
|
// state value surgical.
|
|
185
186
|
beginRenderPass();
|
|
186
187
|
let appContent;
|
|
188
|
+
let failure = null;
|
|
187
189
|
try {
|
|
188
190
|
appContent = typeof App === "function" ? App() : App;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
failure = error;
|
|
189
193
|
} finally {
|
|
190
194
|
endRenderPass();
|
|
191
195
|
}
|
|
192
196
|
|
|
197
|
+
// A throw in the component body — before any widget exists — has no widget
|
|
198
|
+
// to catch it. Without this the root stayed empty and the screen was
|
|
199
|
+
// blank; now it says what went wrong and where.
|
|
200
|
+
if (failure) {
|
|
201
|
+
renderFailure(root, failure, { widgetName: "App", phase: "app" });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
const mainContainer = Container({
|
|
194
206
|
margin: 0,
|
|
195
207
|
padding: 0,
|