@wcstack/lint 1.32.0 → 2.0.0

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/cli.cjs CHANGED
@@ -96,6 +96,9 @@ var WcsDiagnosticCode = {
96
96
  // 同名 tag / filter の後勝ち禁止(§5-3)。override:true が無い再定義もこの collision で表す。
97
97
  ManifestTagCollision: "wcs/manifest-tag-collision",
98
98
  ManifestFilterCollision: "wcs/manifest-filter-collision",
99
+ // 同名 state の stateSchema が複数の application artifact に宣言されている(§5-3 の
100
+ // application 版・D8)。勝者なし: その state は未宣言扱い(schema 検証は沈黙)。
101
+ ManifestStateCollision: "wcs/manifest-state-collision",
99
102
  // 明示 override:true(§5-4)。衝突ではなく意図的な shadow の告知(info)。
100
103
  ManifestOverride: "wcs/manifest-override",
101
104
  // --- sidecar vs live declaration drift ---
@@ -168,7 +171,15 @@ var WcsDiagnosticCode = {
168
171
  // router/auto があるのに <base href> がない(SPA の basename 誤導出)。
169
172
  BaseHrefMissing: "wcs/base-href-missing",
170
173
  // @wcstack/signals と /dom エントリの同一ページ混在(リアクティブコア二重化)。
171
- SignalsDualEntry: "wcs/signals-dual-entry"
174
+ SignalsDualEntry: "wcs/signals-dual-entry",
175
+ // --- deprecations ---
176
+ // 名前付き State(`<wcs-state name>` / `path@name`)。v2 でマウント(`mount=` と接頭辞付きパス)に
177
+ // 置き換わる(docs/state-mount-design.md D16)。1.x では warning、v2 では parse error と同時に error。
178
+ NamedStateDeprecated: "wcs/named-state-deprecated",
179
+ // --- volume mount ---
180
+ // `<wcs-state mount="...">` の値が runtime の validateVolumeMountPath で raise する形
181
+ // (空・空セグメント・ワイルドカード・予約文字 $ # @)。runtime と同条件・同文言(v2)。
182
+ MountPathInvalid: "wcs/mount-path-invalid"
172
183
  };
173
184
  function sortDiagnostics(diagnostics) {
174
185
  const severityRank = { error: 0, warning: 1, info: 2 };
@@ -784,7 +795,6 @@ var MAX_WILDCARD_DEPTH = 128;
784
795
  var BINDING_SEPARATOR = ";";
785
796
  var PROP_VALUE_SEPARATOR = ":";
786
797
  var MODIFIER_SEPARATOR = "#";
787
- var STATE_NAME_SEPARATOR = "@";
788
798
  var FILTER_SEPARATOR = "|";
789
799
  var MODIFIER_PREVENT = "prevent";
790
800
  var MODIFIER_STOP = "stop";
@@ -842,7 +852,6 @@ function getWcsManifest() {
842
852
  binding: BINDING_SEPARATOR,
843
853
  propValue: PROP_VALUE_SEPARATOR,
844
854
  modifier: MODIFIER_SEPARATOR,
845
- stateName: STATE_NAME_SEPARATOR,
846
855
  filter: FILTER_SEPARATOR
847
856
  },
848
857
  // 正本 STRUCTURAL_BINDING_TYPE_SET から導出(手書きの二重定義を排除)。
@@ -909,233 +918,13 @@ var STRUCTURAL_DIRECTIVES = [...STRUCTURAL_BINDING_TYPE_SET].map((name) => ({
909
918
  ...STRUCTURAL_DIRECTIVE_INFO[name]
910
919
  }));
911
920
 
912
- // src/language/htmlParse.ts
913
- function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
914
- const blocks = [];
915
- let pos = 0;
916
- const len = html.length;
917
- while (pos < len) {
918
- if (html.startsWith("<!--", pos)) {
919
- const commentEnd = html.indexOf("-->", pos + 4);
920
- if (commentEnd === -1) break;
921
- pos = commentEnd + 3;
922
- continue;
923
- }
924
- const wcsMatch = matchOpenTag(html, pos, stateTagName);
925
- if (wcsMatch === null) {
926
- pos++;
927
- continue;
928
- }
929
- const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
930
- pos = wcsMatch.end;
931
- const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
932
- const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
933
- while (pos < wcsEnd) {
934
- if (html.startsWith("<!--", pos)) {
935
- const commentEnd = html.indexOf("-->", pos + 4);
936
- if (commentEnd === -1) break;
937
- pos = commentEnd + 3;
938
- continue;
939
- }
940
- const scriptMatch = matchOpenTag(html, pos, "script");
941
- if (scriptMatch === null) {
942
- pos++;
943
- continue;
944
- }
945
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
946
- if (typeAttr?.toLowerCase() !== "module") {
947
- pos = scriptMatch.end;
948
- continue;
949
- }
950
- const contentStart = scriptMatch.end;
951
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
952
- if (scriptCloseIdx === -1) {
953
- pos = contentStart;
954
- break;
955
- }
956
- const contentEnd = scriptCloseIdx;
957
- blocks.push({
958
- contentStart,
959
- contentEnd,
960
- content: html.slice(contentStart, contentEnd),
961
- stateName
962
- });
963
- pos = html.indexOf(">", scriptCloseIdx) + 1;
964
- if (pos === 0) break;
965
- }
966
- pos = wcsEnd;
967
- if (wcsCloseIdx !== -1) {
968
- const closeEnd = html.indexOf(">", wcsCloseIdx);
969
- if (closeEnd !== -1) pos = closeEnd + 1;
970
- }
971
- }
972
- return blocks;
973
- }
974
- function parseWcsStateElements(html, stateTagName = "wcs-state") {
975
- const elements = [];
976
- let pos = 0;
977
- const len = html.length;
978
- while (pos < len) {
979
- if (html.startsWith("<!--", pos)) {
980
- const commentEnd = html.indexOf("-->", pos + 4);
981
- if (commentEnd === -1) break;
982
- pos = commentEnd + 3;
983
- continue;
984
- }
985
- const wcsMatch = matchOpenTag(html, pos, stateTagName);
986
- if (wcsMatch === null) {
987
- pos++;
988
- continue;
989
- }
990
- const stateName = extractAttribute(wcsMatch.tagContent, "name") ?? "default";
991
- const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
992
- const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
993
- const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
994
- const tagStart = pos;
995
- const tagEnd = wcsMatch.end;
996
- pos = wcsMatch.end;
997
- const scriptBlocks = [];
998
- const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
999
- const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
1000
- while (pos < wcsEnd) {
1001
- if (html.startsWith("<!--", pos)) {
1002
- const commentEnd = html.indexOf("-->", pos + 4);
1003
- if (commentEnd === -1) break;
1004
- pos = commentEnd + 3;
1005
- continue;
1006
- }
1007
- const scriptMatch = matchOpenTag(html, pos, "script");
1008
- if (scriptMatch === null) {
1009
- pos++;
1010
- continue;
1011
- }
1012
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1013
- if (typeAttr?.toLowerCase() !== "module") {
1014
- pos = scriptMatch.end;
1015
- continue;
1016
- }
1017
- const contentStart = scriptMatch.end;
1018
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1019
- if (scriptCloseIdx === -1) {
1020
- pos = contentStart;
1021
- break;
1022
- }
1023
- scriptBlocks.push({
1024
- contentStart,
1025
- contentEnd: scriptCloseIdx,
1026
- content: html.slice(contentStart, scriptCloseIdx),
1027
- stateName
1028
- });
1029
- pos = html.indexOf(">", scriptCloseIdx) + 1;
1030
- if (pos === 0) break;
1031
- }
1032
- elements.push({ stateName, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
1033
- pos = wcsEnd;
1034
- if (wcsCloseIdx !== -1) {
1035
- const closeEnd = html.indexOf(">", wcsCloseIdx);
1036
- if (closeEnd !== -1) pos = closeEnd + 1;
1037
- }
1038
- }
1039
- return elements;
1040
- }
1041
- function findScriptJsonById(html, id2) {
1042
- let pos = 0;
1043
- const len = html.length;
1044
- while (pos < len) {
1045
- if (html.startsWith("<!--", pos)) {
1046
- const commentEnd = html.indexOf("-->", pos + 4);
1047
- if (commentEnd === -1) break;
1048
- pos = commentEnd + 3;
1049
- continue;
1050
- }
1051
- const scriptMatch = matchOpenTag(html, pos, "script");
1052
- if (scriptMatch === null) {
1053
- pos++;
1054
- continue;
1055
- }
1056
- const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1057
- const idAttr = extractAttribute(scriptMatch.tagContent, "id");
1058
- if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
1059
- const contentStart = scriptMatch.end;
1060
- const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1061
- if (scriptCloseIdx === -1) return null;
1062
- return html.slice(contentStart, scriptCloseIdx);
1063
- }
1064
- pos = scriptMatch.end;
1065
- }
1066
- return null;
1067
- }
1068
- function matchOpenTag(html, pos, tagName) {
1069
- if (html[pos] !== "<") return null;
1070
- const nameStart = pos + 1;
1071
- const nameEnd = nameStart + tagName.length;
1072
- if (nameEnd > html.length) return null;
1073
- const slice3 = html.slice(nameStart, nameEnd);
1074
- if (slice3.toLowerCase() !== tagName.toLowerCase()) return null;
1075
- const charAfter = html[nameEnd];
1076
- if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
1077
- return null;
1078
- }
1079
- let i = nameEnd;
1080
- let inSingleQuote = false;
1081
- let inDoubleQuote = false;
1082
- while (i < html.length) {
1083
- const ch = html[i];
1084
- if (inSingleQuote) {
1085
- if (ch === "'") inSingleQuote = false;
1086
- } else if (inDoubleQuote) {
1087
- if (ch === '"') inDoubleQuote = false;
1088
- } else if (ch === "'") {
1089
- inSingleQuote = true;
1090
- } else if (ch === '"') {
1091
- inDoubleQuote = true;
1092
- } else if (ch === ">") {
1093
- return {
1094
- start: pos,
1095
- end: i + 1,
1096
- tagContent: html.slice(nameEnd, i)
1097
- };
1098
- }
1099
- i++;
1100
- }
1101
- return null;
1102
- }
1103
- function findCloseTag(html, startPos, tagName) {
1104
- const pattern = "</" + tagName;
1105
- const patternLower = pattern.toLowerCase();
1106
- const htmlLower = html.toLowerCase();
1107
- let pos = startPos;
1108
- while (pos < html.length) {
1109
- const idx = htmlLower.indexOf(patternLower, pos);
1110
- if (idx === -1) return -1;
1111
- const afterIdx = idx + pattern.length;
1112
- if (afterIdx < html.length) {
1113
- const ch = html[afterIdx];
1114
- if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
1115
- return idx;
1116
- }
1117
- }
1118
- pos = idx + 1;
1119
- }
1120
- return -1;
1121
- }
1122
- function extractAttribute(tagContent, attrName) {
1123
- const regex = new RegExp(
1124
- `(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
1125
- "i"
1126
- );
1127
- const match = tagContent.match(regex);
1128
- if (!match) return null;
1129
- return match[1] ?? match[2] ?? match[3] ?? null;
1130
- }
1131
-
1132
921
  // src/service/stateAnalyzer.ts
1133
922
  var RESERVED_STREAMS_KEY = "$streams";
1134
923
  var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
1135
924
  var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
1136
925
  var RESERVED_LIST_KEYS_KEY = "$listKeys";
1137
926
  var RESERVED_WATCH_KEY = "$watch";
1138
- function analyzeStatePaths(scriptContent, stateName = "default") {
927
+ function analyzeStatePaths(scriptContent) {
1139
928
  const objectContent = extractDefaultExportObject(scriptContent);
1140
929
  if (!objectContent) return [];
1141
930
  const paths = [];
@@ -1144,27 +933,27 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
1144
933
  const pendingListKeys = [];
1145
934
  for (const prop of topLevelProps) {
1146
935
  if (prop.name.startsWith("$")) {
1147
- collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName);
936
+ collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys);
1148
937
  continue;
1149
938
  }
1150
939
  if (prop.kind === "method") {
1151
- paths.push({ path: prop.name, kind: "method", stateName });
940
+ paths.push({ path: prop.name, kind: "method" });
1152
941
  continue;
1153
942
  }
1154
943
  if (prop.kind === "getter") {
1155
- if (!paths.some((p) => p.stateName === stateName && p.path === prop.name)) {
1156
- paths.push({ path: prop.name, kind: "computed", stateName });
944
+ if (!paths.some((p) => p.path === prop.name)) {
945
+ paths.push({ path: prop.name, kind: "computed" });
1157
946
  }
1158
947
  continue;
1159
948
  }
1160
- pushDataPropertyPaths(prop, paths, stateName);
949
+ pushDataPropertyPaths(prop, paths);
1161
950
  }
1162
951
  for (const streamValue of pendingStreamValues) {
1163
- if (paths.some((p) => p.stateName === stateName && p.path === streamValue.name)) continue;
1164
- pushDataPropertyPaths(streamValue, paths, stateName);
952
+ if (paths.some((p) => p.path === streamValue.name)) continue;
953
+ pushDataPropertyPaths(streamValue, paths);
1165
954
  }
1166
955
  for (const listKeyEntry of pendingListKeys) {
1167
- pushListKeyPaths(listKeyEntry, paths, stateName);
956
+ pushListKeyPaths(listKeyEntry, paths);
1168
957
  }
1169
958
  return paths;
1170
959
  }
@@ -1251,7 +1040,7 @@ function findNonObjectWatch(scriptContent) {
1251
1040
  if (!isArrowFunction && !isWholeLiteral) return null;
1252
1041
  return span;
1253
1042
  }
1254
- function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName) {
1043
+ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys) {
1255
1044
  if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
1256
1045
  const entries = parseTopLevelProperties(extractObjectContent(prop.value));
1257
1046
  for (const entry of entries) {
@@ -1263,20 +1052,20 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
1263
1052
  value: initial?.value,
1264
1053
  typeHint: initial?.typeHint
1265
1054
  });
1266
- paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string", stateName });
1267
- paths.push({ path: `$streamError.${entry.name}`, kind: "data", stateName });
1055
+ paths.push({ path: `$streamStatus.${entry.name}`, kind: "data", typeHint: "string" });
1056
+ paths.push({ path: `$streamError.${entry.name}`, kind: "data" });
1268
1057
  }
1269
1058
  return;
1270
1059
  }
1271
1060
  if (prop.name === RESERVED_COMMAND_TOKENS_KEY && prop.value) {
1272
1061
  for (const name of extractStringArrayItems(prop.value)) {
1273
- paths.push({ path: `$command.${name}`, kind: "command", stateName });
1062
+ paths.push({ path: `$command.${name}`, kind: "command" });
1274
1063
  }
1275
1064
  return;
1276
1065
  }
1277
1066
  if (prop.name === RESERVED_EVENT_TOKENS_KEY && prop.value) {
1278
1067
  for (const name of extractStringArrayItems(prop.value)) {
1279
- paths.push({ path: name, kind: "eventToken", stateName });
1068
+ paths.push({ path: name, kind: "eventToken" });
1280
1069
  }
1281
1070
  return;
1282
1071
  }
@@ -1288,22 +1077,22 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKe
1288
1077
  return;
1289
1078
  }
1290
1079
  }
1291
- function pushListKeyPaths(entry, paths, stateName) {
1080
+ function pushListKeyPaths(entry, paths) {
1292
1081
  const listPath = entry.name;
1293
1082
  const segments = listPath.split(".");
1294
1083
  if (listPath.length === 0 || segments.some((s) => s.length === 0) || segments[segments.length - 1] === "*") {
1295
1084
  return;
1296
1085
  }
1297
- const has = (path) => paths.some((p) => p.stateName === stateName && p.path === path);
1298
- if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array", stateName });
1299
- if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list", stateName });
1086
+ const has = (path) => paths.some((p) => p.path === path);
1087
+ if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array" });
1088
+ if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list" });
1300
1089
  if (!has(`${listPath}.length`)) {
1301
- paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number", stateName });
1090
+ paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number" });
1302
1091
  }
1303
1092
  const keyField = extractStringLiteralValue(entry.value);
1304
1093
  if (keyField === null || keyField.includes(".") || keyField.includes("*")) return;
1305
1094
  if (!has(`${listPath}.*.${keyField}`)) {
1306
- paths.push({ path: `${listPath}.*.${keyField}`, kind: "data", stateName });
1095
+ paths.push({ path: `${listPath}.*.${keyField}`, kind: "data" });
1307
1096
  }
1308
1097
  }
1309
1098
  function extractStringLiteralValue(value) {
@@ -1326,17 +1115,17 @@ function extractStringArrayItems(value) {
1326
1115
  return items;
1327
1116
  }
1328
1117
  var MAX_OBJECT_NEST_DEPTH = 5;
1329
- function pushDataPropertyPaths(prop, paths, stateName) {
1330
- pushDataPropertyPathsAt(prop.name, prop, paths, stateName, 0);
1118
+ function pushDataPropertyPaths(prop, paths) {
1119
+ pushDataPropertyPathsAt(prop.name, prop, paths, 0);
1331
1120
  }
1332
- function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
1333
- paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim(), stateName });
1121
+ function pushDataPropertyPathsAt(path, prop, paths, depth) {
1122
+ paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim() });
1334
1123
  if (prop.value && isArrayLiteral(prop.value)) {
1335
- paths.push({ path: `${path}.*`, kind: "list", stateName });
1336
- paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
1124
+ paths.push({ path: `${path}.*`, kind: "list" });
1125
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
1337
1126
  if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1338
1127
  for (const childProp of extractArrayElementDataProperties(prop.value)) {
1339
- pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, stateName, depth + 1);
1128
+ pushDataPropertyPathsAt(`${path}.*.${childProp.name}`, childProp, paths, depth + 1);
1340
1129
  }
1341
1130
  return;
1342
1131
  }
@@ -1345,11 +1134,11 @@ function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
1345
1134
  const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
1346
1135
  for (const childProp of childProps) {
1347
1136
  if (childProp.kind !== "data") continue;
1348
- pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, stateName, depth + 1);
1137
+ pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, depth + 1);
1349
1138
  }
1350
1139
  }
1351
1140
  }
1352
- function analyzeJsonPaths(jsonString, stateName = "default") {
1141
+ function analyzeJsonPaths(jsonString) {
1353
1142
  let data;
1354
1143
  try {
1355
1144
  data = JSON.parse(jsonString);
@@ -1358,31 +1147,31 @@ function analyzeJsonPaths(jsonString, stateName = "default") {
1358
1147
  }
1359
1148
  if (typeof data !== "object" || data === null || Array.isArray(data)) return [];
1360
1149
  const paths = [];
1361
- collectJsonPaths(data, "", paths, stateName, 0);
1150
+ collectJsonPaths(data, "", paths, 0);
1362
1151
  return paths;
1363
1152
  }
1364
- function collectJsonPaths(obj, prefix, paths, stateName, depth) {
1153
+ function collectJsonPaths(obj, prefix, paths, depth) {
1365
1154
  if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1366
1155
  for (const [key, value] of Object.entries(obj)) {
1367
1156
  if (prefix === "" && key.startsWith("$")) continue;
1368
1157
  const path = prefix ? `${prefix}.${key}` : key;
1369
- pushJsonValuePaths(path, value, paths, stateName, depth);
1158
+ pushJsonValuePaths(path, value, paths, depth);
1370
1159
  }
1371
1160
  }
1372
- function pushJsonValuePaths(path, value, paths, stateName, depth) {
1373
- paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value), stateName });
1161
+ function pushJsonValuePaths(path, value, paths, depth) {
1162
+ paths.push({ path, kind: "data", typeHint: inferJsonTypeHint(value) });
1374
1163
  if (Array.isArray(value)) {
1375
- paths.push({ path: `${path}.*`, kind: "list", stateName });
1376
- paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
1164
+ paths.push({ path: `${path}.*`, kind: "list" });
1165
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
1377
1166
  if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1378
1167
  if (value.length > 0 && typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0])) {
1379
1168
  const firstElement = value[0];
1380
1169
  for (const [childKey, childValue] of Object.entries(firstElement)) {
1381
- pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths, stateName, depth + 1);
1170
+ pushJsonValuePaths(`${path}.*.${childKey}`, childValue, paths, depth + 1);
1382
1171
  }
1383
1172
  }
1384
1173
  } else if (typeof value === "object" && value !== null) {
1385
- collectJsonPaths(value, path, paths, stateName, depth + 1);
1174
+ collectJsonPaths(value, path, paths, depth + 1);
1386
1175
  }
1387
1176
  }
1388
1177
  function inferJsonTypeHint(value) {
@@ -1585,42 +1374,369 @@ function extractJsDocType(content, propIndex) {
1585
1374
  const typeExpr = jsdocMatch[1].trim();
1586
1375
  return normalizeJsDocType(typeExpr);
1587
1376
  }
1588
- function normalizeJsDocType(typeExpr) {
1589
- const parts = typeExpr.split("|").map((p) => p.trim());
1590
- const normalized = parts.map((p) => {
1591
- const lower = p.toLowerCase();
1592
- if (lower === "string") return "string";
1593
- if (lower === "number") return "number";
1594
- if (lower === "boolean") return "boolean";
1595
- if (lower === "null") return "null";
1596
- if (lower === "undefined") return "null";
1597
- if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
1598
- if (lower === "object") return "object";
1377
+ function normalizeJsDocType(typeExpr) {
1378
+ const parts = typeExpr.split("|").map((p) => p.trim());
1379
+ const normalized = parts.map((p) => {
1380
+ const lower = p.toLowerCase();
1381
+ if (lower === "string") return "string";
1382
+ if (lower === "number") return "number";
1383
+ if (lower === "boolean") return "boolean";
1384
+ if (lower === "null") return "null";
1385
+ if (lower === "undefined") return "null";
1386
+ if (lower.endsWith("[]") || lower.startsWith("array")) return "array";
1387
+ if (lower === "object") return "object";
1388
+ return null;
1389
+ }).filter((p) => p !== null);
1390
+ if (normalized.length === 0) return void 0;
1391
+ const unique = [...new Set(normalized)].sort();
1392
+ return unique.join("|");
1393
+ }
1394
+ function isEscaped(text, i) {
1395
+ let backslashCount = 0;
1396
+ let j = i - 1;
1397
+ while (j >= 0 && text[j] === "\\") {
1398
+ backslashCount++;
1399
+ j--;
1400
+ }
1401
+ return backslashCount % 2 === 1;
1402
+ }
1403
+ function inferTypeHint(valueStart) {
1404
+ const v = valueStart.trim().replace(/,\s*$/, "");
1405
+ if (/^-?\d+\.\d/.test(v)) return "number";
1406
+ if (/^-?\d/.test(v)) return "number";
1407
+ if (/^["'`]/.test(v)) return "string";
1408
+ if (v === "true" || v === "false") return "boolean";
1409
+ if (v === "null") return "null";
1410
+ if (v.startsWith("[")) return "array";
1411
+ if (v.startsWith("{")) return "object";
1412
+ return void 0;
1413
+ }
1414
+ function analyzeSchemaPaths(schema) {
1415
+ const paths = [];
1416
+ const defs = schema.$defs ?? {};
1417
+ collectSchemaObjectPaths(schema, "", paths, defs, 0);
1418
+ return paths;
1419
+ }
1420
+ function mergeSchemaCandidates(candidates, applicationSchema) {
1421
+ if (applicationSchema === void 0) return candidates;
1422
+ const schemaCandidates = [];
1423
+ const schemaKeys = /* @__PURE__ */ new Set();
1424
+ for (const p of analyzeSchemaPaths(applicationSchema)) {
1425
+ schemaCandidates.push(p);
1426
+ schemaKeys.add(p.path);
1427
+ }
1428
+ const kept = candidates.filter((p) => !schemaKeys.has(p.path));
1429
+ return [...kept, ...schemaCandidates];
1430
+ }
1431
+ function derefSchemaNodes(node, defs) {
1432
+ const out = [];
1433
+ const stack = [{ node, chain: /* @__PURE__ */ new Set() }];
1434
+ while (stack.length > 0) {
1435
+ const { node: n, chain } = stack.pop();
1436
+ if (n === null || typeof n !== "object") continue;
1437
+ if (typeof n.$ref === "string") {
1438
+ const match = /^#\/\$defs\/(.+)$/.exec(n.$ref);
1439
+ if (match === null || chain.has(n.$ref)) continue;
1440
+ const target = defs[match[1].replace(/~1/g, "/").replace(/~0/g, "~")];
1441
+ if (target === void 0) continue;
1442
+ stack.push({ node: target, chain: /* @__PURE__ */ new Set([...chain, n.$ref]) });
1443
+ continue;
1444
+ }
1445
+ if (Array.isArray(n.anyOf)) {
1446
+ for (let i = n.anyOf.length - 1; i >= 0; i--) stack.push({ node: n.anyOf[i], chain });
1447
+ continue;
1448
+ }
1449
+ out.push(n);
1450
+ }
1451
+ return out;
1452
+ }
1453
+ function schemaTypeHint(nodes) {
1454
+ const hints = /* @__PURE__ */ new Set();
1455
+ for (const n of nodes) {
1456
+ const types = typeof n.type === "string" ? [n.type] : Array.isArray(n.type) ? n.type : [];
1457
+ if (types.length > 0) {
1458
+ for (const t of types) {
1459
+ if (t === "null") continue;
1460
+ hints.add(t === "integer" ? "number" : t);
1461
+ }
1462
+ continue;
1463
+ }
1464
+ if (Array.isArray(n.enum)) {
1465
+ for (const v of n.enum) {
1466
+ const h = inferJsonTypeHint(v);
1467
+ if (h !== void 0 && h !== "null") hints.add(h);
1468
+ }
1469
+ } else if (n.const !== void 0) {
1470
+ const h = inferJsonTypeHint(n.const);
1471
+ if (h !== void 0 && h !== "null") hints.add(h);
1472
+ } else if (n.properties !== void 0) {
1473
+ hints.add("object");
1474
+ } else if (n.items !== void 0) {
1475
+ hints.add("array");
1476
+ }
1477
+ }
1478
+ return hints.size === 0 ? void 0 : [...hints].join("|");
1479
+ }
1480
+ function collectSchemaObjectPaths(node, prefix, paths, defs, depth) {
1481
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1482
+ const seen = /* @__PURE__ */ new Set();
1483
+ for (const n of derefSchemaNodes(node, defs)) {
1484
+ for (const [key, child] of Object.entries(n.properties ?? {})) {
1485
+ if (prefix === "" && key.startsWith("$")) continue;
1486
+ if (seen.has(key)) continue;
1487
+ seen.add(key);
1488
+ const path = prefix ? `${prefix}.${key}` : key;
1489
+ pushSchemaValuePaths(path, child, paths, defs, depth);
1490
+ }
1491
+ }
1492
+ }
1493
+ function pushSchemaValuePaths(path, node, paths, defs, depth) {
1494
+ const nodes = derefSchemaNodes(node, defs);
1495
+ const typeHint = schemaTypeHint(nodes);
1496
+ paths.push(withHint({ path, kind: "data", fromSchema: true }, typeHint));
1497
+ const items = nodes.map((n) => n.items).find((i) => i !== void 0 && i !== null && typeof i === "object");
1498
+ const isArray = items !== void 0 || (typeHint?.split("|").includes("array") ?? false);
1499
+ if (isArray) {
1500
+ const itemNodes = items !== void 0 ? derefSchemaNodes(items, defs) : [];
1501
+ paths.push(withHint({ path: `${path}.*`, kind: "list", fromSchema: true }, schemaTypeHint(itemNodes)));
1502
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", fromSchema: true });
1503
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1504
+ const seen = /* @__PURE__ */ new Set();
1505
+ for (const n of itemNodes) {
1506
+ for (const [childKey, childNode] of Object.entries(n.properties ?? {})) {
1507
+ if (seen.has(childKey)) continue;
1508
+ seen.add(childKey);
1509
+ pushSchemaValuePaths(`${path}.*.${childKey}`, childNode, paths, defs, depth + 1);
1510
+ }
1511
+ }
1512
+ return;
1513
+ }
1514
+ if (nodes.some((n) => n.properties !== void 0)) {
1515
+ collectSchemaObjectPaths(node, path, paths, defs, depth + 1);
1516
+ }
1517
+ }
1518
+ function withHint(candidate, typeHint) {
1519
+ return typeHint === void 0 ? candidate : { ...candidate, typeHint };
1520
+ }
1521
+
1522
+ // src/language/htmlParse.ts
1523
+ function parseWcsScriptBlocks(html, stateTagName = "wcs-state") {
1524
+ const blocks = [];
1525
+ let pos = 0;
1526
+ const len = html.length;
1527
+ while (pos < len) {
1528
+ if (html.startsWith("<!--", pos)) {
1529
+ const commentEnd = html.indexOf("-->", pos + 4);
1530
+ if (commentEnd === -1) break;
1531
+ pos = commentEnd + 3;
1532
+ continue;
1533
+ }
1534
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
1535
+ if (wcsMatch === null) {
1536
+ pos++;
1537
+ continue;
1538
+ }
1539
+ const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
1540
+ pos = wcsMatch.end;
1541
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
1542
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
1543
+ while (pos < wcsEnd) {
1544
+ if (html.startsWith("<!--", pos)) {
1545
+ const commentEnd = html.indexOf("-->", pos + 4);
1546
+ if (commentEnd === -1) break;
1547
+ pos = commentEnd + 3;
1548
+ continue;
1549
+ }
1550
+ const scriptMatch = matchOpenTag(html, pos, "script");
1551
+ if (scriptMatch === null) {
1552
+ pos++;
1553
+ continue;
1554
+ }
1555
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1556
+ if (typeAttr?.toLowerCase() !== "module") {
1557
+ pos = scriptMatch.end;
1558
+ continue;
1559
+ }
1560
+ const contentStart = scriptMatch.end;
1561
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1562
+ if (scriptCloseIdx === -1) {
1563
+ pos = contentStart;
1564
+ break;
1565
+ }
1566
+ const contentEnd = scriptCloseIdx;
1567
+ blocks.push({
1568
+ contentStart,
1569
+ contentEnd,
1570
+ content: html.slice(contentStart, contentEnd),
1571
+ mountPath
1572
+ });
1573
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
1574
+ if (pos === 0) break;
1575
+ }
1576
+ pos = wcsEnd;
1577
+ if (wcsCloseIdx !== -1) {
1578
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
1579
+ if (closeEnd !== -1) pos = closeEnd + 1;
1580
+ }
1581
+ }
1582
+ return blocks;
1583
+ }
1584
+ function parseWcsStateElements(html, stateTagName = "wcs-state") {
1585
+ const elements = [];
1586
+ let pos = 0;
1587
+ const len = html.length;
1588
+ while (pos < len) {
1589
+ if (html.startsWith("<!--", pos)) {
1590
+ const commentEnd = html.indexOf("-->", pos + 4);
1591
+ if (commentEnd === -1) break;
1592
+ pos = commentEnd + 3;
1593
+ continue;
1594
+ }
1595
+ const wcsMatch = matchOpenTag(html, pos, stateTagName);
1596
+ if (wcsMatch === null) {
1597
+ pos++;
1598
+ continue;
1599
+ }
1600
+ const mountPath = extractAttribute(wcsMatch.tagContent, "mount");
1601
+ const jsonAttr = extractAttribute(wcsMatch.tagContent, "json") ?? void 0;
1602
+ const stateAttr = extractAttribute(wcsMatch.tagContent, "state") ?? void 0;
1603
+ const srcAttr = extractAttribute(wcsMatch.tagContent, "src") ?? void 0;
1604
+ const tagStart = pos;
1605
+ const tagEnd = wcsMatch.end;
1606
+ pos = wcsMatch.end;
1607
+ const scriptBlocks = [];
1608
+ const wcsCloseIdx = findCloseTag(html, pos, stateTagName);
1609
+ const wcsEnd = wcsCloseIdx === -1 ? len : wcsCloseIdx;
1610
+ while (pos < wcsEnd) {
1611
+ if (html.startsWith("<!--", pos)) {
1612
+ const commentEnd = html.indexOf("-->", pos + 4);
1613
+ if (commentEnd === -1) break;
1614
+ pos = commentEnd + 3;
1615
+ continue;
1616
+ }
1617
+ const scriptMatch = matchOpenTag(html, pos, "script");
1618
+ if (scriptMatch === null) {
1619
+ pos++;
1620
+ continue;
1621
+ }
1622
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1623
+ if (typeAttr?.toLowerCase() !== "module") {
1624
+ pos = scriptMatch.end;
1625
+ continue;
1626
+ }
1627
+ const contentStart = scriptMatch.end;
1628
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1629
+ if (scriptCloseIdx === -1) {
1630
+ pos = contentStart;
1631
+ break;
1632
+ }
1633
+ scriptBlocks.push({
1634
+ contentStart,
1635
+ contentEnd: scriptCloseIdx,
1636
+ content: html.slice(contentStart, scriptCloseIdx),
1637
+ mountPath
1638
+ });
1639
+ pos = html.indexOf(">", scriptCloseIdx) + 1;
1640
+ if (pos === 0) break;
1641
+ }
1642
+ elements.push({ mountPath, jsonAttr, stateAttr, srcAttr, scriptBlocks, tagStart, tagEnd });
1643
+ pos = wcsEnd;
1644
+ if (wcsCloseIdx !== -1) {
1645
+ const closeEnd = html.indexOf(">", wcsCloseIdx);
1646
+ if (closeEnd !== -1) pos = closeEnd + 1;
1647
+ }
1648
+ }
1649
+ return elements;
1650
+ }
1651
+ function findScriptJsonById(html, id2) {
1652
+ let pos = 0;
1653
+ const len = html.length;
1654
+ while (pos < len) {
1655
+ if (html.startsWith("<!--", pos)) {
1656
+ const commentEnd = html.indexOf("-->", pos + 4);
1657
+ if (commentEnd === -1) break;
1658
+ pos = commentEnd + 3;
1659
+ continue;
1660
+ }
1661
+ const scriptMatch = matchOpenTag(html, pos, "script");
1662
+ if (scriptMatch === null) {
1663
+ pos++;
1664
+ continue;
1665
+ }
1666
+ const typeAttr = extractAttribute(scriptMatch.tagContent, "type");
1667
+ const idAttr = extractAttribute(scriptMatch.tagContent, "id");
1668
+ if (typeAttr?.toLowerCase() === "application/json" && idAttr === id2) {
1669
+ const contentStart = scriptMatch.end;
1670
+ const scriptCloseIdx = findCloseTag(html, contentStart, "script");
1671
+ if (scriptCloseIdx === -1) return null;
1672
+ return html.slice(contentStart, scriptCloseIdx);
1673
+ }
1674
+ pos = scriptMatch.end;
1675
+ }
1676
+ return null;
1677
+ }
1678
+ function matchOpenTag(html, pos, tagName) {
1679
+ if (html[pos] !== "<") return null;
1680
+ const nameStart = pos + 1;
1681
+ const nameEnd = nameStart + tagName.length;
1682
+ if (nameEnd > html.length) return null;
1683
+ const slice3 = html.slice(nameStart, nameEnd);
1684
+ if (slice3.toLowerCase() !== tagName.toLowerCase()) return null;
1685
+ const charAfter = html[nameEnd];
1686
+ if (charAfter !== ">" && charAfter !== " " && charAfter !== " " && charAfter !== "\n" && charAfter !== "\r" && charAfter !== "/") {
1599
1687
  return null;
1600
- }).filter((p) => p !== null);
1601
- if (normalized.length === 0) return void 0;
1602
- const unique = [...new Set(normalized)].sort();
1603
- return unique.join("|");
1688
+ }
1689
+ let i = nameEnd;
1690
+ let inSingleQuote = false;
1691
+ let inDoubleQuote = false;
1692
+ while (i < html.length) {
1693
+ const ch = html[i];
1694
+ if (inSingleQuote) {
1695
+ if (ch === "'") inSingleQuote = false;
1696
+ } else if (inDoubleQuote) {
1697
+ if (ch === '"') inDoubleQuote = false;
1698
+ } else if (ch === "'") {
1699
+ inSingleQuote = true;
1700
+ } else if (ch === '"') {
1701
+ inDoubleQuote = true;
1702
+ } else if (ch === ">") {
1703
+ return {
1704
+ start: pos,
1705
+ end: i + 1,
1706
+ tagContent: html.slice(nameEnd, i)
1707
+ };
1708
+ }
1709
+ i++;
1710
+ }
1711
+ return null;
1604
1712
  }
1605
- function isEscaped(text, i) {
1606
- let backslashCount = 0;
1607
- let j = i - 1;
1608
- while (j >= 0 && text[j] === "\\") {
1609
- backslashCount++;
1610
- j--;
1713
+ function findCloseTag(html, startPos, tagName) {
1714
+ const pattern = "</" + tagName;
1715
+ const patternLower = pattern.toLowerCase();
1716
+ const htmlLower = html.toLowerCase();
1717
+ let pos = startPos;
1718
+ while (pos < html.length) {
1719
+ const idx = htmlLower.indexOf(patternLower, pos);
1720
+ if (idx === -1) return -1;
1721
+ const afterIdx = idx + pattern.length;
1722
+ if (afterIdx < html.length) {
1723
+ const ch = html[afterIdx];
1724
+ if (ch === ">" || ch === " " || ch === " " || ch === "\n" || ch === "\r") {
1725
+ return idx;
1726
+ }
1727
+ }
1728
+ pos = idx + 1;
1611
1729
  }
1612
- return backslashCount % 2 === 1;
1730
+ return -1;
1613
1731
  }
1614
- function inferTypeHint(valueStart) {
1615
- const v = valueStart.trim().replace(/,\s*$/, "");
1616
- if (/^-?\d+\.\d/.test(v)) return "number";
1617
- if (/^-?\d/.test(v)) return "number";
1618
- if (/^["'`]/.test(v)) return "string";
1619
- if (v === "true" || v === "false") return "boolean";
1620
- if (v === "null") return "null";
1621
- if (v.startsWith("[")) return "array";
1622
- if (v.startsWith("{")) return "object";
1623
- return void 0;
1732
+ function extractAttribute(tagContent, attrName) {
1733
+ const regex = new RegExp(
1734
+ `(?:^|\\s)${attrName}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`,
1735
+ "i"
1736
+ );
1737
+ const match = tagContent.match(regex);
1738
+ if (!match) return null;
1739
+ return match[1] ?? match[2] ?? match[3] ?? null;
1624
1740
  }
1625
1741
 
1626
1742
  // src/service/statePathResolver.ts
@@ -1634,33 +1750,45 @@ function getStatePathsFromHtml(html, stateTagName = "wcs-state", fileReader) {
1634
1750
  return allPaths;
1635
1751
  }
1636
1752
  function resolveElementPaths(element, html, fileReader) {
1753
+ const raw = resolveElementPathsRaw(element, html, fileReader);
1754
+ if (element.mountPath === null) return raw;
1755
+ const prefix = element.mountPath + ".";
1756
+ const out = [];
1757
+ for (const p of raw) {
1758
+ if (p.path.startsWith("$")) continue;
1759
+ if (p.kind === "method" || p.kind === "eventToken") continue;
1760
+ out.push({ ...p, path: prefix + p.path });
1761
+ }
1762
+ return out;
1763
+ }
1764
+ function resolveElementPathsRaw(element, html, fileReader) {
1637
1765
  if (element.stateAttr) {
1638
1766
  const jsonContent = findScriptJsonById(html, element.stateAttr);
1639
1767
  if (jsonContent) {
1640
- const paths = analyzeJsonPaths(jsonContent, element.stateName);
1768
+ const paths = analyzeJsonPaths(jsonContent);
1641
1769
  if (paths.length > 0) return paths;
1642
1770
  }
1643
1771
  }
1644
1772
  if (element.srcAttr && fileReader) {
1645
- const paths = resolveSrcAttribute(element.srcAttr, element.stateName, fileReader);
1773
+ const paths = resolveSrcAttribute(element.srcAttr, fileReader);
1646
1774
  if (paths.length > 0) return paths;
1647
1775
  }
1648
1776
  if (element.jsonAttr) {
1649
- const paths = analyzeJsonPaths(element.jsonAttr, element.stateName);
1777
+ const paths = analyzeJsonPaths(element.jsonAttr);
1650
1778
  if (paths.length > 0) return paths;
1651
1779
  }
1652
1780
  if (element.scriptBlocks.length > 0) {
1653
1781
  return element.scriptBlocks.flatMap(
1654
- (block) => analyzeStatePaths(block.content, block.stateName)
1782
+ (block) => analyzeStatePaths(block.content)
1655
1783
  );
1656
1784
  }
1657
1785
  return [];
1658
1786
  }
1659
- function resolveSrcAttribute(srcPath, stateName, fileReader) {
1787
+ function resolveSrcAttribute(srcPath, fileReader) {
1660
1788
  if (srcPath.endsWith(".json")) {
1661
1789
  const content = fileReader(srcPath);
1662
1790
  if (content) {
1663
- return analyzeJsonPaths(content, stateName);
1791
+ return analyzeJsonPaths(content);
1664
1792
  }
1665
1793
  return [];
1666
1794
  }
@@ -1668,18 +1796,18 @@ function resolveSrcAttribute(srcPath, stateName, fileReader) {
1668
1796
  const tsPath = srcPath.replace(/\.js$/, ".ts");
1669
1797
  const tsContent = fileReader(tsPath);
1670
1798
  if (tsContent) {
1671
- return analyzeStatePaths(tsContent, stateName);
1799
+ return analyzeStatePaths(tsContent);
1672
1800
  }
1673
1801
  const jsContent = fileReader(srcPath);
1674
1802
  if (jsContent) {
1675
- return analyzeStatePaths(jsContent, stateName);
1803
+ return analyzeStatePaths(jsContent);
1676
1804
  }
1677
1805
  return [];
1678
1806
  }
1679
1807
  if (srcPath.endsWith(".ts")) {
1680
1808
  const content = fileReader(srcPath);
1681
1809
  if (content) {
1682
- return analyzeStatePaths(content, stateName);
1810
+ return analyzeStatePaths(content);
1683
1811
  }
1684
1812
  return [];
1685
1813
  }
@@ -1812,6 +1940,8 @@ var ja = {
1812
1940
  commandTokenUndeclared: (t) => `\u30B3\u30DE\u30F3\u30C9\u30C8\u30FC\u30AF\u30F3 "${t}" \u306F $commandTokens \u306B\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u305B\u3093`,
1813
1941
  streamPathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F $streams \u5BA3\u8A00\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
1814
1942
  pathMissing: (p) => `\u30D1\u30B9 "${p}" \u306F\u72B6\u614B\u5B9A\u7FA9\u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
1943
+ pathNonexistent: (p) => `\u30D1\u30B9 "${p}" \u306F\u5BA3\u8A00\u3055\u308C\u305F stateSchema \u306B\u5B58\u5728\u3057\u307E\u305B\u3093`,
1944
+ pathTypeMismatch: (p, label, expected, actual) => `\u30D1\u30B9 "${p}" \u306F stateSchema \u4E0A\u3067 ${actual} \u578B\u3067\u3059\u304C\u3001${label} \u306B\u306F${JA_EXPECTED_LABEL[expected]}\u304C\u5FC5\u8981\u3067\u3059`,
1815
1945
  expansionSuffix: (x) => `\uFF08\u5C55\u958B: ${x}\uFF09`,
1816
1946
  patternPathOutsideFor: (p) => `\u30D1\u30BF\u30FC\u30F3\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
1817
1947
  omittedPathOutsideFor: (p) => `\u7701\u7565\u30D1\u30B9 "${p}" \u306F <template for> \u306E\u5916\u5074\u3067\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093`,
@@ -1851,7 +1981,21 @@ var ja = {
1851
1981
  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\u521D\u671F\u66F8\u304D\u623B\u3057\u304C\u4FDD\u5B58\u5024\u3092\u4E0A\u66F8\u304D\u3057\u307E\u3059 \u2014 undefined \u3067\u30B7\u30FC\u30C9\uFF08\`${path}: undefined\`\uFF09\u3059\u308B\u304B manual \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044`,
1852
1982
  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`,
1853
1983
  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`,
1854
- 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`
1984
+ 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`,
1985
+ 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`,
1986
+ 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`,
1987
+ mountPathInvalid: (problem, mountPath) => {
1988
+ switch (problem) {
1989
+ case "empty":
1990
+ return `"mount" \u306B\u306F\u7A7A\u3067\u306A\u3044\u30C4\u30EA\u30FC\u30D1\u30B9\u304C\u5FC5\u8981\u3067\u3059\uFF08runtime: "mount" requires a non-empty tree path.\uFF09`;
1991
+ case "emptySegment":
1992
+ return `"mount" \u30D1\u30B9 "${mountPath}" \u306B\u7A7A\u306E\u30BB\u30B0\u30E1\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\uFF08runtime: has an empty segment.\uFF09`;
1993
+ case "wildcard":
1994
+ return `"mount" \u30D1\u30B9 "${mountPath}" \u306F\u9759\u7684\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093 \u2014 \u30EF\u30A4\u30EB\u30C9\u30AB\u30FC\u30C9\u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08runtime: must be static.\uFF09`;
1995
+ default:
1996
+ return `"mount" \u30D1\u30B9 "${mountPath}" \u306B\u4E88\u7D04\u6587\u5B57\uFF08$, #, @\uFF09\u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08runtime: must not use reserved characters.\uFF09`;
1997
+ }
1998
+ }
1855
1999
  };
1856
2000
  var EN_EXPECTED_LABEL = {
1857
2001
  array: "an array-typed path",
@@ -1867,6 +2011,8 @@ var en = {
1867
2011
  commandTokenUndeclared: (t) => `Command token "${t}" is not declared in $commandTokens`,
1868
2012
  streamPathMissing: (p) => `Path "${p}" does not exist in the $streams declaration`,
1869
2013
  pathMissing: (p) => `Path "${p}" does not exist in the state definition`,
2014
+ pathNonexistent: (p) => `Path "${p}" does not exist in the declared stateSchema`,
2015
+ pathTypeMismatch: (p, label, expected, actual) => `Path "${p}" is ${actual} in the stateSchema, but ${label} requires ${expected === "array" ? "an array" : expected === "boolean" ? "a boolean" : "a string"}`,
1870
2016
  expansionSuffix: (x) => ` (expanded: ${x})`,
1871
2017
  patternPathOutsideFor: (p) => `Pattern path "${p}" cannot be used outside a <template for>`,
1872
2018
  omittedPathOutsideFor: (p) => `Shorthand path "${p}" cannot be used outside a <template for>`,
@@ -1906,25 +2052,225 @@ var en = {
1906
2052
  storageSeedClobber: (path, raw) => `The <wcs-storage> value-bound slot "${path}" is seeded with ${raw}. The initial write-back overwrites the persisted value \u2014 seed it with undefined (\`${path}: undefined\`) or add manual`,
1907
2053
  devtoolsAfterState: () => `Load @wcstack/devtools/auto BEFORE @wcstack/state/auto (otherwise the wiring ledger is not captured live)`,
1908
2054
  baseHrefMissing: () => `An SPA using @wcstack/router needs <base href="/"> in <head> (without it, deep links misderive the basename)`,
1909
- 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`
2055
+ 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`,
2056
+ 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)`,
2057
+ 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)`,
2058
+ mountPathInvalid: (problem, mountPath) => {
2059
+ switch (problem) {
2060
+ case "empty":
2061
+ return `"mount" requires a non-empty tree path.`;
2062
+ case "emptySegment":
2063
+ return `"mount" path "${mountPath}" has an empty segment.`;
2064
+ case "wildcard":
2065
+ return `"mount" path "${mountPath}" must be static (wildcards are not allowed).`;
2066
+ default:
2067
+ return `"mount" path "${mountPath}" must not use reserved characters ($, #, @).`;
2068
+ }
2069
+ }
1910
2070
  };
1911
2071
  var CATALOGS = { ja, en };
1912
2072
  function getMessages(locale3) {
1913
2073
  return CATALOGS[resolveLocale(locale3)];
1914
2074
  }
1915
2075
 
2076
+ // src/core/sidecar/schemaSubset.ts
2077
+ var ALLOWED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
2078
+ "type",
2079
+ "properties",
2080
+ "required",
2081
+ "items",
2082
+ "enum",
2083
+ "const",
2084
+ "anyOf",
2085
+ "$defs",
2086
+ "$ref"
2087
+ ]);
2088
+ var DiagnosticContext = class {
2089
+ constructor(spans) {
2090
+ this.spans = spans;
2091
+ }
2092
+ diagnostics = [];
2093
+ add(code, pointer2, message, severity, extra = {}, useKeySpan = false) {
2094
+ const span = this.spans.get(pointer2);
2095
+ const start = span === void 0 ? 0 : useKeySpan ? span.keyStart ?? span.start : span.start;
2096
+ const end = span === void 0 ? 0 : useKeySpan ? span.keyEnd ?? span.end : span.end;
2097
+ this.diagnostics.push({ code, start, end, message, severity, ...extra });
2098
+ }
2099
+ };
2100
+ function isSchemaObject(value) {
2101
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2102
+ }
2103
+ function isSchemaMap(value) {
2104
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2105
+ }
2106
+ function validateSchemaSubset(schema, pointerBase, ctx, rootDefs) {
2107
+ walkKeywords(schema, pointerBase, ctx, rootDefs);
2108
+ const safe = /* @__PURE__ */ new Set();
2109
+ detectCycles(schema, pointerBase, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
2110
+ for (const [name, def] of Object.entries(rootDefs)) {
2111
+ detectCycles(def, `${pointerBase}/$defs/${escape(name)}`, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
2112
+ }
2113
+ }
2114
+ function walkKeywords(node, ptr, ctx, rootDefs) {
2115
+ if (!isSchemaObject(node)) return;
2116
+ for (const keyword of Object.keys(node)) {
2117
+ if (!ALLOWED_SCHEMA_KEYWORDS.has(keyword)) {
2118
+ ctx.add(
2119
+ WcsDiagnosticCode.ManifestUnknownKeyword,
2120
+ `${ptr}/${escape(keyword)}`,
2121
+ `Unsupported schema keyword "${keyword}". Allowed: ${[...ALLOWED_SCHEMA_KEYWORDS].join(", ")}.`,
2122
+ "warning",
2123
+ {},
2124
+ true
2125
+ );
2126
+ }
2127
+ }
2128
+ if (typeof node.$ref === "string") {
2129
+ if (!node.$ref.startsWith("#/")) {
2130
+ ctx.add(
2131
+ WcsDiagnosticCode.ManifestExternalRef,
2132
+ `${ptr}/$ref`,
2133
+ `External $ref "${node.$ref}" is forbidden; only local "#/$defs/..." references are allowed.`,
2134
+ "error"
2135
+ );
2136
+ } else if (resolveLocalRef(node.$ref, rootDefs) === void 0) {
2137
+ ctx.add(
2138
+ WcsDiagnosticCode.ManifestRefUnresolved,
2139
+ `${ptr}/$ref`,
2140
+ `Unresolved local $ref "${node.$ref}".`,
2141
+ "error"
2142
+ );
2143
+ }
2144
+ }
2145
+ if (isSchemaMap(node.properties)) {
2146
+ for (const [name, child] of Object.entries(node.properties)) {
2147
+ walkKeywords(child, `${ptr}/properties/${escape(name)}`, ctx, rootDefs);
2148
+ }
2149
+ }
2150
+ if (node.items !== void 0 && isSchemaObject(node.items)) {
2151
+ walkKeywords(node.items, `${ptr}/items`, ctx, rootDefs);
2152
+ }
2153
+ if (Array.isArray(node.anyOf)) {
2154
+ node.anyOf.forEach((child, i) => walkKeywords(child, `${ptr}/anyOf/${i}`, ctx, rootDefs));
2155
+ }
2156
+ if (isSchemaMap(node.$defs)) {
2157
+ for (const [name, child] of Object.entries(node.$defs)) {
2158
+ walkKeywords(child, `${ptr}/$defs/${escape(name)}`, ctx, rootDefs);
2159
+ }
2160
+ }
2161
+ }
2162
+ function detectCycles(node, ptr, ctx, rootDefs, refStack, safe) {
2163
+ if (!isSchemaObject(node)) return;
2164
+ if (typeof node.$ref === "string") {
2165
+ const ref = node.$ref;
2166
+ if (!ref.startsWith("#/")) return;
2167
+ if (refStack.has(ref)) {
2168
+ ctx.add(WcsDiagnosticCode.ManifestRefCycle, `${ptr}/$ref`, `Cyclic $ref detected at "${ref}".`, "error");
2169
+ return;
2170
+ }
2171
+ if (safe.has(ref)) return;
2172
+ const target = resolveLocalRef(ref, rootDefs);
2173
+ if (target === void 0) return;
2174
+ refStack.add(ref);
2175
+ detectCycles(target, ptr, ctx, rootDefs, refStack, safe);
2176
+ refStack.delete(ref);
2177
+ safe.add(ref);
2178
+ return;
2179
+ }
2180
+ if (isSchemaMap(node.properties)) {
2181
+ for (const child of Object.values(node.properties)) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
2182
+ }
2183
+ if (node.items !== void 0 && isSchemaObject(node.items)) {
2184
+ detectCycles(node.items, ptr, ctx, rootDefs, refStack, safe);
2185
+ }
2186
+ if (Array.isArray(node.anyOf)) {
2187
+ for (const child of node.anyOf) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
2188
+ }
2189
+ }
2190
+ function resolveLocalRef(ref, rootDefs) {
2191
+ const match = /^#\/\$defs\/(.+)$/.exec(ref);
2192
+ if (match === null) return void 0;
2193
+ const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
2194
+ return rootDefs[name];
2195
+ }
2196
+ function resolveSchemaPath(root, rootDefs, segments) {
2197
+ let current = root;
2198
+ for (let depth = 0; depth < segments.length; depth++) {
2199
+ const segment = segments[depth];
2200
+ const resolved = derefUnion(current, rootDefs);
2201
+ if (resolved.kind === "ref-error") return resolved;
2202
+ const candidates = resolved.nodes;
2203
+ if (segment === "*") {
2204
+ const items = firstDefined(candidates, (n) => isSchemaObject(n.items) ? n.items : void 0);
2205
+ if (items === void 0) {
2206
+ return { kind: "unknown" };
2207
+ }
2208
+ current = items;
2209
+ continue;
2210
+ }
2211
+ if (segment === "length" && candidates.some((n) => hasType(n, "array"))) {
2212
+ current = { type: "number" };
2213
+ continue;
2214
+ }
2215
+ const child = firstDefined(candidates, (n) => isSchemaMap(n.properties) ? n.properties[segment] : void 0);
2216
+ if (child !== void 0) {
2217
+ current = child;
2218
+ continue;
2219
+ }
2220
+ const anyObject = candidates.some((n) => hasType(n, "object") || isSchemaMap(n.properties));
2221
+ if (anyObject) {
2222
+ return { kind: "nonexistent", segment, depth };
2223
+ }
2224
+ return { kind: "unknown" };
2225
+ }
2226
+ const final = derefUnion(current, rootDefs);
2227
+ if (final.kind === "ref-error") return final;
2228
+ return { kind: "resolved", schema: final.nodes.length === 1 ? final.nodes[0] : current };
2229
+ }
2230
+ function derefUnion(node, rootDefs) {
2231
+ const out = [];
2232
+ const stack = [{ node, chain: /* @__PURE__ */ new Set() }];
2233
+ while (stack.length > 0) {
2234
+ const { node: n, chain } = stack.pop();
2235
+ if (typeof n.$ref === "string") {
2236
+ if (!n.$ref.startsWith("#/") || chain.has(n.$ref)) {
2237
+ return { kind: "ref-error", ref: n.$ref };
2238
+ }
2239
+ const target = resolveLocalRef(n.$ref, rootDefs);
2240
+ if (target === void 0) return { kind: "ref-error", ref: n.$ref };
2241
+ stack.push({ node: target, chain: /* @__PURE__ */ new Set([...chain, n.$ref]) });
2242
+ continue;
2243
+ }
2244
+ if (Array.isArray(n.anyOf)) {
2245
+ for (const branch of n.anyOf) stack.push({ node: branch, chain });
2246
+ continue;
2247
+ }
2248
+ out.push(n);
2249
+ }
2250
+ return { kind: "ok", nodes: out };
2251
+ }
2252
+ function firstDefined(nodes, pick) {
2253
+ for (const n of nodes) {
2254
+ const v = pick(n);
2255
+ if (v !== void 0) return v;
2256
+ }
2257
+ return void 0;
2258
+ }
2259
+ function hasType(node, t) {
2260
+ const type = node.type;
2261
+ if (type === void 0) return false;
2262
+ return Array.isArray(type) ? type.includes(t) : type === t;
2263
+ }
2264
+ function escape(key) {
2265
+ return key.replace(/~/g, "~0").replace(/\//g, "~1");
2266
+ }
2267
+
1916
2268
  // src/service/bindingValidator.ts
1917
2269
  var filterMap = new Map(BUILTIN_FILTERS.map((f) => [f.name, f]));
1918
- function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader) {
2270
+ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, fileReader, applicationSchema) {
1919
2271
  const diagnostics = [];
1920
2272
  const msgs = getMessages(locale3);
1921
- const statePaths = getStatePathsFromHtml(html, stateTagName, fileReader);
1922
- const pathsByState = /* @__PURE__ */ new Map();
1923
- for (const p of statePaths) {
1924
- const list = pathsByState.get(p.stateName) ?? [];
1925
- list.push(p);
1926
- pathsByState.set(p.stateName, list);
1927
- }
2273
+ const statePaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
1928
2274
  const attrs = findAllBindAttributes(html, attrName);
1929
2275
  let structuralTemplates = null;
1930
2276
  const getStructuralTemplates = () => {
@@ -1957,7 +2303,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
1957
2303
  for (const binding of bindings) {
1958
2304
  const bindingStart = attr.valueStart + pos;
1959
2305
  const parsed = parseBindingExpression(binding);
1960
- const scopedPaths = pathsByState.get(parsed.targetState) ?? [];
2306
+ const scopedPaths = statePaths;
1961
2307
  const scopedPathSet = new Set(scopedPaths.map((p) => p.path));
1962
2308
  const propNoMod = parsed.property.replace(/#.*$/, "").trim();
1963
2309
  if (propNoMod === "...") {
@@ -2041,16 +2387,17 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
2041
2387
  }
2042
2388
  }
2043
2389
  if (checkPath) {
2044
- const message = validatePathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, msgs);
2045
- if (message) {
2390
+ const schema = applicationSchema;
2391
+ const verdict = schema !== void 0 ? validateSchemaPathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, schema, msgs) : toMissingVerdict(validatePathExistence(checkPath, pathTrimmed, scopedPaths, scopedPathSet, commandNames, msgs));
2392
+ if (verdict) {
2046
2393
  const pathOffset = binding.indexOf(parsed.path);
2047
2394
  const pathStart = bindingStart + pathOffset;
2048
2395
  diagnostics.push({
2049
- code: WcsDiagnosticCode.BindingPathMissing,
2396
+ code: verdict.code,
2050
2397
  start: pathStart,
2051
2398
  end: pathStart + pathTrimmed.length,
2052
- message: `${message}${pathTrimmed.startsWith(".") ? msgs.expansionSuffix(checkPath) : ""}`,
2053
- severity: "warning"
2399
+ message: `${verdict.message}${pathTrimmed.startsWith(".") ? msgs.expansionSuffix(checkPath) : ""}`,
2400
+ severity: verdict.severity
2054
2401
  });
2055
2402
  }
2056
2403
  }
@@ -2169,12 +2516,13 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale3, f
2169
2516
  if (typeReq && resultType !== typeReq.expected) {
2170
2517
  const pathOffset = binding.indexOf(parsed.path);
2171
2518
  const pathStart = bindingStart + pathOffset;
2519
+ const schemaDefinite = typeReq.expected === "array" && parsed.filters.length === 0 && applicationSchema !== void 0 && scopedPaths.some((p) => p.path === pathTrimmed && p.fromSchema === true);
2172
2520
  diagnostics.push({
2173
- code: WcsDiagnosticCode.BindingTypeExpectation,
2521
+ code: schemaDefinite ? WcsDiagnosticCode.PathTypeMismatch : WcsDiagnosticCode.BindingTypeExpectation,
2174
2522
  start: pathStart,
2175
2523
  end: pathStart + pathTrimmed.length,
2176
- message: msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
2177
- severity: typeReq.severity
2524
+ message: schemaDefinite ? msgs.pathTypeMismatch(pathTrimmed, typeReq.label, typeReq.expected, resultType) : msgs.typeExpectation(typeReq.label, typeReq.expected, resultType),
2525
+ severity: schemaDefinite ? "error" : typeReq.severity
2178
2526
  });
2179
2527
  }
2180
2528
  }
@@ -2222,7 +2570,7 @@ function splitBindingExpressions(value) {
2222
2570
  function parseBindingExpression(expr) {
2223
2571
  const colonIndex = expr.indexOf(":");
2224
2572
  if (colonIndex === -1) {
2225
- return { property: expr.trim(), path: null, targetState: "default", filters: [], inputFilters: [] };
2573
+ return { property: expr.trim(), path: null, filters: [], inputFilters: [] };
2226
2574
  }
2227
2575
  const rawProp = expr.slice(0, colonIndex);
2228
2576
  const propSegments = splitByPipe(rawProp);
@@ -2234,9 +2582,8 @@ function parseBindingExpression(expr) {
2234
2582
  const filterSegments = segments.slice(1);
2235
2583
  const atIndex = pathSegment.indexOf("@");
2236
2584
  const path = atIndex !== -1 ? pathSegment.slice(0, atIndex) : pathSegment;
2237
- const targetState = atIndex !== -1 ? pathSegment.slice(atIndex + 1).trim() || "default" : "default";
2238
2585
  const filters = parseFilterSegments(expr, filterSegments, colonIndex + 1 + pathSegment.length + 1);
2239
- return { property, path: path.trim() || null, targetState, filters, inputFilters };
2586
+ return { property, path: path.trim() || null, filters, inputFilters };
2240
2587
  }
2241
2588
  function parseFilterSegments(expr, segments, searchStart) {
2242
2589
  const filters = [];
@@ -2345,6 +2692,20 @@ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSe
2345
2692
  }
2346
2693
  return null;
2347
2694
  }
2695
+ function toMissingVerdict(message) {
2696
+ return message ? { code: WcsDiagnosticCode.BindingPathMissing, message, severity: "warning" } : null;
2697
+ }
2698
+ function validateSchemaPathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, schema, msgs) {
2699
+ if (checkPath.startsWith("$")) {
2700
+ return toMissingVerdict(validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSet, commandNames, msgs));
2701
+ }
2702
+ if (scopedPathSet.has(checkPath)) return null;
2703
+ const resolution = resolveSchemaPath(schema, schema.$defs ?? {}, checkPath.split("."));
2704
+ if (resolution.kind === "nonexistent") {
2705
+ return { code: WcsDiagnosticCode.PathNonexistent, message: msgs.pathNonexistent(displayPath), severity: "error" };
2706
+ }
2707
+ return null;
2708
+ }
2348
2709
  function collectStructuralTemplates(html, attrName) {
2349
2710
  const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2350
2711
  const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
@@ -2746,12 +3107,21 @@ function isInsideTag(html, offset, tagName) {
2746
3107
  }
2747
3108
 
2748
3109
  // src/service/templateSyntaxValidator.ts
2749
- function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader) {
3110
+ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", locale3, fileReader, applicationSchema) {
2750
3111
  const diagnostics = [];
2751
3112
  const msgs = getMessages(locale3);
2752
- const allPaths = getStatePathsFromHtml(html, stateTagName, fileReader);
3113
+ const allPaths = mergeSchemaCandidates(getStatePathsFromHtml(html, stateTagName, fileReader), applicationSchema);
3114
+ const defaultSchema = applicationSchema;
3115
+ const missingVerdict = (path, displayPath, pathSet2, scoped) => {
3116
+ if (isValidTemplatePath(path, pathSet2, scoped)) return null;
3117
+ if (defaultSchema !== void 0 && !path.startsWith("$")) {
3118
+ const resolution = resolveSchemaPath(defaultSchema, defaultSchema.$defs ?? {}, path.split("."));
3119
+ return resolution.kind === "nonexistent" ? { code: WcsDiagnosticCode.PathNonexistent, severity: "error", message: msgs.pathNonexistent(displayPath) } : null;
3120
+ }
3121
+ return { code: WcsDiagnosticCode.BindingPathMissing, severity: "warning", message: msgs.pathMissing(displayPath) };
3122
+ };
2753
3123
  if (allPaths.length === 0) return diagnostics;
2754
- const defaultPaths = allPaths.filter((p) => p.stateName === "default");
3124
+ const defaultPaths = allPaths;
2755
3125
  const pathSet = new Set(defaultPaths.map((p) => p.path));
2756
3126
  const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
2757
3127
  const mustaches = findAllMustacheSyntax(html);
@@ -2777,9 +3147,8 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
2777
3147
  }
2778
3148
  if (!item.expression) continue;
2779
3149
  const parts = item.expression.split("|");
2780
- let pathPart = (parts[0] || "").trim();
2781
- const atIdx = pathPart.indexOf("@");
2782
- if (atIdx !== -1) pathPart = pathPart.slice(0, atIdx).trim();
3150
+ const pathPart = (parts[0] || "").trim();
3151
+ if (pathPart.includes("@")) continue;
2783
3152
  const insideFor = item.insideTemplate && isInsideForTemplate(html, item.matchStart, bindAttrName);
2784
3153
  if (pathPart && !/^-?\d|^["'`]|^true$|^false$|^null$/.test(pathPart)) {
2785
3154
  if (!insideFor && pathPart.includes("*")) {
@@ -2800,7 +3169,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
2800
3169
  severity: "warning"
2801
3170
  });
2802
3171
  }
2803
- if (insideFor && !pathPart.startsWith(".") && !pathPart.includes("@")) {
3172
+ if (insideFor && !pathPart.startsWith(".")) {
2804
3173
  const indexMatch = /^\$(\d+)$/.exec(pathPart);
2805
3174
  const needed = indexMatch !== null ? Number(indexMatch[1]) : pathPart.includes("*") ? countWildcardSegments(pathPart) : 0;
2806
3175
  if (needed > 0) {
@@ -2829,24 +3198,28 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
2829
3198
  const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
2830
3199
  if (forPath && !forPath.startsWith(".")) {
2831
3200
  const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
2832
- if (!isValidTemplatePath(expandedPath, pathSet, defaultPaths)) {
3201
+ const verdict = missingVerdict(expandedPath, pathPart, pathSet, defaultPaths);
3202
+ if (verdict) {
2833
3203
  diagnostics.push({
2834
- code: WcsDiagnosticCode.BindingPathMissing,
3204
+ code: verdict.code,
2835
3205
  start: item.exprStart,
2836
3206
  end: item.exprStart + pathPart.length,
2837
- message: msgs.pathMissing(pathPart) + msgs.expansionSuffix(expandedPath),
2838
- severity: "warning"
3207
+ message: verdict.message + msgs.expansionSuffix(expandedPath),
3208
+ severity: verdict.severity
2839
3209
  });
2840
3210
  }
2841
3211
  }
2842
- } else if (!isValidTemplatePath(pathPart, pathSet, defaultPaths)) {
2843
- diagnostics.push({
2844
- code: WcsDiagnosticCode.BindingPathMissing,
2845
- start: item.exprStart,
2846
- end: item.exprStart + pathPart.length,
2847
- message: msgs.pathMissing(pathPart),
2848
- severity: "warning"
2849
- });
3212
+ } else {
3213
+ const verdict = missingVerdict(pathPart, pathPart, pathSet, defaultPaths);
3214
+ if (verdict) {
3215
+ diagnostics.push({
3216
+ code: verdict.code,
3217
+ start: item.exprStart,
3218
+ end: item.exprStart + pathPart.length,
3219
+ message: verdict.message,
3220
+ severity: verdict.severity
3221
+ });
3222
+ }
2850
3223
  }
2851
3224
  }
2852
3225
  for (let i = 1; i < parts.length; i++) {
@@ -3732,11 +4105,14 @@ var BUILTIN_TAGS = {
3732
4105
  "wcs-raf": {
3733
4106
  "package": "raf",
3734
4107
  "hasWcBindable": true,
3735
- "observedAttributes": [],
4108
+ "observedAttributes": [
4109
+ "reduced-motion"
4110
+ ],
3736
4111
  "inputs": {
3737
4112
  "once": "once",
3738
4113
  "repeat": "repeat",
3739
4114
  "manual": "manual",
4115
+ "reducedMotion": "reduced-motion",
3740
4116
  "trigger": null
3741
4117
  },
3742
4118
  "properties": [
@@ -4255,7 +4631,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
4255
4631
  return;
4256
4632
  }
4257
4633
  if (property === "trigger" && "trigger" in contract.inputs && parsed.path) {
4258
- const cand = findDataSlot(getPaths(), parsed.path, parsed.targetState);
4634
+ const cand = findDataSlot(getPaths(), parsed.path);
4259
4635
  if (cand?.rawInitial === "true") {
4260
4636
  diagnostics.push({
4261
4637
  code: WcsDiagnosticCode.TriggerSeededTruthy,
@@ -4269,7 +4645,7 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
4269
4645
  }
4270
4646
  }
4271
4647
  if (tagName === "wcs-storage" && property === "value" && !hasManual && parsed.path && !/(?:^|,)init=(?:element|auto)\b/.test(modifiers)) {
4272
- const cand = findDataSlot(getPaths(), parsed.path, parsed.targetState);
4648
+ const cand = findDataSlot(getPaths(), parsed.path);
4273
4649
  if (cand?.rawInitial !== void 0 && EMPTYISH_SEEDS.has(normalizeSeed(cand.rawInitial))) {
4274
4650
  diagnostics.push({
4275
4651
  code: WcsDiagnosticCode.StorageSeedClobber,
@@ -4283,8 +4659,8 @@ function validateBindingAgainstContract(tagName, contract, parsed, property, sta
4283
4659
  }
4284
4660
  }
4285
4661
  }
4286
- function findDataSlot(paths, path, stateName) {
4287
- return paths.find((c) => c.kind === "data" && c.path === path && c.stateName === stateName);
4662
+ function findDataSlot(paths, path) {
4663
+ return paths.find((c) => c.kind === "data" && c.path === path);
4288
4664
  }
4289
4665
  function normalizeSeed(raw) {
4290
4666
  const compact = raw.replace(/\s+/g, "");
@@ -4559,7 +4935,7 @@ function blankJsComments(code) {
4559
4935
  }
4560
4936
 
4561
4937
  // src/service/watchDeclarationValidator.ts
4562
- var STATE_NAME_SEPARATOR2 = "@";
4938
+ var STATE_NAME_SEPARATOR = "@";
4563
4939
  function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
4564
4940
  const msgs = getMessages(locale3);
4565
4941
  const out = [];
@@ -4576,7 +4952,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
4576
4952
  }
4577
4953
  const entries = analyzeWatchEntries(block.content);
4578
4954
  if (entries.length === 0) continue;
4579
- const paths = analyzeStatePaths(block.content, block.stateName);
4955
+ const paths = analyzeStatePaths(block.content);
4580
4956
  const pathSet = new Set(paths.map((p) => p.path));
4581
4957
  for (const entry of entries) {
4582
4958
  const diagnostic = validateEntry(entry, pathSet, msgs);
@@ -4595,7 +4971,7 @@ function validateWatchDeclarations(html, stateTagName = "wcs-state", locale3) {
4595
4971
  function validateEntry(entry, pathSet, msgs) {
4596
4972
  const { key } = entry;
4597
4973
  const invalid = (message) => ({ code: WcsDiagnosticCode.WatchDeclarationInvalid, message, severity: "error" });
4598
- if (key.includes(STATE_NAME_SEPARATOR2)) {
4974
+ if (key.includes(STATE_NAME_SEPARATOR)) {
4599
4975
  return invalid(msgs.watchKeyCrossState(key));
4600
4976
  }
4601
4977
  if (key.startsWith("$")) {
@@ -4617,6 +4993,112 @@ function validateEntry(entry, pathSet, msgs) {
4617
4993
  return null;
4618
4994
  }
4619
4995
 
4996
+ // src/service/namedStateValidator.ts
4997
+ function findStateSelector(expr, embedded = false) {
4998
+ const colon = embedded ? -1 : expr.indexOf(":");
4999
+ const from = colon + 1;
5000
+ let depth = 0;
5001
+ let end = expr.length;
5002
+ for (let i = from; i < expr.length; i++) {
5003
+ const ch = expr[i];
5004
+ if (ch === "(") depth++;
5005
+ else if (ch === ")") depth = Math.max(0, depth - 1);
5006
+ else if (ch === "|" && depth === 0) {
5007
+ end = i;
5008
+ break;
5009
+ }
5010
+ }
5011
+ const at = expr.indexOf("@", from);
5012
+ if (at === -1 || at >= end) return null;
5013
+ const raw = expr.slice(at + 1, end);
5014
+ const name = raw.trim();
5015
+ const nameStart = at + 1 + (raw.length - raw.trimStart().length);
5016
+ return { start: at, end: name.length === 0 ? at + 1 : nameStart + name.length, name: name.length === 0 ? "default" : name };
5017
+ }
5018
+ function validateNamedState(html, attrName, stateTagName = "wcs-state", locale3) {
5019
+ const msgs = getMessages(locale3);
5020
+ const diagnostics = [];
5021
+ for (const element of parseWcsStateElements(html, stateTagName)) {
5022
+ const tagText = html.slice(element.tagStart, element.tagEnd);
5023
+ const match = /(?:^|\s)name\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tagText);
5024
+ if (match === null) continue;
5025
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
5026
+ const quoted = match[1] !== void 0 || match[2] !== void 0;
5027
+ const valueEnd = element.tagStart + match.index + match[0].length - (quoted ? 1 : 0);
5028
+ diagnostics.push({
5029
+ code: WcsDiagnosticCode.NamedStateDeprecated,
5030
+ start: valueEnd - value.length,
5031
+ end: valueEnd,
5032
+ message: msgs.namedStateAttrDeprecated(value),
5033
+ severity: "error"
5034
+ });
5035
+ }
5036
+ for (const attr of findAllBindAttributes(html, attrName)) {
5037
+ let pos = 0;
5038
+ for (const expr of splitBindingExpressions(attr.value)) {
5039
+ const selector = findStateSelector(expr);
5040
+ if (selector !== null) {
5041
+ diagnostics.push({
5042
+ code: WcsDiagnosticCode.NamedStateDeprecated,
5043
+ start: attr.valueStart + pos + selector.start,
5044
+ end: attr.valueStart + pos + selector.end,
5045
+ message: msgs.namedStatePathDeprecated(selector.name),
5046
+ severity: "error"
5047
+ });
5048
+ }
5049
+ pos += expr.length + 1;
5050
+ }
5051
+ }
5052
+ for (const item of [...findAllMustacheSyntax(html), ...findAllCommentBindings(html)]) {
5053
+ const selector = findStateSelector(item.expression, true);
5054
+ if (selector !== null) {
5055
+ diagnostics.push({
5056
+ code: WcsDiagnosticCode.NamedStateDeprecated,
5057
+ start: item.exprStart + selector.start,
5058
+ end: item.exprStart + selector.end,
5059
+ message: msgs.namedStatePathDeprecated(selector.name),
5060
+ severity: "error"
5061
+ });
5062
+ }
5063
+ }
5064
+ return diagnostics;
5065
+ }
5066
+
5067
+ // src/service/mountAttrValidator.ts
5068
+ function findMountPathProblem(mountPath) {
5069
+ if (mountPath.length === 0) return "empty";
5070
+ for (const segment of mountPath.split(".")) {
5071
+ if (segment.length === 0) return "emptySegment";
5072
+ if (segment === "*") return "wildcard";
5073
+ if (segment.includes("$") || segment.includes("#") || segment.includes("@")) return "reserved";
5074
+ }
5075
+ return null;
5076
+ }
5077
+ function validateMountAttributes(html, stateTagName = "wcs-state", locale3) {
5078
+ const msgs = getMessages(locale3);
5079
+ const diagnostics = [];
5080
+ for (const element of parseWcsStateElements(html, stateTagName)) {
5081
+ if (element.mountPath === null) continue;
5082
+ const problem = findMountPathProblem(element.mountPath);
5083
+ if (problem === null) continue;
5084
+ const tagText = html.slice(element.tagStart, element.tagEnd);
5085
+ const match = /(?:^|\s)mount\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tagText);
5086
+ if (match === null) continue;
5087
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
5088
+ const quoted = match[1] !== void 0 || match[2] !== void 0;
5089
+ const valueEnd = element.tagStart + match.index + match[0].length - (quoted ? 1 : 0);
5090
+ const attrStart = element.tagStart + match.index + (/^\s/.test(match[0]) ? 1 : 0);
5091
+ diagnostics.push({
5092
+ code: WcsDiagnosticCode.MountPathInvalid,
5093
+ start: value.length === 0 ? attrStart : valueEnd - value.length,
5094
+ end: valueEnd + (quoted && value.length === 0 ? 1 : 0),
5095
+ message: msgs.mountPathInvalid(problem, element.mountPath),
5096
+ severity: "error"
5097
+ });
5098
+ }
5099
+ return diagnostics;
5100
+ }
5101
+
4620
5102
  // ../state/dist/parser.esm.js
4621
5103
  var DELIMITER2 = ".";
4622
5104
  var WILDCARD2 = "*";
@@ -4624,7 +5106,6 @@ var MAX_WILDCARD_DEPTH2 = 128;
4624
5106
  var BINDING_SEPARATOR2 = ";";
4625
5107
  var PROP_VALUE_SEPARATOR2 = ":";
4626
5108
  var MODIFIER_SEPARATOR2 = "#";
4627
- var STATE_NAME_SEPARATOR3 = "@";
4628
5109
  var FILTER_SEPARATOR2 = "|";
4629
5110
  var ELSE_KEYWORD2 = "else";
4630
5111
  var SPREAD_PROP2 = "...";
@@ -5480,10 +5961,12 @@ function parseStatePart(statePart) {
5480
5961
  } else {
5481
5962
  stateAndPath = statePart.trim();
5482
5963
  }
5483
- const [statePathName, stateName = "default"] = stateAndPath.split(STATE_NAME_SEPARATOR3).map(trimFn);
5964
+ if (stateAndPath.indexOf("@") !== -1) {
5965
+ raiseError2(`"${stateAndPath}": 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 by its path prefix instead.`);
5966
+ }
5967
+ const statePathName = stateAndPath;
5484
5968
  const pathInfo = getPathInfo(statePathName);
5485
5969
  return {
5486
- stateName,
5487
5970
  statePathName,
5488
5971
  statePathInfo: pathInfo,
5489
5972
  outFilters: filters
@@ -5506,7 +5989,6 @@ function parseBindTextsForElement(bindText) {
5506
5989
  propModifiers: [],
5507
5990
  statePathName: "#else",
5508
5991
  statePathInfo: pathInfo,
5509
- stateName: "",
5510
5992
  inFilters: [],
5511
5993
  outFilters: [],
5512
5994
  bindingType: "else"
@@ -5606,24 +6088,18 @@ function parseEmbeddedTextWithPositions(expression) {
5606
6088
  error = e.message;
5607
6089
  }
5608
6090
  if (parsed === null) {
5609
- return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null, stateNameRange: null };
6091
+ return { exprRange, exprText: expression, parsed, error, propRange: null, pathRange: null };
5610
6092
  }
5611
6093
  const firstPipe = expression.indexOf(delimiters.filter);
5612
6094
  const pathScopeEnd = firstPipe === -1 ? expression.length : firstPipe;
5613
6095
  const pathLocal = locate(expression, parsed.statePathName, 0, pathScopeEnd);
5614
- let stateNameLocal = null;
5615
- const at = expression.indexOf(delimiters.stateName);
5616
- if (at !== -1 && at < pathScopeEnd) {
5617
- stateNameLocal = locate(expression, parsed.stateName, at + 1, pathScopeEnd);
5618
- }
5619
6096
  return {
5620
6097
  exprRange,
5621
6098
  exprText: expression,
5622
6099
  parsed,
5623
6100
  error,
5624
6101
  propRange: null,
5625
- pathRange: pathLocal,
5626
- stateNameRange: stateNameLocal
6102
+ pathRange: pathLocal
5627
6103
  };
5628
6104
  }
5629
6105
  function parseBindTextWithPositions(bindText) {
@@ -5645,23 +6121,18 @@ function parseBindTextWithPositions(bindText) {
5645
6121
  error = e.message;
5646
6122
  }
5647
6123
  if (parsed === null) {
5648
- results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null, stateNameRange: null });
6124
+ results.push({ exprRange, exprText: expr, parsed, error, propRange: null, pathRange: null });
5649
6125
  continue;
5650
6126
  }
5651
6127
  const colon = expr.indexOf(delimiters.propValue);
5652
6128
  const propEndLimit = colon === -1 ? expr.length : colon;
5653
6129
  const propLocal = locate(expr, parsed.propName, 0, propEndLimit);
5654
6130
  let pathLocal = null;
5655
- let stateNameLocal = null;
5656
6131
  if (colon !== -1) {
5657
6132
  const stateBase = colon + 1;
5658
6133
  const firstPipe = expr.indexOf(delimiters.filter, stateBase);
5659
6134
  const pathScopeEnd = firstPipe === -1 ? expr.length : firstPipe;
5660
6135
  pathLocal = locate(expr, parsed.statePathName, stateBase, pathScopeEnd);
5661
- const at = expr.indexOf(delimiters.stateName, stateBase);
5662
- if (at !== -1 && at < pathScopeEnd) {
5663
- stateNameLocal = locate(expr, parsed.stateName, at + 1, pathScopeEnd);
5664
- }
5665
6136
  }
5666
6137
  const lift = (range) => range === null ? null : { start: exprStart + range.start, end: exprStart + range.end };
5667
6138
  results.push({
@@ -5670,17 +6141,13 @@ function parseBindTextWithPositions(bindText) {
5670
6141
  parsed,
5671
6142
  error,
5672
6143
  propRange: lift(propLocal),
5673
- pathRange: lift(pathLocal),
5674
- stateNameRange: lift(stateNameLocal)
6144
+ pathRange: lift(pathLocal)
5675
6145
  });
5676
6146
  }
5677
6147
  return results;
5678
6148
  }
5679
6149
 
5680
6150
  // src/core/index/referenceIndex.ts
5681
- function keyOf(stateName, path) {
5682
- return `${stateName}\0${path}`;
5683
- }
5684
6151
  function buildReferenceIndex(html, options = {}) {
5685
6152
  clearParserCaches();
5686
6153
  const bindAttribute = options.bindAttribute ?? "data-wcs";
@@ -5698,13 +6165,11 @@ function buildReferenceIndex(html, options = {}) {
5698
6165
  occurrences.push({
5699
6166
  source: "attribute",
5700
6167
  kind: binding.parsed.propSegments[0] === "eventToken" ? "eventToken" : "path",
5701
- stateName: binding.parsed.stateName,
5702
6168
  path: binding.parsed.statePathName,
5703
6169
  pathRange: lift(binding.pathRange),
5704
6170
  exprRange: lift(binding.exprRange),
5705
6171
  propName: binding.parsed.propName,
5706
6172
  propRange: binding.propRange === null ? null : lift(binding.propRange),
5707
- stateNameRange: binding.stateNameRange === null ? null : lift(binding.stateNameRange),
5708
6173
  bindingType: binding.parsed.bindingType
5709
6174
  });
5710
6175
  }
@@ -5727,22 +6192,21 @@ function buildReferenceIndex(html, options = {}) {
5727
6192
  occurrences.push({
5728
6193
  source: match.kind,
5729
6194
  kind: "path",
5730
- stateName: binding.parsed.stateName,
5731
6195
  path: binding.parsed.statePathName,
5732
6196
  pathRange: shift(binding.pathRange),
5733
6197
  exprRange: { start: match.exprStart, end: match.exprEnd },
5734
6198
  propName: null,
5735
6199
  propRange: null,
5736
- stateNameRange: binding.stateNameRange === null ? null : shift(binding.stateNameRange),
5737
6200
  bindingType: "text"
5738
6201
  });
5739
6202
  }
5740
6203
  const declarations = [];
5741
6204
  for (const block of parseWcsScriptBlocks(html, stateTagName)) {
6205
+ const prefix = block.mountPath === null ? "" : block.mountPath + ".";
5742
6206
  for (const span of analyzeDeclarationSpans(block.content)) {
6207
+ if (prefix !== "" && span.name.startsWith("$")) continue;
5743
6208
  declarations.push({
5744
- stateName: block.stateName,
5745
- name: span.name,
6209
+ name: prefix + span.name,
5746
6210
  kind: span.kind,
5747
6211
  range: { start: block.contentStart + span.start, end: block.contentStart + span.end }
5748
6212
  });
@@ -5751,7 +6215,7 @@ function buildReferenceIndex(html, options = {}) {
5751
6215
  const byPath = /* @__PURE__ */ new Map();
5752
6216
  for (const occurrence of occurrences) {
5753
6217
  if (occurrence.kind !== "path") continue;
5754
- const key = keyOf(occurrence.stateName, occurrence.path);
6218
+ const key = occurrence.path;
5755
6219
  const list = byPath.get(key);
5756
6220
  if (list === void 0) {
5757
6221
  byPath.set(key, [occurrence]);
@@ -5761,22 +6225,22 @@ function buildReferenceIndex(html, options = {}) {
5761
6225
  }
5762
6226
  const declarationByName = /* @__PURE__ */ new Map();
5763
6227
  for (const declaration of declarations) {
5764
- const key = keyOf(declaration.stateName, declaration.name);
6228
+ const key = declaration.name;
5765
6229
  if (!declarationByName.has(key)) declarationByName.set(key, declaration);
5766
6230
  }
5767
6231
  return {
5768
6232
  occurrences,
5769
6233
  declarations,
5770
6234
  problems,
5771
- referencesOf(stateName, path) {
5772
- return (byPath.get(keyOf(stateName, path)) ?? []).slice();
6235
+ referencesOf(path) {
6236
+ return (byPath.get(path) ?? []).slice();
5773
6237
  },
5774
- declarationOf(stateName, path) {
5775
- const exact = declarationByName.get(keyOf(stateName, path));
6238
+ declarationOf(path) {
6239
+ const exact = declarationByName.get(path);
5776
6240
  if (exact !== void 0) return exact;
5777
6241
  const firstSegment = path.split(".")[0];
5778
6242
  if (firstSegment === path) return null;
5779
- return declarationByName.get(keyOf(stateName, firstSegment)) ?? null;
6243
+ return declarationByName.get(firstSegment) ?? null;
5780
6244
  },
5781
6245
  occurrenceAt(offset) {
5782
6246
  for (const occurrence of occurrences) {
@@ -6021,8 +6485,9 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
6021
6485
  for (const block of blocks) {
6022
6486
  const callback = analyzeCallableBodies(block.content).find((entry) => entry.name === STATE_UPDATED_CALLBACK && entry.kind === "method");
6023
6487
  if (callback === void 0) continue;
6024
- const declared = new Set(analyzeStatePaths(block.content, block.stateName).map((p) => p.path));
6025
- const bound = boundPaths.get(block.stateName) ?? /* @__PURE__ */ new Set();
6488
+ if (block.mountPath !== null) continue;
6489
+ const declared = new Set(analyzeStatePaths(block.content).map((p) => p.path));
6490
+ const bound = boundPaths;
6026
6491
  const body = blankComments(callback.body);
6027
6492
  PATH_TEST_LITERAL.lastIndex = 0;
6028
6493
  let match;
@@ -6045,27 +6510,18 @@ function validateUpdatedCallbackDemand(html, stateTagName, bindAttrName, locale3
6045
6510
  return out;
6046
6511
  }
6047
6512
  function collectBoundPaths(html, stateTagName, bindAttrName) {
6048
- const byState = /* @__PURE__ */ new Map();
6049
- const add = (stateName, path) => {
6050
- let set = byState.get(stateName);
6051
- if (set === void 0) {
6052
- set = /* @__PURE__ */ new Set();
6053
- byState.set(stateName, set);
6054
- }
6055
- set.add(path);
6056
- };
6513
+ const bound = /* @__PURE__ */ new Set();
6057
6514
  const index = buildReferenceIndex(html, { bindAttribute: bindAttrName, stateTagName });
6058
6515
  for (const occurrence of index.occurrences) {
6059
- add(occurrence.stateName, occurrence.path);
6516
+ bound.add(occurrence.path);
6060
6517
  if (!occurrence.path.startsWith(".")) continue;
6061
6518
  const forPath = getInnermostForPath(html, occurrence.pathRange.start, bindAttrName);
6062
6519
  if (forPath === null || forPath.startsWith(".")) continue;
6063
- add(
6064
- occurrence.stateName,
6520
+ bound.add(
6065
6521
  occurrence.path === "." ? `${forPath}.*` : `${forPath}.*.${occurrence.path.slice(1)}`
6066
6522
  );
6067
6523
  }
6068
- return byState;
6524
+ return bound;
6069
6525
  }
6070
6526
  function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrName = "data-wcs") {
6071
6527
  const out = [];
@@ -6077,154 +6533,6 @@ function validateSemantics(html, stateTagName = "wcs-state", locale3, bindAttrNa
6077
6533
  return out;
6078
6534
  }
6079
6535
 
6080
- // src/core/validateDocument.ts
6081
- function validateDocument(text, options = {}) {
6082
- const bindAttribute = options.bindAttribute ?? "data-wcs";
6083
- const stateTagName = options.stateTagName ?? "wcs-state";
6084
- const locale3 = options.locale;
6085
- const fileReader = options.fileReader;
6086
- const out = [];
6087
- out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader));
6088
- out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader));
6089
- out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
6090
- out.push(...validateAriaAttributes(text, bindAttribute, locale3));
6091
- out.push(...validateDocumentEnv(text, locale3));
6092
- out.push(...validateSemantics(text, stateTagName, locale3, bindAttribute));
6093
- out.push(...validateArrayMutations(text, stateTagName, locale3));
6094
- out.push(...validateWatchDeclarations(text, stateTagName, locale3));
6095
- for (const d of validateStateTypes(text, stateTagName, locale3)) {
6096
- out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
6097
- }
6098
- for (const d of validateNestedAssigns(text, stateTagName, locale3)) {
6099
- out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
6100
- }
6101
- return sortDiagnostics(out);
6102
- }
6103
-
6104
- // src/core/sidecar/schemaSubset.ts
6105
- var ALLOWED_SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
6106
- "type",
6107
- "properties",
6108
- "required",
6109
- "items",
6110
- "enum",
6111
- "const",
6112
- "anyOf",
6113
- "$defs",
6114
- "$ref"
6115
- ]);
6116
- var DiagnosticContext = class {
6117
- constructor(spans) {
6118
- this.spans = spans;
6119
- }
6120
- diagnostics = [];
6121
- add(code, pointer2, message, severity, extra = {}, useKeySpan = false) {
6122
- const span = this.spans.get(pointer2);
6123
- const start = span === void 0 ? 0 : useKeySpan ? span.keyStart ?? span.start : span.start;
6124
- const end = span === void 0 ? 0 : useKeySpan ? span.keyEnd ?? span.end : span.end;
6125
- this.diagnostics.push({ code, start, end, message, severity, ...extra });
6126
- }
6127
- };
6128
- function isSchemaObject(value) {
6129
- return value !== null && typeof value === "object" && !Array.isArray(value);
6130
- }
6131
- function isSchemaMap(value) {
6132
- return value !== null && typeof value === "object" && !Array.isArray(value);
6133
- }
6134
- function validateSchemaSubset(schema, pointerBase, ctx, rootDefs) {
6135
- walkKeywords(schema, pointerBase, ctx, rootDefs);
6136
- const safe = /* @__PURE__ */ new Set();
6137
- detectCycles(schema, pointerBase, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
6138
- for (const [name, def] of Object.entries(rootDefs)) {
6139
- detectCycles(def, `${pointerBase}/$defs/${escape(name)}`, ctx, rootDefs, /* @__PURE__ */ new Set(), safe);
6140
- }
6141
- }
6142
- function walkKeywords(node, ptr, ctx, rootDefs) {
6143
- if (!isSchemaObject(node)) return;
6144
- for (const keyword of Object.keys(node)) {
6145
- if (!ALLOWED_SCHEMA_KEYWORDS.has(keyword)) {
6146
- ctx.add(
6147
- WcsDiagnosticCode.ManifestUnknownKeyword,
6148
- `${ptr}/${escape(keyword)}`,
6149
- `Unsupported schema keyword "${keyword}". Allowed: ${[...ALLOWED_SCHEMA_KEYWORDS].join(", ")}.`,
6150
- "warning",
6151
- {},
6152
- true
6153
- );
6154
- }
6155
- }
6156
- if (typeof node.$ref === "string") {
6157
- if (!node.$ref.startsWith("#/")) {
6158
- ctx.add(
6159
- WcsDiagnosticCode.ManifestExternalRef,
6160
- `${ptr}/$ref`,
6161
- `External $ref "${node.$ref}" is forbidden; only local "#/$defs/..." references are allowed.`,
6162
- "error"
6163
- );
6164
- } else if (resolveLocalRef(node.$ref, rootDefs) === void 0) {
6165
- ctx.add(
6166
- WcsDiagnosticCode.ManifestRefUnresolved,
6167
- `${ptr}/$ref`,
6168
- `Unresolved local $ref "${node.$ref}".`,
6169
- "error"
6170
- );
6171
- }
6172
- }
6173
- if (isSchemaMap(node.properties)) {
6174
- for (const [name, child] of Object.entries(node.properties)) {
6175
- walkKeywords(child, `${ptr}/properties/${escape(name)}`, ctx, rootDefs);
6176
- }
6177
- }
6178
- if (node.items !== void 0 && isSchemaObject(node.items)) {
6179
- walkKeywords(node.items, `${ptr}/items`, ctx, rootDefs);
6180
- }
6181
- if (Array.isArray(node.anyOf)) {
6182
- node.anyOf.forEach((child, i) => walkKeywords(child, `${ptr}/anyOf/${i}`, ctx, rootDefs));
6183
- }
6184
- if (isSchemaMap(node.$defs)) {
6185
- for (const [name, child] of Object.entries(node.$defs)) {
6186
- walkKeywords(child, `${ptr}/$defs/${escape(name)}`, ctx, rootDefs);
6187
- }
6188
- }
6189
- }
6190
- function detectCycles(node, ptr, ctx, rootDefs, refStack, safe) {
6191
- if (!isSchemaObject(node)) return;
6192
- if (typeof node.$ref === "string") {
6193
- const ref = node.$ref;
6194
- if (!ref.startsWith("#/")) return;
6195
- if (refStack.has(ref)) {
6196
- ctx.add(WcsDiagnosticCode.ManifestRefCycle, `${ptr}/$ref`, `Cyclic $ref detected at "${ref}".`, "error");
6197
- return;
6198
- }
6199
- if (safe.has(ref)) return;
6200
- const target = resolveLocalRef(ref, rootDefs);
6201
- if (target === void 0) return;
6202
- refStack.add(ref);
6203
- detectCycles(target, ptr, ctx, rootDefs, refStack, safe);
6204
- refStack.delete(ref);
6205
- safe.add(ref);
6206
- return;
6207
- }
6208
- if (isSchemaMap(node.properties)) {
6209
- for (const child of Object.values(node.properties)) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
6210
- }
6211
- if (node.items !== void 0 && isSchemaObject(node.items)) {
6212
- detectCycles(node.items, ptr, ctx, rootDefs, refStack, safe);
6213
- }
6214
- if (Array.isArray(node.anyOf)) {
6215
- for (const child of node.anyOf) detectCycles(child, ptr, ctx, rootDefs, refStack, safe);
6216
- }
6217
- }
6218
- function resolveLocalRef(ref, rootDefs) {
6219
- const match = /^#\/\$defs\/(.+)$/.exec(ref);
6220
- if (match === null) return void 0;
6221
- const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
6222
- return rootDefs[name];
6223
- }
6224
- function escape(key) {
6225
- return key.replace(/~/g, "~0").replace(/\//g, "~1");
6226
- }
6227
-
6228
6536
  // src/core/sidecar/jsonSource.ts
6229
6537
  var JsonReader = class {
6230
6538
  constructor(text) {
@@ -6398,8 +6706,8 @@ function parseJsonWithSpans(text) {
6398
6706
  }
6399
6707
 
6400
6708
  // src/core/sidecar/types.ts
6401
- var SUPPORTED_SCHEMA_VERSION = 1;
6402
- var SUPPORTED_NAMESPACE_VERSION = 1;
6709
+ var SUPPORTED_SCHEMA_VERSION = 2;
6710
+ var SUPPORTED_NAMESPACE_VERSION = 2;
6403
6711
 
6404
6712
  // src/core/sidecar/loader.ts
6405
6713
  var NAMESPACE_KEYS = ["wcstack.types", "wcstack.async", "wcstack.platformCapabilities", "wcstack.application"];
@@ -6436,10 +6744,11 @@ function loadManifest(artifact) {
6436
6744
  return { artifact, manifest: null, ctx, spans: parsed.spans };
6437
6745
  }
6438
6746
  if (obj.schemaVersion !== SUPPORTED_SCHEMA_VERSION) {
6747
+ const migration = obj.schemaVersion === 1 ? ` schemaVersion 1 (states[name]) predates v2's single state tree \u2014 regenerate with \`wcs-schema emit\`.` : "";
6439
6748
  ctx.add(
6440
6749
  WcsDiagnosticCode.ManifestSchemaVersion,
6441
6750
  pointer("schemaVersion"),
6442
- `Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}.`,
6751
+ `Unsupported schemaVersion ${obj.schemaVersion}; this reader supports ${SUPPORTED_SCHEMA_VERSION}.${migration}`,
6443
6752
  "error"
6444
6753
  );
6445
6754
  return { artifact, manifest: null, ctx, spans: parsed.spans };
@@ -6488,8 +6797,12 @@ function resolvePackageContracts(loaded) {
6488
6797
  const collided = /* @__PURE__ */ new Set();
6489
6798
  const firstSource = /* @__PURE__ */ new Map();
6490
6799
  const filterOwner = /* @__PURE__ */ new Map();
6800
+ let schemaOwner;
6801
+ let schemaWinner;
6802
+ let hasApplicationArtifact = false;
6491
6803
  for (const lm of loaded) {
6492
6804
  if (lm.manifest === null) continue;
6805
+ if (lm.manifest.kind === "application") hasApplicationArtifact = true;
6493
6806
  const types = lm.manifest.manifestExtensions?.["wcstack.types"];
6494
6807
  if (lm.manifest.kind === "package" && types !== void 0) {
6495
6808
  for (const [tag, component] of Object.entries(types.components ?? {})) {
@@ -6541,13 +6854,97 @@ function resolvePackageContracts(loaded) {
6541
6854
  );
6542
6855
  }
6543
6856
  }
6857
+ if (lm.manifest.kind === "application" && application?.stateSchema !== void 0) {
6858
+ const schema = application.stateSchema;
6859
+ if (schema !== null && typeof schema === "object" && !Array.isArray(schema)) {
6860
+ if (schemaOwner === void 0) {
6861
+ schemaOwner = lm.artifact.source;
6862
+ schemaWinner = schema;
6863
+ } else {
6864
+ schemaWinner = void 0;
6865
+ ctxFor(lm).add(
6866
+ WcsDiagnosticCode.ManifestStateCollision,
6867
+ pointer("manifestExtensions", "wcstack.application", "stateSchema"),
6868
+ `Multiple application artifacts declare a stateSchema (also in "${schemaOwner}"); neither is used.`,
6869
+ "error",
6870
+ void 0,
6871
+ true
6872
+ );
6873
+ }
6874
+ }
6875
+ }
6544
6876
  }
6545
6877
  const diagnosticsBySource = /* @__PURE__ */ new Map();
6546
6878
  for (const [source, diags] of perSource) {
6547
6879
  const kept = diags.filter((d) => !(d.code === WcsDiagnosticCode.ManifestOverride && d.tag !== void 0 && collided.has(d.tag)));
6548
6880
  if (kept.length > 0) diagnosticsBySource.set(source, kept);
6549
6881
  }
6550
- return { tags: winners, diagnosticsBySource };
6882
+ return { tags: winners, applicationSchema: schemaWinner, hasApplicationArtifact, diagnosticsBySource };
6883
+ }
6884
+
6885
+ // src/core/sidecar/discover.ts
6886
+ var APPLICATION_MANIFEST_FILENAME = "wcstack.manifest.json";
6887
+ var MAX_ASCEND = 16;
6888
+ function discoverApplicationManifest(fileReader) {
6889
+ for (let up = 0; up <= MAX_ASCEND; up++) {
6890
+ const relativePath = `${"../".repeat(up)}${APPLICATION_MANIFEST_FILENAME}`;
6891
+ const text = fileReader(relativePath);
6892
+ if (text === void 0) continue;
6893
+ const loaded = loadManifest({ text, source: relativePath });
6894
+ return { relativePath, text, loaded, schema: applicationSchemaOf(loaded) };
6895
+ }
6896
+ return void 0;
6897
+ }
6898
+ function applicationSchemaOf(loaded) {
6899
+ const manifest = loaded.manifest;
6900
+ if (manifest === null || manifest.kind !== "application") return void 0;
6901
+ const application = manifest.manifestExtensions?.["wcstack.application"];
6902
+ const schema = application?.stateSchema;
6903
+ if (schema !== null && typeof schema === "object" && !Array.isArray(schema)) {
6904
+ return schema;
6905
+ }
6906
+ return void 0;
6907
+ }
6908
+ function joinRelativeSource(htmlSource, relativePath) {
6909
+ const sepIndex = Math.max(htmlSource.lastIndexOf("/"), htmlSource.lastIndexOf("\\"));
6910
+ const dirSegments = sepIndex === -1 ? [] : htmlSource.slice(0, sepIndex).split(/[\\/]/);
6911
+ for (const segment of relativePath.split("/")) {
6912
+ if (segment === "" || segment === ".") continue;
6913
+ if (segment === "..") {
6914
+ if (dirSegments.length > 0 && dirSegments[dirSegments.length - 1] !== "..") dirSegments.pop();
6915
+ else dirSegments.push("..");
6916
+ continue;
6917
+ }
6918
+ dirSegments.push(segment);
6919
+ }
6920
+ return dirSegments.join("/");
6921
+ }
6922
+
6923
+ // src/core/validateDocument.ts
6924
+ function validateDocument(text, options = {}) {
6925
+ const bindAttribute = options.bindAttribute ?? "data-wcs";
6926
+ const stateTagName = options.stateTagName ?? "wcs-state";
6927
+ const locale3 = options.locale;
6928
+ const fileReader = options.fileReader;
6929
+ const applicationSchema = options.applicationSchema ?? (fileReader !== void 0 ? discoverApplicationManifest(fileReader)?.schema : void 0);
6930
+ const out = [];
6931
+ out.push(...validateBindings(text, bindAttribute, stateTagName, locale3, fileReader, applicationSchema));
6932
+ out.push(...validateTemplateSyntax(text, stateTagName, bindAttribute, locale3, fileReader, applicationSchema));
6933
+ out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale3, fileReader));
6934
+ out.push(...validateAriaAttributes(text, bindAttribute, locale3));
6935
+ out.push(...validateDocumentEnv(text, locale3));
6936
+ out.push(...validateSemantics(text, stateTagName, locale3, bindAttribute));
6937
+ out.push(...validateArrayMutations(text, stateTagName, locale3));
6938
+ out.push(...validateWatchDeclarations(text, stateTagName, locale3));
6939
+ out.push(...validateNamedState(text, bindAttribute, stateTagName, locale3));
6940
+ out.push(...validateMountAttributes(text, stateTagName, locale3));
6941
+ for (const d of validateStateTypes(text, stateTagName, locale3)) {
6942
+ out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
6943
+ }
6944
+ for (const d of validateNestedAssigns(text, stateTagName, locale3)) {
6945
+ out.push({ code: WcsDiagnosticCode.NestedAssign, start: d.start, end: d.end, message: d.message, severity: d.severity });
6946
+ }
6947
+ return sortDiagnostics(out);
6551
6948
  }
6552
6949
 
6553
6950
  // src/core/sidecar/drift.ts
@@ -6606,13 +7003,23 @@ function checkDrift(tag, component, live, ctx) {
6606
7003
  }
6607
7004
 
6608
7005
  // src/core/sidecar/validate.ts
7006
+ function validateManifestArtifact(artifact) {
7007
+ const loaded = loadManifest(artifact);
7008
+ validateLoadedSchemas(loaded);
7009
+ return sortDiagnostics(loaded.ctx.diagnostics);
7010
+ }
6609
7011
  function validateLoadedSchemas(loaded) {
6610
7012
  if (loaded.manifest === null) return;
6611
7013
  const types = loaded.manifest.manifestExtensions?.["wcstack.types"];
6612
- if (types === void 0) return;
6613
- for (const [tag, component] of Object.entries(types.components ?? {})) {
7014
+ for (const [tag, component] of Object.entries(types?.components ?? {})) {
6614
7015
  validateComponentSchemas(tag, component, loaded.ctx);
6615
7016
  }
7017
+ const application = loaded.manifest.manifestExtensions?.["wcstack.application"];
7018
+ const stateSchema = application?.stateSchema;
7019
+ if (stateSchema !== void 0 && stateSchema !== null && typeof stateSchema === "object" && !Array.isArray(stateSchema)) {
7020
+ const ptr = pointer("manifestExtensions", "wcstack.application", "stateSchema");
7021
+ validateSchemaSubset(stateSchema, ptr, loaded.ctx, stateSchema.$defs ?? {});
7022
+ }
6616
7023
  }
6617
7024
  function validateComponentSchemas(tag, component, ctx) {
6618
7025
  const base = pointer("manifestExtensions", "wcstack.types", "components", tag);
@@ -6662,7 +7069,9 @@ function validateManifestSet(input) {
6662
7069
  return {
6663
7070
  diagnostics: sortDiagnostics(all),
6664
7071
  byArtifact: sortedByArtifact,
6665
- resolvedTags
7072
+ resolvedTags,
7073
+ resolvedSchema: resolved.applicationSchema,
7074
+ hasApplicationArtifact: resolved.hasApplicationArtifact
6666
7075
  };
6667
7076
  }
6668
7077
  function escapePtr(key) {
@@ -6673,13 +7082,10 @@ function escapePtr(key) {
6673
7082
  var severityLabel = { error: "error", warning: "warning", info: "info" };
6674
7083
  function runValidation(inputs, options = {}) {
6675
7084
  const diagnosticsBySource = /* @__PURE__ */ new Map();
6676
- for (const input of inputs) {
6677
- if (input.kind === "html") {
6678
- const docOptions = input.fileReader !== void 0 ? { ...options, fileReader: input.fileReader } : options;
6679
- diagnosticsBySource.set(input.source, validateDocument(input.text, docOptions));
6680
- }
6681
- }
7085
+ const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
6682
7086
  const manifestInputs = inputs.filter((i) => i.kind === "manifest");
7087
+ let explicitSchema;
7088
+ let haveExplicitApplication = false;
6683
7089
  if (manifestInputs.length > 0) {
6684
7090
  const result = validateManifestSet({
6685
7091
  artifacts: manifestInputs.map((m) => ({ text: m.text, source: m.source })),
@@ -6688,8 +7094,32 @@ function runValidation(inputs, options = {}) {
6688
7094
  for (const input of manifestInputs) {
6689
7095
  diagnosticsBySource.set(input.source, result.byArtifact.get(input.source) ?? []);
6690
7096
  }
7097
+ if (result.hasApplicationArtifact) {
7098
+ haveExplicitApplication = true;
7099
+ explicitSchema = result.resolvedSchema;
7100
+ }
7101
+ }
7102
+ for (const input of inputs) {
7103
+ if (input.kind !== "html") continue;
7104
+ let applicationSchema = explicitSchema;
7105
+ if (!haveExplicitApplication && input.fileReader !== void 0) {
7106
+ const discovered = discoverApplicationManifest(input.fileReader);
7107
+ applicationSchema = discovered?.schema;
7108
+ if (discovered !== void 0) {
7109
+ const source = joinRelativeSource(input.source, discovered.relativePath);
7110
+ if (!diagnosticsBySource.has(source)) {
7111
+ textBySource.set(source, discovered.text);
7112
+ diagnosticsBySource.set(source, validateManifestArtifact({ text: discovered.text, source }));
7113
+ }
7114
+ }
7115
+ }
7116
+ const docOptions = {
7117
+ ...options,
7118
+ ...input.fileReader !== void 0 ? { fileReader: input.fileReader } : {},
7119
+ ...applicationSchema !== void 0 ? { applicationSchema } : {}
7120
+ };
7121
+ diagnosticsBySource.set(input.source, validateDocument(input.text, docOptions));
6691
7122
  }
6692
- const textBySource = new Map(inputs.map((i) => [i.source, i.text]));
6693
7123
  const lines = [];
6694
7124
  let errorCount = 0;
6695
7125
  let warningCount = 0;
@@ -6711,7 +7141,7 @@ function runValidation(inputs, options = {}) {
6711
7141
  errorCount,
6712
7142
  warningCount,
6713
7143
  infoCount,
6714
- exitCode: errorCount > 0 ? 1 : 0,
7144
+ exitCode: errorCount > 0 || options.strict === true && warningCount > 0 ? 1 : 0,
6715
7145
  diagnosticsBySource
6716
7146
  };
6717
7147
  }
@@ -6728,6 +7158,7 @@ function parseArgs(argv) {
6728
7158
  else if (arg.startsWith("--state-tag=")) options.stateTagName = arg.slice("--state-tag=".length);
6729
7159
  else if (arg.startsWith("--lang=")) options.locale = arg.slice("--lang=".length);
6730
7160
  else if (arg === "--errors-only" || arg === "--quiet") options.errorsOnly = true;
7161
+ else if (arg === "--strict") options.strict = true;
6731
7162
  else if (!arg.startsWith("-")) files.push(arg);
6732
7163
  }
6733
7164
  return { options, files };
@@ -6746,7 +7177,7 @@ function main(argv) {
6746
7177
  const { options, files } = parseArgs(argv);
6747
7178
  const locale3 = resolveCliLocale(options.locale);
6748
7179
  if (files.length === 0) {
6749
- process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] <file> [<file> ...]\n");
7180
+ process.stderr.write("usage: wcs-validate [--attr=data-wcs] [--state-tag=wcs-state] [--lang=ja|en] [--errors-only] [--strict] <file> [<file> ...]\n");
6750
7181
  return 2;
6751
7182
  }
6752
7183
  const inputs = [];
@@ -6768,7 +7199,7 @@ function main(argv) {
6768
7199
  }
6769
7200
  process.stdout.write(
6770
7201
  `
6771
- ${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info
7202
+ ${result.errorCount} error(s), ${result.warningCount} warning(s), ${result.infoCount} info${options.strict ? " (strict)" : ""}
6772
7203
  `
6773
7204
  );
6774
7205
  return result.exitCode;