@webjsdev/cli 0.10.61 → 0.10.62

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/lib/create.js CHANGED
@@ -1303,6 +1303,18 @@ ${uiThemeRaw}
1303
1303
  --text-h1: clamp(2rem, 1.5rem + 1.6vw, 2.85rem);
1304
1304
  --text-h2: clamp(1.35rem, 1.15rem + 0.7vw, 1.7rem);
1305
1305
  --text-lede: clamp(1.05rem, 0.95rem + 0.3vw, 1.2rem);
1306
+ /* Each size carries its line height (and, for the large steps, its
1307
+ tracking). Tailwind v4's text-* utility sets
1308
+ line-height: var(--tw-leading, var(--text-<name>--line-height)), so a
1309
+ size with no companion leaves an invalid value and the heading falls
1310
+ back to the body's 1.6, opening huge gaps between wrapped lines.
1311
+ A leading-* / tracking-* class on the element still wins. */
1312
+ --text-display--line-height: 1.04;
1313
+ --text-display--letter-spacing: -0.025em;
1314
+ --text-h1--line-height: 1.1;
1315
+ --text-h1--letter-spacing: -0.02em;
1316
+ --text-h2--line-height: 1.2;
1317
+ --text-lede--line-height: 1.55;
1306
1318
  --duration-fast: 140ms;
1307
1319
  --duration-slow: 380ms;
1308
1320
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.61",
3
+ "version": "0.10.62",
4
4
  "type": "module",
5
5
  "description": "The CLI for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Runs the dev and production servers, scaffolds apps, validates conventions, and drives the database. Node 24+ or Bun.",
6
6
  "bin": {
@@ -185,9 +185,9 @@ For an array-typed prop pass `Array`, not `Object` (`array-prop-uses-array-type`
185
185
 
186
186
  **A `reflect: true` property holding a FUNCTION drops its attribute instead of writing one, and so does one holding an array that carries a function, unless the prop is `Object` or `Array` typed.** A function has no HTML attribute representation, and the serializations it would otherwise get are both useless and dangerous. `String(fn)` is the function's SOURCE, so a reflected `'use server'` action would ship its whole body, closure secrets included, to every visitor, and `JSON.stringify(fn)` is `undefined`, which lands in the attribute as the literal four-character string. So the reflection path treats a function like `null`, removes the attribute, and warns naming the property, the tag, and the attribute. This holds on both sides, since SSR and the client-side setter run the same path, and it holds for every property name (the leak was never specific to one called `action`). Two exceptions. A property with a custom `converter.toAttribute` runs that converter first and is left alone, because an author who writes one has taken responsibility for serializing whatever they are handed. And an `Object` or `Array` typed property CARRYING a function keeps its data, because `JSON.stringify` drops the function to `null` and omits the key, so `[1, 2, fn]` reflects as `[1,2,null]` with no source and nothing else lost. If you need a function on a component, use a plain property or a signal and do not mark it `reflect`.
187
187
 
188
- **An `Object` or `Array` typed reflected property whose value `JSON.stringify` cannot serialize AT ALL drops its attribute the same way, and warns.** Three shapes do this: a cycle (an object or array that reaches itself, which arrives from a parent/child graph, a linked node, a memo table, or anything a library hands back with a back-reference), a `BigInt` anywhere inside the value, and an author `toJSON()` that throws. The line to keep straight is that a value which serializes WITH A GAP in it keeps its data (the carried-function case above), while one that does not serialize at all has no string to put in the attribute and so has no attribute representation, exactly like a function. The property itself is untouched and still holds the value; only the attribute goes. Before this guard the throw escaped reflection entirely, which meant a client upgrade threw before the component's first render, and an SSR render was swallowed by per-component error isolation, which shows an error box in dev and renders the component EMPTY on a page that still returned 200 in production. To reflect something about a graph-shaped value, reflect a derived scalar (an id, a count) and keep the graph on a non-reflected property. On the read side an attribute that is PRESENT but not parseable JSON reads back as `null` rather than as the raw string, on both the SSR and the client reader. An ABSENT attribute is a different case: neither reader sees it, so the property keeps its constructor value. The two readers also see the same attribute SET, not merely the same fallback: a `state: true` prop, a camelCase attribute name, and an attribute matching no declared property are all ignored by both, and both are handed a value whose HTML character references are already decoded.
188
+ **An `Object` or `Array` typed reflected property whose value `JSON.stringify` cannot serialize AT ALL drops its attribute the same way, and warns.** Three shapes do this: a cycle (an object or array that reaches itself, which arrives from a parent/child graph, a linked node, a memo table, or anything a library hands back with a back-reference), a `BigInt` anywhere inside the value, and an author `toJSON()` that throws. The line to keep straight is that a value which serializes WITH A GAP in it keeps its data (the carried-function case above), while one that does not serialize at all has no string to put in the attribute and so has no attribute representation, exactly like a function. The property itself is untouched and still holds the value; only the attribute goes. Before this guard the throw escaped reflection entirely, which meant a client upgrade threw before the component's first render, and an SSR render was swallowed by per-component error isolation, which shows an error box in dev and renders the component EMPTY on a page that still returned 200 in production. To reflect something about a graph-shaped value, reflect a derived scalar (an id, a count) and keep the graph on a non-reflected property. On the read side an attribute that is PRESENT but not parseable JSON reads back as `null` rather than as the raw string, on both the SSR and the client reader. An ABSENT attribute is a different case: neither reader sees it, so the property keeps its constructor value. The two readers also see the same attribute SET, not merely the same fallback: a `state: true` prop and an attribute matching no declared property are ignored by both, and a camelCase attribute name reaches its prop on both through the lowercased alias, and both are handed a value whose HTML character references are already decoded.
189
189
 
190
- **Writing attributes in markup.** Names are case-insensitive and the browser lowercases them while parsing, so write kebab-case (`user-name`); a camelCase attribute (`userName="…"`) reaches no property on either side. A prop that renames its attribute answers to the new name ONLY, so `open: prop(Boolean, { attribute: 'is-open' })` is written `<my-el is-open>` and `<my-el open>` reaches nothing. Character references are decoded before the value is coerced, so `cfg="&#123;&quot;a&quot;:1&#125;"` parses as the object it spells and `label="Tom &amp; Jerry"` is `Tom & Jerry`; the legacy semicolon-less forms decode exactly where a browser decodes them (`&nbsp` at the end of a value is a non-breaking space, `&nbspx` and `&nbsp=x` stay literal), and writing the semicolon avoids the question. A `state: true` prop takes an SSR value only through a `.prop=${value}` binding from the parent template, never from an attribute.
190
+ **Writing attributes in markup.** Names are case-insensitive and the browser lowercases them while parsing, so a camelCase prop answers to BOTH its kebab-case name (`user-name`, the one reflection writes) and its lowercased name (`username`, lit's default), which is what a camelCase attribute in markup (`userName="…"`) arrives as; all three spellings reach `userName` on both sides. Prefer kebab-case. A prop that renames its attribute answers to the new name ONLY, so `open: prop(Boolean, { attribute: 'is-open' })` is written `<my-el is-open>` and `<my-el open>` reaches nothing. Character references are decoded before the value is coerced, so `cfg="&#123;&quot;a&quot;:1&#125;"` parses as the object it spells and `label="Tom &amp; Jerry"` is `Tom & Jerry`; the legacy semicolon-less forms decode exactly where a browser decodes them (`&nbsp` at the end of a value is a non-breaking space, `&nbspx` and `&nbsp=x` stay literal), and writing the semicolon avoids the question. A `state: true` prop takes an SSR value only through a `.prop=${value}` binding from the parent template, never from an attribute.
191
191
 
192
192
  **Never use a class-field declaration OR initializer** (`count = 0`, `student: Student = {...}`, `todos!: Todo[]`). Under `useDefineForClassFields` even a type-only `todos!: Todo[]` compiles to define an own property after `super()`, which clobbers the prototype's reactive accessor and silently breaks reactivity. Only declare props in the factory and read/write them off `this`. The `reactive-props-no-class-field` rule catches this.
193
193
 
@@ -42,7 +42,7 @@ Three seams pick a runtime-specific implementation, all inside the framework, no
42
42
 
43
43
  **The in-place dev refresh (#1398) needs the server process to SURVIVE the edit,** which is the whole of the Node-versus-Bun difference in that row. A page or layout never hydrates, so a freshly rendered page is the complete truth for it and the client router can swap it in without a reload, keeping scroll and (for a page edit) the hydrated state of components outside the changed region. The server classifies the changed file and puts the verdict on the live-reload event, so this needs a process that is still alive to do the classifying.
44
44
 
45
- Bun's `bun --hot` invalidates modules in place without restarting, so it gets the refresh. On Node the `webjs dev` supervisor (it replaced `node --watch` in #1521) RESTARTS the server process on a change under `app`, `components`, `modules`, `lib`, or `actions`, or to a root `middleware.{ts,js,mts,mjs}`, and a fresh process holds no record of what changed, so those edits are always a full reload. Two Node cases still refresh in place: an edit OUTSIDE that watched set (`db/schema.server.ts`, a `webjs.dev.watch` content dir), and running `npm run dev -- --no-hot`, which keeps the server in one process on either runtime. A component edit is a full reload everywhere by design, because `customElements.define` is once-per-tag and swapping fresh markup onto the old class would be worse than the reload.
45
+ Bun's `bun --hot` invalidates modules in place without restarting, so it gets the refresh. On Node the `webjs dev` supervisor (it replaced `node --watch` in #1521) RESTARTS the server process on a change under `app`, `components`, `modules`, `lib`, or `actions`, or to a root `middleware.{ts,js,mts,mjs}`, and a fresh process holds no record of what changed, so those edits are always a full reload. Two Node cases still refresh in place: an edit OUTSIDE that watched set (`db/schema.server.ts`, a `webjs.dev.watch` content dir), and running `npm run dev -- --no-hot`, which keeps the server in one process on either runtime. An in-place refresh loads the rebuilt stylesheets (a `webjs.dev.regenerate` compile runs on that request) BEFORE it swaps the new markup in, and drops the old sheets only after, so an element that gained a utility class never paints without its rule (#1535; a full reload never had the gap, since a head stylesheet is render-blocking). A component edit is a full reload everywhere by design, because `customElements.define` is once-per-tag and swapping fresh markup onto the old class would be worse than the reload.
46
46
 
47
47
  **`webjs dev` and `webjs start` serve the directory they are started in, and refuse anywhere else (#1526).** Run them in the app directory, the one holding `app/`. In a workspace (`apps/web` under a root `package.json` with `workspaces`) that is the member, even when the CLI is hoisted to the root `node_modules`: the hoisted bin and the Bun `--hot` child both keep the directory they were started in. Started where there is no `app/` (the workspace root, or a subdirectory such as `app/` itself), both exit 1 before any `before` step runs, naming the app to start (`cd apps/web && webjs dev`), instead of booting a server that answers 404 for every route.
48
48