@wcstack/typescript 4.0.2 → 4.0.4

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/dist/index.esm.js CHANGED
@@ -572,7 +572,7 @@ function loadSchemaCore() {
572
572
  return cached;
573
573
  }
574
574
 
575
- var version = "4.0.2";
575
+ var version = "4.0.4";
576
576
  var pkg = {
577
577
  version: version};
578
578
 
@@ -216,6 +216,10 @@ var WcsDiagnosticCode = {
216
216
  ScriptOrder: "wcs/script-order",
217
217
  // router/auto があるのに <base href> がない(SPA の basename 誤導出)。
218
218
  BaseHrefMissing: "wcs/base-href-missing",
219
+ // <wcs-router> の直後に無い <wcs-outlet>(どの router にも使われない)と、直後に outlet を持たない
220
+ // router。router は自前の outlet を直後に作り、ページはそこに描かれ、置いた outlet は空のまま。
221
+ // ランタイムも同じ code で警告する(docs/router-outlet-placement.md)。
222
+ RouterOutletMisplaced: "wcs/router-outlet-misplaced",
219
223
  // @wcstack/signals と /dom エントリの同一ページ混在(リアクティブコア二重化)。
220
224
  SignalsDualEntry: "wcs/signals-dual-entry",
221
225
  // --- deprecations ---
@@ -3148,7 +3152,9 @@ function analyzeStatePaths(scriptContent) {
3148
3152
  }
3149
3153
  if (prop.kind === "getter") {
3150
3154
  if (!paths.some((p) => p.path === prop.name)) {
3151
- paths.push({ path: prop.name, kind: hasRecursionWildcard(prop.name) ? "recursive" : "computed" });
3155
+ const recursive = hasRecursionWildcard(prop.name);
3156
+ const settable = !recursive && effective !== "data" && effective.set;
3157
+ paths.push({ path: prop.name, kind: recursive ? "recursive" : "computed", ...settable ? { settable } : {} });
3152
3158
  }
3153
3159
  continue;
3154
3160
  }
@@ -10991,9 +10997,12 @@ var ja = {
10991
10997
  triggerSeededTruthy: (path) => `trigger \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C true \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002trigger \u306F\u30A8\u30C3\u30B8\u691C\u51FA\u306A\u3057\uFF08truthy \u66F8\u304D\u8FBC\u307F\u3067\u5373\u767A\u706B\u30FBmanual \u3082\u30D0\u30A4\u30D1\u30B9\uFF09\u306E\u305F\u3081\u3001\u30D0\u30A4\u30F3\u30C9\u6642\u306B\u5373\u767A\u706B\u3057\u307E\u3059\u3002false \u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
10992
10998
  storageSeedClobber: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${raw} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 \u30D0\u30A4\u30F3\u30C9\u306B #init=element \u3092\u4ED8\u3051\u3066\uFF08\`value#init=element: ${path}\`\uFF09\u4FDD\u5B58\u5024\u3092\u52DD\u305F\u305B\u3001\u30B9\u30ED\u30C3\u30C8\u306F null\uFF08\u7A7A\u306E\u30AD\u30FC\u304C\u8AAD\u3080\u5024\uFF09\u3067\u30B7\u30FC\u30C9\u3057\u3066\u304F\u3060\u3055\u3044`,
10993
10999
  storageInitSeed: (path, raw) => `<wcs-storage> \u306E value \u30D0\u30A4\u30F3\u30C9\u5148 "${path}" \u304C ${shortSeed(raw)} \u3067\u30B7\u30FC\u30C9\u3055\u308C\u3066\u3044\u307E\u3059\u304C\u3001#init=element \u306F\u8981\u7D20\u306E\u5024\u3092\u3053\u306E\u30B9\u30ED\u30C3\u30C8\u3078\u5F15\u304D\u8FBC\u307F\u307E\u3059\u3002\u521D\u56DE\uFF08\u7A7A\u306E\u30AD\u30FC\uFF09\u306F null \u306A\u306E\u3067\u3001\u30B7\u30FC\u30C9\u306F null \u306B\u7F6E\u304D\u63DB\u308F\u308A\u307E\u3059 \u2014 null \u3067\u30B7\u30FC\u30C9\u3057\u3066\u8AAD\u3080\u5074\u3067\u65E2\u5B9A\u5024\u3092\u88DC\u3046\uFF08\`this.${path} ?? \u2026\`\uFF09\u304B\u3001null \u3092\u7121\u8996\u3059\u308B setter \u3092\u6301\u3064\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
11000
+ storagePairClobber: (path) => `<wcs-storage> \u306E value \u3092 #init=element \u306A\u3057\u3067\u30A2\u30AF\u30BB\u30B5\u30DA\u30A2 "${path}" \u306B\u675F\u306D\u3066\u3044\u307E\u3059\u3002state \u306E\u5074\u304C\u5148\u306B\u66F8\u304F\u306E\u3067\u3001\u8AAD\u307F\u8FBC\u307F\u306E\u305F\u3073\u306B getter \u306E\u6700\u521D\u306E\u5024\u304C\u4FDD\u5B58\u5024\u306E\u4E0A\u306B\u4FDD\u5B58\u3055\u308C\u3001setter \u306F\u4FDD\u5B58\u5024\u3092\u53D7\u3051\u53D6\u308C\u307E\u305B\u3093 \u2014 #init=element \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\uFF08\`value#init=element: ${path}\`\uFF09`,
10994
11001
  inputNestedWrite: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u306A\u3044\u306E\u3067\u3001\u4E0B\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306F <${tag}> \u306B\u5C4A\u304D\u307E\u305B\u3093 \u2014 \u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u30D1\u30B9\u3067\u8AAD\u3093\u3067\u65B0\u3057\u3044\u5024\u3092\u8FD4\u3059 getter\uFF08\`this["${example}"]\`\u30FB\`$getAll\`\uFF09\u3092\u675F\u306D\u308B\u304B\u3001\u4E0B\u3078\u66F8\u304B\u305A\u306B "${path}" \u3092\u5DEE\u3057\u66FF\u3048\u3066\u304F\u3060\u3055\u3044`,
11002
+ inputNestedWriteOneShot: (tag, input, path, example) => `<${tag}> \u306E\u5165\u529B "${input}" \u306B "${path}" \u3092\u675F\u306D\u3066\u3044\u307E\u3059\u304C\u3001\u30DA\u30FC\u30B8\u306F\u305D\u306E\u4E0B\uFF08"${example}"\uFF09\u3078\u66F8\u304D\u8FBC\u3093\u3067\u3044\u307E\u3059\u3002\u675F\u7E1B\u306F "${path}" \u81EA\u8EAB\u3078\u306E\u4EE3\u5165\u3067\u3057\u304B\u5F53\u3066\u76F4\u3055\u308C\u305A\u3001<${tag}> \u306F\u8981\u6C42\u306E\u305F\u3073\u306B "${input}" \u3092 null \u306B\u623B\u3059\u306E\u3067\u3001\u6B21\u306E\u8981\u6C42\u306F "${input}" \u306A\u3057\u3067\u9001\u3089\u308C\u307E\u3059 \u2014 \u8981\u6C42\u3092\u59CB\u3081\u308B\u30CF\u30F3\u30C9\u30E9\u3067\u65B0\u3057\u3044\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u4F5C\u3063\u3066\u4EE3\u5165\u3057\uFF08\`this.submitBody = { \u2026 }\`\uFF09\u3001\u305D\u308C\u3092\u675F\u306D\u3066\u304F\u3060\u3055\u3044`,
10995
11003
  devtoolsAfterState: () => `@wcstack/devtools/auto \u306F @wcstack/state/auto \u3088\u308A\u5148\u306B\u8AAD\u307F\u8FBC\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u5F8C\u3060\u3068\u914D\u7DDA\u53F0\u5E33\u304C\u30E9\u30A4\u30D6\u3067 captured \u3055\u308C\u307E\u305B\u3093\uFF09`,
10996
11004
  baseHrefMissing: () => `@wcstack/router \u3092\u4F7F\u3046 SPA \u306B\u306F <head> \u5185\u306E <base href="/"> \u304C\u5FC5\u8981\u3067\u3059\uFF08\u7121\u3044\u3068\u30C7\u30A3\u30FC\u30D7\u30EA\u30F3\u30AF\u3067 basename \u304C\u8AA4\u5C0E\u51FA\u3055\u308C\u307E\u3059\uFF09`,
11005
+ routerOutletMisplaced: () => `\u3053\u306E <wcs-outlet> \u306F <wcs-router> \u306E\u76F4\u5F8C\u306B\u7121\u3044\u306E\u3067\u3001\u3069\u306E router \u3082\u4F7F\u3044\u307E\u305B\u3093\u3002router \u306F\u81EA\u5206\u306E\u76F4\u5F8C\u306E\u8981\u7D20\u3060\u3051\u3092 outlet \u3068\u3057\u3066\u4F7F\u3044\u3001\u305D\u3053\u306B <wcs-outlet> \u304C\u7121\u3051\u308C\u3070\u81EA\u524D\u306E outlet \u3092\u4F5C\u308A\u307E\u3059\u3002\u30EB\u30FC\u30C8\u306F router \u306E\u76F4\u5F8C\u306B\u63CF\u304B\u308C\u3001\u3053\u306E outlet \u306F\u7A7A\u306E\u307E\u307E\u3067\u3059 \u2014 <wcs-router> \u306E\u76F4\u5F8C\u3078\u79FB\u3057\u3066\u304F\u3060\u3055\u3044`,
10997
11006
  signalsDualEntry: () => `@wcstack/signals \u3068 @wcstack/signals/dom \u304C\u540C\u4E00\u30DA\u30FC\u30B8\u304B\u3089 import \u3055\u308C\u3066\u3044\u307E\u3059\u3002CDN \u3067\u306F\u5404\u30A8\u30F3\u30C8\u30EA\u304C\u81EA\u5DF1\u5B8C\u7D50\u30D0\u30F3\u30C9\u30EB\u306E\u305F\u3081\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u30B3\u30A2\u304C\u4E8C\u91CD\u5316\u3057\u3001\u5883\u754C\u3067\u53CD\u5FDC\u304C\u58CA\u308C\u307E\u3059 \u2014 \u3059\u3079\u3066 /dom \u30A8\u30F3\u30C8\u30EA\u304B\u3089 import \u3057\u3066\u304F\u3060\u3055\u3044`,
10998
11007
  namedStateAttrDeprecated: (name) => `name \u5C5E\u6027\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30EB\u30FC\u30C8\u30C4\u30EA\u30FC\u3078\u306E\u30DE\u30A6\u30F3\u30C8 <wcs-state mount="${name}"> \u306B\u7F6E\u304D\u63DB\u3048\u3001\u30D1\u30B9\u306F "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
10999
11008
  namedStatePathDeprecated: (name) => name === "default" ? `"@default" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\u3002"@default" \u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09` : `"@name" \u30BB\u30EC\u30AF\u30BF\u306F v2 \u3067\u64A4\u53BB\u3055\u308C\u307E\u3057\u305F\uFF081 root 1 \u30C4\u30EA\u30FC\uFF09\u3002\u30DE\u30A6\u30F3\u30C8\u3057\u305F\u30C4\u30EA\u30FC\u3092 "${name}.<path>" \u3067\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF08docs/state-mount-design.md \xA79\uFF09`,
@@ -11179,9 +11188,12 @@ var en = {
11179
11188
  triggerSeededTruthy: (path) => `The trigger-bound slot "${path}" is seeded with true. trigger has no edge detection (any truthy write fires, and it bypasses manual), so it fires immediately at bind. Seed it with false`,
11180
11189
  storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value on every load \u2014 add #init=element to the binding (\`value#init=element: ${path}\`) so the stored value wins, and seed the slot with null (what an empty key loads as)`,
11181
11190
  storageInitSeed: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${shortSeed(raw)}, but #init=element pulls the element's value into it. On a first visit (an empty key) that is null, so the seed is replaced by null \u2014 seed it with null and supply the default where you read it (\`this.${path} ?? \u2026\`), or bind an accessor pair whose setter ignores null`,
11191
+ storagePairClobber: (path) => `The <wcs-storage> value is bound to the accessor pair "${path}" without #init=element. The state's side goes first, so on every load the getter's starting value is saved over the stored one, and the setter never receives it \u2014 add #init=element (\`value#init=element: ${path}\`)`,
11182
11192
  inputNestedWrite: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, so those writes never reach <${tag}> \u2014 bind a getter that reads the fields by path (\`this["${example}"]\`, \`$getAll\`) and returns a new value, or replace "${path}" instead of writing below it`,
11193
+ inputNestedWriteOneShot: (tag, input, path, example) => `"${path}" is bound to the input "${input}" of <${tag}>, but the page writes below it ("${example}"). A binding is re-applied only when "${path}" itself is assigned, and <${tag}> resets "${input}" to null after every request, so the next request goes without it \u2014 build a new object in the handler that starts the request (\`this.submitBody = { \u2026 }\`) and bind that`,
11183
11194
  devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
11184
11195
  baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
11196
+ routerOutletMisplaced: () => `This <wcs-outlet> is not right after a <wcs-router>, so no router uses it: a router uses only the element right after it as its outlet, and makes an outlet of its own there when that is not a <wcs-outlet>. The routes render right after the router and this outlet stays empty \u2014 move it right after the <wcs-router>`,
11185
11197
  signalsDualEntry: () => `Both @wcstack/signals and @wcstack/signals/dom are imported on this page. On a CDN each entry is a self-contained bundle, so the reactive core is duplicated and reactivity breaks at the seam \u2014 import everything from the single /dom entry`,
11186
11198
  namedStateAttrDeprecated: (name) => `The "name" attribute was removed in v2 \u2014 there is a single state tree per root. Mount this state onto the tree instead: <wcs-state mount="${name}"> and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
11187
11199
  namedStatePathDeprecated: (name) => name === "default" ? `The "@default" selector was removed in v2 \u2014 drop it (docs/state-mount-design.md \xA79)` : `The "@name" selector was removed in v2 \u2014 there is a single state tree. Mount the named state onto the tree (<wcs-state mount="...">) and read it as "${name}.<path>" (docs/state-mount-design.md \xA79)`,
@@ -14188,7 +14200,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14188
14200
  }
14189
14201
  }
14190
14202
  if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
14191
- const cand = findDataSlot(getPaths(), parsed.path);
14203
+ const paths = getPaths();
14204
+ const cand = findDataSlot(paths, parsed.path);
14192
14205
  if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
14193
14206
  diagnostics.push({
14194
14207
  code: WcsDiagnosticCode.StorageSeedClobber,
@@ -14199,6 +14212,16 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
14199
14212
  statePath: parsed.path,
14200
14213
  message: msgs.storageSeedClobber(parsed.path, cand.rawInitial)
14201
14214
  });
14215
+ } else if (cand === void 0 && paths.some((c) => c.kind === "computed" && c.settable && c.path === parsed.path)) {
14216
+ diagnostics.push({
14217
+ code: WcsDiagnosticCode.StorageSeedClobber,
14218
+ start,
14219
+ end,
14220
+ severity: "warning",
14221
+ tag: tagName,
14222
+ statePath: parsed.path,
14223
+ message: msgs.storagePairClobber(parsed.path)
14224
+ });
14202
14225
  }
14203
14226
  }
14204
14227
  if (tagName === "wcs-storage" && property === "value" && parsed.path && /(?:^|,)init=element\b/.test(modifiers)) {
@@ -14356,10 +14379,10 @@ function validateDocumentEnv(html, locale2) {
14356
14379
  const msgs = getMessages(locale2);
14357
14380
  const scanText = blankHtmlComments(html);
14358
14381
  const autos = findWcstackAutoScripts(scanText);
14359
- const stateIndex = autos.findIndex((a) => a.pkg === "state");
14382
+ const stateIndex = autos.findIndex((a) => a.pkgs.includes("state"));
14360
14383
  if (stateIndex !== -1) {
14361
14384
  for (const later of autos.slice(stateIndex + 1)) {
14362
- if (later.pkg !== "devtools") continue;
14385
+ if (!later.pkgs.includes("devtools")) continue;
14363
14386
  diagnostics.push({
14364
14387
  code: WcsDiagnosticCode.ScriptOrder,
14365
14388
  start: later.start,
@@ -14369,7 +14392,7 @@ function validateDocumentEnv(html, locale2) {
14369
14392
  });
14370
14393
  }
14371
14394
  }
14372
- const router = autos.find((a) => a.pkg === "router");
14395
+ const router = autos.find((a) => a.pkgs.includes("router") && (!a.bundle || /<wcs-router\b/i.test(scanText)));
14373
14396
  if (router && !/<base\b[^>]*\bhref\s*=/i.test(scanText)) {
14374
14397
  diagnostics.push({
14375
14398
  code: WcsDiagnosticCode.BaseHrefMissing,
@@ -14379,6 +14402,15 @@ function validateDocumentEnv(html, locale2) {
14379
14402
  message: msgs.baseHrefMissing()
14380
14403
  });
14381
14404
  }
14405
+ for (const outlet of findMisplacedOutlets(scanText)) {
14406
+ diagnostics.push({
14407
+ code: WcsDiagnosticCode.RouterOutletMisplaced,
14408
+ start: outlet.start,
14409
+ end: outlet.end,
14410
+ severity: "warning",
14411
+ message: msgs.routerOutletMisplaced()
14412
+ });
14413
+ }
14382
14414
  const refs = collectSignalsRefs(scanText);
14383
14415
  const dom = refs.find((r) => r.kind === "dom");
14384
14416
  const bare = refs.find((r) => r.kind === "bare");
@@ -14394,6 +14426,9 @@ function validateDocumentEnv(html, locale2) {
14394
14426
  }
14395
14427
  return diagnostics;
14396
14428
  }
14429
+ var SCOPED_AUTO = /@wcstack\/([a-z0-9-]+)(?:@[^/"'\s]+)?\/(?:auto\b|dist\/(?:split\/)?auto(?:\.min)?\.js\b)/;
14430
+ var BUNDLE_AUTO = /(?:^|\/)wcstack(?:@[^/"'\s]+)?\/(?:auto\b|dist\/auto\.min\.js\b)/;
14431
+ var BUNDLE_PACKAGES = ["state", "router", "fetch", "storage", "autoloader"];
14397
14432
  function findWcstackAutoScripts(html) {
14398
14433
  const out = [];
14399
14434
  const scriptRegex = /<script\b(?:"[^"]*"|'[^']*'|[^>"'])*>/gi;
@@ -14401,10 +14436,12 @@ function findWcstackAutoScripts(html) {
14401
14436
  while ((match = scriptRegex.exec(html)) !== null) {
14402
14437
  const src = extractSrc(match[0]);
14403
14438
  if (!src) continue;
14404
- const pkgMatch = /@wcstack\/([a-z0-9-]+)\/auto\b/.exec(src.value);
14405
- if (!pkgMatch) continue;
14439
+ const pkgMatch = SCOPED_AUTO.exec(src.value);
14440
+ const bundle = pkgMatch === null && BUNDLE_AUTO.test(src.value);
14441
+ if (pkgMatch === null && !bundle) continue;
14406
14442
  out.push({
14407
- pkg: pkgMatch[1],
14443
+ pkgs: bundle ? BUNDLE_PACKAGES : [pkgMatch[1]],
14444
+ bundle,
14408
14445
  start: match.index + src.offsetInTag,
14409
14446
  end: match.index + src.offsetInTag + src.value.length
14410
14447
  });
@@ -14461,6 +14498,34 @@ function blankHtmlComments(html) {
14461
14498
  function blankJsComments(code) {
14462
14499
  return code.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/(^|[^:])\/\/[^\n]*/g, (m, pre) => pre + " ".repeat(m.length - pre.length));
14463
14500
  }
14501
+ var ROUTER_TAG2 = "wcs-router";
14502
+ var OUTLET_TAG = "wcs-outlet";
14503
+ function findMisplacedOutlets(html) {
14504
+ const scan = html.replace(
14505
+ /(<(script|style)\b[^>]*>)([\s\S]*?)(<\/\2\s*>)/gi,
14506
+ (_m, open, _name, body, close) => open + " ".repeat(body.length) + close
14507
+ );
14508
+ const tokens = [];
14509
+ const tagRegex = /<(\/?)([a-zA-Z][\w-]*)(?:"[^"]*"|'[^']*'|[^>"'])*>/g;
14510
+ let match;
14511
+ while ((match = tagRegex.exec(scan)) !== null) {
14512
+ const nameStart = match.index + 1 + match[1].length;
14513
+ tokens.push({ name: match[2].toLowerCase(), close: match[1] === "/", start: nameStart, end: nameStart + match[2].length });
14514
+ }
14515
+ let routerWithoutOutlet = false;
14516
+ const misplaced = [];
14517
+ tokens.forEach((token, i) => {
14518
+ if (token.name === ROUTER_TAG2 && token.close) {
14519
+ const next = tokens[i + 1];
14520
+ if (!next || next.close || next.name !== OUTLET_TAG) routerWithoutOutlet = true;
14521
+ }
14522
+ if (token.name === OUTLET_TAG && !token.close) {
14523
+ const prev = tokens[i - 1];
14524
+ if (!(prev && prev.close && prev.name === ROUTER_TAG2)) misplaced.push({ start: token.start, end: token.end });
14525
+ }
14526
+ });
14527
+ return routerWithoutOutlet ? misplaced : [];
14528
+ }
14464
14529
 
14465
14530
  // src/service/watchDeclarationValidator.ts
14466
14531
  var STATE_NAME_SEPARATOR = "@";
@@ -15863,7 +15928,8 @@ function validateGetterUntrackedReads(script, scriptStart, nestedWriteRoots, loc
15863
15928
  return out;
15864
15929
  }
15865
15930
  var NESTED_WRITE_INPUTS = {
15866
- "wcs-storage": { inputs: ["value"], exemptWhenManual: true }
15931
+ "wcs-storage": { inputs: ["value"], exemptWhenManual: true },
15932
+ "wcs-fetch": { inputs: ["body"], exemptWhenManual: false, oneShot: true }
15867
15933
  };
15868
15934
  var MANUAL_ATTRIBUTE = /(?:^|\s)manual(?:\s|=|$)/i;
15869
15935
  function validateInputNestedWrites(html, stateTagName, bindAttrName, nestedWriteRoots, locale2) {
@@ -15885,6 +15951,7 @@ function validateInputNestedWrites(html, stateTagName, bindAttrName, nestedWrite
15885
15951
  if (!NESTED_WRITE_INPUTS[tag.tagName].inputs.includes(input)) continue;
15886
15952
  const example = nestedWriteRoots().get(occ.path);
15887
15953
  if (example === void 0) continue;
15954
+ const message = NESTED_WRITE_INPUTS[tag.tagName].oneShot ? msgs.inputNestedWriteOneShot(tag.tagName, input, occ.path, example) : msgs.inputNestedWrite(tag.tagName, input, occ.path, example);
15888
15955
  out.push({
15889
15956
  code: WcsDiagnosticCode.InputNestedWrite,
15890
15957
  start: occ.pathRange.start,
@@ -15892,7 +15959,7 @@ function validateInputNestedWrites(html, stateTagName, bindAttrName, nestedWrite
15892
15959
  severity: "warning",
15893
15960
  tag: tag.tagName,
15894
15961
  statePath: occ.path,
15895
- message: msgs.inputNestedWrite(tag.tagName, input, occ.path, example)
15962
+ message
15896
15963
  });
15897
15964
  }
15898
15965
  return out;
@@ -573,7 +573,7 @@ function loadSchemaCore() {
573
573
  return cached;
574
574
  }
575
575
 
576
- var version = "4.0.2";
576
+ var version = "4.0.4";
577
577
  var pkg = {
578
578
  version: version};
579
579
 
package/dist/wcs-tsc.mjs CHANGED
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { dirname, join, isAbsolute, resolve, basename } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- var version = "4.0.2";
8
+ var version = "4.0.4";
9
9
  var pkg = {
10
10
  version: version};
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/typescript",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "TypeScript tooling for wcstack apps: wcs-schema generates the sidecar stateSchema from a typed state file, so wcs-validate checks data-wcs paths against real types in CI and every editor.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",