flet-box 1.0.9 → 1.1.1
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/bin/commands/checkUi.js +327 -46
- package/bin/commands/create.js +48 -19
- package/bin/commands/createScreen.js +1 -1
- package/bin/commands/runBundle.js +1 -1
- package/bin/utils/colors.js +0 -8
- package/bin/utils/helpers.js +0 -5
- package/bin/utils/images/jpeg.js +2 -3
- package/bin/utils/skills.js +59 -0
- package/bin/utils/templates.js +196 -575
- package/docs/README.md +3 -1
- package/docs/cli/README.md +18 -1
- package/docs/guides/README.md +6 -1
- package/docs/guides/accessibility.md +282 -0
- package/docs/guides/clean-ui.md +84 -21
- package/docs/guides/minification-and-protection.md +1 -1
- package/docs/guides/mobile-and-platforms.md +51 -0
- package/docs/guides/utilities.md +116 -4
- package/docs/guides/widget-structure.md +30 -0
- package/docs/tools/README.md +1 -1
- package/docs/tools/animation.md +134 -11
- package/docs/tools/device.md +75 -3
- package/docs/tools/text-and-time.md +116 -6
- package/docs/widget/Accordion.md +2 -0
- package/docs/widget/AnimatedBox.md +46 -20
- package/docs/widget/AnimatedText.md +13 -7
- package/docs/widget/Badge.md +1 -0
- package/docs/widget/COMMON_PROPS.md +26 -3
- package/docs/widget/Chart.md +1 -0
- package/docs/widget/Checkbox.md +7 -1
- package/docs/widget/CircularChart.md +2 -0
- package/docs/widget/Drawer.md +1 -0
- package/docs/widget/GridView.md +4 -1
- package/docs/widget/Input.md +2 -0
- package/docs/widget/InstallButton.md +1 -0
- package/docs/widget/Markdown.md +4 -0
- package/docs/widget/Rating.md +2 -0
- package/docs/widget/Slider.md +2 -0
- package/docs/widget/Stack.md +6 -5
- package/docs/widget/Stepper.md +2 -0
- package/docs/widget/Tooltip.md +3 -1
- package/package.json +4 -2
- package/skills/fletbox-design/SKILL.md +949 -0
- package/skills/fletbox-memory/SKILL.md +134 -0
- package/skills/fletbox-motion/SKILL.md +266 -0
- package/skills/fletbox-rules/SKILL.md +293 -0
- package/src/animations/AnimatedBox.js +321 -226
- package/src/animations/MatrixRain.js +7 -1
- package/src/animations/ParallaxBox.js +13 -10
- package/src/animations/animate.js +269 -106
- package/src/animations/color.js +91 -0
- package/src/animations/easings.js +275 -0
- package/src/animations/index.js +21 -0
- package/src/animations/motion.js +204 -0
- package/src/animations/transitions.js +327 -0
- package/src/core/runApp.js +0 -6
- package/src/helpers/boxEdges.js +69 -0
- package/src/helpers/html.js +39 -0
- package/src/index.d.ts +377 -25
- package/src/index.js +77 -0
- package/src/navigations/AdaptiveScaffold.js +6 -2
- package/src/navigations/AppBar.js +16 -7
- package/src/navigations/BottomNavigation.js +24 -1
- package/src/navigations/CollapsibleSideBar.js +0 -1
- package/src/navigations/Drawer.js +32 -1
- package/src/navigations/DrawerItem.js +1 -1
- package/src/navigations/Router.js +0 -1
- package/src/navigations/Scaffold.js +9 -3
- package/src/navigations/Tabs.js +104 -31
- package/src/services/RamStore.js +1 -2
- package/src/tools/breakpoints.js +113 -0
- package/src/tools/createList.js +0 -2
- package/src/tools/fonts.js +99 -0
- package/src/tools/index.js +74 -1
- package/src/tools/margin.js +5 -63
- package/src/tools/padding.js +5 -63
- package/src/tools/print.js +0 -8
- package/src/tools/scale.js +9 -2
- package/src/tools/time.js +56 -15
- package/src/tools/useState.js +1 -54
- package/src/utils/fieldFont.js +77 -0
- package/src/utils/focusRing.js +73 -0
- package/src/utils/focusScope.js +113 -0
- package/src/utils/markdownParser.js +7 -9
- package/src/utils/readableInk.js +7 -1
- package/src/utils/stackLayout.js +55 -0
- package/src/utils/styleInjector.js +25 -0
- package/src/utils/syntaxHighlight.js +1 -17
- package/src/utils/themes.js +31 -0
- package/src/utils/visualEffects.js +49 -47
- package/src/widget-factory/createWidget.js +16 -3
- package/src/widget-factory/effects.js +76 -3
- package/src/widget-factory/reactiveValue.js +0 -7
- package/src/widget-factory/tools.js +26 -143
- package/src/widget-factory/translateProps.js +5 -0
- package/src/widget-factory/widgetFactory.js +1 -1
- package/src/widgets/Accordion.js +70 -12
- package/src/widgets/AdMob.js +1 -1
- package/src/widgets/AdSense.js +1 -1
- package/src/widgets/AlertDialog.js +11 -9
- package/src/widgets/Avatar.js +10 -1
- package/src/widgets/Badge.js +7 -1
- package/src/widgets/BottomSheet.js +39 -5
- package/src/widgets/Chart.js +41 -9
- package/src/widgets/Checkbox.js +59 -9
- package/src/widgets/Chip.js +13 -1
- package/src/widgets/CircularBar.js +17 -5
- package/src/widgets/CircularChart.js +24 -2
- package/src/widgets/CodeViewer.js +5 -3
- package/src/widgets/Dropdown.js +223 -20
- package/src/widgets/GridView.js +17 -1
- package/src/widgets/Icon.js +21 -0
- package/src/widgets/Input.js +75 -44
- package/src/widgets/InstallButton.js +8 -2
- package/src/widgets/ListTile.js +6 -5
- package/src/widgets/ListView.js +21 -7
- package/src/widgets/Markdown.js +25 -44
- package/src/widgets/Modal.js +43 -6
- package/src/widgets/Pagination.js +35 -3
- package/src/widgets/ProgressBar.js +18 -0
- package/src/widgets/Radio.js +7 -0
- package/src/widgets/Rating.js +104 -16
- package/src/widgets/Skeleton.js +9 -5
- package/src/widgets/Slider.js +87 -7
- package/src/widgets/SnackBar.js +30 -1
- package/src/widgets/Stack.js +25 -13
- package/src/widgets/Stepper.js +112 -64
- package/src/widgets/Switch.js +10 -1
- package/src/widgets/Tooltip.js +93 -10
- package/src/widgets/TreeView.js +5 -5
- package/src/create-index.sh +0 -237
- package/src/widget-factory/pixTool.js +0 -40
- package/src/widget-factory/stringTool.js +0 -26
package/bin/utils/templates.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// bin/utils/templates.js
|
|
2
2
|
|
|
3
|
+
import * as skills from "./skills.js";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* The plate behind everything a reader sees before the app paints: the PWA
|
|
5
7
|
* splash (`manifest.json`), the Android and iOS launch screens, the adaptive
|
|
@@ -707,54 +709,6 @@ echo "Press Ctrl+C to stop"
|
|
|
707
709
|
python3 -m http.server $PORT --bind $BIND
|
|
708
710
|
`;
|
|
709
711
|
|
|
710
|
-
export const createBundleSh = (target = "www") => `#!/bin/bash
|
|
711
|
-
# createBundle.sh — Bundle UNIFICADO a \${TARGET}/ (un solo app.js + PWA)
|
|
712
|
-
# Uso: bash createBundle.sh [target]
|
|
713
|
-
set -euo pipefail
|
|
714
|
-
cd "\$(dirname "\$0")"
|
|
715
|
-
TARGET="\${1:-${target}}"
|
|
716
|
-
|
|
717
|
-
if ! npx --no-install esbuild --version >/dev/null 2>&1; then
|
|
718
|
-
echo "📦 Instalando esbuild (devDependency)…"
|
|
719
|
-
npm install --save-dev esbuild >/dev/null
|
|
720
|
-
fi
|
|
721
|
-
|
|
722
|
-
rm -rf "\$TARGET"
|
|
723
|
-
mkdir -p "\$TARGET/src" "\$TARGET/assets"
|
|
724
|
-
|
|
725
|
-
# Root static files (PWA + preview)
|
|
726
|
-
for f in index.html manifest.json run.sh service-worker.js; do
|
|
727
|
-
[ -f "\$f" ] && cp "\$f" "\$TARGET/"
|
|
728
|
-
done
|
|
729
|
-
[ -f "\$TARGET/run.sh" ] && chmod +x "\$TARGET/run.sh"
|
|
730
|
-
|
|
731
|
-
# Assets: iconos PWA + fuentes de iconos
|
|
732
|
-
if [ -d "src/assets" ]; then
|
|
733
|
-
cp -R src/assets/. "\$TARGET/src/assets/"
|
|
734
|
-
cp -R src/assets/. "\$TARGET/assets/"
|
|
735
|
-
fi
|
|
736
|
-
|
|
737
|
-
# CSS global (lo carga index.html)
|
|
738
|
-
if [ -f "src/styles/global.css" ]; then
|
|
739
|
-
mkdir -p "\$TARGET/src/styles"
|
|
740
|
-
cp src/styles/global.css "\$TARGET/src/styles/"
|
|
741
|
-
fi
|
|
742
|
-
|
|
743
|
-
# BUNDLE UNIFICADO: flet-box + widgets en UN solo app.js
|
|
744
|
-
echo "📦 Bundling (esbuild + tree-shaking)…"
|
|
745
|
-
npx esbuild src/app.js \\
|
|
746
|
-
--bundle --outfile="\$TARGET/src/app.js" --format=esm --minify \\
|
|
747
|
-
--tree-shaking=true --target=es2020 --define:FLETBOX_DEV=false \\
|
|
748
|
-
--external:*.css --external:*.woff2 --resolve-extensions=.js,.json
|
|
749
|
-
|
|
750
|
-
# App source code (required by the "View code" button)
|
|
751
|
-
for d in modules screens database components; do
|
|
752
|
-
[ -d "src/\$d" ] && cp -R "src/\$d" "\$TARGET/src/"
|
|
753
|
-
done
|
|
754
|
-
|
|
755
|
-
echo "✅ Bundle listo: \$TARGET/ Prueba: cd \$TARGET && bash run.sh"
|
|
756
|
-
`;
|
|
757
|
-
|
|
758
712
|
export const serviceWorkerJs = () => `// service-worker.js - PWA offline support
|
|
759
713
|
const CACHE_NAME = 'fletbox-v2';
|
|
760
714
|
// Deployment prefix: a worker's own URL always sits at the app root, so this
|
|
@@ -877,6 +831,28 @@ self.addEventListener('fetch', event => {
|
|
|
877
831
|
});
|
|
878
832
|
`;
|
|
879
833
|
|
|
834
|
+
// Not TypeScript: an editor reads this file and runs its bundled JS language
|
|
835
|
+
// service over `flet-box`'s own .d.ts, so a prop autocompletes and a prop that
|
|
836
|
+
// does not exist is a squiggle. `bundler` is the resolution an unbundled-ESM
|
|
837
|
+
// app needs, and TypeScript 7 removed `node10` outright — an older editor that
|
|
838
|
+
// still only knows `node` ignores the whole jsconfig, which is no worse than
|
|
839
|
+
// not having one. Nothing here is compiled or installed.
|
|
840
|
+
export const jsconfig = () => `{
|
|
841
|
+
"compilerOptions": {
|
|
842
|
+
"target": "ES2020",
|
|
843
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
844
|
+
"module": "ESNext",
|
|
845
|
+
"moduleResolution": "bundler",
|
|
846
|
+
"allowJs": true,
|
|
847
|
+
"checkJs": true,
|
|
848
|
+
"strict": false,
|
|
849
|
+
"noEmit": true,
|
|
850
|
+
"skipLibCheck": true
|
|
851
|
+
},
|
|
852
|
+
"include": ["src/**/*.js"],
|
|
853
|
+
"exclude": ["node_modules", "www", "dist"]
|
|
854
|
+
}`;
|
|
855
|
+
|
|
880
856
|
export const packageJson = (name) => `{
|
|
881
857
|
"name": ${JSON.stringify(name)},
|
|
882
858
|
"version": "1.0.0",
|
|
@@ -1661,6 +1637,15 @@ npm install
|
|
|
1661
1637
|
npm run dev
|
|
1662
1638
|
\`\`\`
|
|
1663
1639
|
|
|
1640
|
+
## Autocomplete without TypeScript
|
|
1641
|
+
|
|
1642
|
+
\`jsconfig.json\` points the editor's own JavaScript language service at
|
|
1643
|
+
\`flet-box\`'s bundled \`.d.ts\` — nothing to install, nothing to compile. Every
|
|
1644
|
+
widget, prop, colour token and helper autocompletes in VS Code (or any editor
|
|
1645
|
+
that reads jsconfig), and a prop that does not exist is underlined as an error
|
|
1646
|
+
instead of failing silently at runtime. \`src/\` is the only folder checked;
|
|
1647
|
+
\`npm run check\` remains the gate that runs in CI.
|
|
1648
|
+
|
|
1664
1649
|
## Working with an AI assistant
|
|
1665
1650
|
|
|
1666
1651
|
This project ships an in-project agent contract, so an AI editing the app uses the
|
|
@@ -1669,10 +1654,13 @@ framework from scratch:
|
|
|
1669
1654
|
|
|
1670
1655
|
- \`AGENTS.md\` — the rules: FletBox syntax only, real widgets and props only, and the
|
|
1671
1656
|
verification loop it must run after every edit.
|
|
1672
|
-
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1657
|
+
- \`skills/\` — four files, one per subject, mirrored into \`.claude/skills/\`,
|
|
1658
|
+
\`.qoder/skills/\` and \`.cursor/rules/\` so any assistant reads the same contract:
|
|
1659
|
+
\`fletbox-rules\` (FletBox only, look an API up before using it, the rem contract),
|
|
1660
|
+
\`fletbox-design\` (the numbered design rules — tokens, the scale, hierarchy, the
|
|
1661
|
+
four states, measured contrast, the dated tells), \`fletbox-motion\` (when a screen
|
|
1662
|
+
may move and through which widget) and \`fletbox-memory\` (the journal that hands
|
|
1663
|
+
progress from one AI to the next). \`AGENTS.md\` §0 is the index.
|
|
1676
1664
|
- \`.claude/CLAUDE.md\`, \`.cursor/rules/fletbox.mdc\` — short pointers to \`AGENTS.md\` for
|
|
1677
1665
|
Claude Code and Cursor.
|
|
1678
1666
|
- \`.fletbox/api/cheatsheet.md\` — every widget and its real props, generated from the
|
|
@@ -2061,7 +2049,7 @@ import { AppBar, Icon, Tooltip, colors, openDrawer, toggleTheme, getTheme } from
|
|
|
2061
2049
|
// It reads the theme instead of subscribing to it: setTheme() re-renders the app,
|
|
2062
2050
|
// so a component that subscribed would add a listener on every render and never
|
|
2063
2051
|
// remove it.
|
|
2064
|
-
export const AppBarComponent = ({ title, isMobile = false, showBack = false, onBack }) => {
|
|
2052
|
+
export const AppBarComponent = ({ title, isMobile = false, showBack = false, onBack = () => {} }) => {
|
|
2065
2053
|
const isDark = getTheme() === 'dark';
|
|
2066
2054
|
|
|
2067
2055
|
return AppBar({
|
|
@@ -2239,7 +2227,7 @@ runApp(App, 'root');
|
|
|
2239
2227
|
`;
|
|
2240
2228
|
|
|
2241
2229
|
export const basicAppJs = () => `// app.js - Basic template (AppBar + Drawer)
|
|
2242
|
-
import { runApp, Scaffold, colors, useState,
|
|
2230
|
+
import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
|
|
2243
2231
|
// Registers this app's design tokens. Without it every screen paints
|
|
2244
2232
|
// with the framework's own palette, and the file that looks like the design
|
|
2245
2233
|
// system is decoration.
|
|
@@ -2259,13 +2247,14 @@ const routes = {
|
|
|
2259
2247
|
};
|
|
2260
2248
|
|
|
2261
2249
|
// The drawer is only reachable through the AppBar's menu button, so the shell has
|
|
2262
|
-
// to know whether it is on a phone. Key-based state keeps the
|
|
2263
|
-
// re-renders; the
|
|
2264
|
-
|
|
2265
|
-
|
|
2250
|
+
// to know whether it is on a phone. Key-based state keeps the answer across
|
|
2251
|
+
// re-renders; the subscription is registered once, at module scope, and only
|
|
2252
|
+
// fires when the viewport crosses a breakpoint, not on every pixel of a resize.
|
|
2253
|
+
const [isWide, setIsWide] = useState('isWide', atLeast('md'));
|
|
2254
|
+
onBreakpointChange(() => setIsWide(atLeast('md')));
|
|
2266
2255
|
|
|
2267
2256
|
const MyApp = () => {
|
|
2268
|
-
const isMobile =
|
|
2257
|
+
const isMobile = !isWide.valueOf();
|
|
2269
2258
|
return Scaffold({
|
|
2270
2259
|
routes,
|
|
2271
2260
|
appBar: AppBarComponent({ title: 'FletBox', isMobile }),
|
|
@@ -2279,7 +2268,7 @@ runApp(MyApp, 'root');
|
|
|
2279
2268
|
|
|
2280
2269
|
export const fullAppJs =
|
|
2281
2270
|
() => `// app.js - Full template (AppBar + Drawer + BottomNav)
|
|
2282
|
-
import { runApp, Scaffold, colors, useState,
|
|
2271
|
+
import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
|
|
2283
2272
|
// Registers this app's design tokens. Without it every screen paints
|
|
2284
2273
|
// with the framework's own palette, and the file that looks like the design
|
|
2285
2274
|
// system is decoration.
|
|
@@ -2299,15 +2288,15 @@ const routes = {
|
|
|
2299
2288
|
'/about': AboutScreen
|
|
2300
2289
|
};
|
|
2301
2290
|
|
|
2302
|
-
// State is key-based, so the
|
|
2291
|
+
// State is key-based, so the layout answer survives re-renders. One breakpoint
|
|
2303
2292
|
// subscription feeds it from module scope: registering it inside the component
|
|
2304
|
-
// would add a listener on every render.
|
|
2305
|
-
const [
|
|
2306
|
-
|
|
2293
|
+
// would add a listener on every render, and it only fires when a step is crossed.
|
|
2294
|
+
const [isWide, setIsWide] = useState('isWide', atLeast('md'));
|
|
2295
|
+
onBreakpointChange(() => setIsWide(atLeast('md')));
|
|
2307
2296
|
|
|
2308
2297
|
const MyApp = () => {
|
|
2309
2298
|
// Structural read of the key, so crossing the breakpoint re-renders the shell.
|
|
2310
|
-
const isMobile =
|
|
2299
|
+
const isMobile = !isWide.valueOf();
|
|
2311
2300
|
return Scaffold({
|
|
2312
2301
|
routes,
|
|
2313
2302
|
appBar: AppBarComponent({ title: 'FletBox', isMobile }),
|
|
@@ -2352,7 +2341,7 @@ runApp(MyApp, 'root');
|
|
|
2352
2341
|
|
|
2353
2342
|
export const adaptiveAppJs =
|
|
2354
2343
|
() => `// app.js - Adaptive template (mobile + desktop)
|
|
2355
|
-
import { runApp, Scaffold, colors, useState,
|
|
2344
|
+
import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
|
|
2356
2345
|
// Registers this app's design tokens. Without it every screen paints
|
|
2357
2346
|
// with the framework's own palette, and the file that looks like the design
|
|
2358
2347
|
// system is decoration.
|
|
@@ -2375,11 +2364,11 @@ const routes = {
|
|
|
2375
2364
|
'/profile': ProfileScreen
|
|
2376
2365
|
};
|
|
2377
2366
|
|
|
2378
|
-
// State is key-based, so the
|
|
2367
|
+
// State is key-based, so the layout answer survives re-renders. The breakpoint
|
|
2379
2368
|
// subscription is registered once at module scope; doing it inside the component
|
|
2380
2369
|
// would add another listener on every render.
|
|
2381
|
-
const [
|
|
2382
|
-
|
|
2370
|
+
const [isWide, setIsWide] = useState('isWide', atLeast('md'));
|
|
2371
|
+
onBreakpointChange(() => setIsWide(atLeast('md')));
|
|
2383
2372
|
|
|
2384
2373
|
// One menu, three surfaces: the drawer and the bottom bar on a phone, the
|
|
2385
2374
|
// sidebar on a desktop. The labels stay short because the bottom bar has to fit
|
|
@@ -2392,9 +2381,9 @@ const menu = [
|
|
|
2392
2381
|
];
|
|
2393
2382
|
|
|
2394
2383
|
const MyApp = () => {
|
|
2395
|
-
// Structural read of the keyed
|
|
2384
|
+
// Structural read of the keyed layout state, so crossing the breakpoint
|
|
2396
2385
|
// re-renders the shell (drawer + bottom bar <-> sidebar).
|
|
2397
|
-
const isMobile =
|
|
2386
|
+
const isMobile = !isWide.valueOf();
|
|
2398
2387
|
return Scaffold({
|
|
2399
2388
|
routes,
|
|
2400
2389
|
appBar: AppBarComponent({ title: 'FletBox', isMobile }),
|
|
@@ -2433,6 +2422,26 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2433
2422
|
'fixes every size and color you may use, and the checker in section 5',
|
|
2434
2423
|
'proves you right or wrong in seconds.',
|
|
2435
2424
|
'',
|
|
2425
|
+
'## 0. Start here — the skills folder',
|
|
2426
|
+
'',
|
|
2427
|
+
'Everything this framework knows about building a professional screen is grouped',
|
|
2428
|
+
'in four files under `skills/`, one per subject. They are copied byte-for-byte',
|
|
2429
|
+
'into `.claude/skills/` and `.qoder/skills/` so those assistants load them',
|
|
2430
|
+
'automatically, and into `.cursor/rules/` as pointer rules — `skills/` is the',
|
|
2431
|
+
'text, the rest are mirrors of it. Open the file your task touches before',
|
|
2432
|
+
'writing code, not after:',
|
|
2433
|
+
'',
|
|
2434
|
+
'| Skill | What it owns | Read it when |',
|
|
2435
|
+
'| --- | --- | --- |',
|
|
2436
|
+
'| [fletbox-rules](skills/fletbox-rules/SKILL.md) | FletBox syntax only: no widget or prop from React, Vue, Svelte or Flutter; look an API up before using it; the rem contract; aliases; cleanup; the two gates | always, first |',
|
|
2437
|
+
'| [fletbox-design](skills/fletbox-design/SKILL.md) | the numbered design rules — tokens, the scale, hierarchy, the four states, measured contrast, the framework traps, the dated tells, density | any screen, form, list, dialog or hero |',
|
|
2438
|
+
'| [fletbox-motion](skills/fletbox-motion/SKILL.md) | whether a thing may move at all, and through which of `AnimatedBox`, `AnimatedText`, `ParallaxBox` or `animate` | any transition, entrance, pulse or parallax |',
|
|
2439
|
+
'| [fletbox-memory](skills/fletbox-memory/SKILL.md) | the journal that hands progress from one AI to the next | at the start and at the end of every session |',
|
|
2440
|
+
'',
|
|
2441
|
+
'Sections 1 to 7 below are the short version of those files, kept here because a',
|
|
2442
|
+
'session that stops reading early still gets the rules that break the build. When',
|
|
2443
|
+
'a summary here and a skill disagree, the skill is the longer argument and wins.',
|
|
2444
|
+
'',
|
|
2436
2445
|
'## 1. The API is a fact, not a guess',
|
|
2437
2446
|
'',
|
|
2438
2447
|
'Never invent a widget, prop, hook or helper. Look it up:',
|
|
@@ -2522,9 +2531,13 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2522
2531
|
'Real `Scaffold` props: `appBar body bottomBar fab drawer leftNavBar',
|
|
2523
2532
|
'leftNavBarWidth rightNavBar rightNavBarWidth routes basePath backgroundColor',
|
|
2524
2533
|
'closeDrawerOnNavigate`. (`--props Scaffold` is authoritative.)',
|
|
2525
|
-
'For a responsive app, branch in JS on the
|
|
2526
|
-
'seeded from `
|
|
2527
|
-
'module scope —
|
|
2534
|
+
'For a responsive app, branch in JS on the breakpoint table, not on CSS alone:',
|
|
2535
|
+
'`useState` seeded from `atLeast("md")` plus one `onBreakpointChange()` at',
|
|
2536
|
+
'module scope — it fires when the viewport crosses a step, not on every pixel',
|
|
2537
|
+
'of a resize. `md` is 768, the same line `AdaptiveScaffold` crosses, so a',
|
|
2538
|
+
'screen and its shell cannot disagree. A number that really wants to be',
|
|
2539
|
+
'different at 400px and 1400px comes from `pick({ xs: 16, md: 24 })`.',
|
|
2540
|
+
'See `src/app.js` in this project.',
|
|
2528
2541
|
'',
|
|
2529
2542
|
'Custom widgets follow the same shape every framework widget uses',
|
|
2530
2543
|
'(`QRCode.js` is the reference):',
|
|
@@ -2541,8 +2554,25 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2541
2554
|
' idempotent `onUnmount` disposer. Never monkey-patch `_cleanup`.',
|
|
2542
2555
|
'- State is key-based: `useState("key", 0)` plus subscribers. There is no',
|
|
2543
2556
|
' component re-render tree to reason about.',
|
|
2544
|
-
'-
|
|
2545
|
-
'
|
|
2557
|
+
'- The package exports 123 helpers next to the widgets and they are the short',
|
|
2558
|
+
' form of the code you would otherwise write: `mapList` (an array, a number or',
|
|
2559
|
+
" an object), `createList`, `range`, `repeat`, `sort`, `unique`, `chunk`,",
|
|
2560
|
+
' `shadow`, `gradient`, `rgba`, `transform`, `transition`, `filter` (a CSS',
|
|
2561
|
+
' filter, not a list filter), `color(hex).darken(12).hex()`, `random.*`,',
|
|
2562
|
+
' `shortId`, `uuid`, `formatDate`, `formatTimeAmPm`, `relativeTime`, `timeAgo`,',
|
|
2563
|
+
' `addDays`, `subtractDays`, `dateDiff`, `dayOfWeek`, `isWeekend`, `today`,',
|
|
2564
|
+
' `seconds`, `days`, `parseDate`, `timer`, `every`, `everySecond`, `waitUntil`,',
|
|
2565
|
+
' `split`, `join`, `trim`, `startsWith`, `endsWith`, `includes`,',
|
|
2566
|
+
' `delay`, `withMinDelay`,',
|
|
2567
|
+
' `retry`, `memo`, `dict`, `print`, `ref`, `atLeast`, `pick`,',
|
|
2568
|
+
' `onBreakpointChange`, `loadFont`, `clipboard`. Hand-write the long version',
|
|
2569
|
+
' only when it is genuinely shorter or clearer. `npm run check -- --list`',
|
|
2570
|
+
' prints the set for the version actually installed.',
|
|
2571
|
+
' The trap: a routed prop already IS the short form. `padding: 16` reaches the',
|
|
2572
|
+
' rem contract; `padding(16)` returns a pixel string. Use the helpers for a',
|
|
2573
|
+
' CSS value a prop cannot express, never instead of a number.',
|
|
2574
|
+
' Second trap: `months(1)` is 30 days and `years(1)` is 365, and `addDays`',
|
|
2575
|
+
' counts calendar days while `addHours` counts real elapsed ones.',
|
|
2546
2576
|
'',
|
|
2547
2577
|
'One rendering engine exists: `src/widget-factory/`. Never add a second one, and',
|
|
2548
2578
|
'never style by reaching around the factory. **If you believe the engine is',
|
|
@@ -2600,7 +2630,7 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2600
2630
|
' 40 · the smallest tap target, a roomy control',
|
|
2601
2631
|
' 48 · hero spacing',
|
|
2602
2632
|
' 64 / 96 · a full-bleed section, a landing hero',
|
|
2603
|
-
'radius 4 / 8 / 12 / 16 / 999 (pill)',
|
|
2633
|
+
'radius 4 / 8 / 12 / 16 / 20 / 24 / 999 (pill)',
|
|
2604
2634
|
'type 12 caption · 14 body-small · 16 body · 18 subtitle · 24 title',
|
|
2605
2635
|
' 32 headline · 40 / 48 display (a hero, a big number)',
|
|
2606
2636
|
'weight 400 body · 500 label · 600 title · 700 headline',
|
|
@@ -2634,7 +2664,7 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2634
2664
|
'warning is allowed to stay only when you can justify it in one sentence.',
|
|
2635
2665
|
'',
|
|
2636
2666
|
'`npm run ui` proves what is *written*; the rules it cites are the numbered ones',
|
|
2637
|
-
'in [
|
|
2667
|
+
'in [skills/fletbox-design/SKILL.md](skills/fletbox-design/SKILL.md),',
|
|
2638
2668
|
'and that file is the part a checker cannot do — hierarchy, copy, whether the',
|
|
2639
2669
|
'screen solves the problem. Read it before writing a screen, not after.',
|
|
2640
2670
|
'',
|
|
@@ -2647,12 +2677,14 @@ export const agentsMd = (appName, template, version) =>
|
|
|
2647
2677
|
'',
|
|
2648
2678
|
'## 6. Memory — do not relearn the same thing',
|
|
2649
2679
|
'',
|
|
2650
|
-
'
|
|
2651
|
-
'
|
|
2652
|
-
'
|
|
2680
|
+
'The full format lives in [skills/fletbox-memory/SKILL.md](skills/fletbox-memory/SKILL.md).',
|
|
2681
|
+
'The short version: at the start of every session read',
|
|
2682
|
+
'`.fletbox/memory/MEMORY.md` and the entries it points at, including',
|
|
2683
|
+
'`notes/current-status.md`. That is how this agent remembers the app between',
|
|
2684
|
+
'chats — and between models — instead of rediscovering it each time.',
|
|
2653
2685
|
'',
|
|
2654
2686
|
'- When you learn something non-obvious about this app or the framework, write',
|
|
2655
|
-
' `.fletbox/memory/<kebab-case-topic>.md` with: what (fact or rule), **Why:**',
|
|
2687
|
+
' `.fletbox/memory/notes/<kebab-case-topic>.md` with: what (fact or rule), **Why:**',
|
|
2656
2688
|
' (the incident or constraint behind it), **How to apply:** (when it kicks in),',
|
|
2657
2689
|
' and an absolute date for anything time-bound.',
|
|
2658
2690
|
'- Add a one-line pointer to it in `MEMORY.md`. Keep the index short.',
|
|
@@ -2685,7 +2717,7 @@ export const claudeMd = () =>
|
|
|
2685
2717
|
[
|
|
2686
2718
|
'# CLAUDE.md',
|
|
2687
2719
|
'',
|
|
2688
|
-
'The rules for this project live in [AGENTS.md](
|
|
2720
|
+
'The rules for this project live in [AGENTS.md](../AGENTS.md). Read it first and',
|
|
2689
2721
|
'follow it exactly — FletBox syntax only, real props only.',
|
|
2690
2722
|
'',
|
|
2691
2723
|
'Two shortcuts replace guessing:',
|
|
@@ -2695,8 +2727,12 @@ export const claudeMd = () =>
|
|
|
2695
2727
|
' `--diff` lists the props that are documented but dead or used but undeclared.',
|
|
2696
2728
|
' `.fletbox/api/api.json` is the same surface for all widgets, as data.',
|
|
2697
2729
|
'- Design is not improvised. The rules are a file: read',
|
|
2698
|
-
' [skills/fletbox-
|
|
2699
|
-
'
|
|
2730
|
+
' [skills/fletbox-design/SKILL.md](skills/fletbox-design/SKILL.md) before writing',
|
|
2731
|
+
' a screen (the same text is mirrored to `.claude/skills/` and `.qoder/skills/`,',
|
|
2732
|
+
' so those loaders find it by themselves). The set is four files: `fletbox-rules`',
|
|
2733
|
+
' for the API contract, `fletbox-design` for the screen, `fletbox-motion` before',
|
|
2734
|
+
' anything moves, `fletbox-memory` for the journal.',
|
|
2735
|
+
' `npm run ui` proves the part a checker can prove (tokens,',
|
|
2700
2736
|
' the scale, 40px targets, labels, contrast) and cites the rule number it',
|
|
2701
2737
|
' broke; AGENTS.md §4 has the tables. Scale: 4 · 8 · 12 · 16 · 18 · 24 · 32 ·',
|
|
2702
2738
|
' 40 · 48 · 64 · 96 spacing, radii 4/8/12/16/999, type 12 · 14 · 16 · 18 · 24',
|
|
@@ -2705,8 +2741,9 @@ export const claudeMd = () =>
|
|
|
2705
2741
|
'After every edit: `npm run check` (does it exist), `npm run ui` (is it clean),',
|
|
2706
2742
|
'then look at the screen — the checker has no eyes.',
|
|
2707
2743
|
'',
|
|
2708
|
-
'Memory: read `.fletbox/memory/MEMORY.md`
|
|
2709
|
-
'
|
|
2744
|
+
'Memory: read `.fletbox/memory/MEMORY.md` and `notes/current-status.md` before',
|
|
2745
|
+
'working, and write what you learn there — `fletbox-memory` is the format — when',
|
|
2746
|
+
'the next session should not have to rediscover it.',
|
|
2710
2747
|
'',
|
|
2711
2748
|
].join('\n');
|
|
2712
2749
|
|
|
@@ -2727,8 +2764,8 @@ export const cursorRules = () =>
|
|
|
2727
2764
|
' `.fletbox/api/cheatsheet.md`, look it up: `flet-box unpack <Widget>` (or',
|
|
2728
2765
|
' `npm run check -- --props <Widget>`). If it is not listed, it does not exist —',
|
|
2729
2766
|
' do not invent it. `unpack --example` prints a call that is safe to paste.',
|
|
2730
|
-
'- Design rules: read [fletbox-
|
|
2731
|
-
' folder — the numbered
|
|
2767
|
+
'- Design rules: read [fletbox-design.mdc](./fletbox-design.mdc) in this',
|
|
2768
|
+
' folder — the numbered design rules the agent is expected to follow. One',
|
|
2732
2769
|
' brand color per screen, taken from the `colors` tokens — never a hex literal.',
|
|
2733
2770
|
' Sizes come from the scale only: 4 · 8 · 12 · 16 · 18 · 24 · 32 · 40 · 48 · 64',
|
|
2734
2771
|
' · 96 spacing, radii 4/8/12/16/999, type 12 · 14 · 16 · 18 · 24 · 32 · 40 · 48.',
|
|
@@ -2741,6 +2778,45 @@ export const cursorRules = () =>
|
|
|
2741
2778
|
'',
|
|
2742
2779
|
].join('\n');
|
|
2743
2780
|
|
|
2781
|
+
/**
|
|
2782
|
+
* The handover note every session rewrites instead of appending to. It ships
|
|
2783
|
+
* filled with the scaffold's own state, so the first real session has the shape
|
|
2784
|
+
* to copy, and `fletbox-memory` owns the rules for keeping it honest.
|
|
2785
|
+
*
|
|
2786
|
+
* @param {string} appName
|
|
2787
|
+
* @param {string} template
|
|
2788
|
+
* @returns {string}
|
|
2789
|
+
*/
|
|
2790
|
+
export const memoryStatusMd = (appName, template) => {
|
|
2791
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2792
|
+
return [
|
|
2793
|
+
'# Status',
|
|
2794
|
+
'',
|
|
2795
|
+
'The handover file. Rewrite these lines as you work; do not append to them.',
|
|
2796
|
+
'Whoever opens this next may be a different model with no memory of this',
|
|
2797
|
+
'session, so "done" has to mean "verified" and "next" has to be a sentence,',
|
|
2798
|
+
'not a mood.',
|
|
2799
|
+
'',
|
|
2800
|
+
`**Doing:** nothing yet. This is the fresh \`${template}\` scaffold of \`${appName}\`.`,
|
|
2801
|
+
'',
|
|
2802
|
+
'**Done and verified:** the app boots and both gates pass on the template as',
|
|
2803
|
+
'created — `npm run check` (only real APIs) and `npm run ui` (tokens, the scale,',
|
|
2804
|
+
'tap targets, labels, contrast). That is the baseline; anything under it is',
|
|
2805
|
+
"whatever the next session changed.",
|
|
2806
|
+
'',
|
|
2807
|
+
'**Not done / half-done:** the screens and copy are the demo the template ships',
|
|
2808
|
+
'with. They are placeholders until a real screen replaces them.',
|
|
2809
|
+
'',
|
|
2810
|
+
'**Next:** write one sentence about what this app is for, then delete the demo',
|
|
2811
|
+
'screens it does not need.',
|
|
2812
|
+
'',
|
|
2813
|
+
'**Blocked:** none. Put the question to ask here the moment one exists.',
|
|
2814
|
+
'',
|
|
2815
|
+
`**Updated:** ${today} · scaffold.`,
|
|
2816
|
+
'',
|
|
2817
|
+
].join('\n');
|
|
2818
|
+
};
|
|
2819
|
+
|
|
2744
2820
|
export const memoryIndexMd = (appName) =>
|
|
2745
2821
|
[
|
|
2746
2822
|
'# Memory index',
|
|
@@ -2748,6 +2824,7 @@ export const memoryIndexMd = (appName) =>
|
|
|
2748
2824
|
`Persistent notes for the AI agent working on \`${appName}\`. One line per entry.`,
|
|
2749
2825
|
'Read this at the start of every session; open the files it points at.',
|
|
2750
2826
|
'',
|
|
2827
|
+
'- [Current status](notes/current-status.md) — what is built now, what is verified, what is next: the handover between AIs',
|
|
2751
2828
|
'- [Project baseline](notes/baseline.md) — template, flet-box version, verified commands',
|
|
2752
2829
|
'',
|
|
2753
2830
|
].join('\n');
|
|
@@ -2821,500 +2898,44 @@ export const checkAppScript = (appName) =>
|
|
|
2821
2898
|
].join('\n');
|
|
2822
2899
|
|
|
2823
2900
|
// ---------------------------------------------------------------------------
|
|
2824
|
-
//
|
|
2901
|
+
// Skills
|
|
2825
2902
|
// ---------------------------------------------------------------------------
|
|
2826
|
-
// The
|
|
2827
|
-
//
|
|
2828
|
-
//
|
|
2829
|
-
//
|
|
2830
|
-
//
|
|
2831
|
-
//
|
|
2832
|
-
//
|
|
2833
|
-
//
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2903
|
+
// The text lives in the package-root skills/ folder, one kebab-case directory per
|
|
2904
|
+
// subject (rules, design, motion, memory) with a SKILL.md inside. A real file keeps
|
|
2905
|
+
// the contract editable, diffable, and checkable by scripts/check-skills.mjs, which
|
|
2906
|
+
// proves every widget and prop a skill names actually exists. The design rules come
|
|
2907
|
+
// from Angelene Wallis, "No bullshit guide to UX" (2022), and "The UI/UX Playbook"
|
|
2908
|
+
// (uxpeak), measured against this framework: every number is read off the installed
|
|
2909
|
+
// palette or src/tools/scale.js, and they are numbered so `flet-box check-ui` can
|
|
2910
|
+
// cite the one it broke.
|
|
2911
|
+
|
|
2912
|
+
/**
|
|
2913
|
+
* Cursor has no skills folder, so every skill also ships as a pointer rule: its own
|
|
2914
|
+
* frontmatter description as the trigger, and the path an agent must open. One text,
|
|
2915
|
+
* three loaders, never three edits — and no summary that can drift from the skill.
|
|
2916
|
+
*
|
|
2917
|
+
* @param {string} name kebab-case skill folder name
|
|
2918
|
+
* @param {{alwaysApply?: boolean, globs?: string}} [options]
|
|
2919
|
+
* @returns {string}
|
|
2920
|
+
*/
|
|
2921
|
+
export const skillCursorRule = (name, options = {}) =>
|
|
2837
2922
|
[
|
|
2838
2923
|
"---",
|
|
2839
|
-
|
|
2840
|
-
|
|
2924
|
+
`description: ${skills.skillDescription(name).split(" - ")[0]}`,
|
|
2925
|
+
`globs: ${JSON.stringify(options.globs || "src/**/*.js")}`,
|
|
2926
|
+
`alwaysApply: ${options.alwaysApply ? "true" : "false"}`,
|
|
2841
2927
|
"---",
|
|
2842
2928
|
"",
|
|
2843
|
-
|
|
2844
|
-
"",
|
|
2845
|
-
"
|
|
2846
|
-
"
|
|
2847
|
-
"a style.",
|
|
2848
|
-
"",
|
|
2849
|
-
"When two goals conflict, take them in this order:",
|
|
2850
|
-
"",
|
|
2851
|
-
"1 Consistency · 2 Clarity · 3 Accessibility · 4 Usability · 5 Scalability",
|
|
2852
|
-
"· 6 Maintainability · 7 Conversion",
|
|
2853
|
-
"",
|
|
2854
|
-
"Never optimize for aesthetics alone. Beauty is what is left when the system is",
|
|
2855
|
-
"correct; it is an outcome, never a target you can hit directly.",
|
|
2856
|
-
"",
|
|
2857
|
-
"## 00 The two gates",
|
|
2858
|
-
"",
|
|
2859
|
-
"```bash",
|
|
2860
|
-
"npm run check # the API is real: widgets, props, imports",
|
|
2861
|
-
"npm run ui # the screen follows these rules (codes map to rule numbers)",
|
|
2862
|
-
"npm run ui -- --tokens # measured contrast of every token, on both surfaces",
|
|
2863
|
-
"npm run dev # then look at it: the checker has no eyes",
|
|
2864
|
-
"```",
|
|
2865
|
-
"",
|
|
2866
|
-
"A finding you keep needs one sentence of justification, in the commit or in",
|
|
2867
|
-
"`.fletbox/memory/`. If that sentence is \"the design wants it\", the design is",
|
|
2868
|
-
"what needs writing down, because the next screen will copy it.",
|
|
2869
|
-
"",
|
|
2870
|
-
"Sources, cited as `NBG p.N` (No bullshit guide to UX) and `Playbook p.N`",
|
|
2871
|
-
"(The UI/UX Playbook; the PDF page — the printed folio is one lower). Every",
|
|
2872
|
-
"number below is either stated by one of them or measured on this palette.",
|
|
2873
|
-
"Every prop in every call below exists in `.fletbox/api/api.json`.",
|
|
2874
|
-
"",
|
|
2875
|
-
"## 01 System before component",
|
|
2876
|
-
"",
|
|
2877
|
-
"Define the token, then the component, then the screen. In that order, always.",
|
|
2878
|
-
"Components are outputs; the system is the source of truth.",
|
|
2879
|
-
"",
|
|
2880
|
-
"- A screen that needs a value the system does not have adds a token to",
|
|
2881
|
-
" `src/database/themes.js`. It does not write the value inline.",
|
|
2882
|
-
"- A component that needs a special exception: challenge the exception first, and",
|
|
2883
|
-
" if it survives, change its siblings too. Two components solving one problem",
|
|
2884
|
-
" differently get unified, not documented.",
|
|
2885
|
-
"- If you cannot say which rule a decision follows, do not make it.",
|
|
2886
|
-
"",
|
|
2887
|
-
"## 02 Consistency is sacred",
|
|
2888
|
-
"",
|
|
2889
|
-
"No arbitrary differences. If a person learns one pattern here, they must not",
|
|
2890
|
-
"relearn it anywhere else (Playbook p.038).",
|
|
2891
|
-
"",
|
|
2892
|
-
"- One button scheme for the whole app: the primary is `variant: \"filled\"`, the",
|
|
2893
|
-
" secondary `variant: \"outlined\"`, the tertiary `variant: \"text\"`. Never a",
|
|
2894
|
-
" per-card button color chosen to match the picture — people then wonder whether",
|
|
2895
|
-
" each button does something different (p.040-041).",
|
|
2896
|
-
" A filled `Button` defaults to `bgColor: colors.surface` with `colors.text` on",
|
|
2897
|
-
" it, so the primary action must name the brand pair: `bgColor: colors.primary`",
|
|
2898
|
-
" and `color: colors.onPrimary`. An outlined one must name its border (`color:`),",
|
|
2899
|
-
" or it inherits the surface it sits on and disappears.",
|
|
2900
|
-
"- One radius language per screen. A rounded picture next to a square button reads",
|
|
2901
|
-
" as two products (p.039).",
|
|
2902
|
-
"- One icon family, one weight, one size per screen. The one allowed mix is state:",
|
|
2903
|
-
" a selected tab filled, unselected tabs outlined (p.045-046).",
|
|
2904
|
-
"- Cards in the same row share a height. When the copy is uncontrollable, fix the",
|
|
2905
|
-
" height and bottom-align the action; the empty space goes in the middle",
|
|
2906
|
-
" (p.043-044).",
|
|
2907
|
-
"- Same purpose, same shape: identical inputs get the same `height`,",
|
|
2908
|
-
" `borderRadius`, states and validation behavior (rule 13).",
|
|
2909
|
-
"- Images inside a repeated card sit in the same slot, same size, same crop",
|
|
2910
|
-
" (`fit`, `shape`), whatever the picture is (p.042).",
|
|
2911
|
-
"",
|
|
2912
|
-
"## 03 Design tokens only",
|
|
2913
|
-
"",
|
|
2914
|
-
"No arbitrary values, no hex, no `px` strings. Every value comes from the scale.",
|
|
2915
|
-
"",
|
|
2916
|
-
"```",
|
|
2917
|
-
"space 4 · 8 · 12 · 16 · 18 · 24 · 32 · 40 · 48 · 64 · 96",
|
|
2918
|
-
"radius 4 · 8 · 12 · 16 · 999 (pill)",
|
|
2919
|
-
"type 12 caption · 14 body-small · 16 body · 18 subtitle · 24 title",
|
|
2920
|
-
" 32 headline · 40 display-sm · 48 display",
|
|
2921
|
-
"weight 400 body · 500 label · 600 title · 700 headline",
|
|
2922
|
-
"line 1.5-1.6 body · 1.2-1.3 heading",
|
|
2923
|
-
"depth elevation 0 · 1 · 2 · 3 · 4 · 5",
|
|
2924
|
-
"```",
|
|
2925
|
-
"",
|
|
2926
|
-
"- The base unit is 4 and everything is a multiple of it (Playbook p.028). A value",
|
|
2927
|
-
" like 13, 19, 21 or 31 is not a design decision, it is drift.",
|
|
2928
|
-
"- Never type a hex or `rgb()` outside `src/database/themes.js`",
|
|
2929
|
-
" (`RAW_COLOR`). If a token is missing, add it there.",
|
|
2930
|
-
"- Never build a color by appending alpha to a token — `colors.primary + \"15\"` is",
|
|
2931
|
-
" a one-off shade the rest of the app cannot reuse (`HAND_MIXED_COLOR`). The",
|
|
2932
|
-
" tints exist: `colors.primaryBg`, `colors.errorBg`, `colors.successBg`,",
|
|
2933
|
-
" `colors.warningBg`, `colors.infoBg`.",
|
|
2934
|
-
"- A frosted surface is `colors.glass` with `colors.glassBorder` and a",
|
|
2935
|
-
" `backdropFilter`; it is translucent on purpose, so put it over imagery,",
|
|
2936
|
-
" not over body text. A hand-written `rgba()` is a `RAW_COLOR` like any other.",
|
|
2937
|
-
"- Text on a brand fill is `colors.onPrimary`, never a literal white.",
|
|
2938
|
-
"- Tap targets are at least 40 on a side (`SMALL_TAP_TARGET`). Reach it with",
|
|
2939
|
-
" `height`, not with padding. The books argue this qualitatively (Fitts, p.099);",
|
|
2940
|
-
" 40 is the floor this system enforces and 44 is what a phone primary action",
|
|
2941
|
-
" gets.",
|
|
2942
|
-
"- `size: \"small\"` on a `Button` is for dense secondary actions, never for the one",
|
|
2943
|
-
" thing a phone screen asks for.",
|
|
2944
|
-
"",
|
|
2945
|
-
"## 04 Hierarchy must be obvious in three seconds",
|
|
2929
|
+
`Read [skills/${name}/SKILL.md](../../skills/${name}/SKILL.md) before the work it`,
|
|
2930
|
+
"describes. The same bytes sit at `.claude/skills/…` and `.qoder/skills/…` so the",
|
|
2931
|
+
"loaders find them automatically, and that mirror is generated — edit the file",
|
|
2932
|
+
"above and regenerate, never the copy.",
|
|
2946
2933
|
"",
|
|
2947
|
-
"A
|
|
2948
|
-
"matters, what happens next. There are exactly four levers — size, color, weight,",
|
|
2949
|
-
"position (Playbook p.007). If everything is emphasized, nothing is.",
|
|
2934
|
+
"A summary of the skill here would only drift, so this rule is only a pointer.",
|
|
2950
2935
|
"",
|
|
2951
|
-
"
|
|
2952
|
-
" Never two filled buttons on one view (`MULTIPLE_PRIMARY_ACTIONS`).",
|
|
2953
|
-
"- Actions rank by emphasis, information ranks by size: the secondary button keeps",
|
|
2954
|
-
" the same `height` as the primary and loses color (p.008); the data gets the",
|
|
2955
|
-
" size ladder (p.088). That is how p.008 and p.088 stop contradicting each other.",
|
|
2956
|
-
"- In a stat card the value is the largest and darkest text and the label is",
|
|
2957
|
-
" smaller and muted. Never the inverse (p.010).",
|
|
2958
|
-
"- Darkness means importance: heading darker than body, body muted. Never let a",
|
|
2959
|
-
" thumbnail outsize the price (p.009, p.011).",
|
|
2960
|
-
"- Never render a detail panel as uniform label:value rows — it has no hierarchy",
|
|
2961
|
-
" and cannot be scanned. Promote the one value that matters, demote the rest,",
|
|
2962
|
-
" add an icon per row (p.012).",
|
|
2963
|
-
"- Before building, list the elements and rank them by user importance; then map",
|
|
2964
|
-
" size, color, weight and position onto that ranking (p.010).",
|
|
2936
|
+
"Never invent a widget or prop: `flet-box unpack <Widget>` prints the real ones.",
|
|
2965
2937
|
"",
|
|
2966
|
-
|
|
2967
|
-
"",
|
|
2968
|
-
"Write the sentence first: \"this screen lets <who> do <what>\". If it needs an",
|
|
2969
|
-
"\"and\", split the screen (NBG pp.40-43).",
|
|
2970
|
-
"",
|
|
2971
|
-
"- Conflicting goals reduce usability. A task screen carries nothing that does not",
|
|
2972
|
-
" serve the task: no promo panel, no second call to action (Playbook p.127).",
|
|
2973
|
-
"- Decorative imagery next to a form is allowed only when it is action-free — no",
|
|
2974
|
-
" text overlay, no button — and it still has to be about this product, not merely",
|
|
2975
|
-
" pretty (p.128).",
|
|
2976
|
-
"- Navigation exposes what the job needs and nothing else. Every extra link is a",
|
|
2977
|
-
" decision taken before the user made theirs.",
|
|
2978
|
-
"",
|
|
2979
|
-
"## 06 Reduce cognitive load",
|
|
2980
|
-
"",
|
|
2981
|
-
"People recognize; they must never memorize, guess or interpret.",
|
|
2982
|
-
"",
|
|
2983
|
-
"- Chunk to about four items per group and cap a visible group at 7; past that,",
|
|
2984
|
-
" truncate and expose one action — \"Show all 21 amenities\" (Playbook p.101).",
|
|
2985
|
-
" The 7±2 figure is a myth in its loosest form; plan on four and the group still",
|
|
2986
|
-
" holds when someone adds a fifth.",
|
|
2987
|
-
"- Recognition over recall: common icons, common words, state visible on screen",
|
|
2988
|
-
" rather than in memory (p.104). \"Continue watching\" beats \"remember where you",
|
|
2989
|
-
" stopped\".",
|
|
2990
|
-
"- Do not hide choices behind a control that must be opened to be seen: variants",
|
|
2991
|
-
" as swatches, quantity as a stepper, not a nested menu (p.107-108).",
|
|
2992
|
-
"- Never oversimplify into vagueness either: a switch with an icon and no words is",
|
|
2993
|
-
" not clean, it is unreadable (p.014).",
|
|
2994
|
-
"- If no user has shown they need a feature, remove it rather than park it behind",
|
|
2995
|
-
" a toggle (NBG p.136).",
|
|
2996
|
-
"",
|
|
2997
|
-
"## 07 Spacing communicates the relationship",
|
|
2998
|
-
"",
|
|
2999
|
-
"Whitespace is functional, not decorative. Related things sit closer than",
|
|
3000
|
-
"unrelated things, and that difference is the whole message (Playbook p.013, p.029).",
|
|
3001
|
-
"",
|
|
3002
|
-
"- Field pattern: label to its own input `12`, that field to the next label `32`.",
|
|
3003
|
-
" Equal spacing at both levels makes label ownership ambiguous (p.013).",
|
|
3004
|
-
"- Card padding is `16`; the rhythm between cards is `16` or `24`; sections",
|
|
3005
|
-
" separate at `24` or `32`; page blocks at `48`. Never mix two rhythms in one list.",
|
|
3006
|
-
"- Start a layout with more room than you think you need and tighten it; clutter",
|
|
3007
|
-
" is easier to see than cramp is to undo (p.025-027).",
|
|
3008
|
-
"- Do not stretch a form or a checkout to the viewport. 1440 is a canvas size, not",
|
|
3009
|
-
" a target; give the card the width it needs and let the margin carry the rest",
|
|
3010
|
-
" (p.129-130). `Container({ maxWidth: 480 })` around a form, `fullWidth: true`",
|
|
3011
|
-
" only on the button inside it.",
|
|
3012
|
-
"- Layout is `useState` bindings, `flex`, `gap` and `wrap`. An absolute offset is",
|
|
3013
|
-
" for overlays, and then through `stackPosition`, not raw coordinates.",
|
|
3014
|
-
"",
|
|
3015
|
-
"## 08 Typography is structure",
|
|
3016
|
-
"",
|
|
3017
|
-
"- Body is `size: 16` (Playbook p.090). Nothing that must be read goes below 12;",
|
|
3018
|
-
" the book contradicts itself between 12 (p.090) and 10 (p.096) — 12 is the floor",
|
|
3019
|
-
" here.",
|
|
3020
|
-
"- Heading: bigger, heavier, darker, tighter. Body: smaller, regular, muted,",
|
|
3021
|
-
" looser. Never the same weight for both, never extra-light for body (p.084-087).",
|
|
3022
|
-
"- Line-height: 1.5-1.6 on body, 1.2-1.3 on headings; a 32px heading sits at about",
|
|
3023
|
-
" 1.25 (p.091-092). A heading never gets more air than a paragraph.",
|
|
3024
|
-
"- Measure: 45-75 characters per line on desktop, 30-40 on mobile. A paragraph",
|
|
3025
|
-
" that runs the full width is unreadable at 1440 (p.076-078).",
|
|
3026
|
-
"- One typeface; two at the absolute maximum, and then headings against body",
|
|
3027
|
-
" (p.075-076). Declare it once in the theme, never per `Text`.",
|
|
3028
|
-
"- `align: \"left\"` for every run of text and every list. `right` for numbers and",
|
|
3029
|
-
" prices, so magnitudes compare. `center` only for a short title or a lone CTA,",
|
|
3030
|
-
" never for a paragraph (p.018, p.094).",
|
|
3031
|
-
"- Long copy becomes scannable copy: a short lead, a heading, then one item per",
|
|
3032
|
-
" feature with an icon, a bold name and two lines of body; four or more items go",
|
|
3033
|
-
" in a two-column grid (p.123-125).",
|
|
3034
|
-
"",
|
|
3035
|
-
"## 09 Color carries meaning",
|
|
3036
|
-
"",
|
|
3037
|
-
"- One brand color per screen, and it is the color of the one action the screen",
|
|
3038
|
-
" asks for. A second accent on the same view means one of them is wrong.",
|
|
3039
|
-
"- Reserve the brand for things you can click. Headers, labels and icons stay",
|
|
3040
|
-
" neutral — brand color on a decorative surface teaches people that color means",
|
|
3041
|
-
" nothing (Playbook p.066-067).",
|
|
3042
|
-
"- Backgrounds are neutral (`colors.background`, `colors.surface`, `colors.card`);",
|
|
3043
|
-
" color belongs in the action, not the wall (p.067-068). A dark theme is equally",
|
|
3044
|
-
" valid; offer both, because dark mode is expected, not a bonus (p.070).",
|
|
3045
|
-
"- Feedback colors are fixed and never redefined: `colors.success`,",
|
|
3046
|
-
" `colors.warning`, `colors.danger`, `colors.info`. Red is error, green is",
|
|
3047
|
-
" success, amber is warning; never pink for success, never green for delete.",
|
|
3048
|
-
"- The signal red and the readable red are different tokens. `colors.danger`",
|
|
3049
|
-
" (#ef4444) is for a badge, a border, an icon: as words on a white page it",
|
|
3050
|
-
" measures 3.76:1 and that is not AA. `colors.dangerStrong` (#b91c1c) is the red",
|
|
3051
|
-
" you may read and press — 6.47:1 as words on white, 6.47:1 with white written on",
|
|
3052
|
-
" it, 5.42:1 inside `colors.errorBg`. Same for the others: `colors.warning`",
|
|
3053
|
-
" (2.15:1 with white on it) and `colors.success` (2.54:1) are plates and dots,",
|
|
3054
|
-
" never a sentence — `colors.warningStrong` (5.02:1) and `colors.successStrong`",
|
|
3055
|
-
" (5.48:1) are the readable ones, in either direction. Measure any pair you are",
|
|
3056
|
-
" about to ship: `npm run ui -- --tokens`.",
|
|
3057
|
-
"- Never carry meaning in color alone — a color-blind person and a phone in",
|
|
3058
|
-
" sunlight both lose it. Pair the color with an icon and a word (p.070): a status",
|
|
3059
|
-
" chip is `Badge` plus text, a field error is `error` plus `showValidationMessage`.",
|
|
3060
|
-
"- Hierarchy inside a fill comes from opacity, not from a second hue.",
|
|
3061
|
-
"",
|
|
3062
|
-
"## 10 Depth is a scale",
|
|
3063
|
-
"",
|
|
3064
|
-
"- `elevation: 0-5` is the shadow scale the framework ships (`SHADOW_LEVELS`), and",
|
|
3065
|
-
" these widgets read it: `Accordion`, `AppBar`, `BottomNavigation`, `Button`,",
|
|
3066
|
-
" `Card`, `Chip`, `Drawer`, `FloatingActionButton`, `ListTile`, `Modal`, `SnackBar`.",
|
|
3067
|
-
" Elsewhere — `Container` and `ListView` included — the number reaches",
|
|
3068
|
-
" `style.boxShadow` as a bare number, CSS drops it, and the box stays flat",
|
|
3069
|
-
" without saying so: pass a `shadow: \"0 12px 32px rgba(15,23,42,0.12)\"` string",
|
|
3070
|
-
" there instead (`DEAD_ELEVATION`).",
|
|
3071
|
-
"- Anything that floats gets depth: a dropdown, a menu, a modal, a card on a map.",
|
|
3072
|
-
" A shadowless layer merges with the page and the user cannot tell what is on top",
|
|
3073
|
-
" (p.050-051).",
|
|
3074
|
-
"- Soft, not harsh: depth comes from a wide blur at low opacity, and the shadow",
|
|
3075
|
-
" is tinted toward the background hue, never pure grey or black (p.052-054).",
|
|
3076
|
-
"- Do not put a white card on a white page — the interface goes flat. Page",
|
|
3077
|
-
" `colors.background`, card `colors.card` (p.056-057).",
|
|
3078
|
-
"- Borders are thin and rare. Separation comes from space and surface, not from a",
|
|
3079
|
-
" rule under every row; a bordered list of everything reads as busy and dated",
|
|
3080
|
-
" (p.059-061).",
|
|
3081
|
-
"- Glass and blur are a brand decision, and they never survive a light background:",
|
|
3082
|
-
" text on frosted glass over white loses the contrast war (p.063-065).",
|
|
3083
|
-
"",
|
|
3084
|
-
"## 11 Accessibility is a floor, not a feature",
|
|
3085
|
-
"",
|
|
3086
|
-
"When accessibility and aesthetics conflict, accessibility wins. Every time.",
|
|
3087
|
-
"",
|
|
3088
|
-
"- Text needs 4.5:1 against what it sits on; large text (24px, or 18px bold) and",
|
|
3089
|
-
" meaningful graphics need 3:1. Disabled text is exempt. The book agrees: its",
|
|
3090
|
-
" passing examples measure 4.8-6.54:1 and its failures 1.81-2.84:1 (p.021), and",
|
|
3091
|
-
" #626262 is the lightest grey that survives on white (p.086).",
|
|
3092
|
-
"- Measure instead of arguing: `npm run ui -- --tokens`. On this palette `text` is",
|
|
3093
|
-
" 17.85:1, `textSecondary` 4.76:1, `textDisabled` 2.56:1 (decoration only), and",
|
|
3094
|
-
" the brand pair `onPrimary` on `primary` is 4.55:1 — which is why `primary` may",
|
|
3095
|
-
" carry a button label and not a paragraph. By hand:",
|
|
3096
|
-
" `color(\"#0f172a\").contrastRatio(\"#ffffff\")` answers 17.85.",
|
|
3097
|
-
"- Text over a photograph needs a scrim: a dark overlay or a blurred plate under",
|
|
3098
|
-
" the words. Light text over one dark photo proves nothing about the next photo",
|
|
3099
|
-
" (p.022).",
|
|
3100
|
-
"- Every field has a visible `label`; a placeholder is a format hint, never a",
|
|
3101
|
-
" substitute (p.115). Every switch and checkbox has one too (`UNLABELED_TOGGLE`).",
|
|
3102
|
-
"- Every control that is only an icon gets a name: wrap it in",
|
|
3103
|
-
" `Tooltip({ text, child })` or give it a visible label (`ICON_ONLY_CONTROL`).",
|
|
3104
|
-
"- Every `Image` that carries information has `alt`; `alt: \"\"` is the correct",
|
|
3105
|
-
" answer for a decoration (`IMAGE_NO_ALT`).",
|
|
3106
|
-
"- Focus must be visible, keyboard must reach everything, and `Esc` must close",
|
|
3107
|
-
" whatever opened (`Modal({ closeOnEsc: true, showCloseButton: true })`).",
|
|
3108
|
-
"- Motion is optional content: respect a reduced-motion preference; never",
|
|
3109
|
-
" auto-play a carousel or a banner.",
|
|
3110
|
-
"",
|
|
3111
|
-
"## 12 Every interaction has four states",
|
|
3112
|
-
"",
|
|
3113
|
-
"A control that handles only the happy path is unfinished (NBG pp.94-97). Write",
|
|
3114
|
-
"all four, in the same component, before the screen counts as done.",
|
|
3115
|
-
"",
|
|
3116
|
-
"```js",
|
|
3117
|
-
"Column({ gap: 16, children: [",
|
|
3118
|
-
" loading ? Skeleton({ width: \"100%\", height: 48, count: 3 }) : null,",
|
|
3119
|
-
" !loading && rows.length === 0",
|
|
3120
|
-
" ? EmptyState() // headline + illustration + tip + CTA",
|
|
3121
|
-
" : null,",
|
|
3122
|
-
" failed",
|
|
3123
|
-
" ? SnackBar({ message: \"Could not load the list.\", action: \"Retry\", onAction: retry })",
|
|
3124
|
-
" : null,",
|
|
3125
|
-
" !loading && rows.length > 0 ? ListView({ children: rows }) : null,",
|
|
3126
|
-
"]})",
|
|
3127
|
-
"```",
|
|
3128
|
-
"",
|
|
3129
|
-
"- Empty has four parts: a solution-oriented headline, an illustration, two",
|
|
3130
|
-
" concrete tips, one primary action. \"You have no projects\" is a dead end, not a",
|
|
3131
|
-
" state (Playbook p.135-136).",
|
|
3132
|
-
"- Loading appears before the wait, not after it: a `Skeleton` at once beats a",
|
|
3133
|
-
" spinner two seconds later (NBG p.134, Doherty).",
|
|
3134
|
-
"- Error does exactly three things: says what went wrong in the user's words,",
|
|
3135
|
-
" gives the way back, keeps the visual style of the app. Show the code, keep the",
|
|
3136
|
-
" navigation, offer links (p.137-138). Never an error code alone, never a shrug.",
|
|
3137
|
-
"- Success confirms in place — a `SnackBar`, not a new screen — and offers the",
|
|
3138
|
-
" next step while it is there.",
|
|
3139
|
-
"- Progress is honest. Show real progress or show a `Skeleton`; a bar that lies",
|
|
3140
|
-
" buys one visit and costs the next one (NBG defends inflating them; do not).",
|
|
3141
|
-
"",
|
|
3142
|
-
"## 13 Forms: prevent the error, do not report it",
|
|
3143
|
-
"",
|
|
3144
|
-
"- Ask for 3 to 7 things at once. More than that is more than one step, and a step",
|
|
3145
|
-
" the user can see: `Stepper({ steps, activeStep, onFinish })` (NBG pp.40-43).",
|
|
3146
|
-
"- Width fits the data: CVC is 3 or 4 characters, so the field is short; card",
|
|
3147
|
-
" number and name run full width; pair Expiry with CVC on one row (p.115-116).",
|
|
3148
|
-
"- A column of three or more fields lives inside a cap — `Container({ maxWidth:",
|
|
3149
|
-
" 480 })`. A field as wide as the window answers \"type anything\" and the eye",
|
|
3150
|
-
" loses the next line (`UNCONSTRAINED_FORM_WIDTH`).",
|
|
3151
|
-
"- A fixed-length code gets one box per character in enlarged type, not one long",
|
|
3152
|
-
" field (p.114).",
|
|
3153
|
-
"- `Input({ label, placeholder, required, maxLength, validation, type })`:",
|
|
3154
|
-
" placeholder carries the format (\"MM/YY\", \"12345\"), `maxLength` and `type`",
|
|
3155
|
-
" constrain the input, and validation runs inline as they type, not on submit",
|
|
3156
|
-
" (p.119).",
|
|
3157
|
-
"- No \"Confirm password\" field. It adds cost, repeats the same typo twice, and",
|
|
3158
|
-
" buys a false sense of safety. One field, `passwordToggle: true`, and a strength",
|
|
3159
|
-
" line under it (p.117-119).",
|
|
3160
|
-
"- Good defaults and autofill remove keystrokes: prefill the country, the saved",
|
|
3161
|
-
" address, the last card (p.105).",
|
|
3162
|
-
"- Required fields are marked, and the label of the submit button names the",
|
|
3163
|
-
" outcome — \"Pay $65\", not \"Submit\" (p.115).",
|
|
3164
|
-
"- Never clear a form because one field failed. Keep what they typed.",
|
|
3165
|
-
"",
|
|
3166
|
-
"## 14 Destructive actions are designed last, and carefully",
|
|
3167
|
-
"",
|
|
3168
|
-
"- The button carries the verb: \"Delete 3 files\", never \"OK\" or \"Yes\"",
|
|
3169
|
-
" (`GENERIC_ACTION_LABEL`) (Playbook p.121).",
|
|
3170
|
-
"- Destructive is red — `colors.dangerStrong` for the words, `colors.danger` for",
|
|
3171
|
-
" the dot beside them — because green reads safe and a person presses it twice",
|
|
3172
|
-
" (`DESTRUCTIVE_COLOR`) (p.120). `colors.danger` alone is 3.76:1 as a label.",
|
|
3173
|
-
"- Order in a two-button row: Cancel on the left, destructive on the right. Left",
|
|
3174
|
-
" is what a reading eye hits first (`CONFIRM_ORDER`) (p.121-122).",
|
|
3175
|
-
"- The dialog states reversibility: \"This action cannot be undone.\" Say it even",
|
|
3176
|
-
" when it can, and then offer the way back.",
|
|
3177
|
-
"- `AlertDialog({ title, message, confirmText, cancelText, showCancel, onCancel, onConfirm, onClose })`",
|
|
3178
|
-
" always leaves an escape: a Cancel, and an X on any layer that can be opened.",
|
|
3179
|
-
"- Undo is cheaper than a warning. If the action is reversible, let people undo it",
|
|
3180
|
-
" instead of interrupting them to confirm it (NBG pp.116-118).",
|
|
3181
|
-
"- On a phone, a destructive action is not in the thumb zone; the thing you do",
|
|
3182
|
-
" often is (p.133-135).",
|
|
3183
|
-
"",
|
|
3184
|
-
"## 15 Reduce interaction cost",
|
|
3185
|
-
"",
|
|
3186
|
-
"Interaction cost is cognitive effort + physical effort + time (Playbook p.098).",
|
|
3187
|
-
"Count it, then cut it: reading, scrolling, searching, deciding, clicking, typing,",
|
|
3188
|
-
"waiting, remembering (p.099).",
|
|
3189
|
-
"",
|
|
3190
|
-
"- Count the steps in the main flow and delete every one that does not change the",
|
|
3191
|
-
" outcome (p.105). Three is a target; one is better.",
|
|
3192
|
-
"- Put the action next to the thing it acts on: the CTA beside the quantity",
|
|
3193
|
-
" stepper, the row action inside the row (p.108, Fitts).",
|
|
3194
|
-
"- Keep the control that runs all the time in a fixed, reachable place — a player",
|
|
3195
|
-
" bar at the bottom, a primary tab at the thumb (p.100, p.133-135).",
|
|
3196
|
-
"- Show the content instead of a door to the content: three real cards beat a",
|
|
3197
|
-
" banner that says \"Explore now\" (p.111-113).",
|
|
3198
|
-
"- One click where a confirmation is not needed. Two where it is.",
|
|
3199
|
-
"",
|
|
3200
|
-
"## 16 Respect the platform",
|
|
3201
|
-
"",
|
|
3202
|
-
"- Use the words, icons and gestures people already brought with them (NBG p.131,",
|
|
3203
|
-
" Jakob's law). An invented gesture is a bug report waiting to be written.",
|
|
3204
|
-
"- iOS, Android and web each have conventions; meet them instead of overriding",
|
|
3205
|
-
" them with a custom pattern. A back that lands where the user expects, a sheet",
|
|
3206
|
-
" that dismisses down, a form that advances on Enter.",
|
|
3207
|
-
"- Break a convention only when the measurement says the convention is wrong here,",
|
|
3208
|
-
" and write that measurement down.",
|
|
3209
|
-
"",
|
|
3210
|
-
"## 17 The review",
|
|
3211
|
-
"",
|
|
3212
|
-
"When asked to review a screen, answer in this order, and say why every time:",
|
|
3213
|
-
"",
|
|
3214
|
-
"1 Goal — what the screen is for, and whether it is one goal.",
|
|
3215
|
-
"2 UX problems — cost, states, recall, flow.",
|
|
3216
|
-
"3 UI problems — hierarchy, spacing, type, color, depth.",
|
|
3217
|
-
"4 System violations — the values that are not tokens, the component that is an",
|
|
3218
|
-
" exception.",
|
|
3219
|
-
"5 Accessibility violations — measured ratios, targets, labels, focus.",
|
|
3220
|
-
"6 Consistency violations — what this screen does differently for no reason.",
|
|
3221
|
-
"7 Scalability risk — what breaks when this screen has eleven siblings.",
|
|
3222
|
-
"8 Fix — the call, not the mood. Then the rule it establishes.",
|
|
3223
|
-
"9 Score — see below.",
|
|
3224
|
-
"",
|
|
3225
|
-
"Score the seven axes 0-100, but only against evidence, never against taste:",
|
|
3226
|
-
"an axis starts at 100 and loses 10 for every open finding of its own codes, so",
|
|
3227
|
-
"the number is a count and not an opinion. Below 85 on any axis means the fix is",
|
|
3228
|
-
"a rule change, not a pixel move. An axis with no findings and no evidence is",
|
|
3229
|
-
"unmeasured, not perfect — say so.",
|
|
3230
|
-
"",
|
|
3231
|
-
"| Axis | Its codes (from `npm run ui`) |",
|
|
3232
|
-
"| --- | --- |",
|
|
3233
|
-
"| Consistency | OFF_SCALE_SPACING · OFF_SCALE_RADIUS · OFF_SCALE_TYPE · RAW_COLOR · HAND_MIXED_COLOR · DEAD_ELEVATION |",
|
|
3234
|
-
"| Accessibility | LOW_CONTRAST · LOW_CONTRAST_UI · SMALL_TAP_TARGET · UNLABELED_FIELD · UNLABELED_TOGGLE · ICON_ONLY_CONTROL · IMAGE_NO_ALT |",
|
|
3235
|
-
"| Usability | CROWDED_FORM · LIST_WITHOUT_STATE · UNCONSTRAINED_FORM_WIDTH |",
|
|
3236
|
-
"| Hierarchy | MULTIPLE_PRIMARY_ACTIONS · CENTERED_BODY_TEXT · GENERIC_ACTION_LABEL · DESTRUCTIVE_COLOR · CONFIRM_ORDER |",
|
|
3237
|
-
"| Scalability | every token that had to be added to make the screen pass |",
|
|
3238
|
-
"| Conversion | the label of the primary action, counted in words and in steps |",
|
|
3239
|
-
"| Visual quality | measured only by the axes above; state what you looked at |",
|
|
3240
|
-
"",
|
|
3241
|
-
"## 18 Where these books are wrong",
|
|
3242
|
-
"",
|
|
3243
|
-
"Both are worth more read than copied. The parts that do not survive measurement:",
|
|
3244
|
-
"",
|
|
3245
|
-
"- The Playbook bans pure black on white and pure white on dark (p.083). This",
|
|
3246
|
-
" system puts white text on deep brand fills, and the ratio is what decides —",
|
|
3247
|
-
" 4.55:1 there, not the rule of thumb.",
|
|
3248
|
-
"- p.008 says a secondary button keeps the primary's size; p.088 says size is",
|
|
3249
|
-
" the hierarchy lever. Both are right about different things: emphasis ranks",
|
|
3250
|
-
" actions, size ranks information.",
|
|
3251
|
-
"- p.079 marks center-aligned body as good; p.094 says center is for titles only.",
|
|
3252
|
-
" The later rule is the readable one.",
|
|
3253
|
-
"- p.103 bans pop-ups as distractions; p.120 requires one to confirm a deletion.",
|
|
3254
|
-
" Reconciled: never unsolicited, always on destructive.",
|
|
3255
|
-
"- p.019 (gutter 70, margin 79, overlay 17%) and p.052-054 (Y:12, blur:48,",
|
|
3256
|
-
" #E9E9E9) are one designer's Figma fields in a screenshot, not a scale.",
|
|
3257
|
-
" Do not harvest them as thresholds.",
|
|
3258
|
-
"- The Playbook's own sign-up example (p.127) is white on a pale blue fill that",
|
|
3259
|
-
" fails its own contrast chapter. Copy the rule, never the screenshot.",
|
|
3260
|
-
"- \"7 ± 2\" (p.101) is Miller's number quoted loosely; chunk size is a design",
|
|
3261
|
-
" choice, and four is the one that survives a fifth item.",
|
|
3262
|
-
"- NBG defends inflating progress bars (its \"little lie\"). Do not.",
|
|
3263
|
-
"- Neither book states a tap-target size in px. Fitts gives the direction; 40 is",
|
|
3264
|
-
" this system's floor, and the number to defend if someone asks.",
|
|
3265
|
-
"",
|
|
3266
|
-
"## 19 What no checker can see",
|
|
3267
|
-
"",
|
|
3268
|
-
"`npm run ui` cannot tell you whether the screen is understood. For that:",
|
|
3269
|
-
"",
|
|
3270
|
-
"- Ask one person to do one task and stay quiet while they do it. Their first three",
|
|
3271
|
-
" sentences are the specification.",
|
|
3272
|
-
"- Watch where the pointer hesitates and where it misclicks: that is where the",
|
|
3273
|
-
" layout is lying about what is tappable.",
|
|
3274
|
-
"- Five people find most of what is broken. Two rounds of five beat one round of",
|
|
3275
|
-
" ten.",
|
|
3276
|
-
"- Write what you saw into `.fletbox/memory/`, or the next session designs this",
|
|
3277
|
-
" screen again from taste.",
|
|
3278
|
-
"",
|
|
3279
|
-
"## 20 The loop",
|
|
3280
|
-
"",
|
|
3281
|
-
"```bash",
|
|
3282
|
-
"npm run check",
|
|
3283
|
-
"npm run ui",
|
|
3284
|
-
"npm run dev # look at the screen, at 375px and at 1440px",
|
|
3285
|
-
"```",
|
|
3286
|
-
"",
|
|
3287
|
-
"Fix the code, not the checker. Then make the rule reusable: if the fix was right",
|
|
3288
|
-
"for this screen, it is a rule for every screen, and it belongs in this file.",
|
|
3289
|
-
"",
|
|
3290
|
-
].join('\n');
|
|
3291
|
-
|
|
3292
|
-
// Cursor has no skills folder, so the same rules ship as one always-apply rule
|
|
3293
|
-
// that loads the skill file instead of duplicating it.
|
|
3294
|
-
export const cleanUiCursorRule = () =>
|
|
3295
|
-
[
|
|
3296
|
-
'---',
|
|
3297
|
-
'description: Clean-UI rules for every screen - tokens, the 8-based scale, tap targets, states',
|
|
3298
|
-
'globs: "src/**/*.js"',
|
|
3299
|
-
'alwaysApply: true',
|
|
3300
|
-
'---',
|
|
3301
|
-
'',
|
|
3302
|
-
'Design a screen, do not decorate one. The full rules are in',
|
|
3303
|
-
'[.claude/skills/fletbox-clean-ui/SKILL.md](../../.claude/skills/fletbox-clean-ui/SKILL.md)',
|
|
3304
|
-
'- read it before writing a screen, form, list or dialog.',
|
|
3305
|
-
'',
|
|
3306
|
-
'- One screen, one job, one primary action; secondaries are `Button({ variant: "text" })`.',
|
|
3307
|
-
'- Colors come from `colors.*` only. Never a hex literal outside `src/database/themes.js`.',
|
|
3308
|
-
' `colors.onPrimary` writes on a brand fill; `colors.primaryBg` is the brand tint.',
|
|
3309
|
-
'- Sizes come from the scale: 4 - 8 - 12 - 16 - 18 - 24 - 32 - 48 space, 4 / 8 / 12 / 16 /',
|
|
3310
|
-
' 999 radius, 12 / 14 / 16 / 18 / 24 / 32 type.',
|
|
3311
|
-
'- Anything pressable is at least 40px on a side, and 44 is the number to aim at.',
|
|
3312
|
-
'- Every interaction shows loading, done, failed and empty; errors say the problem and',
|
|
3313
|
-
' the way out in the user\'s own words.',
|
|
3314
|
-
'- Verify with `npm run check` then `npm run ui` (add `-- --tokens` to read the measured',
|
|
3315
|
-
' contrast table), and then look at the screen - the checker has no eyes.',
|
|
3316
|
-
'',
|
|
3317
|
-
].join('\n');
|
|
2938
|
+
].join("\n");
|
|
3318
2939
|
|
|
3319
2940
|
/**
|
|
3320
2941
|
* The app-side twin of scripts/check-app.mjs for the clean-UI gate. Same
|