@kudzujs/core 0.9.0 → 0.9.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
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.9.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.9.0 - Semantic compression.** Ordinary React-shaped state, derived data, shared actions, effects, component ownership, and keyed collections compile through a bounded package-neutral semantic core while static pages remain zero JavaScript. Read the [release notes](./RELEASES.md#090---semantic-compression), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.9.0), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.9.2 - Legacy CSS safety.** Projects with source CSS but no explicit style ownership retain their pre-0.8.53 global styles with an actionable migration warning; explicit apps keep route-aware CSS unchanged. Read the [release notes](./RELEASES.md#092---legacy-css-safety), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.9.2), or follow the [architecture packet](./docs/next-architecture/README.md).
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.9.2 - Legacy CSS safety
4
+
5
+ Kudzu 0.9.2 prevents pre-0.8.53 applications from silently producing an
6
+ unstyled deploy after upgrading to route-aware CSS ownership.
7
+
8
+ ### Changed in 0.9.2
9
+
10
+ - When no route imports CSS and `kudzu.config styles` is empty, source CSS is
11
+ linked globally in deterministic path order using the legacy behavior.
12
+ - The build prints an actionable warning directing applications to explicit
13
+ source imports or global configuration.
14
+ - Projects with any explicit CSS ownership retain unchanged route-aware output;
15
+ the fallback adds no browser JavaScript or explicit-path build work.
16
+
17
+ ### Validation
18
+
19
+ - A two-route legacy fixture emits both unimported styles on both routes in path
20
+ order and keeps both routes JavaScript-free.
21
+ - Configured global styles and explicit source-style tests retain their existing
22
+ output.
23
+ - `npm run check`, `npm test`, and `npm run test:package` pass with 254 tests.
24
+
25
+ ### Upgrade
26
+
27
+ ```sh
28
+ npm install @kudzujs/core@^0.9.2
29
+ ```
30
+
31
+ ## 0.9.1 - Positional primitive lists
32
+
33
+ Kudzu 0.9.1 allows JSON-safe primitive values in lists keyed by their authored
34
+ position while retaining the plain-object requirement for property-keyed rows.
35
+
36
+ ### Changed in 0.9.1
37
+
38
+ - Build and browser list validation now accept primitive positional rows.
39
+ - Compact object-row serialization ignores primitive arrays instead of treating
40
+ their values as object records.
41
+ - Lupin-derived fixtures prove realtime rooms, lobby and private-room entry,
42
+ stopwatch ownership, chat and spectator replacement, Canvas/RAF input,
43
+ IME handling, panic shortcuts, and exact cleanup without a game runtime.
44
+
45
+ ### Validation
46
+
47
+ - Static sibling routes remain complete HTML with zero JavaScript.
48
+ - `npm run check`, `npm test`, and `npm run test:package` pass with 253 tests.
49
+
50
+ ### Upgrade
51
+
52
+ ```sh
53
+ npm install @kudzujs/core@^0.9.1
54
+ ```
55
+
3
56
  ## 0.9.0 - Semantic compression
4
57
 
5
58
  Kudzu 0.9.0 consolidates evidence-backed React migration shapes onto the existing signal, handler, effect, component-ownership, and keyed-list semantics without adding React, hydration, a VDOM, or a retained browser component tree.
@@ -80,7 +80,7 @@ Static routes receive no browser runtime. Command routes receive `runtime.js`; d
80
80
 
81
81
  Exact relative `.worker.ts` constructors in inline effects are validated and bundled in a separate content-hashed ESM graph under `dist/assets/workers/`. Those files are referenced only by rendered effect handlers and never become document capability scripts, preloads, or window imports; unreachable source effects do not emit their Worker roots. Worker graphs allow relative TypeScript ESM runtime imports only and reject JSX, package runtime imports, import-equals declarations, dynamic imports, `require()`, and paths outside `src`. Ordinary runtime imports or re-exports of `.worker.ts` and Worker construction in imported keyed-row effects are rejected.
82
82
 
83
- Page `metadata` can emit description, canonical, favicon, manifest, Open Graph, and Twitter Card tags without a client runtime. Relative source CSS imports follow each page's reachable TypeScript graph and link only from those routes; `kudzu.config` styles remain explicitly global. Enhanced navigation loads incoming managed styles before route replacement, reuses shared links, and removes outgoing links. All styles are emitted before `afterBuild()` runs; static stylesheet links in component JSX fail compilation instead of loading from the body.
83
+ Page `metadata` can emit description, canonical, favicon, manifest, Open Graph, and Twitter Card tags without a client runtime. Relative source CSS imports follow each page's reachable TypeScript graph and link only from those routes; `kudzu.config` styles remain explicitly global. For pre-0.8.53 projects with source CSS but no explicit style ownership at all, Kudzu preserves the former global path-ordered output and prints a migration warning instead of silently emitting an unstyled deploy. Enhanced navigation loads incoming managed styles before route replacement, reuses shared links, and removes outgoing links. All styles are emitted before `afterBuild()` runs; static stylesheet links in component JSX fail compilation instead of loading from the body.
84
84
 
85
85
  Inline SVG rendering normalizes an explicit set of common React presentation aliases before static serialization and binding descriptor creation. Reactive aliases use the existing generic `setAttribute` path. Reactive conditionals and flat intrinsic keyed lists store inert branch or row markup on SVG markers and parse it in the actual parent namespace only when replacement nodes are needed; existing condition/list ownership then handles insertion, identity, updates, and removal. Focus, keyboard, and click handlers on keyed SVG points may update parent state for an external accessible HTML tooltip, and retained handlers read the latest item after list updates. Builds without structural SVG compile out that fragment path, and static SVG adds no JavaScript. Keyed-item conditions, nested SVG lists, MathML structures, and namespaced attributes remain unsupported.
86
86
 
@@ -45,7 +45,7 @@ export async function buildWithSession(project, { changedFiles, quiet = false, m
45
45
  try {
46
46
  await recoverOutput(outputDirectory, backupOutput)
47
47
  await rm(stagedOutput, { recursive: true, force: true })
48
- const { result, pageCount, behaviorCount, cache } = await buildInto(project, stagedOutput, { changedFiles, minify, retainCache })
48
+ const { result, pageCount, behaviorCount, cache } = await buildInto(project, stagedOutput, { changedFiles, minify, quiet, retainCache })
49
49
  await promoteOutput(stagedOutput, outputDirectory, backupOutput)
50
50
  project.buildCache = retainCache ? cache : undefined
51
51
  if (!quiet) console.log(`Built ${pageCount} page(s), ${behaviorCount} interactive page(s) into dist/`)
@@ -63,7 +63,7 @@ export async function buildWithSession(project, { changedFiles, quiet = false, m
63
63
  }
64
64
  }
65
65
 
66
- async function buildInto(project, outputDirectory, { changedFiles, minify, retainCache }) {
66
+ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet, retainCache }) {
67
67
  const { root, sourceDirectory, pagesDirectory, workDirectory } = project
68
68
  const previous = retainCache ? project.buildCache : undefined
69
69
  project.buildGeneration = (project.buildGeneration ?? 0) + 1
@@ -108,6 +108,14 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, retai
108
108
  for (const file of sourceFiles) sourceFileSet.add(file)
109
109
  const staticFiles = await safeStaticFiles(projectFiles)
110
110
  const stylesByPage = new Map(pageFiles.map(file => [file, orderSourceStyles([file], sourceFiles, sourceIndex, staticFiles).filter(style => !configuredStyleSources.has(style))]))
111
+ const explicitSourceStyles = new Set([...stylesByPage.values()].flat())
112
+ const legacyStyles = configuredStyles.sources.length || configuredStyles.urls.length || explicitSourceStyles.size
113
+ ? []
114
+ : [...staticFiles].filter(file => file.toLowerCase().endsWith(".css")).sort()
115
+ if (legacyStyles.length) {
116
+ for (const file of pageFiles) stylesByPage.set(file, legacyStyles)
117
+ if (!quiet) console.warn("Kudzu: no route imports CSS and kudzu.config styles is empty; applying the legacy global CSS fallback. Import CSS from reachable source or declare config.styles for route-aware output.")
118
+ }
111
119
  const cssFiles = [...new Set([...stylesByPage.values()].flat())]
112
120
  const importedAssets = new Set()
113
121
  const { cssModules, cssOutputs } = await prepareSourceStyles(cssFiles, staticFiles, importedAssets, base, project)
@@ -319,7 +319,7 @@ export function list(items, keyField, render, ownerField, selector = [], indexed
319
319
  for (const [index, item] of values.entries()) {
320
320
  const key = keyField === null ? index : item?.[keyField]
321
321
  if (!validListKey(key)) throw new Error(`Keyed list key "${keyField}" must be a string or finite number`)
322
- assertListItem(item)
322
+ assertListItem(item, keyField === null)
323
323
  assertListValue(item, new Set())
324
324
  const token = `${typeof key}:${key}`
325
325
  if (keys.has(token)) throw new Error(`Duplicate keyed list key: ${String(key)}`)
@@ -360,7 +360,8 @@ function validListKey(key) {
360
360
  return typeof key === "string" || typeof key === "number" && Number.isFinite(key)
361
361
  }
362
362
 
363
- function assertListItem(item) {
363
+ function assertListItem(item, allowPrimitive = false) {
364
+ if (allowPrimitive && (item === null || typeof item !== "object")) return
364
365
  const prototype = item && typeof item === "object" ? Object.getPrototypeOf(item) : undefined
365
366
  if (!item || Array.isArray(item) || prototype !== Object.prototype) throw new Error("Keyed list items must be ordinary plain objects")
366
367
  }
@@ -1103,6 +1104,7 @@ function escapeJsonAttribute(value) {
1103
1104
 
1104
1105
  function compactListState(value) {
1105
1106
  if (!Array.isArray(value) || !value.length) return undefined
1107
+ if (!value[0] || typeof value[0] !== "object" || Array.isArray(value[0]) || Object.getPrototypeOf(value[0]) !== Object.prototype) return undefined
1106
1108
  const fields = Object.keys(value[0])
1107
1109
  if (!fields.length || !value.every(item => Object.keys(item).length === fields.length && fields.every(field => Object.hasOwn(item, field)))) return undefined
1108
1110
  return [fields, value.map(item => fields.map(field => item[field]))]
@@ -170,7 +170,7 @@ function updateList(list) {
170
170
  for (const [index, item] of items.entries()) {
171
171
  const key = list.descriptor.key === null ? index : item?.[list.descriptor.key]
172
172
  if (!validListKey(key)) throw new Error(`Keyed list key "${list.descriptor.key}" must be a string or finite number`)
173
- assertListItem(item)
173
+ assertListItem(item, list.descriptor.key === null)
174
174
  const seededValue = __KUDZU_LIST_SEEDS__ ? list.seedFields && seededListValue(item, list.seedFields, list.valueSeed, !referenceOnly) : undefined
175
175
  if (seededValue === undefined) assertListValue(item, seen, true)
176
176
  const token = keyToken(key)
@@ -1155,7 +1155,8 @@ function validListKey(key) {
1155
1155
  return typeof key === "string" || typeof key === "number" && Number.isFinite(key)
1156
1156
  }
1157
1157
 
1158
- function assertListItem(item) {
1158
+ function assertListItem(item, allowPrimitive = false) {
1159
+ if (allowPrimitive && (item === null || typeof item !== "object")) return
1159
1160
  const prototype = item && typeof item === "object" ? Object.getPrototypeOf(item) : undefined
1160
1161
  if (!item || Array.isArray(item) || prototype !== Object.prototype) throw new Error("Keyed list items must be ordinary plain objects")
1161
1162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",