flet-box 1.0.4 → 1.0.5
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/README.md +10 -0
- package/bin/cli.js +15 -0
- package/bin/commands/checkApi.js +761 -0
- package/bin/commands/create.js +77 -5
- package/bin/utils/dtsProps.js +191 -0
- package/bin/utils/moduleExports.js +165 -0
- package/bin/utils/neutralise.js +64 -0
- package/bin/utils/templates.js +366 -27
- package/docs/CONTRIBUTING.md +156 -0
- package/docs/README.md +68 -0
- package/docs/arquitectura.md +164 -0
- package/docs/cli/README.md +542 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/app-templates.md +197 -0
- package/docs/guides/compatibility.md +95 -0
- package/docs/guides/embedding.md +284 -0
- package/docs/guides/first-app.md +208 -0
- package/docs/guides/frontend-services.md +171 -0
- package/docs/guides/minification-and-protection.md +260 -0
- package/docs/guides/mobile-and-platforms.md +439 -0
- package/docs/guides/online-updates.md +196 -0
- package/docs/guides/router.md +282 -0
- package/docs/guides/state.md +283 -0
- package/docs/guides/syntax-philosophy.md +150 -0
- package/docs/guides/utilities.md +241 -0
- package/docs/guides/widget-structure.md +163 -0
- package/docs/server/README.md +68 -0
- package/docs/server/START_HERE.md +121 -0
- package/docs/server/api.md +225 -0
- package/docs/server/data-services.md +300 -0
- package/docs/server/deployment.md +113 -0
- package/docs/server/security.md +196 -0
- package/docs/services/README.md +79 -0
- package/docs/services/RamStore.md +84 -0
- package/docs/services/Session.md +84 -0
- package/docs/services/Storage.md +88 -0
- package/docs/services/http.md +86 -0
- package/docs/tools/README.md +138 -0
- package/docs/tools/animation.md +87 -0
- package/docs/tools/async-ids-log.md +80 -0
- package/docs/tools/device.md +132 -0
- package/docs/tools/introspection.md +67 -0
- package/docs/tools/lists.md +96 -0
- package/docs/tools/markdown.md +79 -0
- package/docs/tools/reactivity.md +288 -0
- package/docs/tools/state.md +103 -0
- package/docs/tools/styling.md +92 -0
- package/docs/tools/text-and-time.md +89 -0
- package/docs/tools/theme.md +102 -0
- package/docs/tools/validation.md +75 -0
- package/docs/tools/visual-effects.md +84 -0
- package/docs/widget/Accordion.md +144 -0
- package/docs/widget/AdMob.md +144 -0
- package/docs/widget/AdSense.md +133 -0
- package/docs/widget/AdaptiveScaffold.md +105 -0
- package/docs/widget/AlertDialog.md +103 -0
- package/docs/widget/AnimatedBox.md +130 -0
- package/docs/widget/AnimatedText.md +123 -0
- package/docs/widget/AppBar.md +118 -0
- package/docs/widget/Audio.md +135 -0
- package/docs/widget/Avatar.md +119 -0
- package/docs/widget/Badge.md +128 -0
- package/docs/widget/BottomNavigation.md +116 -0
- package/docs/widget/BottomSheet.md +125 -0
- package/docs/widget/Button.md +137 -0
- package/docs/widget/COMMON_PROPS.md +150 -0
- package/docs/widget/Card.md +125 -0
- package/docs/widget/Carousel.md +151 -0
- package/docs/widget/Chart.md +151 -0
- package/docs/widget/Checkbox.md +118 -0
- package/docs/widget/Chip.md +126 -0
- package/docs/widget/CircularBar.md +168 -0
- package/docs/widget/CircularChart.md +164 -0
- package/docs/widget/CodeViewer.md +130 -0
- package/docs/widget/CollapsibleSideBar.md +112 -0
- package/docs/widget/Column.md +129 -0
- package/docs/widget/Container.md +136 -0
- package/docs/widget/DataTable.md +150 -0
- package/docs/widget/Divider.md +109 -0
- package/docs/widget/DraggBox.md +129 -0
- package/docs/widget/Drawer.md +135 -0
- package/docs/widget/DrawerItem.md +98 -0
- package/docs/widget/Dropdown.md +139 -0
- package/docs/widget/DroppBox.md +152 -0
- package/docs/widget/FloatingActionButton.md +120 -0
- package/docs/widget/GridView.md +140 -0
- package/docs/widget/Icon.md +117 -0
- package/docs/widget/Image.md +125 -0
- package/docs/widget/Input.md +151 -0
- package/docs/widget/Inspector.md +92 -0
- package/docs/widget/InstallButton.md +128 -0
- package/docs/widget/ListTile.md +132 -0
- package/docs/widget/ListView.md +138 -0
- package/docs/widget/Markdown.md +175 -0
- package/docs/widget/MatrixRain.md +122 -0
- package/docs/widget/Modal.md +129 -0
- package/docs/widget/Pagination.md +135 -0
- package/docs/widget/ParallaxBox.md +128 -0
- package/docs/widget/ProgressBar.md +130 -0
- package/docs/widget/QRCode.md +117 -0
- package/docs/widget/README.md +188 -0
- package/docs/widget/Radio.md +123 -0
- package/docs/widget/Rating.md +131 -0
- package/docs/widget/Row.md +131 -0
- package/docs/widget/START_HERE.md +146 -0
- package/docs/widget/Scaffold.md +152 -0
- package/docs/widget/Skeleton.md +130 -0
- package/docs/widget/Slider.md +146 -0
- package/docs/widget/SnackBar.md +102 -0
- package/docs/widget/Stack.md +128 -0
- package/docs/widget/Stepper.md +145 -0
- package/docs/widget/Switch.md +118 -0
- package/docs/widget/Tabs.md +124 -0
- package/docs/widget/Text.md +133 -0
- package/docs/widget/Tooltip.md +145 -0
- package/docs/widget/TreeView.md +176 -0
- package/docs/widget/Video.md +127 -0
- package/docs/widget/_template.md +61 -0
- package/package.json +3 -2
- package/src/animations/animate.js +180 -70
- package/src/animations/index.js +1 -0
- package/src/create-index.sh +1 -1
- package/src/index.d.ts +45 -22
- package/src/index.js +1 -0
- package/src/navigations/AdaptiveScaffold.js +11 -2
- package/src/navigations/BottomNavigation.js +10 -3
- package/src/navigations/Tabs.js +6 -2
- package/src/tools/useState.js +54 -63
- package/src/widget-factory/assignProps.js +23 -1
- package/src/widget-factory/processProps.js +10 -3
- package/src/widget-factory/reactiveValue.js +43 -18
- package/src/widget-factory/translateProps.js +777 -901
- package/src/widgets/AlertDialog.js +7 -5
- package/src/widgets/Checkbox.js +9 -7
- package/src/widgets/CircularBar.js +12 -1
- package/src/widgets/FloatingActionButton.js +4 -7
- package/src/widgets/Image.js +5 -7
- package/src/widgets/Input.js +92 -0
- package/src/widgets/ListView.js +73 -29
- package/src/widgets/Pagination.js +6 -3
- package/src/widgets/ProgressBar.js +16 -13
- package/src/widgets/Radio.js +6 -7
- package/src/widgets/Slider.js +23 -4
- package/src/widgets/Stack.js +36 -3
- package/src/widgets/Stepper.js +4 -3
- package/src/widgets/Switch.js +3 -4
- package/src/widgets/TreeView.js +0 -1
package/bin/utils/templates.js
CHANGED
|
@@ -710,6 +710,7 @@ export const packageJson = (name) => `{
|
|
|
710
710
|
"type": "module",
|
|
711
711
|
"scripts": {
|
|
712
712
|
"dev": "bash run.sh",
|
|
713
|
+
"check": "node scripts/check-app.mjs",
|
|
713
714
|
"build": "flet-box createBundle www",
|
|
714
715
|
"start": "npm run dev",
|
|
715
716
|
"android:init": "npm run build && cap add android",
|
|
@@ -1177,6 +1178,11 @@ jobs:
|
|
|
1177
1178
|
- name: Install dependencies
|
|
1178
1179
|
run: npm install
|
|
1179
1180
|
|
|
1181
|
+
# Fails the build on an invented widget/prop, a positional widget call or a
|
|
1182
|
+
# foreign-framework import. See AGENTS.md section 4.
|
|
1183
|
+
- name: Check app API usage (npm run check)
|
|
1184
|
+
run: npm run check
|
|
1185
|
+
|
|
1180
1186
|
- name: Bundle web app (flet-box createBundle www)
|
|
1181
1187
|
run: npm run build
|
|
1182
1188
|
|
|
@@ -1322,6 +1328,31 @@ npm install
|
|
|
1322
1328
|
npm run dev
|
|
1323
1329
|
\`\`\`
|
|
1324
1330
|
|
|
1331
|
+
## Working with an AI assistant
|
|
1332
|
+
|
|
1333
|
+
This project ships an in-project agent contract, so an AI editing the app uses the
|
|
1334
|
+
real FletBox API instead of inventing one and burning tokens relearning the
|
|
1335
|
+
framework from scratch:
|
|
1336
|
+
|
|
1337
|
+
- \`AGENTS.md\` — the rules: FletBox syntax only, real widgets and props only, and the
|
|
1338
|
+
verification loop it must run after every edit.
|
|
1339
|
+
- \`.claude/CLAUDE.md\`, \`.cursor/rules/fletbox.mdc\` — short pointers to \`AGENTS.md\` for
|
|
1340
|
+
Claude Code and Cursor.
|
|
1341
|
+
- \`.fletbox/api/cheatsheet.md\` — every widget and its real props, generated from the
|
|
1342
|
+
installed package. This is the lookup an agent reads instead of the framework.
|
|
1343
|
+
- \`.fletbox/memory/\` — notes the agent keeps between sessions, so it does not
|
|
1344
|
+
rediscover the same thing twice.
|
|
1345
|
+
- \`scripts/check-app.mjs\` — the gate that catches hallucinated APIs.
|
|
1346
|
+
|
|
1347
|
+
\`\`\`bash
|
|
1348
|
+
npm run check # scan src/ for invented widgets, props, imports
|
|
1349
|
+
npm run check -- --props Button # print the real props of one widget
|
|
1350
|
+
npm run check -- --list # print every public export
|
|
1351
|
+
\`\`\`
|
|
1352
|
+
|
|
1353
|
+
\`npm run check\` exits non-zero on a real error and also runs in CI. If you upgrade
|
|
1354
|
+
\`flet-box\`, regenerate the cheatsheet with \`flet-box check-api --emit-api\`.
|
|
1355
|
+
|
|
1325
1356
|
## Android
|
|
1326
1357
|
|
|
1327
1358
|
This project is preconfigured for Capacitor. Install Node.js 20.9 or newer,
|
|
@@ -1551,14 +1582,18 @@ export const ProfileScreen = () => {
|
|
|
1551
1582
|
})
|
|
1552
1583
|
});
|
|
1553
1584
|
};
|
|
1585
|
+
|
|
1586
|
+
export default ProfileScreen;
|
|
1554
1587
|
`;
|
|
1555
1588
|
|
|
1556
|
-
// Screens index for easier imports
|
|
1557
|
-
|
|
1589
|
+
// Screens index for easier imports. ProfileScreen only exists in the adaptive
|
|
1590
|
+
// template, so it is re-exported only when that screen file was actually written.
|
|
1591
|
+
export const screensIndexJs = (template) => `// screens/index.js
|
|
1558
1592
|
export { RootScreen } from './RootScreen.js';
|
|
1559
1593
|
export { HomeScreen } from './HomeScreen.js';
|
|
1560
|
-
export { AboutScreen } from './AboutScreen.js'
|
|
1561
|
-
|
|
1594
|
+
export { AboutScreen } from './AboutScreen.js';${
|
|
1595
|
+
template === "adaptive" ? `\nexport { ProfileScreen } from './ProfileScreen.js';` : ""
|
|
1596
|
+
}
|
|
1562
1597
|
`;
|
|
1563
1598
|
|
|
1564
1599
|
// Layout components
|
|
@@ -1587,16 +1622,18 @@ export const AppBarComponent = ({ title, isMobile = false, showBack = false, onB
|
|
|
1587
1622
|
export const bottomNavJs = () => `// components/layouts/BottomNav.js
|
|
1588
1623
|
import { BottomNavigation, colors, goTo } from 'flet-box';
|
|
1589
1624
|
|
|
1590
|
-
export const BottomNav = ({ items = [], currentRoute }) => {
|
|
1625
|
+
export const BottomNav = ({ items = [], currentRoute } = {}) => {
|
|
1591
1626
|
const defaultItems = [
|
|
1592
1627
|
{ icon: 'home', label: 'Home', route: '/home' },
|
|
1593
1628
|
{ icon: 'info', label: 'About', route: '/about' }
|
|
1594
1629
|
];
|
|
1595
1630
|
const navItems = items.length ? items : defaultItems;
|
|
1631
|
+
// BottomNavigation selects a tab by index, not by route.
|
|
1632
|
+
const currentIndex = Math.max(0, navItems.findIndex((item) => item.route === currentRoute));
|
|
1596
1633
|
return BottomNavigation({
|
|
1597
1634
|
items: navItems,
|
|
1598
|
-
|
|
1599
|
-
onTabChange: (
|
|
1635
|
+
currentIndex,
|
|
1636
|
+
onTabChange: (index) => goTo(navItems[index].route),
|
|
1600
1637
|
backgroundColor: colors.surface,
|
|
1601
1638
|
selectedColor: colors.primary
|
|
1602
1639
|
});
|
|
@@ -1606,7 +1643,7 @@ export const BottomNav = ({ items = [], currentRoute }) => {
|
|
|
1606
1643
|
export const sidebarJs = () => `// components/layouts/Sidebar.js
|
|
1607
1644
|
import { Container, Column, Row, Text, Icon, colors, goTo } from 'flet-box';
|
|
1608
1645
|
|
|
1609
|
-
export const Sidebar = ({ items = [], currentRoute }) => {
|
|
1646
|
+
export const Sidebar = ({ items = [], currentRoute } = {}) => {
|
|
1610
1647
|
const defaultItems = [
|
|
1611
1648
|
{ icon: 'dashboard', label: 'Dashboard', route: '/home' },
|
|
1612
1649
|
{ icon: 'info', label: 'About', route: '/about' },
|
|
@@ -1659,7 +1696,7 @@ export const Sidebar = ({ items = [], currentRoute }) => {
|
|
|
1659
1696
|
export const drawerMenuModularJs = () => `// components/layouts/DrawerMenu.js
|
|
1660
1697
|
import { Drawer, DrawerItem, Container, Column, Text, Icon, colors, closeDrawer } from 'flet-box';
|
|
1661
1698
|
|
|
1662
|
-
export const DrawerMenu = ({ items = [] }) => {
|
|
1699
|
+
export const DrawerMenu = ({ items = [] } = {}) => {
|
|
1663
1700
|
const defaultItems = [
|
|
1664
1701
|
{ icon: 'dashboard', label: 'Dashboard', route: '/home' },
|
|
1665
1702
|
{ icon: 'info', label: 'About', route: '/about' },
|
|
@@ -1715,9 +1752,9 @@ import { AppBarComponent } from './components/layouts/AppBarComponent.js';
|
|
|
1715
1752
|
import { DrawerMenu } from './components/layouts/DrawerMenu.js';
|
|
1716
1753
|
|
|
1717
1754
|
// make sure to export all screens in screens/index.js for easier imports
|
|
1718
|
-
import RootScreen from './screens/RootScreen.js';
|
|
1719
|
-
import HomeScreen from './screens/HomeScreen.js';
|
|
1720
|
-
import AboutScreen from './screens/AboutScreen.js';
|
|
1755
|
+
import { RootScreen } from './screens/RootScreen.js';
|
|
1756
|
+
import { HomeScreen } from './screens/HomeScreen.js';
|
|
1757
|
+
import { AboutScreen } from './screens/AboutScreen.js';
|
|
1721
1758
|
|
|
1722
1759
|
const routes = {
|
|
1723
1760
|
'/': RootScreen,
|
|
@@ -1737,15 +1774,15 @@ runApp(MyApp, 'root');
|
|
|
1737
1774
|
|
|
1738
1775
|
export const fullAppJs =
|
|
1739
1776
|
() => `// app.js - Full template (AppBar + Drawer + BottomNav)
|
|
1740
|
-
import { runApp, Scaffold, colors,
|
|
1777
|
+
import { runApp, Scaffold, colors, useState, dimensions } from 'flet-box';
|
|
1741
1778
|
import { AppBarComponent } from './components/layouts/AppBarComponent.js';
|
|
1742
1779
|
import { DrawerMenu } from './components/layouts/DrawerMenu.js';
|
|
1743
1780
|
import { BottomNav } from './components/layouts/BottomNav.js';
|
|
1744
1781
|
|
|
1745
1782
|
// make sure to export all screens in screens/index.js for easier imports
|
|
1746
|
-
import RootScreen from './screens/RootScreen.js';
|
|
1747
|
-
import HomeScreen from './screens/HomeScreen.js';
|
|
1748
|
-
import AboutScreen from './screens/AboutScreen.js';
|
|
1783
|
+
import { RootScreen } from './screens/RootScreen.js';
|
|
1784
|
+
import { HomeScreen } from './screens/HomeScreen.js';
|
|
1785
|
+
import { AboutScreen } from './screens/AboutScreen.js';
|
|
1749
1786
|
|
|
1750
1787
|
const routes = {
|
|
1751
1788
|
'/': RootScreen,
|
|
@@ -1753,9 +1790,15 @@ const routes = {
|
|
|
1753
1790
|
'/about': AboutScreen
|
|
1754
1791
|
};
|
|
1755
1792
|
|
|
1793
|
+
// State is key-based, so the viewport width survives re-renders. One resize
|
|
1794
|
+
// subscription feeds it from module scope: registering it inside the component
|
|
1795
|
+
// would add a listener on every render.
|
|
1796
|
+
const [viewportWidth, setViewportWidth] = useState('viewportWidth', dimensions.getWidth());
|
|
1797
|
+
dimensions.addListener(({ width }) => setViewportWidth(width));
|
|
1798
|
+
|
|
1756
1799
|
const MyApp = () => {
|
|
1757
|
-
|
|
1758
|
-
const isMobile =
|
|
1800
|
+
// Structural read of the key, so crossing the breakpoint re-renders the shell.
|
|
1801
|
+
const isMobile = viewportWidth.valueOf() < 768;
|
|
1759
1802
|
return Scaffold({
|
|
1760
1803
|
routes,
|
|
1761
1804
|
appBar: AppBarComponent({ title: 'FletBox', isMobile }),
|
|
@@ -1783,7 +1826,7 @@ const routes = {
|
|
|
1783
1826
|
const MyApp = () => Scaffold({
|
|
1784
1827
|
routes,
|
|
1785
1828
|
appBar: AppBarComponent({ title: 'FletBox', isMobile: false }),
|
|
1786
|
-
|
|
1829
|
+
leftNavBar: Sidebar(),
|
|
1787
1830
|
backgroundColor: colors.background
|
|
1788
1831
|
});
|
|
1789
1832
|
|
|
@@ -1792,17 +1835,17 @@ runApp(MyApp, 'root');
|
|
|
1792
1835
|
|
|
1793
1836
|
export const adaptiveAppJs =
|
|
1794
1837
|
() => `// app.js - Adaptive template (mobile + desktop)
|
|
1795
|
-
import { runApp, Scaffold, colors,
|
|
1838
|
+
import { runApp, Scaffold, colors, useState, dimensions } from 'flet-box';
|
|
1796
1839
|
import { AppBarComponent } from './components/layouts/AppBarComponent.js';
|
|
1797
1840
|
import { DrawerMenu } from './components/layouts/DrawerMenu.js';
|
|
1798
1841
|
import { BottomNav } from './components/layouts/BottomNav.js';
|
|
1799
1842
|
import { Sidebar } from './components/layouts/Sidebar.js';
|
|
1800
1843
|
|
|
1801
1844
|
// make sure to export all screens in screens/index.js for easier imports
|
|
1802
|
-
import RootScreen from './screens/RootScreen.js';
|
|
1803
|
-
import HomeScreen from './screens/HomeScreen.js';
|
|
1804
|
-
import AboutScreen from './screens/AboutScreen.js';
|
|
1805
|
-
import ProfileScreen from './screens/ProfileScreen.js';
|
|
1845
|
+
import { RootScreen } from './screens/RootScreen.js';
|
|
1846
|
+
import { HomeScreen } from './screens/HomeScreen.js';
|
|
1847
|
+
import { AboutScreen } from './screens/AboutScreen.js';
|
|
1848
|
+
import { ProfileScreen } from './screens/ProfileScreen.js';
|
|
1806
1849
|
|
|
1807
1850
|
const routes = {
|
|
1808
1851
|
'/': RootScreen,
|
|
@@ -1811,18 +1854,314 @@ const routes = {
|
|
|
1811
1854
|
'/profile': ProfileScreen
|
|
1812
1855
|
};
|
|
1813
1856
|
|
|
1857
|
+
// State is key-based, so the viewport width survives re-renders. The resize
|
|
1858
|
+
// subscription is registered once at module scope; doing it inside the component
|
|
1859
|
+
// would add another listener on every render.
|
|
1860
|
+
const [viewportWidth, setViewportWidth] = useState('viewportWidth', dimensions.getWidth());
|
|
1861
|
+
dimensions.addListener(({ width }) => setViewportWidth(width));
|
|
1862
|
+
|
|
1814
1863
|
const MyApp = () => {
|
|
1815
|
-
|
|
1816
|
-
|
|
1864
|
+
// Structural read of the keyed viewport state, so crossing the breakpoint
|
|
1865
|
+
// re-renders the shell (drawer + bottom bar <-> sidebar).
|
|
1866
|
+
const isMobile = viewportWidth.valueOf() < 768;
|
|
1817
1867
|
return Scaffold({
|
|
1818
1868
|
routes,
|
|
1819
1869
|
appBar: AppBarComponent({ title: 'FletBox', isMobile }),
|
|
1820
1870
|
drawer: isMobile ? DrawerMenu() : null,
|
|
1821
1871
|
bottomBar: isMobile ? BottomNav() : null,
|
|
1822
|
-
|
|
1872
|
+
leftNavBar: !isMobile ? Sidebar() : null,
|
|
1823
1873
|
backgroundColor: colors.background
|
|
1824
1874
|
});
|
|
1825
1875
|
};
|
|
1826
1876
|
|
|
1827
1877
|
runApp(MyApp, 'root');
|
|
1828
1878
|
`;
|
|
1879
|
+
|
|
1880
|
+
// ---------------------------------------------------------------------------
|
|
1881
|
+
// AI agent contract
|
|
1882
|
+
// ---------------------------------------------------------------------------
|
|
1883
|
+
// Every generated app ships an in-project agent guide so an AI editing the app
|
|
1884
|
+
// uses real FletBox APIs instead of inventing them. These are built with
|
|
1885
|
+
// array.join() and single-quoted lines: the content is markdown full of fenced
|
|
1886
|
+
// code blocks and backticks, which would break an ordinary template literal.
|
|
1887
|
+
// ---------------------------------------------------------------------------
|
|
1888
|
+
|
|
1889
|
+
export const agentsMd = (appName, template, version) =>
|
|
1890
|
+
[
|
|
1891
|
+
'# AGENTS.md',
|
|
1892
|
+
'',
|
|
1893
|
+
`**App:** \`${appName}\` · **Template:** \`${template}\` · **flet-box@${version}**`,
|
|
1894
|
+
'FletBox is a vanilla-JS UI framework: ESM only, zero runtime dependencies,',
|
|
1895
|
+
'fully offline. There is no build step to learn and no compiler to blame.',
|
|
1896
|
+
'',
|
|
1897
|
+
'This file is the contract. Read it before writing any code in this project.',
|
|
1898
|
+
'You do **not** need to read the whole framework to work here — sections 1 to 3',
|
|
1899
|
+
'tell you where the authoritative answers live, and the checker in section 4',
|
|
1900
|
+
'proves you right or wrong in seconds.',
|
|
1901
|
+
'',
|
|
1902
|
+
'## 1. The API is a fact, not a guess',
|
|
1903
|
+
'',
|
|
1904
|
+
'Never invent a widget, prop, hook or helper. Look it up:',
|
|
1905
|
+
'',
|
|
1906
|
+
'| I want to know… | Look here |',
|
|
1907
|
+
'| --- | --- |',
|
|
1908
|
+
'| Which widgets exist and which props each accepts | `.fletbox/api/cheatsheet.md` (generated, covers every widget) |',
|
|
1909
|
+
'| Everything for one widget | `npm run check -- --props <Widget>` |',
|
|
1910
|
+
'| Prose, types and a worked example | `node_modules/flet-box/docs/widget/<Widget>.md` |',
|
|
1911
|
+
'| Every public export of the package | `npm run check -- --list` |',
|
|
1912
|
+
'',
|
|
1913
|
+
'**If a name is not in those files, it does not exist.** A prop that silently',
|
|
1914
|
+
'does nothing is worse than a compile error: it renders as a broken layout and',
|
|
1915
|
+
'nobody finds it for weeks.',
|
|
1916
|
+
'',
|
|
1917
|
+
'- Do not edit `node_modules/flet-box`. It is a dependency, not source.',
|
|
1918
|
+
'- The cheatsheet is generated output. Regenerate it with',
|
|
1919
|
+
' `flet-box check-api --emit-api` after upgrading the package; never hand-edit it.',
|
|
1920
|
+
'',
|
|
1921
|
+
'## 2. FletBox syntax only — no other framework',
|
|
1922
|
+
'',
|
|
1923
|
+
'A widget is a **function call that returns an HTMLElement**. That is the whole',
|
|
1924
|
+
'model. Do not mix in syntax from another ecosystem:',
|
|
1925
|
+
'',
|
|
1926
|
+
'```js',
|
|
1927
|
+
'// Correct',
|
|
1928
|
+
"Text({ text: 'Hello', size: 18, color: colors.primary })",
|
|
1929
|
+
"Button({ text: 'Save', onPress: save, bgColor: colors.primary })",
|
|
1930
|
+
'Box({ child: Text({ text: count }), padding: 12 })',
|
|
1931
|
+
'```',
|
|
1932
|
+
'',
|
|
1933
|
+
'```js',
|
|
1934
|
+
'// Wrong — never write any of these',
|
|
1935
|
+
'<Text>Hello</Text> // JSX',
|
|
1936
|
+
"h('div', ...) // hyperscript",
|
|
1937
|
+
'Text("Hello") // positional args',
|
|
1938
|
+
"Text('Hello', size: 18) // Dart / Flutter named args",
|
|
1939
|
+
'new Text(...) // classes',
|
|
1940
|
+
"t('div') // Vue template",
|
|
1941
|
+
'```',
|
|
1942
|
+
'',
|
|
1943
|
+
'- No `react`, `vue`, `svelte`, `@angular/core`, `solid-js`, `lit`, `jquery`,',
|
|
1944
|
+
' `tailwindcss` or `bootstrap` imports. This app is offline: nothing installs',
|
|
1945
|
+
' at runtime and nothing fetches a CDN.',
|
|
1946
|
+
'- No `.jsx`, `.tsx`, `.vue` or `.svelte` files. Plain `.js`, ESM imports with a',
|
|
1947
|
+
' explicit `.js` extension.',
|
|
1948
|
+
'- Styling is props (`bgColor`, `radius`, `padding`), not class strings or',
|
|
1949
|
+
' `styled-components`.',
|
|
1950
|
+
'- `child` takes one widget; `children` takes an array or string. Swapping them',
|
|
1951
|
+
' is the most common silent bug.',
|
|
1952
|
+
'- Prop aliases (`bgColor` ≡ `backgroundColor`, `onPress` ≡ `onClick`) are',
|
|
1953
|
+
' intentional. Pick one per file and stay consistent; do not "dedupe" them.',
|
|
1954
|
+
'',
|
|
1955
|
+
'## 3. Architecture the framework expects',
|
|
1956
|
+
'',
|
|
1957
|
+
'```',
|
|
1958
|
+
'app.js -> runApp(App, \'root\')',
|
|
1959
|
+
'screens/ -> one exported widget per route',
|
|
1960
|
+
'components/ -> reusable, stateless unless they own state',
|
|
1961
|
+
'components/layouts/ -> appBar, drawer, sidebar, bottomBar',
|
|
1962
|
+
'```',
|
|
1963
|
+
'',
|
|
1964
|
+
'The app root is `Scaffold`, which owns navigation and chrome. Use it:',
|
|
1965
|
+
'',
|
|
1966
|
+
'```js',
|
|
1967
|
+
'Scaffold({',
|
|
1968
|
+
' routes,',
|
|
1969
|
+
" appBar: AppBarComponent({ title: 'FletBox', isMobile }),",
|
|
1970
|
+
' drawer: isMobile ? DrawerMenu() : null,',
|
|
1971
|
+
' bottomBar: isMobile ? BottomNav() : null,',
|
|
1972
|
+
' leftNavBar: !isMobile ? Sidebar() : null, // NOT `sidebar:`',
|
|
1973
|
+
' leftNavBarWidth: 240,',
|
|
1974
|
+
" fab: isMobile ? ActionButton({ icon: 'add', onPress: open }) : null,",
|
|
1975
|
+
' body: isMobile ? null : HomeScreen(),',
|
|
1976
|
+
'})',
|
|
1977
|
+
'```',
|
|
1978
|
+
'',
|
|
1979
|
+
'Real `Scaffold` props: `appBar body bottomBar fab drawer leftNavBar',
|
|
1980
|
+
'leftNavBarWidth rightNavBar rightNavBarWidth routes basePath backgroundColor',
|
|
1981
|
+
'closeDrawerOnNavigate`. (`--props Scaffold` is authoritative.)',
|
|
1982
|
+
'For a responsive app, branch in JS on the keyed viewport state — `useState`',
|
|
1983
|
+
'seeded from `dimensions.getWidth()` plus one `dimensions.addListener()` at',
|
|
1984
|
+
'module scope — not on CSS alone. See `src/app.js` in this project.',
|
|
1985
|
+
'',
|
|
1986
|
+
'Custom widgets follow the same shape every framework widget uses',
|
|
1987
|
+
'(`QRCode.js` is the reference):',
|
|
1988
|
+
'',
|
|
1989
|
+
'```js',
|
|
1990
|
+
'export const MyWidget = (props = {}) => {',
|
|
1991
|
+
' const widget = WidgetFactory("div", { ...props, widgetName: "MyWidget" });',
|
|
1992
|
+
' // children, then composeUpdate(widget, ...), then return',
|
|
1993
|
+
'};',
|
|
1994
|
+
'```',
|
|
1995
|
+
'',
|
|
1996
|
+
'- Use `composeUpdate()`; never overwrite `container.update`.',
|
|
1997
|
+
'- Register resources (listeners, timers, RAF, observers) and release them in an',
|
|
1998
|
+
' idempotent `onUnmount` disposer. Never monkey-patch `_cleanup`.',
|
|
1999
|
+
'- State is key-based: `useState("key", 0)` plus subscribers. There is no',
|
|
2000
|
+
' component re-render tree to reason about.',
|
|
2001
|
+
'- Reuse the framework utilities (`colors`, `margin`, `padding`, `gradient',
|
|
2002
|
+
' shadow`, `rgba`, `stackPosition`) instead of hand-writing CSS.',
|
|
2003
|
+
'',
|
|
2004
|
+
'One rendering engine exists: `src/widget-factory/`. Never add a second one, and',
|
|
2005
|
+
'never style by reaching around the factory. **If you believe the engine is',
|
|
2006
|
+
'broken, prove it with a minimal repro and record the finding in memory** — do',
|
|
2007
|
+
'not work around it silently.',
|
|
2008
|
+
'',
|
|
2009
|
+
'## 4. Verification loop (required)',
|
|
2010
|
+
'',
|
|
2011
|
+
'After every edit, before you say a task is done:',
|
|
2012
|
+
'',
|
|
2013
|
+
'```bash',
|
|
2014
|
+
'npm run check # static gate: invented widgets/props, positional args,',
|
|
2015
|
+
' # foreign imports, and broken imports between your own',
|
|
2016
|
+
' # modules (a name a file never exports)',
|
|
2017
|
+
'npm run dev # serve on :8000 — check the UI actually renders',
|
|
2018
|
+
'```',
|
|
2019
|
+
'',
|
|
2020
|
+
'`npm run check` exits non-zero on a real error. Fix the code, not the checker.',
|
|
2021
|
+
'It reads the installed package, so it is always in sync with what runs. A',
|
|
2022
|
+
'warning is allowed to stay only when you can justify it in one sentence.',
|
|
2023
|
+
'',
|
|
2024
|
+
'## 5. Memory — do not relearn the same thing',
|
|
2025
|
+
'',
|
|
2026
|
+
'At the start of every session read `.fletbox/memory/MEMORY.md` and any entry it',
|
|
2027
|
+
'points at. That is how this agent remembers the framework between chats instead',
|
|
2028
|
+
'of rediscovering it each time.',
|
|
2029
|
+
'',
|
|
2030
|
+
'- When you learn something non-obvious about this app or the framework, write',
|
|
2031
|
+
' `.fletbox/memory/<kebab-case-topic>.md` with: what (fact or rule), **Why:**',
|
|
2032
|
+
' (the incident or constraint behind it), **How to apply:** (when it kicks in),',
|
|
2033
|
+
' and an absolute date for anything time-bound.',
|
|
2034
|
+
'- Add a one-line pointer to it in `MEMORY.md`. Keep the index short.',
|
|
2035
|
+
'- One entry per topic, under 10 lines. Update or delete an entry that turns out',
|
|
2036
|
+
' to be wrong rather than appending a correction beside it.',
|
|
2037
|
+
'- Commit memory files with the code change they describe.',
|
|
2038
|
+
'',
|
|
2039
|
+
'## 6. Commands',
|
|
2040
|
+
'',
|
|
2041
|
+
'| Command | What it does |',
|
|
2042
|
+
'| --- | --- |',
|
|
2043
|
+
'| `npm run dev` | static dev server on port 8000 |',
|
|
2044
|
+
'| `npm run check` | anti-hallucination gate over `src/` |',
|
|
2045
|
+
'| `npm run check -- --props Button` | print the real props of a widget |',
|
|
2046
|
+
'| `npm run build` | esbuild bundle into `dist/` |',
|
|
2047
|
+
'| `flet-box build android` / `ios` | Capacitor native build |',
|
|
2048
|
+
'| `flet-box run-native android` | compile and launch on device/emulator |',
|
|
2049
|
+
'',
|
|
2050
|
+
'Generated by `flet-box create`. To refresh the guide and cheatsheet after a',
|
|
2051
|
+
'framework upgrade: `flet-box check-api --emit-api`.',
|
|
2052
|
+
'',
|
|
2053
|
+
].join('\n');
|
|
2054
|
+
|
|
2055
|
+
export const claudeMd = () =>
|
|
2056
|
+
[
|
|
2057
|
+
'# CLAUDE.md',
|
|
2058
|
+
'',
|
|
2059
|
+
'The rules for this project live in [AGENTS.md](./AGENTS.md). Read it first and',
|
|
2060
|
+
'follow it exactly — FletBox syntax only, real props only, `npm run check` after',
|
|
2061
|
+
'every edit.',
|
|
2062
|
+
'',
|
|
2063
|
+
'Memory: read `.fletbox/memory/MEMORY.md` before working, and write new entries',
|
|
2064
|
+
'there when you learn something the next session should not rediscover.',
|
|
2065
|
+
'',
|
|
2066
|
+
].join('\n');
|
|
2067
|
+
|
|
2068
|
+
export const cursorRules = () =>
|
|
2069
|
+
[
|
|
2070
|
+
'---',
|
|
2071
|
+
'description: FletBox app rules — real APIs only, no other framework',
|
|
2072
|
+
'globs: "**/*.js"',
|
|
2073
|
+
'alwaysApply: true',
|
|
2074
|
+
'---',
|
|
2075
|
+
'',
|
|
2076
|
+
'This is a FletBox app (vanilla JS, ESM, offline). The full contract is',
|
|
2077
|
+
'[AGENTS.md](../../AGENTS.md); read it before editing.',
|
|
2078
|
+
'',
|
|
2079
|
+
'- Widgets are function calls returning an element: `Text({ text: "Hi", size: 18 })`.',
|
|
2080
|
+
' No JSX, no positional args, no Flutter/Vue/Svelte/React syntax or imports.',
|
|
2081
|
+
'- Before using a widget or prop you have not just seen in',
|
|
2082
|
+
' `.fletbox/api/cheatsheet.md`, look it up: `npm run check -- --props <Widget>`.',
|
|
2083
|
+
' If it is not listed, it does not exist — do not invent it.',
|
|
2084
|
+
'- Run `npm run check` after every change and fix what it reports.',
|
|
2085
|
+
'- Check `.fletbox/memory/MEMORY.md` at session start; record new non-obvious',
|
|
2086
|
+
' findings there instead of re-deriving them.',
|
|
2087
|
+
'',
|
|
2088
|
+
].join('\n');
|
|
2089
|
+
|
|
2090
|
+
export const memoryIndexMd = (appName) =>
|
|
2091
|
+
[
|
|
2092
|
+
'# Memory index',
|
|
2093
|
+
'',
|
|
2094
|
+
`Persistent notes for the AI agent working on \`${appName}\`. One line per entry.`,
|
|
2095
|
+
'Read this at the start of every session; open the files it points at.',
|
|
2096
|
+
'',
|
|
2097
|
+
'- [Project baseline](notes/baseline.md) — template, flet-box version, verified commands',
|
|
2098
|
+
'',
|
|
2099
|
+
].join('\n');
|
|
2100
|
+
|
|
2101
|
+
export const memoryBaselineMd = (appName, template, version) =>
|
|
2102
|
+
[
|
|
2103
|
+
'---',
|
|
2104
|
+
'name: project-baseline',
|
|
2105
|
+
'description: What this app is, which FletBox version it runs, and the commands that are known to work',
|
|
2106
|
+
'metadata:',
|
|
2107
|
+
' type: project',
|
|
2108
|
+
'---',
|
|
2109
|
+
'',
|
|
2110
|
+
`App \`${appName}\` created with \`flet-box create\` using the \`${template}\` template,`,
|
|
2111
|
+
`against \`flet-box@${version}\`. Vanilla JS, ESM, no runtime dependencies, offline.`,
|
|
2112
|
+
'',
|
|
2113
|
+
'**Why:** the agent needs the version and shape of the project up front; API names',
|
|
2114
|
+
'and templates shift between releases, and re-deriving them costs tokens every session.',
|
|
2115
|
+
'',
|
|
2116
|
+
'**How to apply:** trust this for the project layout. For props and widget names,',
|
|
2117
|
+
'trust `.fletbox/api/cheatsheet.md` and `npm run check`, not memory — that file is',
|
|
2118
|
+
'regenerated from the installed package and this one is not.',
|
|
2119
|
+
'',
|
|
2120
|
+
'Verified commands: `npm run dev` (:8000), `npm run check`, `npm run build`.',
|
|
2121
|
+
'',
|
|
2122
|
+
].join('\n');
|
|
2123
|
+
|
|
2124
|
+
export const checkAppScript = (appName) =>
|
|
2125
|
+
[
|
|
2126
|
+
'// scripts/check-app.mjs',
|
|
2127
|
+
`// Anti-hallucination gate for ${appName}: every widget, prop and import in src/`,
|
|
2128
|
+
'// is checked against the flet-box package that is actually installed.',
|
|
2129
|
+
'//',
|
|
2130
|
+
'// npm run check scan src/',
|
|
2131
|
+
'// npm run check -- --props Button print the real props of a widget',
|
|
2132
|
+
'// npm run check -- --list list every public export',
|
|
2133
|
+
'// npm run check -- --json machine-readable findings',
|
|
2134
|
+
'//',
|
|
2135
|
+
'// The package exports map only publishes "." and "./package.json", so the',
|
|
2136
|
+
"// checker's own file is imported by URL rather than by specifier.",
|
|
2137
|
+
'',
|
|
2138
|
+
"import path from 'node:path';",
|
|
2139
|
+
"import { createRequire } from 'node:module';",
|
|
2140
|
+
"import { pathToFileURL } from 'node:url';",
|
|
2141
|
+
'',
|
|
2142
|
+
"const require = createRequire(import.meta.url);",
|
|
2143
|
+
'',
|
|
2144
|
+
'function resolvePackage() {',
|
|
2145
|
+
" const candidates = [path.resolve(process.cwd(), 'node_modules/flet-box')];",
|
|
2146
|
+
' try {',
|
|
2147
|
+
" candidates.push(path.dirname(require.resolve('flet-box/package.json')));",
|
|
2148
|
+
' } catch {',
|
|
2149
|
+
' /* not installed yet */',
|
|
2150
|
+
' }',
|
|
2151
|
+
' for (const dir of candidates) {',
|
|
2152
|
+
" if (require('fs').existsSync(path.join(dir, 'bin/commands/checkApi.js'))) {",
|
|
2153
|
+
' return dir;',
|
|
2154
|
+
' }',
|
|
2155
|
+
' }',
|
|
2156
|
+
" console.error('flet-box package not found. Run: npm install');",
|
|
2157
|
+
' process.exit(1);',
|
|
2158
|
+
'}',
|
|
2159
|
+
'',
|
|
2160
|
+
"const pkgDir = resolvePackage();",
|
|
2161
|
+
"const checker = path.join(pkgDir, 'bin', 'commands', 'checkApi.js');",
|
|
2162
|
+
"const { checkApi } = await import(pathToFileURL(checker).href);",
|
|
2163
|
+
'',
|
|
2164
|
+
"const { exitCode } = await checkApi(process.argv.slice(2));",
|
|
2165
|
+
'process.exit(exitCode);',
|
|
2166
|
+
'',
|
|
2167
|
+
].join('\n');
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Contributing to FletBox
|
|
2
|
+
|
|
3
|
+
Thank you for helping improve FletBox. The project is designed to stay friendly to developers, so contributions should keep the API readable, predictable, and practical.
|
|
4
|
+
|
|
5
|
+
## Project areas
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
src/core/ Runtime, mounting, PWA, and HMR
|
|
9
|
+
src/widgets/ UI widgets
|
|
10
|
+
src/widget-factory/Widget creation and prop processing
|
|
11
|
+
src/navigations/ Scaffold, drawer, tabs, and router
|
|
12
|
+
src/services/ Browser storage and HTTP helpers
|
|
13
|
+
src/tools/ State and general-purpose tools
|
|
14
|
+
src/utils/ Themes, parsing, effects, units, and validation
|
|
15
|
+
bin/ CLI and project generators
|
|
16
|
+
docs/ User and API documentation
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Before changing code
|
|
20
|
+
|
|
21
|
+
1. Read the owning module and its nearest tests or examples.
|
|
22
|
+
2. Check `src/index.js` to understand the public export.
|
|
23
|
+
3. Check `src/index.d.ts` when changing public types.
|
|
24
|
+
4. Search for existing aliases and related widgets.
|
|
25
|
+
5. Keep unrelated refactors out of the change.
|
|
26
|
+
|
|
27
|
+
## Adding a widget
|
|
28
|
+
|
|
29
|
+
A widget should normally include:
|
|
30
|
+
|
|
31
|
+
- A file under `src/widgets/`.
|
|
32
|
+
- An export in `src/widgets/index.js`.
|
|
33
|
+
- An export in `src/index.js`.
|
|
34
|
+
- A prop interface and function declaration in `src/index.d.ts`.
|
|
35
|
+
- Basic, everyday, and full examples.
|
|
36
|
+
- A documentation page under `docs/widget/`.
|
|
37
|
+
- Accessibility notes when it is interactive.
|
|
38
|
+
- Cleanup for timers, listeners, observers, and subscriptions.
|
|
39
|
+
|
|
40
|
+
Use the existing `WidgetFactory` patterns instead of creating a second rendering system.
|
|
41
|
+
|
|
42
|
+
## Adding a prop
|
|
43
|
+
|
|
44
|
+
When adding a public prop:
|
|
45
|
+
|
|
46
|
+
1. Implement it in the widget.
|
|
47
|
+
2. Add its TypeScript type.
|
|
48
|
+
3. Add its default value and behavior documentation.
|
|
49
|
+
4. Add an example that proves the behavior.
|
|
50
|
+
5. Check whether it should have a friendly alias.
|
|
51
|
+
6. Test both the preferred name and aliases when aliases are intentional.
|
|
52
|
+
|
|
53
|
+
FletBox supports developer-friendly aliases, but each alias must have one clear meaning.
|
|
54
|
+
|
|
55
|
+
## Documentation pattern
|
|
56
|
+
|
|
57
|
+
Widget pages follow this structure:
|
|
58
|
+
|
|
59
|
+
1. Overview.
|
|
60
|
+
2. Learn it in one minute.
|
|
61
|
+
3. When to use.
|
|
62
|
+
4. Common props.
|
|
63
|
+
5. Full prop list.
|
|
64
|
+
6. How props work.
|
|
65
|
+
7. Example usage.
|
|
66
|
+
8. Basic, everyday, and full examples.
|
|
67
|
+
9. Common layout and styling examples.
|
|
68
|
+
10. Beginner tips.
|
|
69
|
+
11. Common mistakes.
|
|
70
|
+
12. Behavior notes.
|
|
71
|
+
13. Accessibility.
|
|
72
|
+
14. Related widgets.
|
|
73
|
+
|
|
74
|
+
Regenerate widget documentation with:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python3 scripts/generate_widget_docs.py
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The generator uses `src/index.d.ts` and the supplied snippet library as references.
|
|
81
|
+
|
|
82
|
+
## Runtime and types must agree
|
|
83
|
+
|
|
84
|
+
A public API is not complete until these three surfaces agree:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
runtime implementation
|
|
88
|
+
src/index.js exports
|
|
89
|
+
src/index.d.ts declarations
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If a function is declared but not exported, or exported but not typed, do not ship it as stable: fix the source of truth.
|
|
93
|
+
|
|
94
|
+
`npm test` enforces this with `scripts/check-api-surface.mjs`. It imports `src/index.js` for real, so it fails when a runtime export has no declaration, when a declared value does not exist at runtime, when an `interface`/`type` is not `export`ed (a consumer could not `import type` it), or when the barrel re-exports a name that no longer exists. It also fails when the package stops being importable in Node, so never touch `window` / `document` at module scope — guard with `typeof window !== "undefined"`.
|
|
95
|
+
|
|
96
|
+
## Cleanup rules
|
|
97
|
+
|
|
98
|
+
Components that register resources must release them:
|
|
99
|
+
|
|
100
|
+
- `window` and `document` listeners.
|
|
101
|
+
- Timers and animation frames.
|
|
102
|
+
- `ResizeObserver` and `MutationObserver`.
|
|
103
|
+
- Router subscriptions.
|
|
104
|
+
- Storage subscriptions.
|
|
105
|
+
- WebSocket connections.
|
|
106
|
+
|
|
107
|
+
Use the widget cleanup lifecycle when available.
|
|
108
|
+
|
|
109
|
+
## Validation checklist
|
|
110
|
+
|
|
111
|
+
Before opening a pull request:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm test
|
|
115
|
+
node --check path/to/changed-file.js
|
|
116
|
+
python3 -m py_compile scripts/generate_widget_docs.py
|
|
117
|
+
python3 scripts/generate_widget_docs.py
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`npm test` runs the two static checkers described above — the widget-structure contract (`scripts/check-widgets.mjs`) and the API surface coupling (`scripts/check-api-surface.mjs`) — and must pass before a pull request.
|
|
121
|
+
|
|
122
|
+
The behavioral tests are dependency-free browser harnesses in `tests/`. Serve the repo with the dev server (`npm run dev` or `node bin/cli.js run-spa`), then open:
|
|
123
|
+
|
|
124
|
+
- `/tests/index.html` — widget-factory regression contract
|
|
125
|
+
- `/tests/smoke.html` — instantiates every widget
|
|
126
|
+
- `/tests/doccheck.html` — chapters 1-2 documented behavior
|
|
127
|
+
- `/tests/doccheck-all.html` — chapters 3-7 documented behavior
|
|
128
|
+
- `/tests/propscan.html` — custom-prop scanner
|
|
129
|
+
|
|
130
|
+
Each harness writes a global (`window.__TESTS`, `window.__SMOKE`, `window.__DOCCHECK`) and must pass with 0 failures.
|
|
131
|
+
|
|
132
|
+
Also verify the feature in a real browser when it changes DOM behavior. For build changes, run:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For CLI changes, test:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
node bin/cli.js --help
|
|
142
|
+
node bin/cli.js --version
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Pull request guidance
|
|
146
|
+
|
|
147
|
+
A good pull request explains:
|
|
148
|
+
|
|
149
|
+
- What user problem it solves.
|
|
150
|
+
- Which files changed.
|
|
151
|
+
- Which public API changed.
|
|
152
|
+
- Whether types and docs were updated.
|
|
153
|
+
- Which commands or tests were run.
|
|
154
|
+
- Any remaining compatibility or migration concern.
|
|
155
|
+
|
|
156
|
+
Avoid committing generated output, secrets, local databases, APKs, logs, or credentials.
|