flet-box 1.1.1 → 1.1.2

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 CHANGED
@@ -38,21 +38,24 @@ Real DOM · No Virtual DOM · No build config to learn.
38
38
 
39
39
  ## Installation
40
40
 
41
- FletBox isn't on the npm registry, so clone it and link the CLI locally:
41
+ FletBox is on npm, and it drags nothing in with it — `"dependencies": {}`:
42
42
 
43
43
  ```bash
44
- brew install node # macOS/Linux: ensure Node >= 18
45
-
46
- git clone https://github.com/kuko53348/flet-box.git
47
- cd flet-box
48
- npm link # link the flet-box CLI globally
44
+ npm install -g flet-box # the CLI, once (Node >= 20.9)
49
45
 
50
46
  flet-box create appName # scaffold your first app
51
47
  cd appName
52
- npm link flet-box # link FletBox into the new project
53
- flet-box run-spa # run as SPA with hot reload
48
+ npm install # pulls flet-box itself from the registry
49
+ flet-box run # dev server with hot reload
54
50
  ```
55
51
 
52
+ Nothing to install at all: `npx flet-box create appName` runs the same scaffolder
53
+ straight from the registry, and the project it writes declares `flet-box` as a
54
+ normal dependency.
55
+
56
+ Cloning the repository and `npm link`ing it is still how you *develop the framework
57
+ itself* — see [CONTRIBUTING](docs/CONTRIBUTING.md).
58
+
56
59
  ## Quick start
57
60
 
58
61
  ```javascript
@@ -80,7 +83,7 @@ runApp(App);
80
83
  ## Features
81
84
 
82
85
  - Declarative, widget-based UI · reactive state with `useState` · router for SPAs
83
- - Zero runtime dependencies; ESM only, Node >= 18 · real DOM, no Virtual DOM
86
+ - Zero runtime dependencies; ESM only, Node >= 20.9 · real DOM, no Virtual DOM
84
87
  - Local storage, HTTP services, theme, PWA, and production builds
85
88
  - Android & iOS from the CLI (APK, simulator `.app`, `.xcarchive`)
86
89
  - Utility API for layout, text, color, animation, and more
@@ -137,12 +140,11 @@ invent against.
137
140
 
138
141
  ## Android and iOS
139
142
 
140
- `flet-box create` ships the Capacitor config, so the same SPA builds for both platforms. FletBox isn't on the npm registry — link it before installing:
143
+ `flet-box create` ships the Capacitor config, so the same SPA builds for both platforms. The generated project already declares `flet-box` as a dependency, so one install covers everything:
141
144
 
142
145
  ```bash
143
146
  flet-box create my-app && cd my-app
144
- npm link flet-box # or: npm install ../path/to/flet-box
145
- npm install # Capacitor CLI, sharp, platform package
147
+ npm install # flet-box from the registry, plus the Capacitor toolchain
146
148
  ```
147
149
 
148
150
  - **Android** — Node >= 20.9, a JDK, and the Android SDK. `flet-box build android` → debug APK; `flet-box build android --release [--aab]` → unsigned release artifact to sign yourself (`apksigner` / Play App Signing).
@@ -160,6 +162,8 @@ They are **one continuous book** — the cover maps every page: [The FletBox Boo
160
162
 
161
163
  Rather than read, click: **[the live widget gallery](https://kuko53348.github.io/flet-box-widgets-exemples/)** gives every widget its own page, renders each example in the browser it was written for, and prints the exact call underneath it. The theme toggle in the corner shows the same screen in light and dark, which is the honest way to judge a default color.
162
164
 
165
+ And the book itself is an app: **[`flet-box-docs`](https://www.npmjs.com/package/flet-box-docs)** (`npm install flet-box-docs`) renders these same pages with the framework's own `Markdown` widget, with an index, a theme toggle, an offline cache and Android/iOS wrappers.
166
+
163
167
  - [Your first app](docs/guides/first-app.md) — build a working todo app in 20 minutes
164
168
  - [The widget book](docs/widget/README.md) — every widget with property tables and examples
165
169
  - [State, Router & Services](docs/guides/state.md) — shared state, routing, storage, and HTTP
@@ -546,7 +546,10 @@ export const createProject = async (projectName, rawArgs = []) => {
546
546
  console.log(
547
547
  c(
548
548
  "gray",
549
- `⬆️ OTA: placeholder set — edit fletbox.config.json and replace TU-USUARIO/TU-REPO with your public GitHub repo.\n`,
549
+ `⬆️ OTA: ${templates.OTA_PLACEHOLDER_URL} is a placeholder, and the build knows it —\n` +
550
+ ` it derives the real Pages address from the project's \`repository\`/git remote.\n` +
551
+ ` So: push this folder to a public GitHub repo and build, or name the repo now\n` +
552
+ ` with ${c("brightCyan", "flet-box create <name> --repo user/repo")} / edit fletbox.config.json.\n`,
550
553
  ),
551
554
  );
552
555
  }
@@ -17,7 +17,7 @@ import fs from "fs";
17
17
  import path from "path";
18
18
  import { fileURLToPath } from "url";
19
19
  import { c, banner, gradient, section } from "../utils/colors.js";
20
- import { buildBundleConfig, collectAssets, getGitRemoteUrl, resolveAssetPatterns, absoluteShareTags } from "../utils/bundleConfig.js";
20
+ import { buildBundleConfig, collectAssets, getGitRemoteUrl, resolveAssetPatterns, absoluteShareTags, declaredUpdateUrl, isPlaceholderUpdateUrl } from "../utils/bundleConfig.js";
21
21
  import { normaliseManifest, writeSpaFallback } from "../utils/pwaShell.js";
22
22
  import { INITIAL_VERSION, incrementVersion, isValidVersion } from "../utils/version.js";
23
23
 
@@ -342,12 +342,24 @@ export const createBundle = async (targetArg) => {
342
342
  c("gray", " (or set `update.url` in fletbox.config.json / `repository` in package.json)") +
343
343
  "\n",
344
344
  );
345
- } else if (/TU-USUARIO|TU-REPO/.test(bundleConfig.update.url)) {
345
+ } else if (isPlaceholderUpdateUrl(declaredUpdateUrl(projectRoot))) {
346
+ // The file still says TU-USUARIO/TU-REPO, so this address was derived from
347
+ // the project's own repository — correct for a GitHub-hosted app, and worth
348
+ // saying out loud because the file the developer will read disagrees with it.
346
349
  console.log(
347
- c(
348
- "yellow",
349
- "⚠️ update.url is still the placeholder — OTA points at a fake site. Replace TU-USUARIO/TU-REPO in fletbox.config.json before releasing.",
350
- ),
350
+ c("green", "⬆️ OTA source: ") +
351
+ c("gray", bundleConfig.update.url) +
352
+ "\n" +
353
+ c(
354
+ "yellow",
355
+ " derived from the project's `repository`/git remote: `fletbox.config.json` still holds the placeholder.",
356
+ ) +
357
+ "\n" +
358
+ c(
359
+ "gray",
360
+ " Correct for a GitHub Pages site. Set `update.url` by hand if this app will live on another host.",
361
+ ) +
362
+ "\n",
351
363
  );
352
364
  } else {
353
365
  console.log(
@@ -9,7 +9,8 @@
9
9
  * comparison that drives the OTA is the `hash` — the version is a human label.
10
10
  *
11
11
  * Update source URL resolution order:
12
- * 1. `update.url` in `fletbox.config.json` (explicit wins)
12
+ * 1. `update.url` in `fletbox.config.json`, when it is set and is no longer
13
+ * the scaffold placeholder (explicit wins over the derivation below)
13
14
  * 2. derived from the `repository` field of the project's package.json,
14
15
  * mapped to its GitHub Pages URL: https://<user>.github.io/<repo>/
15
16
  * 3. derived from the CURRENT PROJECT's git remote `origin` (when it is a
@@ -69,6 +70,23 @@ export const parseGithubRepo = (repository) => {
69
70
  return null;
70
71
  };
71
72
 
73
+ /**
74
+ * True when a declared `update.url` is still the scaffold placeholder — the two
75
+ * tokens `flet-box create` writes when it was not told where the app will live
76
+ * (`OTA_PLACEHOLDER_URL` in `templates.js`, and the same tokens in the generated
77
+ * workflow header).
78
+ *
79
+ * A placeholder is not a decision, so it must not outrank derivation: a bundle
80
+ * that carries it points an installed app at a host that does not exist, and the
81
+ * OTA check fails on every launch in silence — OTA is built never to break the
82
+ * app, so the only symptom is that an update is never offered.
83
+ *
84
+ * @param {*} url
85
+ * @returns {boolean}
86
+ */
87
+ export const isPlaceholderUpdateUrl = (url) =>
88
+ typeof url === "string" && /TU-USUARIO|TU-REPO/.test(url);
89
+
72
90
  /**
73
91
  * Resolves the OTA update URL for a project.
74
92
  *
@@ -82,9 +100,15 @@ export const resolveUpdateUrl = (projectRoot) => {
82
100
  {},
83
101
  );
84
102
 
85
- // 1. Explicit configuration always wins.
103
+ // 1. Explicit configuration wins — over the derivations below, not over the
104
+ // truth. An unfilled placeholder falls through, so a project whose origin
105
+ // remote is a GitHub repo gets its real Pages address without editing a file.
86
106
  const explicit = fletboxConfig?.update?.url;
87
- if (typeof explicit === "string" && explicit.trim()) {
107
+ if (
108
+ typeof explicit === "string" &&
109
+ explicit.trim() &&
110
+ !isPlaceholderUpdateUrl(explicit)
111
+ ) {
88
112
  return explicit.endsWith("/") ? explicit : `${explicit}/`;
89
113
  }
90
114
 
@@ -107,6 +131,26 @@ export const resolveUpdateUrl = (projectRoot) => {
107
131
  return null;
108
132
  };
109
133
 
134
+ /**
135
+ * Reads only what the project itself declares as its update source, without
136
+ * resolving or deriving anything.
137
+ *
138
+ * The build prints this when the URL that landed in the manifest came from
139
+ * derivation instead of from the file: a bundle pointing at a real site while
140
+ * `fletbox.config.json` still holds the placeholder is a half-filled config, and
141
+ * only the person editing that file can tell the two apart.
142
+ *
143
+ * @param {string} projectRoot - Absolute path to the project root.
144
+ * @returns {*} The declared `update.url`, or null when the file does not say.
145
+ */
146
+ export const declaredUpdateUrl = (projectRoot) => {
147
+ const declared = readJsonSafe(
148
+ path.join(projectRoot, "fletbox.config.json"),
149
+ {},
150
+ )?.update?.url;
151
+ return typeof declared === "string" && declared.trim() ? declared : null;
152
+ };
153
+
110
154
  /**
111
155
  * The `<meta>` tags a social scraper resolves against its own machine.
112
156
  *
@@ -1296,14 +1296,21 @@ export const githubWorkflow = (appName = "FletBox App", siteUrl = null) => {
1296
1296
  const site = /^https?:\/\//i.test(String(siteUrl || ""))
1297
1297
  ? String(siteUrl).replace(/\/?$/, "/")
1298
1298
  : `https://TU-USUARIO.github.io/${slug}/`;
1299
- // Only when the owner is still a guess: the address lives in two files, and a
1300
- // developer who edits one and not the other ships an app that polls a site
1301
- // that is not where the bundle is.
1299
+ // Only when the owner is still a guess. The build no longer trusts the
1300
+ // placeholder in fletbox.config.json (createBundle derives the real Pages
1301
+ // address from this repository), so what is left to correct here is the
1302
+ // address a human reads before reading a single step.
1302
1303
  const todo = site.includes("TU-USUARIO")
1303
1304
  ? `# The owner above is a placeholder: this project was created without \`--repo\`,
1304
- # so the folder name is only a guess at the repo name. Write the real address in
1305
- # both files that state it — this header, and fletbox.config.json (update.url),
1306
- # which is the URL an installed app polls for an update.
1305
+ # so the folder name is only a guess at the repo name. Put the real owner here
1306
+ # once the repository exists.
1307
+ #
1308
+ # \`fletbox.config.json\` may keep its own placeholder: the build recognises it as
1309
+ # "not configured" and derives the address from this repo's repository field or
1310
+ # git remote, so an installed app polls the site this workflow deploys. The URL
1311
+ # that landed in the manifest is printed by the build and by the
1312
+ # \`Where this lives\` step below. Set \`update.url\` by hand only when this app
1313
+ # will live on another host.
1307
1314
  `
1308
1315
  : "";
1309
1316
  return `name: Deploy ${appName}
@@ -1779,9 +1786,12 @@ Two workflow files ship with the project, and they answer to different triggers:
1779
1786
  The address this project publishes to is written at the top of
1780
1787
  \`.github/workflows/build.yml\`. \`flet-box create <name> --repo <user>/<repo>\`
1781
1788
  fills it in from the repo; without \`--repo\` it stays
1782
- \`https://TU-USUARIO.github.io/<name>/\`, and the header says which two files to
1783
- edit. That is a sub-path, so \`index.html\` uses relative asset paths and works
1784
- without changes.
1789
+ \`https://TU-USUARIO.github.io/<name>/\`, and the header explains what is still a
1790
+ guess. The build ignores that placeholder in \`fletbox.config.json\` and derives
1791
+ the real address from this repository, so the OTA feed is correct either way.
1792
+
1793
+ The Pages address is a sub-path, so \`index.html\` uses relative asset paths and
1794
+ works without changes.
1785
1795
 
1786
1796
  **Why a push does not update anyone's app:** this same address is the OTA feed
1787
1797
  installed apps poll, and the update check compares bundle **hashes**, not version
@@ -1871,9 +1881,10 @@ updates:
1871
1881
  \`\`\`
1872
1882
 
1873
1883
  - The GitHub repository **must be public**.
1874
- - Replace \`TU-USUARIO\` with your GitHub username and \`TU-REPO\` with the
1875
- repository name, or set \`update.url\` to any HTTPS host that serves your
1876
- \`config.json\` + bundle.
1884
+ - \`TU-USUARIO\`/\`TU-REPO\` are a placeholder, not a setting: the build sees them as
1885
+ "not configured" and derives the Pages address from this project's \`repository\`
1886
+ field or git remote, so pushing the folder is enough. Edit \`update.url\` only
1887
+ when the app lives on another HTTPS host that serves your \`config.json\` + bundle.
1877
1888
  - Enable GitHub Pages: **Settings → Pages → Source: GitHub Actions**. Without
1878
1889
  that the deploy step is green and no site exists.
1879
1890
  - Cut a release with \`flet-box release --push\` (bumps the version, rebuilds
package/docs/README.md CHANGED
@@ -71,4 +71,25 @@ Front cover (this page)
71
71
 
72
72
  - [Server toolkit](server/README.md) — Express/SQLite/Redis backend helpers (separate toolkit).
73
73
  - [Architecture](arquitectura.md) — how the framework is built inside.
74
- - [Contributing](CONTRIBUTING.md) — for people who add code or docs to FletBox.
74
+ - [Contributing](CONTRIBUTING.md) — for people who add code or docs to FletBox.
75
+
76
+ ## Read this book as an app
77
+
78
+ The same pages are also a FletBox application: **[flet-box-docs](https://github.com/kuko53348/flet-box-docs)**, published to npm as its own package.
79
+
80
+ ```bash
81
+ npm install flet-box-docs # then: cd node_modules/flet-box-docs && npm install && npm run dev
82
+ ```
83
+
84
+ It is a normal FletBox project — the router, a categorized index, cross-document
85
+ Previous/Next, a light/dark toggle, an offline cache, and Capacitor wrappers for
86
+ Android and iOS — and every page is rendered by the framework's own `Markdown`
87
+ widget, so the book is also a live demo of the widget that writes it.
88
+
89
+ Its `docs/` folder is a **snapshot** of this folder, refreshed before each
90
+ release; when the app is online it reads the published source instead, so a page
91
+ added here arrives on an installed device without a new build. That is why
92
+ `flet-box-docs` carries a version of its own: it is a shipping app, and its
93
+ `npm run build` refuses to bundle until `src/constants.js` states both versions
94
+ out loud — the app's own, which must match its `package.json`, and the framework
95
+ release its pages describe.
@@ -21,14 +21,17 @@ npm --version
21
21
 
22
22
  ## Install FletBox
23
23
 
24
- Inside the FletBox package directory:
24
+ FletBox is published to npm, so the CLI is one install away:
25
25
 
26
26
  ```bash
27
- npm install
28
- npm link
27
+ npm install -g flet-box
29
28
  ```
30
29
 
31
- After linking, the `flet-box` command is available globally on your computer.
30
+ Or run it without installing anything globally — `npx` fetches the same package:
31
+
32
+ ```bash
33
+ npx flet-box create my-app
34
+ ```
32
35
 
33
36
  Check that it works:
34
37
 
@@ -37,12 +40,22 @@ flet-box --version
37
40
  flet-box --help
38
41
  ```
39
42
 
40
- When using the published package, install it in a project with:
43
+ Inside a project the framework itself is an ordinary dependency:
41
44
 
42
45
  ```bash
43
46
  npm install flet-box
44
47
  ```
45
48
 
49
+ Cloning the repository and linking it is the exception, and it is for people
50
+ editing the framework:
51
+
52
+ ```bash
53
+ git clone https://github.com/kuko53348/flet-box.git
54
+ cd flet-box
55
+ npm install
56
+ npm link # the `flet-box` command now points at this checkout
57
+ ```
58
+
46
59
  ## Your first project
47
60
 
48
61
  Create a project with the default template:
@@ -84,9 +97,12 @@ CI: .github/workflows/build.yml publishes the web to GitHub Pages on every
84
97
  ```
85
98
 
86
99
  The first positional argument is always the project name; a flag that arrives
87
- before it is read as the name. Without `--repo` nothing is invented — the
88
- placeholder stays in both files and the workflow says which two to edit. The
89
- reason the workflow log, not the header, is the authority on the live address is
100
+ before it is read as the name. Without `--repo` nothing is invented: the
101
+ placeholder stays in `fletbox.config.json` and in the workflow header, and the
102
+ header says which part of it is still a guess. `flet-box createBundle` reads that
103
+ placeholder as "not configured" and derives the real Pages address from the
104
+ project, so the manifest is never aimed at a host that does not exist. The reason
105
+ the workflow log, not the header, is the authority on the live address is
90
106
  explained in [The Pages address of a generated app](../guides/online-updates.md).
91
107
 
92
108
  ## Project templates
@@ -636,9 +652,17 @@ For normal application development, `npm install flet-box` is usually enough.
636
652
 
637
653
  ### `flet-box: command not found`
638
654
 
639
- The CLI is not linked or installed globally. From the FletBox package directory, run:
655
+ The CLI is not installed globally. Install the published package:
656
+
657
+ ```bash
658
+ npm install -g flet-box
659
+ ```
660
+
661
+ If you are working from a clone of the framework instead, link that checkout so the
662
+ command points at the code you are editing:
640
663
 
641
664
  ```bash
665
+ cd flet-box
642
666
  npm install
643
667
  npm link
644
668
  ```
@@ -4,24 +4,23 @@ This page is for **absolute beginners**. You do not need to know any trick — y
4
4
 
5
5
  ## Before you start
6
6
 
7
- FletBox is not published to the npm registry, so first you install the CLI once and link it globally:
7
+ You need Node.js 20.9 or newer and one command, because FletBox is published on npm:
8
8
 
9
9
  ```bash
10
- git clone https://github.com/kuko53348/flet-box.git
11
- cd flet-box
12
- npm link # link the flet-box CLI globally
10
+ npm install -g flet-box # the CLI, once
13
11
  ```
14
12
 
15
- Then create an empty project, link the framework into it, and start the dev server:
13
+ Then create an empty project, install the framework into it, and start the dev server:
16
14
 
17
15
  ```bash
18
- cd ..
19
16
  flet-box create my-app
20
17
  cd my-app
21
- npm link flet-box # or: npm install ../path/to/flet-box
18
+ npm install # flet-box itself, from the registry
22
19
  npm run dev
23
20
  ```
24
21
 
22
+ If you would rather not install anything globally, `npx flet-box create my-app` does the same thing.
23
+
25
24
  Open your browser at `http://localhost:8000`. You should see "Hello FletBox" (or a small welcome screen). If you do, everything is working.
26
25
 
27
26
  ## Step 1: the smallest app
@@ -116,7 +116,7 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
116
116
 
117
117
  `update.url` is resolved automatically, in this order:
118
118
 
119
- 1. `update.url` in `fletbox.config.json` at your project root (explicit wins).
119
+ 1. `update.url` in `fletbox.config.json` at your project root (explicit wins) — unless it is still the scaffold placeholder, which is not a choice. See [The Pages address of a generated app](#the-pages-address-of-a-generated-app).
120
120
  2. Derived from the `repository` field of `package.json`, mapped to GitHub Pages: `https://<user>.github.io/<repo>/`.
121
121
  3. Derived from the **current project's** git remote `origin` (when it is a GitHub URL). This means `flet-box createBundle` always points at the project it is built in, never at the flet-box framework installed as a dependency.
122
122
  4. If none of these exist, OTA is **disabled** and the bundle simply runs what shipped in it.
@@ -152,10 +152,22 @@ things at once:
152
152
  - writes that address into the header of the generated `.github/workflows/build.yml`;
153
153
  - prints it in the closing summary, with the one-time setting Pages needs.
154
154
 
155
- Without `--repo` nothing is invented. `fletbox.config.json` keeps the generic
156
- placeholder `https://TU-USUARIO.github.io/TU-REPO/`, the workflow header names a
157
- guess built from the folder name, and the workflow says so in its own comment:
158
- write the real address in both files that state it.
155
+ Without `--repo` nothing is invented at scaffold time. `fletbox.config.json` keeps
156
+ the generic placeholder `https://TU-USUARIO.github.io/TU-REPO/`, the workflow
157
+ header names a guess built from the folder name, and the workflow says in its own
158
+ comment which part of that address still has to be checked by hand.
159
+
160
+ The build does not trust that placeholder. `TU-USUARIO` and `TU-REPO` mean "not
161
+ configured yet", so `flet-box createBundle` falls through to rules 2 and 3 and
162
+ writes the address it derived — and prints that it derived, because a manifest
163
+ pointing at a real site next to a file that still says `TU-REPO` is a half-filled
164
+ config you should either finish or leave alone. A project with no GitHub remote
165
+ yet gets `update.url: null`: OTA is off, the bundle runs what shipped in it, and
166
+ nothing polls a host that does not exist.
167
+
168
+ That matters because the failure used to be silent. The app compares bundle
169
+ **hashes**, and OTA is built never to break the app, so a placeholder URL that
170
+ reached a bundle produced no error, no dialog and no update — ever.
159
171
 
160
172
  **The workflow log is the authority.** The header names the address you typed at
161
173
  scaffold time; the `Where this lives` step prints the address the run *actually*
@@ -274,7 +286,7 @@ Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings →
274
286
  | Symptom | Likely cause |
275
287
  | --- | --- |
276
288
  | Build prints "THIS APP DOES NOT SUPPORT OTA UPDATES" | No public GitHub repo is reachable from the project (no `origin` remote, a non-GitHub remote, or nothing configured). The app still works — it runs the bundle it shipped with. Create the repo, push it, and rebuild. |
277
- | No dialog, app runs old code | `update.url` missing/incorrect, or no `v*` tag was published — a normal push updates the site but deliberately leaves the manifest out, so there is nothing to compare against. |
289
+ | No dialog, app runs old code | `update.url` wrong — a real address that is not where the bundle is (the scaffold placeholder is not one of those: the build derives the address and prints it). Or no `v*` tag was published — a normal push updates the site but deliberately leaves the manifest out, so there is nothing to compare against. |
278
290
  | `hash mismatch` in console | The published `config.json` and bundle are from different builds. Re-run `flet-box release` so both are published together. |
279
291
  | Dialog appears but download fails | The Pages site is not reachable/CORS-blocked from the app origin. Pages serves with permissive headers; check you are not behind a proxy that strips them. |
280
292
  | Update applies but reverts on next launch | Cache Storage unavailable (private browsing). The bundle runs once but cannot persist. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flet-box",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Declarative, zero-dependency UI framework in vanilla JS — ship web, PWA, Android and iOS from one codebase",
5
5
  "type": "module",
6
6
  "main": "src/index.js",