tw-framework 2.0.0 → 2.0.1

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/testing.cjs CHANGED
@@ -6906,20 +6906,39 @@ function sha12(data) {
6906
6906
  async function md52(data) {
6907
6907
  return hashWithWebCrypto("md5", data);
6908
6908
  }
6909
+ function webcryptoHashName(algorithm) {
6910
+ const key2 = String(algorithm).toLowerCase().replace(/[-_]/g, "");
6911
+ const map2 = {
6912
+ md5: "MD5",
6913
+ sha1: "SHA-1",
6914
+ sha224: "SHA-224",
6915
+ sha256: "SHA-256",
6916
+ sha384: "SHA-384",
6917
+ sha512: "SHA-512"
6918
+ };
6919
+ return map2[key2] ?? algorithm;
6920
+ }
6909
6921
  async function hmac2(data, key2, algorithm = "sha256") {
6910
6922
  const encoder2 = new TextEncoder();
6911
6923
  const keyData = encoder2.encode(key2);
6912
6924
  const messageData = encoder2.encode(data);
6913
- const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: algorithm }, false, ["sign"]);
6925
+ const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: webcryptoHashName(algorithm) }, false, ["sign"]);
6914
6926
  const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
6915
6927
  const hashArray = Array.from(new Uint8Array(signature));
6916
6928
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
6917
6929
  }
6930
+ async function verifyHmac(data, key2, signature, algorithm = "sha256") {
6931
+ const expected = await hmac2(data, key2, algorithm);
6932
+ if (expected.length !== signature.length) return false;
6933
+ let diff2 = 0;
6934
+ for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
6935
+ return diff2 === 0;
6936
+ }
6918
6937
  async function pbkdf2(password, salt, iterations = 1e5, keyLength = 256) {
6919
6938
  const encoder2 = new TextEncoder();
6920
6939
  const keyMaterial = await crypto.subtle.importKey("raw", encoder2.encode(password), "PBKDF2", false, ["deriveBits"]);
6921
6940
  const derivedBits = await crypto.subtle.deriveBits(
6922
- { name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "sha256" },
6941
+ { name: "PBKDF2", salt: encoder2.encode(salt), iterations, hash: "SHA-256" },
6923
6942
  keyMaterial,
6924
6943
  keyLength
6925
6944
  );
@@ -6964,9 +6983,16 @@ async function decryptString(encryptedData, password, salt) {
6964
6983
  const key2 = await deriveKey(password, salt);
6965
6984
  return decryptAES(encrypted, key2, iv);
6966
6985
  }
6986
+ async function generateRSASigningKeyPair(bits = 2048) {
6987
+ return crypto.subtle.generateKey(
6988
+ { name: "RSASSA-PKCS1-v1_5", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
6989
+ true,
6990
+ ["sign", "verify"]
6991
+ );
6992
+ }
6967
6993
  async function generateRSAKeyPair(bits = 2048) {
6968
6994
  return crypto.subtle.generateKey(
6969
- { name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "sha256" },
6995
+ { name: "RSA-OAEP", modulusLength: bits, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" },
6970
6996
  true,
6971
6997
  ["encrypt", "decrypt"]
6972
6998
  );
@@ -8415,36 +8441,6 @@ var init_etag = __esm({
8415
8441
  });
8416
8442
 
8417
8443
  // ../../packages/shared/tw/hashing/hmac.ts
8418
- function bufferToHex2(buffer) {
8419
- const bytes = new Uint8Array(buffer);
8420
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
8421
- }
8422
- function timingSafeEqual2(a, b) {
8423
- if (a.length !== b.length) return false;
8424
- let result = 0;
8425
- for (let i = 0; i < a.length; i++) {
8426
- result |= a.charCodeAt(i) ^ b.charCodeAt(i);
8427
- }
8428
- return result === 0;
8429
- }
8430
- async function hmac3(key2, message, algorithm = "SHA-256") {
8431
- const encoder2 = new TextEncoder();
8432
- const keyData = encoder2.encode(key2);
8433
- const messageData = encoder2.encode(message);
8434
- const cryptoKey = await crypto.subtle.importKey(
8435
- "raw",
8436
- keyData,
8437
- { name: "HMAC", hash: algorithm },
8438
- false,
8439
- ["sign"]
8440
- );
8441
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
8442
- return bufferToHex2(signature);
8443
- }
8444
- async function verifyHmac(key2, message, signature, algorithm = "SHA-256") {
8445
- const computed6 = await hmac3(key2, message, algorithm);
8446
- return timingSafeEqual2(computed6, signature);
8447
- }
8448
8444
  var init_hmac = __esm({
8449
8445
  "../../packages/shared/tw/hashing/hmac.ts"() {
8450
8446
  }
@@ -9109,6 +9105,7 @@ __export(tw_exports, {
9109
9105
  generatePIN: () => generatePIN,
9110
9106
  generateProofOfKnowledge: () => generateProofOfKnowledge,
9111
9107
  generateRSAKeyPair: () => generateRSAKeyPair,
9108
+ generateRSASigningKeyPair: () => generateRSASigningKeyPair,
9112
9109
  generateSecret: () => generateSecret,
9113
9110
  generateTOTP: () => generateTOTP,
9114
9111
  geolocation: () => geolocation,
@@ -9639,23 +9636,19 @@ function merkleRoot(hashes) {
9639
9636
  return level[0];
9640
9637
  }
9641
9638
  function hmacSign(data, key2) {
9642
- let h1 = 0, h22 = 0;
9643
- for (let i = 0; i < data.length; i++) {
9644
- h1 = (h1 << 5) - h1 + data.charCodeAt(i);
9645
- h1 |= 0;
9646
- }
9647
- for (let i = 0; i < key2.length; i++) {
9648
- h22 = (h22 << 5) - h22 + key2.charCodeAt(i);
9649
- h22 |= 0;
9650
- }
9651
- return (h1 ^ h22).toString(16);
9639
+ return (0, import_node_crypto3.createHmac)("sha256", key2).update(data).digest("hex");
9652
9640
  }
9653
9641
  function hmacVerify(data, key2, signature) {
9654
- return hmacSign(data, key2) === signature;
9642
+ const expected = hmacSign(data, key2);
9643
+ if (expected.length !== signature.length) return false;
9644
+ let diff2 = 0;
9645
+ for (let i = 0; i < expected.length; i++) diff2 |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
9646
+ return diff2 === 0;
9655
9647
  }
9656
- var HTML_TAGS;
9648
+ var import_node_crypto3, HTML_TAGS;
9657
9649
  var init_tw = __esm({
9658
9650
  "../../packages/shared/tw/index.ts"() {
9651
+ import_node_crypto3 = require("node:crypto");
9659
9652
  init_sha();
9660
9653
  init_utils();
9661
9654
  init_manipulate();
@@ -227101,6 +227094,10 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227101
227094
  while (!cursor.done) {
227102
227095
  const ct = cursor.peek();
227103
227096
  if (!ct) break;
227097
+ if (ct.type === "IDENT" && ct.value === "on") {
227098
+ const after2 = cursor.peek(1);
227099
+ if (after2 && after2.type === "COLON") break;
227100
+ }
227104
227101
  if (ct.type === "IDENT" && !nextPrevMinus && nextClass.length > 0 && (CLASS_BOUNDARY_BOOLEAN_ATTRS.has(ct.value) || inlineAttrStartsAt(cursor))) {
227105
227102
  break;
227106
227103
  }
@@ -227391,7 +227388,9 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227391
227388
  const first = cursor.peek();
227392
227389
  const second = cursor.peek(1);
227393
227390
  const CONTROL_FLOW = /* @__PURE__ */ new Set(["for", "if", "while", "switch", "else", "try", "catch"]);
227394
- const isAttrBlock = first && (first.type === "IDENT" || first.type === "KEYWORD") && !CONTROL_FLOW.has(first.value) && HTML_ATTRS_ONLY.has(first.value) && second && (second.type === "STRING" || second.type === "NUMBER" || second.type === "IDENT" || second.type === "KEYWORD");
227391
+ const third = cursor.peek(2);
227392
+ const isPropBlock = second && second.type === "ASSIGN" && third && third.type === "LBRACE";
227393
+ const isAttrBlock = first && (first.type === "IDENT" || first.type === "KEYWORD") && !CONTROL_FLOW.has(first.value) && (HTML_ATTRS_ONLY.has(first.value) || isPropBlock) && second && (second.type === "STRING" || second.type === "NUMBER" || second.type === "IDENT" || second.type === "KEYWORD" || second.type === "ASSIGN");
227395
227394
  if (isAttrBlock) {
227396
227395
  while (!cursor.done) {
227397
227396
  const t3 = cursor.peek();
@@ -227399,6 +227398,34 @@ function parseTWSelector(cursor, errors, state, parseBody) {
227399
227398
  if (t3.type === "IDENT" || t3.type === "KEYWORD") {
227400
227399
  const attrName = t3.value;
227401
227400
  cursor.advance();
227401
+ if (cursor.peek() && (cursor.peek().type === "ASSIGN" || cursor.peek().type === "EQUALS")) {
227402
+ cursor.advance();
227403
+ cursor.skipWhitespace();
227404
+ if (cursor.peek() && cursor.peek().type === "LBRACE") {
227405
+ cursor.advance();
227406
+ let expr = "";
227407
+ let depth = 1;
227408
+ let prevWS = false;
227409
+ while (!cursor.done) {
227410
+ const tk = cursor.peek();
227411
+ if (!tk || tk.type === "EOF") break;
227412
+ if (tk.type === "LBRACE") depth++;
227413
+ else if (tk.type === "RBRACE") {
227414
+ depth--;
227415
+ if (depth === 0) {
227416
+ cursor.advance();
227417
+ break;
227418
+ }
227419
+ }
227420
+ if (expr && prevWS) expr += " ";
227421
+ expr += tk.raw ?? tk.value;
227422
+ prevWS = !!tk.followedByWhitespace;
227423
+ cursor.advance();
227424
+ }
227425
+ el.attrs.push(createAttribute(attrName, expr.trim(), t3.pos.line, t3.pos.col, true));
227426
+ continue;
227427
+ }
227428
+ }
227402
227429
  const vToken = cursor.peek();
227403
227430
  if (vToken && (vToken.type === "STRING" || vToken.type === "NUMBER")) {
227404
227431
  cursor.advance();
@@ -236144,7 +236171,7 @@ var init_types4 = __esm({
236144
236171
  var TW_VERSION, TW_GENERATOR_META;
236145
236172
  var init_version = __esm({
236146
236173
  "../../packages/compiler/tw/codegen/version.ts"() {
236147
- TW_VERSION = "2.0.0";
236174
+ TW_VERSION = "2.0.1";
236148
236175
  TW_GENERATOR_META = `<meta name="generator" content="TW Framework ${TW_VERSION}">`;
236149
236176
  }
236150
236177
  });
@@ -236157,6 +236184,9 @@ function optimizedImageUrl(src, opts = {}) {
236157
236184
  if (opts.quality) p.set("q", String(opts.quality));
236158
236185
  p.set("f", opts.format ?? "auto");
236159
236186
  const qs = p.toString();
236187
+ if (/^https?:\/\//i.test(src)) {
236188
+ return `/_tw/img/_remote?src=${encodeURIComponent(src)}&${qs}`;
236189
+ }
236160
236190
  return `/_tw/img${src.startsWith("/") ? src : "/" + src}?${qs}`;
236161
236191
  }
236162
236192
  function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
@@ -236164,7 +236194,7 @@ function imageAttributesFor(opts, config = DEFAULT_IMAGE_CONFIG) {
236164
236194
  const quality = opts.quality ?? config.quality;
236165
236195
  const format = config.formats[0] ?? "webp";
236166
236196
  const isRemote = /^https?:\/\//i.test(opts.src);
236167
- const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}&w=${opts.width ?? ""}&h=${opts.height ?? ""}&q=${quality}&f=auto` : optimizedImageUrl(opts.src, { width: opts.width, height: opts.height, quality });
236197
+ const src = isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}${opts.width ? `&w=${opts.width}` : ""}${opts.height ? `&h=${opts.height}` : ""}&q=${quality}&f=auto` : optimizedImageUrl(opts.src, { width: opts.width, height: opts.height, quality });
236168
236198
  const widths = config.breakpoints.filter((bp) => width === 0 || bp <= width * 2);
236169
236199
  const srcset = widths.map(
236170
236200
  (bp) => isRemote ? `/_tw/img/_remote?src=${encodeURIComponent(opts.src)}&w=${bp}&q=${quality}&f=auto ${bp}w` : `${optimizedImageUrl(opts.src, { width: bp, quality })} ${bp}w`
@@ -236317,7 +236347,7 @@ function createImageHandler(options) {
236317
236347
  return { data: (0, import_node_fs5.readFileSync)(file), mtime: st.mtimeMs };
236318
236348
  }
236319
236349
  function cacheKey(src, w, h3, q, f, mtime) {
236320
- return (0, import_node_crypto3.createHash)("sha256").update(`${src}|${mtime}|${w}|${h3}|${q}|${f}`).digest("hex").slice(0, 32);
236350
+ return (0, import_node_crypto4.createHash)("sha256").update(`${src}|${mtime}|${w}|${h3}|${q}|${f}`).digest("hex").slice(0, 32);
236321
236351
  }
236322
236352
  async function processBuffer(data, w, h3, q, format) {
236323
236353
  const sharp = await loadSharp();
@@ -236344,7 +236374,7 @@ function createImageHandler(options) {
236344
236374
  }
236345
236375
  if (!warnedNoSharp) {
236346
236376
  warnedNoSharp = true;
236347
- console.warn(" [tw/image] sharp not installed \u2014 serving original images (bun add sharp to enable resizing/WebP/AVIF)");
236377
+ console.warn(" [tw/image] sharp not installed \u2014 using the built-in converter (WebP/AVIF); install sharp for faster resizing");
236348
236378
  }
236349
236379
  return { data, contentType: "", viaSharp: false };
236350
236380
  }
@@ -236428,10 +236458,10 @@ function createImageHandler(options) {
236428
236458
  }
236429
236459
  return { handle, sourceDir, cacheDir, config };
236430
236460
  }
236431
- var import_node_crypto3, import_node_fs5, import_node_path11, import_node_url, DEFAULT_IMAGE_CONFIG, IMAGE_EXTENSIONS, SHARP_FORMATS;
236461
+ var import_node_crypto4, import_node_fs5, import_node_path11, import_node_url, DEFAULT_IMAGE_CONFIG, IMAGE_EXTENSIONS, SHARP_FORMATS;
236432
236462
  var init_tw2 = __esm({
236433
236463
  "../../packages/image/tw/index.ts"() {
236434
- import_node_crypto3 = require("node:crypto");
236464
+ import_node_crypto4 = require("node:crypto");
236435
236465
  import_node_fs5 = require("node:fs");
236436
236466
  import_node_path11 = require("node:path");
236437
236467
  import_node_url = require("node:url");
@@ -236463,7 +236493,11 @@ var init_tw2 = __esm({
236463
236493
  });
236464
236494
 
236465
236495
  // ../../packages/link/tw/index.ts
236496
+ function isDangerousHref(href) {
236497
+ return DANGEROUS_SCHEME.test(href.replace(/[\u0000-\u0020]/g, ""));
236498
+ }
236466
236499
  function isExternalHref(href) {
236500
+ if (isDangerousHref(href)) return false;
236467
236501
  return /^[a-z][a-z0-9+.-]*:/i.test(href) || // any scheme: http(s), mailto, tel, ...
236468
236502
  href.startsWith("//");
236469
236503
  }
@@ -236472,7 +236506,8 @@ function isWebExternal(href) {
236472
236506
  return true;
236473
236507
  }
236474
236508
  function linkAttributesFor(opts) {
236475
- const href = (opts.href ?? "").trim();
236509
+ const rawHref = (opts.href ?? "").trim();
236510
+ const href = isDangerousHref(rawHref) ? "#" : rawHref;
236476
236511
  const external = opts.external === true || isExternalHref(href);
236477
236512
  const attrs = {};
236478
236513
  if (external) {
@@ -236501,8 +236536,10 @@ function linkAttributesFor(opts) {
236501
236536
  if (opts.id) attrs["id"] = opts.id;
236502
236537
  return { href, external, attrs };
236503
236538
  }
236539
+ var DANGEROUS_SCHEME;
236504
236540
  var init_tw3 = __esm({
236505
236541
  "../../packages/link/tw/index.ts"() {
236542
+ DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
236506
236543
  }
236507
236544
  });
236508
236545
 
@@ -237015,12 +237052,31 @@ var init_interpolate = __esm({
237015
237052
  });
237016
237053
 
237017
237054
  // ../../packages/compiler/tw/eval/evaluate.ts
237055
+ function setEvalBindings(bindings) {
237056
+ extraBindings = bindings ?? {};
237057
+ }
237018
237058
  function evaluate(expr, vars, sandbox) {
237019
237059
  if (!expr) return "";
237020
237060
  const sb = sandbox ?? defaultSandbox;
237021
237061
  if (vars[expr] !== void 0) {
237022
237062
  return vars[expr];
237023
237063
  }
237064
+ if (expr.startsWith("(") && expr.endsWith(")")) {
237065
+ let d = 0;
237066
+ let wraps = true;
237067
+ for (let i = 0; i < expr.length; i++) {
237068
+ const ch = expr[i];
237069
+ if (ch === "(") d++;
237070
+ else if (ch === ")") {
237071
+ d--;
237072
+ if (d === 0 && i < expr.length - 1) {
237073
+ wraps = false;
237074
+ break;
237075
+ }
237076
+ }
237077
+ }
237078
+ if (wraps && d === 0) return evaluate(expr.slice(1, -1).trim(), vars, sb);
237079
+ }
237024
237080
  if (/^-?\d+(\.\d+)?$/.test(expr)) return expr;
237025
237081
  if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
237026
237082
  return expr.slice(1, -1);
@@ -237028,11 +237084,54 @@ function evaluate(expr, vars, sandbox) {
237028
237084
  if (expr === "true") return "true";
237029
237085
  if (expr === "false") return "false";
237030
237086
  if (expr === "null" || expr === "undefined") return "";
237031
- const ternMatch = expr.match(/^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/);
237032
- if (ternMatch) {
237033
- const cond = isTruthy(resolve2(ternMatch[1].trim(), vars, sb));
237034
- return cond ? resolve2(ternMatch[2].trim(), vars, sb) : resolve2(ternMatch[3].trim(), vars, sb);
237087
+ {
237088
+ let depth = 0;
237089
+ let pending2 = 0;
237090
+ let q = -1;
237091
+ let quote = "";
237092
+ for (let i = 0; i < expr.length; i++) {
237093
+ const ch = expr[i];
237094
+ if (quote) {
237095
+ if (ch === "\\") {
237096
+ i++;
237097
+ continue;
237098
+ }
237099
+ if (ch === quote) quote = "";
237100
+ continue;
237101
+ }
237102
+ if (ch === "'" || ch === '"' || ch === "`") {
237103
+ quote = ch;
237104
+ continue;
237105
+ }
237106
+ if (ch === "(" || ch === "[" || ch === "{") {
237107
+ depth++;
237108
+ continue;
237109
+ }
237110
+ if (ch === ")" || ch === "]" || ch === "}") {
237111
+ depth--;
237112
+ continue;
237113
+ }
237114
+ if (depth !== 0) continue;
237115
+ if (ch === "?") {
237116
+ if (pending2 === 0) q = i;
237117
+ pending2++;
237118
+ continue;
237119
+ }
237120
+ if (ch === ":" && pending2 > 0) {
237121
+ pending2--;
237122
+ if (pending2 > 0) continue;
237123
+ const cond = expr.slice(0, q).trim();
237124
+ const cons = expr.slice(q + 1, i).trim();
237125
+ const alt = expr.slice(i + 1).trim();
237126
+ if (cond && cons && alt) {
237127
+ return isTruthy(resolve2(cond, vars, sb)) ? resolve2(cons, vars, sb) : resolve2(alt, vars, sb);
237128
+ }
237129
+ break;
237130
+ }
237131
+ }
237035
237132
  }
237133
+ const dyn = evalWithVars(expr, vars);
237134
+ if (dyn !== null) return dyn;
237036
237135
  const orParts = splitLogical(expr, "||");
237037
237136
  if (orParts.length > 1) {
237038
237137
  for (const part of orParts) {
@@ -237109,7 +237208,7 @@ function evaluate(expr, vars, sandbox) {
237109
237208
  return current2 !== void 0 && current2 !== null ? String(current2) : "";
237110
237209
  }
237111
237210
  const fnMatch = expr.match(/^(\w+)\((.*)\)$/);
237112
- if (fnMatch) {
237211
+ if (fnMatch && isBuiltinName(fnMatch[1])) {
237113
237212
  const fn = fnMatch[1];
237114
237213
  const arg = resolve2(fnMatch[2].trim(), vars, sb);
237115
237214
  return callBuiltin(fn, arg, sb);
@@ -237128,6 +237227,38 @@ function evaluate(expr, vars, sandbox) {
237128
237227
  }
237129
237228
  return expr;
237130
237229
  }
237230
+ function evalWithVars(expr, vars) {
237231
+ if (!expr || /[`\\]/.test(expr)) return null;
237232
+ if (!/^[\w\s.$[\]{}()'"`,+\-*/%?:<>=!&|]*$/.test(expr)) return null;
237233
+ for (const m of expr.matchAll(/\.([A-Za-z_$][\w$]*)\s*\(/g)) {
237234
+ if (!SAFE_MEMBERS.has(m[1])) return null;
237235
+ }
237236
+ const names = [];
237237
+ const vals = [];
237238
+ for (const k of Object.keys(vars)) {
237239
+ if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
237240
+ if (k in extraBindings) continue;
237241
+ names.push(k);
237242
+ try {
237243
+ vals.push(JSON.parse(vars[k]));
237244
+ } catch {
237245
+ vals.push(vars[k]);
237246
+ }
237247
+ }
237248
+ for (const k of Object.keys(extraBindings)) {
237249
+ if (!/^[A-Za-z_$][\w$]*$/.test(k)) continue;
237250
+ names.push(k);
237251
+ vals.push(extraBindings[k]);
237252
+ }
237253
+ try {
237254
+ const fn = new Function(...names, `"use strict"; return (${expr});`);
237255
+ const out = fn(...vals);
237256
+ if (out === void 0 || out === null) return null;
237257
+ return typeof out === "object" ? JSON.stringify(out) : String(out);
237258
+ } catch {
237259
+ return null;
237260
+ }
237261
+ }
237131
237262
  function resolve2(token, vars, sandbox) {
237132
237263
  const trimmed = token.trim();
237133
237264
  if (!trimmed) return "";
@@ -237296,7 +237427,7 @@ function coerce(value, targetType) {
237296
237427
  return value;
237297
237428
  }
237298
237429
  }
237299
- var defaultSandbox;
237430
+ var defaultSandbox, extraBindings, SAFE_MEMBERS;
237300
237431
  var init_evaluate = __esm({
237301
237432
  "../../packages/compiler/tw/eval/evaluate.ts"() {
237302
237433
  init_sandbox();
@@ -237304,6 +237435,64 @@ var init_evaluate = __esm({
237304
237435
  init_builtins();
237305
237436
  init_interpolate();
237306
237437
  defaultSandbox = new Sandbox();
237438
+ extraBindings = {};
237439
+ SAFE_MEMBERS = /* @__PURE__ */ new Set([
237440
+ "length",
237441
+ "toUpperCase",
237442
+ "toLowerCase",
237443
+ "trim",
237444
+ "trimStart",
237445
+ "trimEnd",
237446
+ "slice",
237447
+ "substring",
237448
+ "substr",
237449
+ "charAt",
237450
+ "at",
237451
+ "includes",
237452
+ "indexOf",
237453
+ "lastIndexOf",
237454
+ "startsWith",
237455
+ "endsWith",
237456
+ "replace",
237457
+ "replaceAll",
237458
+ "split",
237459
+ "join",
237460
+ "concat",
237461
+ "repeat",
237462
+ "padStart",
237463
+ "padEnd",
237464
+ "toString",
237465
+ "toFixed",
237466
+ "map",
237467
+ "filter",
237468
+ "find",
237469
+ "some",
237470
+ "every",
237471
+ "reduce",
237472
+ "sort",
237473
+ "reverse",
237474
+ // Static helpers on the built-ins (JSON.stringify, Object.keys, Math.round).
237475
+ "stringify",
237476
+ "parse",
237477
+ "keys",
237478
+ "values",
237479
+ "entries",
237480
+ "from",
237481
+ "isArray",
237482
+ "isInteger",
237483
+ "isFinite",
237484
+ "abs",
237485
+ "ceil",
237486
+ "floor",
237487
+ "round",
237488
+ "max",
237489
+ "min",
237490
+ "pow",
237491
+ "sqrt",
237492
+ "random",
237493
+ "now",
237494
+ "toISOString"
237495
+ ]);
237307
237496
  }
237308
237497
  });
237309
237498
 
@@ -237458,9 +237647,78 @@ function callBuiltin(fn, arg, sandbox) {
237458
237647
  function applyFilter(value, filter, args, vars, sandbox) {
237459
237648
  return callBuiltin(filter, value, sandbox);
237460
237649
  }
237650
+ function isBuiltinName(fn) {
237651
+ return BUILTIN_NAMES.has(fn);
237652
+ }
237653
+ var BUILTIN_NAMES;
237461
237654
  var init_builtins = __esm({
237462
237655
  "../../packages/compiler/tw/eval/builtins.ts"() {
237463
237656
  init_evaluate();
237657
+ BUILTIN_NAMES = /* @__PURE__ */ new Set([
237658
+ "Boolean",
237659
+ "Number",
237660
+ "String",
237661
+ "abs",
237662
+ "base64decode",
237663
+ "base64encode",
237664
+ "bool",
237665
+ "camelCase",
237666
+ "capitalize",
237667
+ "ceil",
237668
+ "cos",
237669
+ "date",
237670
+ "decodeURI",
237671
+ "encodeURI",
237672
+ "endsWith",
237673
+ "exp",
237674
+ "first",
237675
+ "float",
237676
+ "floor",
237677
+ "includes",
237678
+ "indexOf",
237679
+ "int",
237680
+ "join",
237681
+ "json",
237682
+ "kebabCase",
237683
+ "last",
237684
+ "len",
237685
+ "length",
237686
+ "log",
237687
+ "lower",
237688
+ "lowercase",
237689
+ "match",
237690
+ "max",
237691
+ "min",
237692
+ "now",
237693
+ "num",
237694
+ "padEnd",
237695
+ "padStart",
237696
+ "parseFloat",
237697
+ "parseInt",
237698
+ "parseJSON",
237699
+ "pow",
237700
+ "random",
237701
+ "repeat",
237702
+ "replace",
237703
+ "reverse",
237704
+ "round",
237705
+ "sin",
237706
+ "size",
237707
+ "slice",
237708
+ "snakeCase",
237709
+ "split",
237710
+ "sqrt",
237711
+ "startsWith",
237712
+ "str",
237713
+ "stringify",
237714
+ "tan",
237715
+ "test",
237716
+ "time",
237717
+ "title",
237718
+ "trim",
237719
+ "upper",
237720
+ "uppercase"
237721
+ ]);
237464
237722
  }
237465
237723
  });
237466
237724
 
@@ -238507,16 +238765,61 @@ function addScope(css, scopeId) {
238507
238765
  out += prelude;
238508
238766
  return out;
238509
238767
  }
238510
- function interpolate2(expr, vars) {
238511
- const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
238512
- const out = masked.replace(/\{([^}]+)\}/g, (_, name) => {
238513
- const key2 = name.trim();
238768
+ function interpolateBraces(input, vars) {
238769
+ let out = "";
238770
+ let i = 0;
238771
+ while (i < input.length) {
238772
+ const ch = input[i];
238773
+ if (ch !== "{") {
238774
+ out += ch;
238775
+ i++;
238776
+ continue;
238777
+ }
238778
+ let depth = 1;
238779
+ let j = i + 1;
238780
+ let quote = "";
238781
+ while (j < input.length && depth > 0) {
238782
+ const c = input[j];
238783
+ if (quote) {
238784
+ if (c === "\\") {
238785
+ j += 2;
238786
+ continue;
238787
+ }
238788
+ if (c === quote) quote = "";
238789
+ j++;
238790
+ continue;
238791
+ }
238792
+ if (c === "'" || c === '"' || c === "`") {
238793
+ quote = c;
238794
+ j++;
238795
+ continue;
238796
+ }
238797
+ if (c === "{") depth++;
238798
+ else if (c === "}") {
238799
+ depth--;
238800
+ if (depth === 0) break;
238801
+ }
238802
+ j++;
238803
+ }
238804
+ if (depth !== 0) {
238805
+ out += ch;
238806
+ i++;
238807
+ continue;
238808
+ }
238809
+ const key2 = input.slice(i + 1, j).trim();
238514
238810
  if (/^[a-zA-Z_$][\w$]*$/.test(key2)) {
238515
- return vars[key2] ?? "";
238811
+ out += vars[key2] ?? "";
238812
+ } else {
238813
+ const val = evaluate(key2, vars);
238814
+ out += val === null ? "" : val;
238516
238815
  }
238517
- const val = evaluate(key2, vars);
238518
- return val === null ? "" : val;
238519
- });
238816
+ i = j + 1;
238817
+ }
238818
+ return out;
238819
+ }
238820
+ function interpolate2(expr, vars) {
238821
+ const masked = expr.replace(/\\\{/g, "O").replace(/\\\}/g, "C");
238822
+ const out = interpolateBraces(masked, vars);
238520
238823
  return out.replace(/\u0001O/g, "{").replace(/\u0001C/g, "}");
238521
238824
  }
238522
238825
  function isTruthy2(val) {
@@ -238995,7 +239298,21 @@ function renderBindings(bindings, ctx) {
238995
239298
  if (bindings.length === 0) return "";
238996
239299
  const parts = [];
238997
239300
  for (const binding of bindings) {
238998
- parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(String(binding.expression ?? ""))}"`);
239301
+ const expr = String(binding.expression ?? "");
239302
+ let initial = "";
239303
+ try {
239304
+ if (/^[A-Za-z_$][\w$]*$/.test(expr)) {
239305
+ const v = ctx.stateVars[expr];
239306
+ if (v !== void 0 && v !== null) initial = String(v);
239307
+ } else if (expr) {
239308
+ initial = interpolate2("{" + expr + "}", ctx.stateVars);
239309
+ }
239310
+ } catch {
239311
+ }
239312
+ if (initial && !/[{}]/.test(initial)) {
239313
+ parts.push(`${binding.property}="${escapeAttr3(initial)}"`);
239314
+ }
239315
+ parts.push(`data-tw-bind-${binding.property}="${escapeAttr3(expr)}"`);
238999
239316
  }
239000
239317
  return parts.join(" ");
239001
239318
  }
@@ -239110,8 +239427,13 @@ function generateComponent(comp, ctx) {
239110
239427
  const savedStateVars = { ...ctx.stateVars };
239111
239428
  for (const prop of comp.props) {
239112
239429
  if (typeof prop.value === "string") {
239113
- const resolved = interpolate2(prop.value, ctx.stateVars);
239114
- ctx.stateVars[prop.name] = resolved;
239430
+ const raw = String(prop.value);
239431
+ const head = raw.split(/[.[]/)[0];
239432
+ if (!/[{}]/.test(raw) && Object.prototype.hasOwnProperty.call(ctx.stateVars, head)) {
239433
+ ctx.stateVars[prop.name] = interpolate2("{" + raw + "}", ctx.stateVars);
239434
+ } else {
239435
+ ctx.stateVars[prop.name] = interpolate2(raw, ctx.stateVars);
239436
+ }
239115
239437
  }
239116
239438
  }
239117
239439
  const componentProgram = componentRegistry.get(comp.name);
@@ -239205,6 +239527,14 @@ function generateFor(node, ctx) {
239205
239527
  }
239206
239528
  }
239207
239529
  }
239530
+ if (items.length === 0 && !/^\s*\[/.test(iterableStr)) {
239531
+ try {
239532
+ const evaluated = evaluate(String(node.iterable ?? "").replace(/^\{/, "").replace(/\}$/, "").trim(), ctx.stateVars);
239533
+ const parsed = JSON.parse(evaluated);
239534
+ if (Array.isArray(parsed)) items = parsed;
239535
+ } catch {
239536
+ }
239537
+ }
239208
239538
  for (let i = 0; i < items.length; i++) {
239209
239539
  const childCtx = { ...ctx };
239210
239540
  childCtx.stateVars = { ...ctx.stateVars };
@@ -243013,16 +243343,22 @@ function generateWithLayoutChain(layoutPrograms, pageProgram, stateVars) {
243013
243343
  }
243014
243344
  };
243015
243345
  let pageTitle = "TW Page";
243346
+ let pageDescription = "";
243016
243347
  for (const dir of pageProgram.directives || []) {
243017
- if (dir.type === "PageDirective" && dir.key === "title") {
243018
- const t3 = dir.value;
243019
- if (t3 != null && String(t3) !== "") pageTitle = String(t3);
243348
+ if (dir.type === "PageDirective") {
243349
+ if (dir.key === "title") {
243350
+ const t3 = dir.value;
243351
+ if (t3 != null && String(t3) !== "") pageTitle = String(t3);
243352
+ } else if (dir.key === "description") {
243353
+ const d = dir.value;
243354
+ if (d != null) pageDescription = String(d);
243355
+ }
243020
243356
  }
243021
243357
  }
243022
243358
  const pageCtx = createContext("ssr");
243023
243359
  seedCtx(pageCtx);
243024
243360
  try {
243025
- pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle });
243361
+ pageCtx.stateVars["page"] = JSON.stringify({ title: pageTitle, description: pageDescription });
243026
243362
  } catch {
243027
243363
  }
243028
243364
  {
@@ -243178,6 +243514,10 @@ function generate(program, stateVars, genOpts2) {
243178
243514
  const startTime = performance.now();
243179
243515
  const ctx = createContext("ssr");
243180
243516
  if (stateVars) ctx.stateVars = stateVars;
243517
+ if (genOpts2?.imports) {
243518
+ ctx.imports = genOpts2.imports;
243519
+ setEvalBindings(genOpts2.imports);
243520
+ } else setEvalBindings({});
243181
243521
  if (genOpts2 && genOpts2.scopedStyles === false) ctx.scopedStyles = false;
243182
243522
  if (genOpts2?.scopeId) ctx.scopeId = genOpts2.scopeId;
243183
243523
  if (genOpts2?.cssPrefix) ctx.cssPrefix = genOpts2.cssPrefix;
@@ -243262,6 +243602,7 @@ var init_vdom = __esm({
243262
243602
  init_types4();
243263
243603
  init_html2();
243264
243604
  init_builtin_components();
243605
+ init_evaluate();
243265
243606
  }
243266
243607
  });
243267
243608
 
@@ -243603,6 +243944,7 @@ var init_codes = __esm({
243603
243944
  TW095: { message: "Invalid for-loop iterable -- use {varName} or a valid comma-separated literal", severity: "error", category: "syntax" },
243604
243945
  TW096: { message: "Unknown state variable in interpolation (renders empty)", severity: "warning", category: "semantic" },
243605
243946
  TW097: { message: "while with a comparison renders its body ONCE at build time (state cannot mutate during SSR) -- use for loops for repetition", severity: "warning", category: "semantic" },
243947
+ TW098: { message: "Interpolation cannot be evaluated at build time -- it renders as its source text", severity: "warning", category: "semantic" },
243606
243948
  // Server blocks (TW099-TW101)
243607
243949
  TW099: { message: "Unknown server modifier (only ':defer' is supported)", severity: "error", category: "syntax" },
243608
243950
  TW100: { message: "Expected '{' after 'server'", severity: "error", category: "syntax" },
@@ -243690,6 +244032,7 @@ function checkUnknownTags(program, filePath, diags) {
243690
244032
  forEachNode(program, (node, ctx) => {
243691
244033
  if (!isElement(node)) return;
243692
244034
  if (node.tag[0] === node.tag[0]?.toUpperCase()) return;
244035
+ if (BUILTIN_COMPONENT_TAGS.has(node.tag)) return;
243693
244036
  const parent = ctx?.parent;
243694
244037
  if (parent && (parent.type === "Element" && typeof parent.tag === "string" && parent.tag[0] === parent.tag[0]?.toUpperCase() || parent.type === "Component")) return;
243695
244038
  if (HTML_ELEMENTS2.has(node.tag.toLowerCase())) return;
@@ -244044,6 +244387,59 @@ function checkUnknownStateVars(program, filePath, diags) {
244044
244387
  "null",
244045
244388
  "undefined"
244046
244389
  ]);
244390
+ const EVALUABLE_METHODS = /* @__PURE__ */ new Set([
244391
+ "length",
244392
+ "toUpperCase",
244393
+ "toLowerCase",
244394
+ "trim",
244395
+ "trimStart",
244396
+ "trimEnd",
244397
+ "slice",
244398
+ "substring",
244399
+ "substr",
244400
+ "charAt",
244401
+ "at",
244402
+ "includes",
244403
+ "indexOf",
244404
+ "lastIndexOf",
244405
+ "startsWith",
244406
+ "endsWith",
244407
+ "replace",
244408
+ "replaceAll",
244409
+ "split",
244410
+ "join",
244411
+ "concat",
244412
+ "repeat",
244413
+ "padStart",
244414
+ "padEnd",
244415
+ "toString",
244416
+ "toFixed",
244417
+ "map",
244418
+ "filter",
244419
+ "find",
244420
+ "some",
244421
+ "every",
244422
+ "reduce",
244423
+ "sort",
244424
+ "reverse",
244425
+ "stringify",
244426
+ "parse",
244427
+ "keys",
244428
+ "values",
244429
+ "entries",
244430
+ "from",
244431
+ "isArray",
244432
+ "round",
244433
+ "floor",
244434
+ "ceil",
244435
+ "abs",
244436
+ "max",
244437
+ "min",
244438
+ "pow",
244439
+ "sqrt",
244440
+ "random",
244441
+ "now"
244442
+ ]);
244047
244443
  for (const node of texts) {
244048
244444
  const text = String(node.value ?? "");
244049
244445
  if (!text.includes("{")) continue;
@@ -244059,6 +244455,22 @@ function checkUnknownStateVars(program, filePath, diags) {
244059
244455
  [`Declare it in the state block, or check the spelling`]
244060
244456
  ));
244061
244457
  }
244458
+ for (const m of text.matchAll(/\{([^{}]+)\}/g)) {
244459
+ const expr = m[1].trim();
244460
+ if (/^[A-Za-z_$][\w$]*$/.test(expr)) continue;
244461
+ for (const c of expr.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
244462
+ const fn = c[1];
244463
+ if (known.has(fn) || BUILTINS2.has(fn) || EVALUABLE_METHODS.has(fn)) continue;
244464
+ diags.push(createDiagnostic(
244465
+ "TW098",
244466
+ node.line,
244467
+ node.col,
244468
+ filePath,
244469
+ `{${expr}}`,
244470
+ [`Cannot evaluate this at build time -- it renders as its source. Check the name, or simplify the expression`]
244471
+ ));
244472
+ }
244473
+ }
244062
244474
  }
244063
244475
  }
244064
244476
  function checkWhileComparisons(program, filePath, diags) {
@@ -244091,7 +244503,7 @@ function checkDeprecatedImports(program, filePath, diags) {
244091
244503
  }
244092
244504
  }
244093
244505
  }
244094
- var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, DEPRECATED_IMPORTS;
244506
+ var HTML_ELEMENTS2, HTML_ATTRIBUTES2, GLOBAL_ATTRIBUTES2, CSS_PROPERTIES2, CSS_ALIASES2, EVENT_TYPES2, ARIA_ROLES2, ARIA_ATTRIBUTES2, BUILTIN_COMPONENT_TAGS, DEPRECATED_IMPORTS;
244095
244507
  var init_rules = __esm({
244096
244508
  "../../packages/compiler/tw/diagnostics/rules.ts"() {
244097
244509
  init_visitors();
@@ -244955,6 +245367,7 @@ var init_rules = __esm({
244955
245367
  "aria-valuenow",
244956
245368
  "aria-valuetext"
244957
245369
  ]);
245370
+ BUILTIN_COMPONENT_TAGS = /* @__PURE__ */ new Set(["optImage", "twImage", "twLink", "twImg"]);
244958
245371
  DEPRECATED_IMPORTS = [
244959
245372
  { source: "utils/string/transform", name: "toHex", replacement: "stringToHex", removal: "2.1.0" },
244960
245373
  { source: "utils/string/transform", name: "fromHex", replacement: "hexToString", removal: "2.1.0" },
@@ -255268,7 +255681,8 @@ function genOpts(opts, scopeId) {
255268
255681
  scopedStyles: opts?.compiler?.scopedStyles,
255269
255682
  scopeId,
255270
255683
  cssPrefix: opts?.cssPrefix,
255271
- cssImportPaths: opts?.cssImportPaths
255684
+ cssImportPaths: opts?.cssImportPaths,
255685
+ imports: opts?.imports
255272
255686
  };
255273
255687
  }
255274
255688
  function scopeIdFor(filePath) {
@@ -256007,14 +256421,14 @@ function verifyJwt_hs256(token, secret) {
256007
256421
  return false;
256008
256422
  }
256009
256423
  if (header?.alg !== "HS256") return false;
256010
- const expected = (0, import_node_crypto4.createHmac)("sha256", secret).update(`${h3}.${p}`).digest();
256424
+ const expected = (0, import_node_crypto5.createHmac)("sha256", secret).update(`${h3}.${p}`).digest();
256011
256425
  let got;
256012
256426
  try {
256013
256427
  got = b64urlDecode(sig);
256014
256428
  } catch {
256015
256429
  return false;
256016
256430
  }
256017
- if (expected.length !== got.length || !(0, import_node_crypto4.timingSafeEqual)(expected, got)) return false;
256431
+ if (expected.length !== got.length || !(0, import_node_crypto5.timingSafeEqual)(expected, got)) return false;
256018
256432
  if (typeof payload?.exp === "number" && payload.exp * 1e3 < Date.now()) return false;
256019
256433
  return true;
256020
256434
  }
@@ -256185,11 +256599,11 @@ function evaluateRules(rules, request) {
256185
256599
  }
256186
256600
  return null;
256187
256601
  }
256188
- var import_node_crypto4, Parser, rateBuckets, RATE_BUCKETS_MAX;
256602
+ var import_node_crypto5, Parser, rateBuckets, RATE_BUCKETS_MAX;
256189
256603
  var init_twm_rules = __esm({
256190
256604
  "../../packages/server/tw/routing/twm-rules.ts"() {
256191
256605
  init_tw();
256192
- import_node_crypto4 = require("node:crypto");
256606
+ import_node_crypto5 = require("node:crypto");
256193
256607
  Parser = class {
256194
256608
  constructor(toks, file) {
256195
256609
  this.toks = toks;
@@ -256700,6 +257114,18 @@ function findGlobalError(rootNode2) {
256700
257114
  function findRootError(rootNode2) {
256701
257115
  return (rootNode2.files || []).find((f) => f.type === "error");
256702
257116
  }
257117
+ function findNearestNotFound(rootNode2, pathParts) {
257118
+ let found = (rootNode2.files || []).find((f) => f.type === "not-found");
257119
+ let node = rootNode2;
257120
+ for (const part of pathParts) {
257121
+ const child = (node.children || []).find((c) => c.segment === part);
257122
+ if (!child) break;
257123
+ const f = (child.files || []).find((x) => x.type === "not-found");
257124
+ if (f) found = f;
257125
+ node = child;
257126
+ }
257127
+ return found;
257128
+ }
256703
257129
  function findRootNotFound(rootNode2) {
256704
257130
  return rootNode2.files.find((f) => f.type === "not-found");
256705
257131
  }
@@ -257502,6 +257928,26 @@ var init_render_pipeline = __esm({
257502
257928
  };
257503
257929
  }
257504
257930
  console.error(`[tw] server block failed for ${pageFile}: ${err2?.message ?? err2}`);
257931
+ try {
257932
+ const errFile = ctx.error ?? findRootError(tree);
257933
+ if (errFile) {
257934
+ const compiled = this.compileFile(errFile);
257935
+ let eh = String(compiled.html || "");
257936
+ const em = eh.match(/<body[^>]*>([\s\S]*)<\/body>/i);
257937
+ eh = (em ? em[1] : eh).replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
257938
+ return {
257939
+ html: this.assembleHTML(eh, compiled.css, "", "Error"),
257940
+ css: "",
257941
+ js: "",
257942
+ status: 500,
257943
+ headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "private, no-store" },
257944
+ fromCache: false,
257945
+ durationMs: 0,
257946
+ serverBlocks: true
257947
+ };
257948
+ }
257949
+ } catch {
257950
+ }
257505
257951
  return {
257506
257952
  html: "<!DOCTYPE html><html><head><title>Server Error</title></head><body><h1>Internal Server Error</h1></body></html>",
257507
257953
  css: "",
@@ -257567,7 +258013,7 @@ var init_render_pipeline = __esm({
257567
258013
  }
257568
258014
  const match = matchRoute(tree, pathname);
257569
258015
  if (!match) {
257570
- const rootNotFound = findRootNotFound(tree);
258016
+ const rootNotFound = findNearestNotFound(tree, pathname.split("/").filter(Boolean));
257571
258017
  if (rootNotFound) {
257572
258018
  const compiled = this.compileFile(rootNotFound);
257573
258019
  let nf = String(compiled.html || "");
@@ -257633,6 +258079,7 @@ var init_render_pipeline = __esm({
257633
258079
  }
257634
258080
  let bodyHTML = pageCompiled.html;
257635
258081
  let allCSS = pageCompiled.css;
258082
+ const parallelSlots = [];
257636
258083
  try {
257637
258084
  const parentNode = match?.node;
257638
258085
  if (parentNode && Array.isArray(parentNode.children)) {
@@ -257643,9 +258090,7 @@ var init_render_pipeline = __esm({
257643
258090
  const sbm = slotBody.match(/<body[^>]*>([\s\S]*)<\/body>/i);
257644
258091
  if (sbm) slotBody = sbm[1];
257645
258092
  slotBody = slotBody.replace(/<\/?html[^>]*>/gi, "").replace(/<\/?body[^>]*>/gi, "");
257646
- const comp = slotName.charAt(0).toUpperCase() + slotName.slice(1);
257647
- const slotRe = new RegExp(`<${comp}(s[^>]*)?>([sS]*?)</${comp}>|<${comp}(s[^>]*)?/>`, "gi");
257648
- bodyHTML = bodyHTML.replace(slotRe, slotBody);
258093
+ parallelSlots.push({ comp: slotName.charAt(0).toUpperCase() + slotName.slice(1), body: slotBody });
257649
258094
  if (slotCompiled.css) allCSS = slotCompiled.css + "\n" + allCSS;
257650
258095
  }
257651
258096
  }
@@ -257664,6 +258109,11 @@ var init_render_pipeline = __esm({
257664
258109
  allJS = templateCompiled.js + "\n" + allJS;
257665
258110
  bodyHTML = this.wrapInLayout(templateCompiled.html, bodyHTML);
257666
258111
  }
258112
+ for (const { comp, body } of parallelSlots) {
258113
+ const slotRe = new RegExp(`<${comp}(\\s[^>]*)?>([\\s\\S]*?)<\\/${comp}>|<${comp}(\\s[^>]*)?/>`, "gi");
258114
+ bodyHTML = bodyHTML.replace(slotRe, body);
258115
+ bodyHTML = bodyHTML.replace(new RegExp(`<!--\\s*TW: unknown component '${comp}'[\\s\\S]*?-->`, "gi"), body);
258116
+ }
257667
258117
  let layoutTitle;
257668
258118
  const liftedParts = [];
257669
258119
  {
@@ -258082,8 +258532,6 @@ ${bodyHTML}`;
258082
258532
  };
258083
258533
  let tags = "";
258084
258534
  const description = pick2("description");
258085
- if (description) tags += ` <meta name="description" content="${esc4(description)}">
258086
- `;
258087
258535
  const keywords = pick2("keywords");
258088
258536
  if (keywords) tags += ` <meta name="keywords" content="${esc4(keywords)}">
258089
258537
  `;
@@ -260265,6 +260713,7 @@ var init_static = __esm({
260265
260713
  if (!(0, import_node_fs13.existsSync)(filePath)) {
260266
260714
  filePath = (0, import_node_path20.join)(this.root, safePath + ".html");
260267
260715
  }
260716
+ let notFoundFallback = false;
260268
260717
  if (!(0, import_node_fs13.existsSync)(filePath)) {
260269
260718
  filePath = (0, import_node_path20.join)(this.root, safePath, this.indexFile);
260270
260719
  }
@@ -260276,6 +260725,7 @@ var init_static = __esm({
260276
260725
  }
260277
260726
  } else {
260278
260727
  filePath = (0, import_node_path20.join)(this.root, "404.html");
260728
+ notFoundFallback = true;
260279
260729
  if (!(0, import_node_fs13.existsSync)(filePath)) {
260280
260730
  return new Response("Not Found", { status: 404, headers: { "Content-Type": "text/plain" } });
260281
260731
  }
@@ -260343,12 +260793,12 @@ var init_static = __esm({
260343
260793
  const gzHeaders = { ...headers, "Content-Encoding": "gzip", "Content-Length": String(gz.length), Vary: "Accept-Encoding" };
260344
260794
  delete gzHeaders["Content-Length"];
260345
260795
  if (this.lastModified) delete gzHeaders["Last-Modified"];
260346
- return new Response(gz, { headers: gzHeaders });
260796
+ return new Response(gz, { headers: gzHeaders, status: notFoundFallback ? 404 : 200 });
260347
260797
  }
260348
260798
  } catch {
260349
260799
  }
260350
260800
  }
260351
- return new Response(bytes, { headers });
260801
+ return new Response(bytes, { headers, status: notFoundFallback ? 404 : 200 });
260352
260802
  }
260353
260803
  /**
260354
260804
  * Parse a `Range: bytes=...` header against a known content length.
@@ -260401,10 +260851,10 @@ var init_static = __esm({
260401
260851
  });
260402
260852
 
260403
260853
  // ../../packages/security/tw/policy.ts
260404
- var import_node_crypto5, CSPBuilder, XSSSanitizer, RateLimiter, CSRFProtection, InputValidator;
260854
+ var import_node_crypto6, CSPBuilder, XSSSanitizer, RateLimiter, CSRFProtection, InputValidator;
260405
260855
  var init_policy = __esm({
260406
260856
  "../../packages/security/tw/policy.ts"() {
260407
- import_node_crypto5 = require("node:crypto");
260857
+ import_node_crypto6 = require("node:crypto");
260408
260858
  CSPBuilder = class _CSPBuilder {
260409
260859
  directives = /* @__PURE__ */ new Map();
260410
260860
  nonces = /* @__PURE__ */ new Set();
@@ -260422,7 +260872,7 @@ var init_policy = __esm({
260422
260872
  return this;
260423
260873
  }
260424
260874
  generateNonce() {
260425
- const nonce = (0, import_node_crypto5.randomBytes)(16).toString("base64");
260875
+ const nonce = (0, import_node_crypto6.randomBytes)(16).toString("base64");
260426
260876
  this.nonces.add(nonce);
260427
260877
  return nonce;
260428
260878
  }
@@ -260671,7 +261121,7 @@ var init_policy = __esm({
260671
261121
  * Generate a Subresource Integrity hash for a script/style.
260672
261122
  */
260673
261123
  generateSRI(content, alg = "sha384") {
260674
- const hash2 = (0, import_node_crypto5.createHash)(alg).update(content).digest("base64");
261124
+ const hash2 = (0, import_node_crypto6.createHash)(alg).update(content).digest("base64");
260675
261125
  return `${alg}-${hash2}`;
260676
261126
  }
260677
261127
  /**
@@ -260752,10 +261202,10 @@ var init_policy = __esm({
260752
261202
  tokens = /* @__PURE__ */ new Map();
260753
261203
  secret;
260754
261204
  constructor(secret) {
260755
- this.secret = secret ?? (0, import_node_crypto5.randomBytes)(32).toString("hex");
261205
+ this.secret = secret ?? (0, import_node_crypto6.randomBytes)(32).toString("hex");
260756
261206
  }
260757
261207
  generateToken(sessionId) {
260758
- const token = (0, import_node_crypto5.randomBytes)(32).toString("hex");
261208
+ const token = (0, import_node_crypto6.randomBytes)(32).toString("hex");
260759
261209
  const expires = Date.now() + 36e5;
260760
261210
  this.tokens.set(sessionId, { value: token, expires });
260761
261211
  return token;
@@ -260998,6 +261448,12 @@ var init_xss_detector = __esm({
260998
261448
  { name: "SQL comment", regex: /--\s/g, severity: "medium", recommendation: "Check for SQL comment injection" },
260999
261449
  { name: "Stacked queries", regex: /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|UNION)/gi, severity: "high", recommendation: "Prevent stacked queries" },
261000
261450
  { name: "UNION injection", regex: /UNION\s+SELECT/gi, severity: "critical", recommendation: "Filter UNION SELECT from user input" },
261451
+ // Comment-only injections (`admin'--`, `admin'#`, `admin'/*`): a quote
261452
+ // followed by a comment truncates the query and bypasses the login
261453
+ // check, with no OR/UNION for the other patterns to catch.
261454
+ { name: "Comment-terminated injection", regex: /['"`]\s*(--|#|\/\*)/g, severity: "high", recommendation: "A quote followed by a comment truncates the query -- use parameterized queries" },
261455
+ // Parenthesised boolean injection: `') OR ('1'='1`.
261456
+ { name: "Parenthesised boolean injection", regex: /['"`]?\s*\)\s*(OR|AND)\s*\(?\s*['"`]?\s*\d/i, severity: "high", recommendation: "Use parameterized queries instead of building boolean expressions" },
261001
261457
  { name: "Boolean-based injection", regex: /OR\s+1\s*=\s*1/gi, severity: "high", recommendation: "Use parameterized queries to prevent boolean injection" },
261002
261458
  { name: "Time-based injection", regex: /WAITFOR\s+DELAY/gi, severity: "high", recommendation: "Filter WAITFOR DELAY from user input" },
261003
261459
  { name: "DROP statement", regex: /DROP\s+(TABLE|DATABASE)/gi, severity: "critical", recommendation: "Prevent DROP statements in queries" },
@@ -261528,6 +261984,7 @@ var init_logger3 = __esm({
261528
261984
  }
261529
261985
  /** Records an audit event. */
261530
261986
  log(event) {
261987
+ event = { type: "system", actor: "unknown", resource: "unknown", ...event };
261531
261988
  const fullEvent = {
261532
261989
  ...event,
261533
261990
  id: crypto.randomUUID(),
@@ -262544,6 +263001,13 @@ var FILE_SIGNATURES, DANGEROUS_EXTENSIONS, DANGEROUS_MIME_TYPES, ContentSecurity
262544
263001
  var init_protector = __esm({
262545
263002
  "../../packages/security/tw/content-security/protector.ts"() {
262546
263003
  FILE_SIGNATURES = [
263004
+ // Executables and HTML: without these, content renamed to an image extension
263005
+ // had no detected type, the signature check was skipped, and the upload was
263006
+ // accepted as risk "low" -- the exact rename bypass the check exists for.
263007
+ { ext: ["exe", "dll", "sys"], mime: "application/x-msdownload", signature: [77, 90] },
263008
+ { ext: ["elf", "so", "bin"], mime: "application/x-executable", signature: [127, 69, 76, 70] },
263009
+ { ext: ["html", "htm", "xhtml"], mime: "text/html", signature: [60, 104, 116, 109, 108] },
263010
+ { ext: ["html", "htm"], mime: "text/html", signature: [60, 33, 68, 79, 67, 84, 89, 80, 69] },
262547
263011
  { ext: ["jpg", "jpeg"], mime: "image/jpeg", signature: [255, 216, 255] },
262548
263012
  { ext: ["png"], mime: "image/png", signature: [137, 80, 78, 71, 13, 10, 26, 10] },
262549
263013
  { ext: ["gif"], mime: "image/gif", signature: [71, 73, 70, 56] },
@@ -262595,10 +263059,39 @@ var init_protector = __esm({
262595
263059
  ".htpasswd",
262596
263060
  ".svg",
262597
263061
  // Can contain JavaScript
262598
- ".xml"
263062
+ ".xml",
262599
263063
  // Can contain XXE
263064
+ // Active-content types the set used to let through while still blocking .svg
263065
+ // "because it can contain JavaScript" -- .html/.js are script-capable by
263066
+ // definition, so an accepted upload served from our own origin is stored XSS.
263067
+ ".html",
263068
+ ".htm",
263069
+ ".xhtml",
263070
+ ".shtml",
263071
+ ".js",
263072
+ ".mjs",
263073
+ ".cjs",
263074
+ ".jsx",
263075
+ ".ts",
263076
+ ".tsx",
263077
+ ".mhtml",
263078
+ ".hta",
263079
+ ".jsp",
263080
+ ".asp",
263081
+ ".aspx",
263082
+ ".cgi",
263083
+ ".pl"
262600
263084
  ]);
262601
263085
  DANGEROUS_MIME_TYPES = /* @__PURE__ */ new Set([
263086
+ // Active-content types: they can carry script (svg, xhtml, xml) or be
263087
+ // rendered as a document (html, pdf). Blocking .svg by extension while
263088
+ // reporting isContentTypeSafe("image/svg+xml") === true was inconsistent.
263089
+ "image/svg+xml",
263090
+ "text/html",
263091
+ "application/xhtml+xml",
263092
+ "application/pdf",
263093
+ "text/xml",
263094
+ "application/xml",
262602
263095
  "application/javascript",
262603
263096
  "text/javascript",
262604
263097
  "application/x-javascript",
@@ -262653,7 +263146,9 @@ var init_protector = __esm({
262653
263146
  reason: "Empty file",
262654
263147
  sanitizedFilename: sanitized,
262655
263148
  detectedType: "unknown",
262656
- risk: "low"
263149
+ // Unknown content is not low risk -- it is content whose type we could
263150
+ // not establish, which is what a rename bypass looks like.
263151
+ risk: "medium"
262657
263152
  };
262658
263153
  }
262659
263154
  if (this.blockedExtensions.has(ext)) {
@@ -263816,6 +264311,7 @@ var init_nonce_manager = __esm({
263816
264311
  validate(nonce) {
263817
264312
  const entry = this.nonces.get(nonce);
263818
264313
  if (!entry) return false;
264314
+ if (entry.used) return false;
263819
264315
  if (Date.now() > entry.expiresAt) {
263820
264316
  this.nonces.delete(nonce);
263821
264317
  return false;
@@ -264608,7 +265104,15 @@ var init_token_manager = __esm({
264608
265104
  rotateAfterUse;
264609
265105
  maxTokens;
264610
265106
  tokens = /* @__PURE__ */ new Map();
265107
+ /** Tokens this manager issued and already consumed. A rotated token is
265108
+ * deleted from `tokens`, so without this marker a replay found no entry and
265109
+ * fell through to the stateless branch, which accepted it -- defeating the
265110
+ * documented single-use guarantee of `rotateAfterUse`. */
265111
+ consumedTokens = /* @__PURE__ */ new Set();
264611
265112
  constructor(options) {
265113
+ if (!options || typeof options.secret !== "string" || options.secret.length === 0) {
265114
+ throw new Error("createCSRFManager: options.secret is required (the HMAC key used to sign tokens)");
265115
+ }
264612
265116
  this.secret = options.secret;
264613
265117
  this.tokenLength = options.tokenLength ?? 32;
264614
265118
  this.ttl = options.ttl ?? 36e5;
@@ -264708,12 +265212,16 @@ var init_token_manager = __esm({
264708
265212
  }
264709
265213
  entry.used = true;
264710
265214
  if (this.rotateAfterUse) {
265215
+ this.consumedTokens.add(headerPart);
264711
265216
  this.tokens.delete(headerPart);
264712
265217
  const rotated = await this.generate(sessionId ?? void 0);
264713
265218
  return { valid: true, rotated };
264714
265219
  }
264715
265220
  return { valid: true };
264716
265221
  }
265222
+ if (this.consumedTokens.has(headerPart)) {
265223
+ return { valid: false, reason: "Token already used" };
265224
+ }
264717
265225
  const expectedSig = await hmacSign3(
264718
265226
  this.secret,
264719
265227
  `${headerPart}.${sessionId ?? ""}`
@@ -269205,14 +269713,14 @@ function getCachedSSR(pathname) {
269205
269713
  const diskPath = (0, import_node_path21.join)(_diskCacheDir, pathname.replace(/[\/]/g, "_") + ".html");
269206
269714
  if ((0, import_node_fs15.existsSync)(diskPath)) {
269207
269715
  const content = (0, import_node_fs14.readFileSync)(diskPath, "utf-8");
269208
- const etag = (0, import_node_crypto6.createHash)("sha256").update(content).digest("hex").slice(0, 16);
269716
+ const etag = (0, import_node_crypto7.createHash)("sha256").update(content).digest("hex").slice(0, 16);
269209
269717
  _ssrCache.set(pathname, { html: content, etag, expires: Date.now() + 6e4 });
269210
269718
  return { html: content, etag };
269211
269719
  }
269212
269720
  return null;
269213
269721
  }
269214
269722
  function setCachedSSR(pathname, html) {
269215
- const etag = (0, import_node_crypto6.createHash)("sha256").update(html).digest("hex").slice(0, 16);
269723
+ const etag = (0, import_node_crypto7.createHash)("sha256").update(html).digest("hex").slice(0, 16);
269216
269724
  _ssrCache.set(pathname, { html, etag, expires: Date.now() + 6e4 });
269217
269725
  try {
269218
269726
  if (!(0, import_node_fs15.existsSync)(_diskCacheDir)) (0, import_node_fs14.mkdirSync)(_diskCacheDir, { recursive: true });
@@ -269244,13 +269752,13 @@ function generatePreloadHints(assets) {
269244
269752
  }
269245
269753
  return hints.join("\n");
269246
269754
  }
269247
- var import_node_fs14, import_node_crypto6, import_node_path21, import_node_fs15, SSRRenderer, _ssrCache, _diskCacheDir;
269755
+ var import_node_fs14, import_node_crypto7, import_node_path21, import_node_fs15, SSRRenderer, _ssrCache, _diskCacheDir;
269248
269756
  var init_ssr = __esm({
269249
269757
  "../../packages/server/tw/ssr/index.ts"() {
269250
269758
  import_node_fs14 = require("node:fs");
269251
269759
  init_fs_compat();
269252
269760
  init_tw();
269253
- import_node_crypto6 = require("node:crypto");
269761
+ import_node_crypto7 = require("node:crypto");
269254
269762
  import_node_path21 = require("node:path");
269255
269763
  import_node_fs15 = require("node:fs");
269256
269764
  SSRRenderer = class {
@@ -269674,7 +270182,7 @@ function generateNonce2(length = 32) {
269674
270182
  if (typeof crypto !== "undefined" && crypto.getRandomValues) {
269675
270183
  crypto.getRandomValues(bytes);
269676
270184
  } else {
269677
- const hash2 = (0, import_node_crypto7.createHash)("sha256");
270185
+ const hash2 = (0, import_node_crypto8.createHash)("sha256");
269678
270186
  hash2.update(`${Date.now()}-${nonceCounter++}-${Math.random()}`);
269679
270187
  return hash2.digest("base64");
269680
270188
  }
@@ -269792,10 +270300,10 @@ function createProdCSP() {
269792
270300
  "block-all-mixed-content": true
269793
270301
  };
269794
270302
  }
269795
- var import_node_crypto7, DEFAULT_CSP, DEFAULT_OPTIONS5, nonceCounter;
270303
+ var import_node_crypto8, DEFAULT_CSP, DEFAULT_OPTIONS5, nonceCounter;
269796
270304
  var init_security_headers2 = __esm({
269797
270305
  "../../packages/server/tw/security-headers.ts"() {
269798
- import_node_crypto7 = require("node:crypto");
270306
+ import_node_crypto8 = require("node:crypto");
269799
270307
  DEFAULT_CSP = {
269800
270308
  "default-src": ["'self'"],
269801
270309
  "script-src": ["'self'"],
@@ -270589,17 +271097,17 @@ async function hs256(payload, secret, maxAge) {
270589
271097
  const header = { alg: "HS256", typ: "JWT" };
270590
271098
  const body = { ...payload, iat: now, exp: now + maxAge };
270591
271099
  const data = enc(header) + "." + enc(body);
270592
- const { createHmac: createHmac2 } = await import("node:crypto");
270593
- const sig = createHmac2("sha256", secret).update(data).digest("base64url");
271100
+ const { createHmac: createHmac3 } = await import("node:crypto");
271101
+ const sig = createHmac3("sha256", secret).update(data).digest("base64url");
270594
271102
  return data + "." + sig;
270595
271103
  }
270596
271104
  async function verifyHs256(token, secret) {
270597
271105
  const parts = token.split(".");
270598
271106
  if (parts.length !== 3) return null;
270599
- const { createHmac: createHmac2, timingSafeEqual: timingSafeEqual4 } = await import("node:crypto");
270600
- const expected = createHmac2("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
271107
+ const { createHmac: createHmac3, timingSafeEqual: timingSafeEqual3 } = await import("node:crypto");
271108
+ const expected = createHmac3("sha256", secret).update(parts[0] + "." + parts[1]).digest("base64url");
270601
271109
  const a = Buffer.from(expected), b = Buffer.from(parts[2]);
270602
- if (a.length !== b.length || !timingSafeEqual4(a, b)) return null;
271110
+ if (a.length !== b.length || !timingSafeEqual3(a, b)) return null;
270603
271111
  try {
270604
271112
  const body = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
270605
271113
  if (typeof body.exp === "number" && body.exp < Math.floor(Date.now() / 1e3)) return null;
@@ -271904,6 +272412,19 @@ ${err2.stack ?? ""}`);
271904
272412
  const isrLookupPath = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
271905
272413
  if (this.staticHandler && !this.isrRoutes.has(isrLookupPath)) {
271906
272414
  let staticResponse = await this.staticHandler.serve(pathname, request);
272415
+ if (staticResponse.status !== 404) {
272416
+ const reqMethod = request.method.toUpperCase();
272417
+ const allow = "GET, HEAD, OPTIONS";
272418
+ if (reqMethod === "TRACE") {
272419
+ return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
272420
+ }
272421
+ if (reqMethod === "OPTIONS") {
272422
+ return new Response(null, { status: 204, headers: { Allow: allow } });
272423
+ }
272424
+ if (reqMethod !== "GET" && reqMethod !== "HEAD") {
272425
+ return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } });
272426
+ }
272427
+ }
271907
272428
  if (staticResponse && this.cspEnabled() && (staticResponse.headers.get("content-type") || "").includes("text/html")) {
271908
272429
  try {
271909
272430
  let buf = Buffer.from(await staticResponse.arrayBuffer());
@@ -272048,6 +272569,32 @@ ${err2.stack ?? ""}`);
272048
272569
  });
272049
272570
 
272050
272571
  // ../../packages/runtime/tw/dependency-graph.ts
272572
+ function flushPendingSubs() {
272573
+ if (isFlushing || !pendingSubs) return;
272574
+ isFlushing = true;
272575
+ try {
272576
+ let guard = 0;
272577
+ while (pendingSubs && pendingSubs.size > 0 && guard++ < 1e5) {
272578
+ const batchOf = pendingSubs;
272579
+ pendingSubs = /* @__PURE__ */ new Set();
272580
+ for (const sub of batchOf) {
272581
+ try {
272582
+ sub();
272583
+ } catch (e) {
272584
+ console.error("[TW Reactivity] Signal subscriber error:", e);
272585
+ }
272586
+ }
272587
+ }
272588
+ } finally {
272589
+ isFlushing = false;
272590
+ pendingSubs = null;
272591
+ }
272592
+ }
272593
+ function enqueueSubs(subs) {
272594
+ if (!pendingSubs) pendingSubs = /* @__PURE__ */ new Set();
272595
+ for (const s of subs) pendingSubs.add(s);
272596
+ if (batchDepth === 0) flushPendingSubs();
272597
+ }
272051
272598
  function startBatch() {
272052
272599
  batchDepth++;
272053
272600
  }
@@ -272057,13 +272604,7 @@ function endBatch() {
272057
272604
  batchDepth = 0;
272058
272605
  const effects = Array.from(batchedEffects);
272059
272606
  batchedEffects.clear();
272060
- for (const eff of effects) {
272061
- try {
272062
- eff();
272063
- } catch (e) {
272064
- console.error("[TW Reactivity] Batch effect error:", e);
272065
- }
272066
- }
272607
+ enqueueSubs(effects);
272067
272608
  }
272068
272609
  }
272069
272610
  function batch(fn) {
@@ -272245,12 +272786,14 @@ function isComputed(value) {
272245
272786
  function createScope3() {
272246
272787
  return new ReactiveScope();
272247
272788
  }
272248
- var currentEffect, batchDepth, batchedEffects, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
272789
+ var currentEffect, batchDepth, batchedEffects, pendingSubs, isFlushing, SignalImpl, ComputedImpl, watchEffect, currentCleanup, ReactiveScope;
272249
272790
  var init_dependency_graph = __esm({
272250
272791
  "../../packages/runtime/tw/dependency-graph.ts"() {
272251
272792
  currentEffect = null;
272252
272793
  batchDepth = 0;
272253
272794
  batchedEffects = /* @__PURE__ */ new Set();
272795
+ pendingSubs = null;
272796
+ isFlushing = false;
272254
272797
  SignalImpl = class {
272255
272798
  value;
272256
272799
  subscribers = /* @__PURE__ */ new Set();
@@ -272269,19 +272812,9 @@ var init_dependency_graph = __esm({
272269
272812
  if (this.equals(this.value, value)) return;
272270
272813
  this.value = value;
272271
272814
  if (batchDepth > 0) {
272272
- for (const sub of this.subscribers) {
272273
- batchedEffects.add(sub);
272274
- }
272275
- } else {
272276
- const subs = Array.from(this.subscribers);
272277
- for (const sub of subs) {
272278
- try {
272279
- sub();
272280
- } catch (e) {
272281
- console.error("[TW Reactivity] Signal subscriber error:", e);
272282
- }
272283
- }
272815
+ for (const sub of this.subscribers) batchedEffects.add(sub);
272284
272816
  }
272817
+ enqueueSubs(this.subscribers);
272285
272818
  }
272286
272819
  update(fn) {
272287
272820
  this.set(fn(this.value));
@@ -272347,14 +272880,11 @@ var init_dependency_graph = __esm({
272347
272880
  return this.cachedValue;
272348
272881
  }
272349
272882
  notify() {
272350
- const subs = Array.from(this.subscribers);
272351
- for (const sub of subs) {
272352
- try {
272353
- sub();
272354
- } catch (e) {
272355
- console.error("[TW Reactivity] Computed subscriber error:", e);
272356
- }
272883
+ if (batchDepth > 0) {
272884
+ for (const sub of this.subscribers) batchedEffects.add(sub);
272885
+ return;
272357
272886
  }
272887
+ enqueueSubs(this.subscribers);
272358
272888
  }
272359
272889
  set(_value) {
272360
272890
  throw new Error("Cannot set a computed signal");
@@ -275539,10 +276069,12 @@ var init_diff = __esm({
275539
276069
  "../../packages/runtime/tw/diff.ts"() {
275540
276070
  init_vdom2();
275541
276071
  HTML_ESCAPE_MAP = {
275542
- "&": "&",
275543
- "<": "<",
275544
- ">": ">",
275545
- '"': '"',
276072
+ // These four mapped to THEMSELVES, so a <script> tag passed through
276073
+ // untouched -- only ' was actually escaped.
276074
+ "&": "&amp;",
276075
+ "<": "&lt;",
276076
+ ">": "&gt;",
276077
+ '"': "&quot;",
275546
276078
  "'": "&#39;"
275547
276079
  };
275548
276080
  HTML_ESCAPE_REGEX = /[&<>"']/g;
@@ -279072,8 +279604,19 @@ async function hydrateElementById(id, component, root = typeof document !== "und
279072
279604
  );
279073
279605
  }
279074
279606
  function markForHydration(element, componentId, props) {
279607
+ if (!element || typeof componentId !== "string") return;
279075
279608
  element.setAttribute(HYDRATE_ATTR, componentId);
279076
- element.setAttribute("data-tw-props", JSON.stringify(props));
279609
+ let serialised = null;
279610
+ if (props && typeof props === "object") {
279611
+ try {
279612
+ serialised = JSON.stringify(props);
279613
+ } catch {
279614
+ serialised = null;
279615
+ }
279616
+ }
279617
+ if (serialised !== null && serialised !== void 0) {
279618
+ element.setAttribute("data-tw-props", serialised);
279619
+ }
279077
279620
  }
279078
279621
  function unmarkForHydration(element) {
279079
279622
  element.removeAttribute(HYDRATE_ATTR);
@@ -282373,15 +282916,21 @@ function usePathname(opts) {
282373
282916
  function matchPathPattern(path, pattern) {
282374
282917
  const pSeg = path.split("/").filter(Boolean);
282375
282918
  const tSeg = pattern.split("/").filter(Boolean);
282376
- if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
282919
+ const catchAllAt = tSeg.findIndex((t3) => t3.startsWith("[...") || t3.startsWith(":..."));
282920
+ if (catchAllAt === -1) {
282921
+ if (pSeg.length !== tSeg.length) return { matches: false, params: {}, score: 0 };
282922
+ } else if (pSeg.length < tSeg.length) {
282923
+ return { matches: false, params: {}, score: 0 };
282924
+ }
282377
282925
  const params = {};
282378
282926
  let literal = 0;
282379
282927
  for (let i = 0; i < tSeg.length; i++) {
282380
282928
  const t3 = tSeg[i];
282381
282929
  const isParam = t3.startsWith("[") && t3.endsWith("]") || t3.startsWith(":");
282382
282930
  if (isParam) {
282383
- const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/\.\.\.$/, "") : t3.slice(1);
282384
- params[name] = decodeURIComponent(pSeg[i]);
282931
+ const name = t3.startsWith("[") ? t3.slice(1, -1).replace(/^\.\.\./, "") : t3.slice(1);
282932
+ const isCatchAll = t3.startsWith("[...") || t3.startsWith(":...");
282933
+ params[name] = isCatchAll ? pSeg.slice(i).map((seg) => decodeURIComponent(seg)).join("/") : decodeURIComponent(pSeg[i]);
282385
282934
  } else if (t3 !== pSeg[i]) {
282386
282935
  return { matches: false, params: {}, score: 0 };
282387
282936
  } else {
@@ -283196,7 +283745,11 @@ function defineStore(idOrDefinition, maybeDefinition) {
283196
283745
  const getterDeps = Object.values(reactiveState);
283197
283746
  for (const key2 of Object.keys(getters)) {
283198
283747
  const getter = getters[key2];
283199
- getterValues[key2] = computed4(getterDeps, getter);
283748
+ getterValues[key2] = computed4(getterDeps, () => {
283749
+ const snap = {};
283750
+ for (const k of Object.keys(reactiveState)) snap[k] = reactiveState[k].value;
283751
+ return getter.call(snap, snap);
283752
+ });
283200
283753
  }
283201
283754
  const subscribers = /* @__PURE__ */ new Set();
283202
283755
  const actionListeners = /* @__PURE__ */ new Set();
@@ -283239,7 +283792,7 @@ function defineStore(idOrDefinition, maybeDefinition) {
283239
283792
  const fn = actions[key2];
283240
283793
  boundActions[key2] = (...args) => {
283241
283794
  actionListeners.forEach((cb) => cb({ name: key2, args }));
283242
- return fn.call(ctx, stateProxy, ...args);
283795
+ return fn.call(stateProxy, stateProxy, ...args);
283243
283796
  };
283244
283797
  }
283245
283798
  for (const rv of Object.values(reactiveState)) {
@@ -283351,9 +283904,10 @@ var init_store = __esm({
283351
283904
  };
283352
283905
  }
283353
283906
  _notify() {
283907
+ const current2 = this.value ?? this._value;
283354
283908
  for (const l of this._listeners) {
283355
283909
  try {
283356
- l();
283910
+ l(current2);
283357
283911
  } catch {
283358
283912
  }
283359
283913
  }
@@ -284359,7 +284913,8 @@ var init_virtual_list = __esm({
284359
284913
  return this.container.scrollTop;
284360
284914
  }
284361
284915
  getFirstVisibleIndex() {
284362
- return this.visibleRange.start;
284916
+ const last = Math.max(0, this.options?.itemCount ?? 0 - 1);
284917
+ return Math.min(Math.max(this.visibleRange.start, 0), last);
284363
284918
  }
284364
284919
  getLastVisibleIndex() {
284365
284920
  return this.visibleRange.end;