@stacksjs/stx 0.2.345 → 0.2.347

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.
Files changed (77) hide show
  1. package/dist/binding-manifest.js +2 -0
  2. package/dist/{chunk-kqzpmwx2.js → chunk-0573wv97.js} +183 -171
  3. package/dist/{chunk-223makqc.js → chunk-0vdng9sh.js} +1 -1
  4. package/dist/{chunk-ybx8n794.js → chunk-16kfapt5.js} +1 -1
  5. package/dist/{chunk-e7gs18rc.js → chunk-1kwtdq06.js} +1 -1
  6. package/dist/{chunk-qkdcbeaw.js → chunk-1vtd57x6.js} +6 -6
  7. package/dist/{chunk-fp868yhg.js → chunk-1zjh3d0f.js} +1 -1
  8. package/dist/{chunk-8rhjt6fh.js → chunk-24rhp7ye.js} +1 -1
  9. package/dist/{chunk-1qyg1q6d.js → chunk-3phs970s.js} +1 -1
  10. package/dist/{chunk-nqz501eq.js → chunk-4fcvaazx.js} +2 -2
  11. package/dist/{chunk-zmazqcb8.js → chunk-4k8rebtb.js} +1 -1
  12. package/dist/{chunk-6fwp7gee.js → chunk-5d3f7rth.js} +1 -1
  13. package/dist/{chunk-1t4ece3v.js → chunk-632jxm61.js} +1 -1
  14. package/dist/{chunk-1vv9g6cj.js → chunk-6be9r0tz.js} +1 -1
  15. package/dist/{chunk-cypg8qwn.js → chunk-6h8t40zg.js} +1 -1
  16. package/dist/{chunk-7g5vk4c3.js → chunk-8753m7f0.js} +1 -1
  17. package/dist/{chunk-esjcyxa9.js → chunk-8b8jwbne.js} +2 -2
  18. package/dist/{chunk-qr3zcm87.js → chunk-8cd3ws01.js} +1 -1
  19. package/dist/{chunk-9jbssgem.js → chunk-8m0g0h4x.js} +146 -10
  20. package/dist/{chunk-77p1kjg2.js → chunk-9rc9gshn.js} +2 -2
  21. package/dist/{chunk-1vtdege3.js → chunk-adevnpdw.js} +1 -1
  22. package/dist/{chunk-aapqf6bj.js → chunk-dmpexr7d.js} +1 -1
  23. package/dist/{chunk-vhg91hnw.js → chunk-e6zk6ntt.js} +1 -1
  24. package/dist/{chunk-3zm4hgcf.js → chunk-e9sk8fqy.js} +1 -1
  25. package/dist/{chunk-kfkf9v2d.js → chunk-f5veee11.js} +1 -1
  26. package/dist/{chunk-6wmjwbq9.js → chunk-fbs0x8r3.js} +1 -1
  27. package/dist/{chunk-gvemapw4.js → chunk-fqp4b8k4.js} +1 -1
  28. package/dist/{chunk-9qtf2b1p.js → chunk-gkr35yjh.js} +1 -1
  29. package/dist/chunk-gnb15a8d.js +32 -0
  30. package/dist/{chunk-4spaszfn.js → chunk-h2sbvyr4.js} +190 -190
  31. package/dist/chunk-h8apy0k1.js +2 -0
  32. package/dist/chunk-hpr2sdfc.js +2 -0
  33. package/dist/{chunk-xq0e2y4x.js → chunk-jxg9vnsy.js} +1 -1
  34. package/dist/{chunk-pajk5680.js → chunk-nqqknczy.js} +2 -2
  35. package/dist/{chunk-2fhk4e68.js → chunk-ns38fhve.js} +1 -1
  36. package/dist/{chunk-v7aa7v85.js → chunk-p3p4d6g1.js} +1 -1
  37. package/dist/{chunk-1yh6pq6b.js → chunk-pwh8pbks.js} +1 -1
  38. package/dist/{chunk-eqzc88et.js → chunk-q018c92j.js} +1 -1
  39. package/dist/{chunk-z8wb7kbj.js → chunk-qrhbqzz0.js} +1 -1
  40. package/dist/{chunk-jnbg3e4r.js → chunk-rd02ysw3.js} +1 -1
  41. package/dist/{chunk-zwr4bn6a.js → chunk-rv28743b.js} +1 -1
  42. package/dist/{chunk-b5mnam29.js → chunk-s2rc73vk.js} +1 -1
  43. package/dist/{chunk-yca761bm.js → chunk-st8wkk98.js} +1 -1
  44. package/dist/{chunk-yg5r7bnm.js → chunk-swvm1wgn.js} +27 -3
  45. package/dist/{chunk-rzda6bd6.js → chunk-tg56j4ka.js} +1 -1
  46. package/dist/{chunk-nrt99ptw.js → chunk-tshyy2br.js} +1 -1
  47. package/dist/{chunk-c8e2deyp.js → chunk-vxk12k35.js} +1 -1
  48. package/dist/{chunk-ek2m0kkw.js → chunk-w7vyd0n0.js} +1 -1
  49. package/dist/{chunk-xrjx7b2v.js → chunk-wjaqhspq.js} +1 -1
  50. package/dist/{chunk-mr8ccsnm.js → chunk-xvr9enmb.js} +1 -1
  51. package/dist/{chunk-72yrrggn.js → chunk-xy99vzs2.js} +1 -1
  52. package/dist/chunk-y8g8neeh.js +2 -0
  53. package/dist/{chunk-h1vax4wg.js → chunk-yr634wtd.js} +1 -1
  54. package/dist/{chunk-7m9qk7da.js → chunk-zrbhpfda.js} +1 -1
  55. package/dist/cli.js +8 -8
  56. package/dist/component-library.d.ts +0 -31
  57. package/dist/component-library.js +279 -17
  58. package/dist/config.js +16 -4
  59. package/dist/craft-entry.js +1 -1
  60. package/dist/craft.js +1 -1
  61. package/dist/dev-server/ts-css.js +16 -47
  62. package/dist/process.js +34 -8
  63. package/dist/pwa.js +1 -1
  64. package/dist/signals.js +146 -10
  65. package/dist/ssg.js +2 -2
  66. package/dist/stx-virtual-ts.d.ts +16 -1
  67. package/dist/stx-virtual-ts.js +33 -2
  68. package/dist/theme-tokens.js +9 -5
  69. package/dist/typecheck.js +2 -0
  70. package/dist/types/config-types.d.ts +10 -0
  71. package/dist/variable-extractor.d.ts +31 -0
  72. package/dist/variable-extractor.js +9 -5
  73. package/package.json +5 -5
  74. package/dist/chunk-57t402mn.js +0 -2
  75. package/dist/chunk-66tgkvqw.js +0 -2
  76. package/dist/chunk-c4tf1vpy.js +0 -2
  77. package/dist/chunk-r38xq7fp.js +0 -33
@@ -354,56 +354,25 @@ async function generateCssUncached(htmlContent, appDir) {
354
354
  cachedConfig = await resolveUserCssConfig(resolveRoot);
355
355
  configFingerprint = fingerprintConfig(cachedConfig);
356
356
  }
357
- const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCssConfig(baseConfig, userConfig), { safelist, includePreflight, minify, tokenCSS } = merged, cssConfig = merged.config, generator = new hw.CSSGenerator(cssConfig);
357
+ const baseConfig = hw.defaultConfig || hw.config, userConfig = cachedConfig || {}, merged = mergeCssConfig(baseConfig, userConfig), { safelist, includePreflight, minify, tokenCSS } = merged, cssConfig = merged.config, generator = new hw.CSSGenerator(cssConfig), shortcuts = cssConfig.shortcuts || userConfig.shortcuts || {}, usedShortcuts = Object.keys(shortcuts).filter((name) => classes.has(name) || safelist.includes(name));
358
358
  for (const cls of safelist)
359
- generator.generate(cls);
359
+ if (!shortcuts[cls])
360
+ generator.generate(cls);
360
361
  for (const className of classes)
361
- generator.generate(className);
362
- let css = generator.toCSS(includePreflight, minify);
363
- const shortcuts = cssConfig.shortcuts || userConfig.shortcuts || {};
364
- for (const [name, classStr] of Object.entries(shortcuts)) {
365
- if (!classes.has(name) && !safelist.includes(name))
366
- continue;
367
- const parts = classStr.split(/\s+/).filter(Boolean);
368
- for (const p of parts)
369
- generator.generate(p);
370
- }
371
- css = generator.toCSS(includePreflight, minify);
372
- const cssLines = css.split(`
373
- `);
374
- for (const [name, classStr] of Object.entries(shortcuts)) {
375
- if (!classes.has(name) && !safelist.includes(name))
376
- continue;
377
- const parts = classStr.split(/\s+/).filter(Boolean), decls = [], darkDecls = [];
378
- for (const cls of parts) {
379
- const isDark = cls.startsWith("dark:"), selectorTarget = `.${(isDark ? cls.slice(5) : cls).replace(/\//g, "\\/").replace(/:/g, "\\:").replace(/\./g, "\\.").replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/%/g, "\\%")}`;
380
- for (let i = 0;i < cssLines.length; i++) {
381
- const line = cssLines[i].trim();
382
- if (line === `${selectorTarget} {` || line.startsWith(`${selectorTarget} {`)) {
383
- let j = i, ruleContent = "";
384
- while (j < cssLines.length) {
385
- ruleContent += cssLines[j];
386
- if (cssLines[j].includes("}"))
387
- break;
388
- j++;
389
- }
390
- const declMatch = ruleContent.match(/\{([^}]+)\}/);
391
- if (declMatch)
392
- if (isDark)
393
- darkDecls.push(declMatch[1].trim());
394
- else
395
- decls.push(declMatch[1].trim());
396
- break;
397
- }
398
- }
399
- }
400
- if (decls.length)
401
- css += `
402
- .${name} { ${decls.join(" ")} }`;
403
- if (darkDecls.length)
404
- css += `
405
- @media (prefers-color-scheme: dark) { .dark .${name} { ${darkDecls.join(" ")} } }`;
362
+ if (!shortcuts[className])
363
+ generator.generate(className);
364
+ let shortcutCSS = "";
365
+ if (usedShortcuts.length > 0) {
366
+ const shortcutGenerator = new hw.CSSGenerator(cssConfig);
367
+ for (const name of usedShortcuts)
368
+ shortcutGenerator.generate(name);
369
+ shortcutCSS = shortcutGenerator.toCSS(!1, minify);
370
+ if (shortcutCSS && !shortcutCSS.endsWith(`
371
+ `))
372
+ shortcutCSS += `
373
+ `;
406
374
  }
375
+ let css = shortcutCSS + generator.toCSS(includePreflight, minify);
407
376
  css = tokenCSS + css;
408
377
  setLruCache(cacheKey, css);
409
378
  writeDiskCache(cacheKey, css);
package/dist/process.js CHANGED
@@ -641,23 +641,49 @@ function scriptSourceOf(attrs) {
641
641
  const match = new RegExp(`${SCRIPT_SOURCE_ATTR}="([^"]*)"`).exec(attrs);
642
642
  return match ? decodeURIComponent(match[1]) : void 0;
643
643
  }
644
+ function escapeForBoundary(value) {
645
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
646
+ }
647
+ function serverScriptErrorBoundary(sourcePath, kind, message) {
648
+ return `<div data-stx-server-script-error role="alert" style="padding:12px 14px;border:1px solid #f0b4b4;border-left-width:4px;background:#fff5f5;color:#7a1f1f;font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace"><strong style="display:block;margin-bottom:4px">stx: a &lt;script server&gt; ${kind === "module-resolution" ? "imports a module that does not resolve" : kind === "syntax" ? "does not parse" : "names something that does not exist"}, so this component rendered nothing.</strong><span style="display:block;opacity:.85">${escapeForBoundary(sourcePath)}</span><span style="display:block;margin-top:4px">${escapeForBoundary(message)}</span></div>`;
649
+ }
644
650
  async function extractServerScriptVariables(output, context, filePath) {
645
- const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
646
- let scriptMatch;
647
- while ((scriptMatch = scriptRegex.exec(output)) !== null) {
648
- const attrs = scriptMatch[1], scriptContent = scriptMatch[2];
649
- if (!/\bserver\b/.test(attrs) || !scriptContent.trim())
651
+ const { scanScriptTags } = await importOnce("stx/signal-processing", () => import("./signal-processing.js")), boundaries = [], showBoundaries = !isProduction() && !isTest();
652
+ for (const script of scanScriptTags(output)) {
653
+ const attrs = script.attrs;
654
+ if (!/\bserver\b/.test(attrs))
655
+ continue;
656
+ const bodyStart = script.start + script.fullMatch.indexOf(">") + 1, closeStart = findScriptBodyEnd(output, bodyStart);
657
+ if (closeStart === -1)
658
+ continue;
659
+ const scriptContent = output.slice(bodyStart, closeStart);
660
+ if (!scriptContent.trim())
650
661
  continue;
651
- const sourcePath = scriptSourceOf(attrs) ?? filePath;
662
+ const sourcePath = scriptSourceOf(attrs) ?? filePath, at = { index: script.start, length: closeStart + 9 - script.start };
652
663
  try {
653
664
  const { extractVariables } = await importOnce("stx/variable-extractor", () => import("./variable-extractor.js"));
654
- await extractVariables(scriptContent, context, sourcePath, { preserveExisting: !0 });
665
+ await extractVariables(scriptContent, context, sourcePath, {
666
+ preserveExisting: !0,
667
+ onFailure: (failure) => {
668
+ if (!showBoundaries || failure.kind === "unknown")
669
+ return;
670
+ boundaries.push({ ...at, markup: serverScriptErrorBoundary(sourcePath, failure.kind, failure.message) });
671
+ }
672
+ });
655
673
  } catch (e) {
656
674
  const err = e instanceof Error ? e : Error(String(e));
657
675
  errorLogger.log(err, { filePath: sourcePath, phase: "server-script-extraction" }, "warning");
658
676
  console.warn(`[stx] server <script> extraction failed in ${sourcePath}: ${err.message}`);
677
+ if (showBoundaries)
678
+ boundaries.push({ ...at, markup: serverScriptErrorBoundary(sourcePath, "unknown", err.message) });
659
679
  }
660
680
  }
681
+ if (boundaries.length === 0)
682
+ return output;
683
+ let result = output;
684
+ for (const boundary of boundaries.sort((a, b) => b.index - a.index))
685
+ result = result.slice(0, boundary.index) + boundary.markup + result.slice(boundary.index + boundary.length);
686
+ return result;
661
687
  }
662
688
  async function interpolateClientScriptExpressions(output, context, filePath) {
663
689
  try {
@@ -673,7 +699,7 @@ async function interpolateClientScriptExpressions(output, context, filePath) {
673
699
  async function processOtherDirectives(template, context, filePath, options, dependencies) {
674
700
  let output = template;
675
701
  const opts = options;
676
- await extractServerScriptVariables(output, context, filePath);
702
+ output = await extractServerScriptVariables(output, context, filePath);
677
703
  output = await interpolateClientScriptExpressions(output, context, filePath);
678
704
  output = await processJsDirectives(output, context, filePath);
679
705
  output = await processTsDirectives(output, context, filePath);
package/dist/pwa.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  import{Fk as wq,Gk as dq,Hk as Cq,Ik as mq,Jk as gq}from"./chunk-wvc7t11n.js";import{Kk as S,Lk as Tq,Mk as _q,Nk as z,Ok as D,Pk as P,Qk as kq,Rk as vq,Sk as w,Tk as Iq,Uk as d,Vk as Hq,Wk as Oq,Xk as Mq}from"./chunk-c30qnb6z.js";import{Ew as Aq,Fw as bq,Gw as Sq,Hw as zq,Iw as Dq,Jw as Pq}from"./chunk-9nrgjtah.js";import{Nw as N}from"./chunk-ejqpq0wc.js";import p from"fs";import E from"path";import C from"fs";import L from"path";var k=[72,96,128,144,152,192,384,512],v=[120,152,167,180],I=[16,32];async function H(G,B){let q={success:!0,generatedFiles:[],errors:[],warnings:[]},K=G.pwa?.icons;if(!K?.src)return q.errors.push("No source icon specified in pwa.icons.src"),q.success=!1,q;let Y=L.isAbsolute(K.src)?K.src:L.resolve(process.cwd(),K.src);if(!await Bun.file(Y).exists())return q.errors.push(`Source icon not found: ${Y}`),q.success=!1,q;let Q=L.resolve(B,K.outputDir||"pwa-icons");try{C.mkdirSync(Q,{recursive:!0})}catch{return q.errors.push(`Failed to create output directory: ${Q}`),q.success=!1,q}let X=K.sizes||k,j=K.generateWebP??!0,J=K.generateAppleIcons??!0;try{let y;try{y=await import("sharp")}catch{return q.errors.push(`sharp is not installed. Install it with: bun add sharp
3
- Icon generation requires the sharp package for image processing.`),q.success=!1,q}let R=await y.default(Y).metadata();if(R.width&&R.width<512)q.warnings.push(`Source icon is ${R.width}x${R.height}. Recommend using at least 512x512 for best quality.`);for(let $ of X){let x=L.join(Q,`icon-${$}x${$}.png`);if(await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x),j){let F=L.join(Q,`icon-${$}x${$}.webp`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).webp({quality:90}).toFile(F),q.generatedFiles.push(F)}}if(J){for(let x of v){let F=L.join(Q,`apple-touch-icon-${x}x${x}.png`);await y.default(Y).resize(x,x,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile(F),q.generatedFiles.push(F)}let $=L.join(Q,"apple-touch-icon.png");await y.default(Y).resize(180,180,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile($),q.generatedFiles.push($)}for(let $ of I){let x=L.join(Q,`favicon-${$}x${$}.png`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x)}let U=512,T=L.join(Q,`icon-maskable-${U}x${U}.png`),Z=Math.floor(U*0.1),_=U-Z*2;await y.default(Y).resize(_,_,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).extend({top:Z,bottom:Z,left:Z,right:Z,background:{r:255,g:255,b:255,alpha:1}}).png().toFile(T),q.generatedFiles.push(T)}catch(y){let V=y instanceof Error?y.message:String(y);q.errors.push(`Icon generation failed: ${V}`),q.success=!1}return q}function n(G){let B=G.sizes||k,q=G.outputDir?`/${G.outputDir}`:"/pwa-icons",K=G.generateWebP??!0,Y=G.generateAppleIcons??!0,Q=[];for(let X of B)if(Q.push(`${q}/icon-${X}x${X}.png`),K)Q.push(`${q}/icon-${X}x${X}.webp`);if(Y){Q.push(`${q}/apple-touch-icon.png`);for(let X of v)Q.push(`${q}/apple-touch-icon-${X}x${X}.png`)}for(let X of I)Q.push(`${q}/favicon-${X}x${X}.png`);return Q.push(`${q}/icon-maskable-512x512.png`),Q}async function O(){try{return await import("sharp"),!0}catch{return!1}}function m(G,B,q,K){let Y=[],Q=K.join(" ");for(let X of G)if(Y.push({src:`${B}/icon-${X}x${X}.png`,sizes:`${X}x${X}`,type:"image/png",purpose:Q}),q)Y.push({src:`${B}/icon-${X}x${X}.webp`,sizes:`${X}x${X}`,type:"image/webp",purpose:Q});return Y}function g(G){return G.map((B)=>({name:B.name,short_name:B.shortName,description:B.description,url:B.url,icons:B.icons?.map((q)=>({src:q.src,sizes:q.sizes,type:q.type}))}))}function h(G){let B=G.pwa?.shareTarget;if(!B?.enabled)return;let q={action:B.action||"/share",method:B.method||"POST",enctype:B.enctype||"multipart/form-data",params:{title:B.params?.title||"title",text:B.params?.text||"text",url:B.params?.url||"url"}};if(B.acceptFiles&&B.acceptFiles.length>0)q.params.files=B.acceptFiles.map((K)=>({name:K.name,accept:K.accept}));return q}function c(G){let B=G.pwa?.fileHandlers;if(!B?.enabled||!B.accept)return;let q=[{action:B.action||"/",accept:B.accept,launch_type:B.launchType||"single-client"}];if(B.icons)q[0].icons=B.icons.map((K)=>({src:K.src,sizes:K.sizes,type:K.type}));return q}function l(G){let B=G.pwa?.protocolHandlers;if(!B||B.length===0)return;return B.map((q)=>({protocol:q.protocol,url:q.url}))}function u(G){let B=G.pwa;if(!B?.enabled||!B.manifest)return null;let{manifest:q,icons:K}=B,Y=K?.sizes||[72,96,128,144,152,192,384,512],Q=K?.outputDir?`/${K.outputDir}`:"/pwa-icons",X=K?.generateWebP??!0,j=K?.purpose||["any","maskable"],J={name:q.name,short_name:q.shortName||q.name,description:q.description,start_url:q.startUrl||"/",display:q.display||"standalone",display_override:q.displayOverride,orientation:q.orientation,theme_color:q.themeColor,background_color:q.backgroundColor,scope:q.scope||"/",lang:q.lang,dir:q.dir,id:q.id,categories:q.categories,icons:m(Y,Q,X,j)};if(q.shortcuts&&q.shortcuts.length>0)J.shortcuts=g(q.shortcuts);if(q.screenshots&&q.screenshots.length>0)J.screenshots=q.screenshots.map((U)=>({src:U.src,sizes:U.sizes,type:U.type,label:U.label,form_factor:U.platform}));let y=h(G);if(y)J.share_target=y;let V=c(G);if(V)J.file_handlers=V;let R=l(G);if(R)J.protocol_handlers=R;if(q.launchHandler)J.launch_handler={client_mode:q.launchHandler};if(q.handleLinks)J.handle_links=q.handleLinks;if(q.edgeSidePanel?.enabled)J.edge_side_panel={preferred_width:q.edgeSidePanel.preferredWidth};return J}function M(G){let B=u(G);if(!B)return"";let q=JSON.parse(JSON.stringify(B));return JSON.stringify(q,null,2)}function i(G,B){return{...B,...G,shortcuts:G.shortcuts||B.shortcuts,screenshots:G.screenshots||B.screenshots,categories:G.categories||B.categories}}function s(G){let B=[],q=G.pwa;if(!q?.enabled)return B;let K=q.manifest;if(!K)return B.push("PWA manifest configuration is missing"),B;if(!K.name)B.push("Manifest name is required");let Y=["fullscreen","standalone","minimal-ui","browser"];if(K.display&&!Y.includes(K.display))B.push(`Invalid display mode: ${K.display}. Must be one of: ${Y.join(", ")}`);let Q=["any","natural","landscape","landscape-primary","landscape-secondary","portrait","portrait-primary","portrait-secondary"];if(K.orientation&&!Q.includes(K.orientation))B.push(`Invalid orientation: ${K.orientation}`);let X=/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;if(K.themeColor&&!X.test(K.themeColor))B.push(`Invalid theme color: ${K.themeColor}. Must be a valid hex color.`);if(K.backgroundColor&&!X.test(K.backgroundColor))B.push(`Invalid background color: ${K.backgroundColor}. Must be a valid hex color.`);if(q.shareTarget?.enabled){if(!q.shareTarget.action)B.push("Share target action URL is required")}if(q.fileHandlers?.enabled){if(!q.fileHandlers.accept||Object.keys(q.fileHandlers.accept).length===0)B.push("File handlers must specify accepted MIME types")}if(q.protocolHandlers)for(let j of q.protocolHandlers){if(!j.protocol)B.push("Protocol handler missing protocol");if(!j.url||!j.url.includes("%s"))B.push(`Protocol handler URL must contain %s placeholder: ${j.protocol}`)}return B}import A from"path";async function b(G){let B=G.pwa?.offline;if(!B?.enabled)return"";if(B.page){let q=A.isAbsolute(B.page)?B.page:A.resolve(process.cwd(),B.page);if(await Bun.file(q).exists())try{let{processDirectives:K}=await import("./chunk-2fhk4e68.js"),Y=await Bun.file(q).text();return await K(Y,{},q,G,new Set)}catch(K){console.warn(`[PWA] Failed to process custom offline page: ${K}`)}else console.warn(`[PWA] Custom offline page not found: ${q}`)}return f(G)}function f(G){let B=G.pwa?.offline,q=G.pwa?.manifest,K=B?.fallbackTitle||"You are offline",Y=B?.fallbackMessage||"Please check your internet connection and try again.",Q=q?.themeColor||"#4f46e5",X=q?.shortName||q?.name||"App";return`<!DOCTYPE html>
3
+ Icon generation requires the sharp package for image processing.`),q.success=!1,q}let R=await y.default(Y).metadata();if(R.width&&R.width<512)q.warnings.push(`Source icon is ${R.width}x${R.height}. Recommend using at least 512x512 for best quality.`);for(let $ of X){let x=L.join(Q,`icon-${$}x${$}.png`);if(await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x),j){let F=L.join(Q,`icon-${$}x${$}.webp`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).webp({quality:90}).toFile(F),q.generatedFiles.push(F)}}if(J){for(let x of v){let F=L.join(Q,`apple-touch-icon-${x}x${x}.png`);await y.default(Y).resize(x,x,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile(F),q.generatedFiles.push(F)}let $=L.join(Q,"apple-touch-icon.png");await y.default(Y).resize(180,180,{fit:"contain",background:{r:255,g:255,b:255,alpha:1}}).png().toFile($),q.generatedFiles.push($)}for(let $ of I){let x=L.join(Q,`favicon-${$}x${$}.png`);await y.default(Y).resize($,$,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).png().toFile(x),q.generatedFiles.push(x)}let U=512,T=L.join(Q,`icon-maskable-${U}x${U}.png`),Z=Math.floor(U*0.1),_=U-Z*2;await y.default(Y).resize(_,_,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}}).extend({top:Z,bottom:Z,left:Z,right:Z,background:{r:255,g:255,b:255,alpha:1}}).png().toFile(T),q.generatedFiles.push(T)}catch(y){let V=y instanceof Error?y.message:String(y);q.errors.push(`Icon generation failed: ${V}`),q.success=!1}return q}function n(G){let B=G.sizes||k,q=G.outputDir?`/${G.outputDir}`:"/pwa-icons",K=G.generateWebP??!0,Y=G.generateAppleIcons??!0,Q=[];for(let X of B)if(Q.push(`${q}/icon-${X}x${X}.png`),K)Q.push(`${q}/icon-${X}x${X}.webp`);if(Y){Q.push(`${q}/apple-touch-icon.png`);for(let X of v)Q.push(`${q}/apple-touch-icon-${X}x${X}.png`)}for(let X of I)Q.push(`${q}/favicon-${X}x${X}.png`);return Q.push(`${q}/icon-maskable-512x512.png`),Q}async function O(){try{return await import("sharp"),!0}catch{return!1}}function m(G,B,q,K){let Y=[],Q=K.join(" ");for(let X of G)if(Y.push({src:`${B}/icon-${X}x${X}.png`,sizes:`${X}x${X}`,type:"image/png",purpose:Q}),q)Y.push({src:`${B}/icon-${X}x${X}.webp`,sizes:`${X}x${X}`,type:"image/webp",purpose:Q});return Y}function g(G){return G.map((B)=>({name:B.name,short_name:B.shortName,description:B.description,url:B.url,icons:B.icons?.map((q)=>({src:q.src,sizes:q.sizes,type:q.type}))}))}function h(G){let B=G.pwa?.shareTarget;if(!B?.enabled)return;let q={action:B.action||"/share",method:B.method||"POST",enctype:B.enctype||"multipart/form-data",params:{title:B.params?.title||"title",text:B.params?.text||"text",url:B.params?.url||"url"}};if(B.acceptFiles&&B.acceptFiles.length>0)q.params.files=B.acceptFiles.map((K)=>({name:K.name,accept:K.accept}));return q}function c(G){let B=G.pwa?.fileHandlers;if(!B?.enabled||!B.accept)return;let q=[{action:B.action||"/",accept:B.accept,launch_type:B.launchType||"single-client"}];if(B.icons)q[0].icons=B.icons.map((K)=>({src:K.src,sizes:K.sizes,type:K.type}));return q}function l(G){let B=G.pwa?.protocolHandlers;if(!B||B.length===0)return;return B.map((q)=>({protocol:q.protocol,url:q.url}))}function u(G){let B=G.pwa;if(!B?.enabled||!B.manifest)return null;let{manifest:q,icons:K}=B,Y=K?.sizes||[72,96,128,144,152,192,384,512],Q=K?.outputDir?`/${K.outputDir}`:"/pwa-icons",X=K?.generateWebP??!0,j=K?.purpose||["any","maskable"],J={name:q.name,short_name:q.shortName||q.name,description:q.description,start_url:q.startUrl||"/",display:q.display||"standalone",display_override:q.displayOverride,orientation:q.orientation,theme_color:q.themeColor,background_color:q.backgroundColor,scope:q.scope||"/",lang:q.lang,dir:q.dir,id:q.id,categories:q.categories,icons:m(Y,Q,X,j)};if(q.shortcuts&&q.shortcuts.length>0)J.shortcuts=g(q.shortcuts);if(q.screenshots&&q.screenshots.length>0)J.screenshots=q.screenshots.map((U)=>({src:U.src,sizes:U.sizes,type:U.type,label:U.label,form_factor:U.platform}));let y=h(G);if(y)J.share_target=y;let V=c(G);if(V)J.file_handlers=V;let R=l(G);if(R)J.protocol_handlers=R;if(q.launchHandler)J.launch_handler={client_mode:q.launchHandler};if(q.handleLinks)J.handle_links=q.handleLinks;if(q.edgeSidePanel?.enabled)J.edge_side_panel={preferred_width:q.edgeSidePanel.preferredWidth};return J}function M(G){let B=u(G);if(!B)return"";let q=JSON.parse(JSON.stringify(B));return JSON.stringify(q,null,2)}function i(G,B){return{...B,...G,shortcuts:G.shortcuts||B.shortcuts,screenshots:G.screenshots||B.screenshots,categories:G.categories||B.categories}}function s(G){let B=[],q=G.pwa;if(!q?.enabled)return B;let K=q.manifest;if(!K)return B.push("PWA manifest configuration is missing"),B;if(!K.name)B.push("Manifest name is required");let Y=["fullscreen","standalone","minimal-ui","browser"];if(K.display&&!Y.includes(K.display))B.push(`Invalid display mode: ${K.display}. Must be one of: ${Y.join(", ")}`);let Q=["any","natural","landscape","landscape-primary","landscape-secondary","portrait","portrait-primary","portrait-secondary"];if(K.orientation&&!Q.includes(K.orientation))B.push(`Invalid orientation: ${K.orientation}`);let X=/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;if(K.themeColor&&!X.test(K.themeColor))B.push(`Invalid theme color: ${K.themeColor}. Must be a valid hex color.`);if(K.backgroundColor&&!X.test(K.backgroundColor))B.push(`Invalid background color: ${K.backgroundColor}. Must be a valid hex color.`);if(q.shareTarget?.enabled){if(!q.shareTarget.action)B.push("Share target action URL is required")}if(q.fileHandlers?.enabled){if(!q.fileHandlers.accept||Object.keys(q.fileHandlers.accept).length===0)B.push("File handlers must specify accepted MIME types")}if(q.protocolHandlers)for(let j of q.protocolHandlers){if(!j.protocol)B.push("Protocol handler missing protocol");if(!j.url||!j.url.includes("%s"))B.push(`Protocol handler URL must contain %s placeholder: ${j.protocol}`)}return B}import A from"path";async function b(G){let B=G.pwa?.offline;if(!B?.enabled)return"";if(B.page){let q=A.isAbsolute(B.page)?B.page:A.resolve(process.cwd(),B.page);if(await Bun.file(q).exists())try{let{processDirectives:K}=await import("./chunk-ns38fhve.js"),Y=await Bun.file(q).text();return await K(Y,{},q,G,new Set)}catch(K){console.warn(`[PWA] Failed to process custom offline page: ${K}`)}else console.warn(`[PWA] Custom offline page not found: ${q}`)}return f(G)}function f(G){let B=G.pwa?.offline,q=G.pwa?.manifest,K=B?.fallbackTitle||"You are offline",Y=B?.fallbackMessage||"Please check your internet connection and try again.",Q=q?.themeColor||"#4f46e5",X=q?.shortName||q?.name||"App";return`<!DOCTYPE html>
4
4
  <html lang="en">
5
5
  <head>
6
6
  <meta charset="UTF-8">
package/dist/signals.js CHANGED
@@ -6487,6 +6487,89 @@ catch (e) {} }
6487
6487
  // Both return Promises and render into a temporary modal overlay.
6488
6488
 
6489
6489
  var _dialogId = 0;
6490
+
6491
+ // Shared across every open dialog, because four of the defects in #1998 were
6492
+ // each dialog acting as if it were the only one.
6493
+ var _dialogStack = [];
6494
+ // What WE set inert, so restoring never clears an app's own inert.
6495
+ var _dialogInerted = [];
6496
+ // Saved once, when the first dialog opens.
6497
+ var _dialogScrollLock = null;
6498
+
6499
+ /*
6500
+ * The page behind an aria-modal dialog does not scroll and is not reachable
6501
+ * by a virtual cursor (stacksjs/stx#1998, items 1 and 2).
6502
+ *
6503
+ * The backdrop is position:fixed;inset:0, so it stayed put while the content
6504
+ * behind it scrolled away underneath - the dialog claimed to be modal and the
6505
+ * page moved anyway. And the Tab trap, which #1875 added, only covers the
6506
+ * keyboard: aria-modal support for the virtual cursor is uneven, so a screen
6507
+ * reader could still browse the page behind the dialog.
6508
+ *
6509
+ * inert rather than aria-hidden on the siblings: aria-hidden on an element
6510
+ * that contains focus is itself a violation, and inert removes the subtree
6511
+ * from the tab order and the accessibility tree together. Everything except
6512
+ * the TOPMOST dialog goes inert, which also settles the two traps competing
6513
+ * for Tab while both were open - a backgrounded dialog's buttons stop being
6514
+ * focusable at all.
6515
+ */
6516
+ function _dialogSyncModality() {
6517
+ var top = _dialogStack.length ? _dialogStack[_dialogStack.length - 1] : null;
6518
+
6519
+ for (var i = 0; i < _dialogInerted.length; i++) {
6520
+ try { _dialogInerted[i].inert = false; }
6521
+ catch (_e) { /* element may be gone */ }
6522
+ }
6523
+ _dialogInerted = [];
6524
+
6525
+ if (!top) {
6526
+ if (_dialogScrollLock) {
6527
+ document.body.style.overflow = _dialogScrollLock.body;
6528
+ document.documentElement.style.overflow = _dialogScrollLock.html;
6529
+ document.body.style.paddingRight = _dialogScrollLock.pad;
6530
+ _dialogScrollLock = null;
6531
+ }
6532
+ return;
6533
+ }
6534
+
6535
+ if (!_dialogScrollLock) {
6536
+ _dialogScrollLock = {
6537
+ body: document.body.style.overflow,
6538
+ html: document.documentElement.style.overflow,
6539
+ pad: document.body.style.paddingRight
6540
+ };
6541
+ // Compensate for the scrollbar the lock removes, or the whole page shifts
6542
+ // sideways the moment a dialog opens.
6543
+ var gap = window.innerWidth - document.documentElement.clientWidth;
6544
+ if (gap > 0) {
6545
+ var existing = Number.parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
6546
+ document.body.style.paddingRight = (existing + gap) + 'px';
6547
+ }
6548
+ document.body.style.overflow = 'hidden';
6549
+ document.documentElement.style.overflow = 'hidden';
6550
+ }
6551
+
6552
+ var kids = document.body.children;
6553
+ for (var k = 0; k < kids.length; k++) {
6554
+ var kid = kids[k];
6555
+ if (kid === top || kid.inert) continue;
6556
+ try {
6557
+ kid.inert = true;
6558
+ _dialogInerted.push(kid);
6559
+ }
6560
+ catch (_e) { /* inert unsupported; the Tab trap still holds */ }
6561
+ }
6562
+ }
6563
+
6564
+ /** Dialogs animate unless the reader asked them not to (#1998 item 5). */
6565
+ function _dialogReducedMotion() {
6566
+ try {
6567
+ return !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
6568
+ }
6569
+ catch (_e) {
6570
+ return false;
6571
+ }
6572
+ }
6490
6573
  var _dialogIcons = {
6491
6574
  info: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>',
6492
6575
  warning: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
@@ -6514,14 +6597,23 @@ catch (e) {} }
6514
6597
  var icon = _dialogIcons[type] || _dialogIcons.info;
6515
6598
 
6516
6599
  return new Promise(function(resolve) {
6600
+ // Inline transitions cannot be reached by a stylesheet media query, so the
6601
+ // preference is read here instead (#1998 item 5). One matchMedia probe
6602
+ // rather than usePreferredReducedMotion(): that returns a signal with a
6603
+ // change listener, and an imperative dialog that reads the value once at
6604
+ // open time would leak one listener per call.
6605
+ var still = _dialogReducedMotion();
6606
+ var fade = still ? '' : 'transition:opacity 0.2s ease;';
6607
+ var scale = still ? '' : 'transition:transform 0.2s ease;';
6608
+
6517
6609
  var backdrop = document.createElement('div');
6518
6610
  backdrop.id = id;
6519
- backdrop.style.cssText = 'position:fixed;inset:0;z-index:999999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.5);backdrop-filter:blur(2px);opacity:0;transition:opacity 0.2s ease;font-family:system-ui,-apple-system,sans-serif';
6611
+ backdrop.style.cssText = 'position:fixed;inset:0;z-index:999999;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.5);backdrop-filter:blur(2px);opacity:' + (still ? '1' : '0') + ';' + fade + 'font-family:system-ui,-apple-system,sans-serif';
6520
6612
  backdrop.setAttribute('role', 'alertdialog');
6521
6613
  backdrop.setAttribute('aria-modal', 'true');
6522
6614
 
6523
6615
  var panel = document.createElement('div');
6524
- panel.style.cssText = 'max-width:24rem;width:calc(100% - 2rem);border-radius:0.75rem;padding:1.5rem;background:' + bg + ';color:' + textColor + ';box-shadow:0 20px 60px rgba(0,0,0,0.3);transform:scale(0.95);transition:transform 0.2s ease;text-align:center';
6616
+ panel.style.cssText = 'max-width:24rem;width:calc(100% - 2rem);border-radius:0.75rem;padding:1.5rem;background:' + bg + ';color:' + textColor + ';box-shadow:0 20px 60px rgba(0,0,0,0.3);transform:scale(' + (still ? '1' : '0.95') + ');' + scale + 'text-align:center';
6525
6617
 
6526
6618
  var iconDiv = document.createElement('div');
6527
6619
  iconDiv.style.cssText = 'display:flex;justify-content:center;margin-bottom:1rem';
@@ -6562,9 +6654,25 @@ catch (e) {} }
6562
6654
  if (closed) return;
6563
6655
  closed = true;
6564
6656
  document.removeEventListener('keydown', keyHandler, true);
6657
+
6658
+ // Out of the stack BEFORE resolving, so a caller that opens another
6659
+ // dialog the moment this one answers does not land under a backdrop
6660
+ // that is still fading.
6661
+ var at = _dialogStack.indexOf(backdrop);
6662
+ if (at !== -1) _dialogStack.splice(at, 1);
6663
+
6664
+ // The fade is 200ms and resolve() is immediate, so for those 200ms a
6665
+ // full-viewport element at z-index 999999 was still hit-testing at the
6666
+ // centre of the screen - after the caller's await had already returned
6667
+ // (#1998 item 6). Fading out is right; swallowing clicks is not.
6668
+ backdrop.style.pointerEvents = 'none';
6565
6669
  backdrop.style.opacity = '0';
6566
- panel.style.transform = 'scale(0.95)';
6567
- setTimeout(function() { backdrop.remove(); }, 200);
6670
+ if (!still) panel.style.transform = 'scale(0.95)';
6671
+ setTimeout(function() { backdrop.remove(); }, still ? 0 : 200);
6672
+
6673
+ // Unlocks the page and clears inert when this was the last dialog, or
6674
+ // hands modality back to the one underneath when it was not.
6675
+ _dialogSyncModality();
6568
6676
  if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
6569
6677
  try { previouslyFocused.focus(); }
6570
6678
  catch (_e) { /* element may be gone; not worth failing the dialog over */ }
@@ -6572,8 +6680,11 @@ catch (e) {} }
6572
6680
  resolve(result);
6573
6681
  }
6574
6682
 
6683
+ // Declared out here rather than inside the branch: the focus decision
6684
+ // below needs to know whether a Cancel button exists at all.
6685
+ var cancelBtn = null;
6575
6686
  if (isConfirm) {
6576
- var cancelBtn = document.createElement('button');
6687
+ cancelBtn = document.createElement('button');
6577
6688
  cancelBtn.textContent = cancelText;
6578
6689
  cancelBtn.style.cssText = 'padding:0.5rem 1.25rem;border-radius:0.5rem;font-size:0.875rem;font-weight:500;cursor:pointer;border:1px solid ' + (isDark ? '#374151' : '#d1d5db') + ';background:transparent;color:' + textColor + ';transition:background 0.15s';
6579
6690
  cancelBtn.onmouseover = function() { this.style.background = isDark ? '#374151' : '#f3f4f6'; };
@@ -6594,11 +6705,15 @@ catch (e) {} }
6594
6705
  panel.appendChild(btnRow);
6595
6706
  backdrop.appendChild(panel);
6596
6707
  document.body.appendChild(backdrop);
6708
+ _dialogStack.push(backdrop);
6709
+ _dialogSyncModality();
6597
6710
 
6598
6711
  // Animate in
6599
- void backdrop.offsetHeight;
6600
- backdrop.style.opacity = '1';
6601
- panel.style.transform = 'scale(1)';
6712
+ if (!still) {
6713
+ void backdrop.offsetHeight;
6714
+ backdrop.style.opacity = '1';
6715
+ panel.style.transform = 'scale(1)';
6716
+ }
6602
6717
 
6603
6718
  // Escape, plus a real focus trap.
6604
6719
  //
@@ -6611,6 +6726,11 @@ catch (e) {} }
6611
6726
  // walked straight out into the page behind it. Capture phase so the trap
6612
6727
  // wins over anything the page has bound.
6613
6728
  var keyHandler = function(e) {
6729
+ // Every dialog binds its own capture-phase handler on document and none
6730
+ // of them checked whether they were on top, so ONE Escape resolved every
6731
+ // open dialog at once (#1998 item 4). The backgrounded dialogs are inert
6732
+ // too, which is what stops their traps competing for Tab.
6733
+ if (_dialogStack.length && _dialogStack[_dialogStack.length - 1] !== backdrop) return;
6614
6734
  if (e.key === 'Escape') {
6615
6735
  e.preventDefault();
6616
6736
  cleanup(isConfirm ? false : undefined);
@@ -6640,8 +6760,24 @@ catch (e) {} }
6640
6760
  };
6641
6761
  document.addEventListener('keydown', keyHandler, true);
6642
6762
 
6643
- // Focus the primary button
6644
- okBtn.focus();
6763
+ /*
6764
+ * Initial focus, which was an unconditional okBtn.focus() (#1998 item 3).
6765
+ *
6766
+ * type: 'error' already paints that button red, so the API knows the
6767
+ * action is destructive - and still handed it the keyboard, where a stray
6768
+ * Enter or Space confirmed the delete. An error-type confirm now defaults
6769
+ * to Cancel, and defaultFocus overrides either way.
6770
+ *
6771
+ * An alert has only one button, so it keeps it: there is nothing
6772
+ * destructive to land on and focus has to go somewhere inside the trap.
6773
+ */
6774
+ var focusTarget = okBtn;
6775
+ if (isConfirm && cancelBtn) {
6776
+ var want = opts.defaultFocus;
6777
+ if (want === 'cancel' || (!want && type === 'error')) focusTarget = cancelBtn;
6778
+ else if (want === 'confirm') focusTarget = okBtn;
6779
+ }
6780
+ focusTarget.focus();
6645
6781
  });
6646
6782
  }
6647
6783
 
package/dist/ssg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{Bk as Fq,wk as Uq,xk as Iq}from"./chunk-v7aa7v85.js";import{Ck as Eq,Dk as Tq,Ek as Dq}from"./chunk-c0n802j1.js";import{Zk as _q,al as zq,bl as Aq}from"./chunk-223makqc.js";import"./chunk-77p1kjg2.js";import"./chunk-fvsefztj.js";import{Jp as P,Sm as Oq,io as l,lo as Hq,sr as n}from"./chunk-4spaszfn.js";import"./chunk-8y1ve8tc.js";import"./chunk-njx93b4z.js";import"./chunk-bqdfbwdf.js";import"./chunk-89mqnh67.js";import"./chunk-xvh1k1a4.js";import{it as Xq}from"./chunk-r38xq7fp.js";import{nt as d}from"./chunk-y2f41kaq.js";import"./chunk-xj6bcwqa.js";import"./chunk-z8wb7kbj.js";import"./chunk-vhta1be7.js";import{Ut as Bq}from"./chunk-tc4pj651.js";import"./chunk-san1eq0r.js";import"./chunk-avwywzsj.js";import"./chunk-wj21f1e0.js";import"./chunk-d86q27j8.js";import"./chunk-72x62kch.js";import{rw as Yq,sw as Vq,tw as jq,uw as Gq,vw as Lq,ww as Nq}from"./chunk-yg5r7bnm.js";import"./chunk-9nrgjtah.js";import{Nw as y}from"./chunk-ejqpq0wc.js";import O from"fs";import j from"path";import{createHash as xq}from"crypto";function yq(q){try{return new Bun.Transpiler({loader:"js",minifyWhitespace:!0}).transformSync(q).replace(/\}(var |let |const |function )/g,"};$1").trim()}catch{return q}}var Pq=/<script\s+type="stx\/island"\s+data-stx-island="([^"]+)"([^>]*)>([\s\S]*?)<\/script>/g;function Cq(q){return Bun.hash(q).toString(16)}function Qq(q,Z={}){if(!q.includes('type="stx/island"'))return{html:q,chunks:[]};let $=new Map,Q=q.replace(Pq,(J,W,z,A)=>{let G=A.trim();if(!G)return J;if(/\bdata-stx-src=/.test(z))return J;if(Z.minify)G=yq(G);let X=Cq(G);if(!$.has(X))$.set(X,G);let B=Z.integrity?` data-stx-integrity="sha384-${xq("sha384").update(G).digest("base64")}"`:"";return`<script type="stx/island" data-stx-island="${W}" data-stx-src="/_stx/islands/${X}.js"${B}${z}></script>`}),K=Array.from($,([J,W])=>({hash:J,code:W}));return{html:Q,chunks:K}}var gq=new Set(["visible","idle"]);function Zq(q){if(!q.includes("data-stx-src="))return q;let Z=new Map,$=/data-stx-scope="([^"]+)"[^>]*?stx-hydrate="([^"]+)"/g,Q;while((Q=$.exec(q))!==null)Z.set(Q[1],Q[2]);let K=new Set,J=/data-stx-island="([^"]+)"[^>]*?data-stx-src="([^"]+)"/g,W;while((W=J.exec(q))!==null)if(gq.has(Z.get(W[1])||""))K.add(W[2]);let z=q.lastIndexOf("</head>");if(K.size===0||z===-1)return q;let A=Array.from(K).filter((G)=>!q.includes(`rel="prefetch" href="${G}"`)).map((G)=>`<link rel="prefetch" href="${G}" as="script">`).join("");if(!A)return q;return q.slice(0,z)+A+q.slice(z)}var fq=["STX_SITE_URL","SITE_URL","URL","VERCEL_URL","CF_PAGES_URL"];function h(q){let Z=q.trim();if(!Z)return null;let $=/^https?:\/\//i.test(Z)?Z:`https://${Z}`;try{return new URL($).origin+new URL($).pathname.replace(/\/$/,"")}catch{return null}}function mq(q){for(let Z of fq){let $=q[Z];if(!$)continue;let Q=h($);if(Q)return{url:Q,source:"env",via:Z}}return null}function $q(q){if(!q||typeof q!=="object")return null;let Z=q,$=Z.site?.url??Z.url??Z.app?.url;return typeof $==="string"?h($):null}async function dq(q){let{existsSync:Z}=await import("fs"),$=await import("path");for(let Q of["site.config.ts","site.config.js",".config/site.config.ts"]){let K=$.resolve(q,Q);if(!Z(K))continue;try{let J=await import(K),W=$q(J.default??J);if(W)return W}catch{}}return null}async function Kq(q={}){let Z=q.cwd??process.cwd(),$=q.env??process.env;if(q.explicit){let W=h(q.explicit);if(W)return{url:W,source:"option"}}let Q=$q(q.stxConfig);if(Q)return{url:Q,source:"stx.config",via:"site.url"};let K=await dq(Z);if(K)return{url:K,source:"site.config",via:"url"};let J=mq($);if(J)return J;return{url:"http://localhost",source:"fallback"}}function Wq(){return["No site URL configured \u2014 sitemap.xml and robots.txt will point at http://localhost."," Set one in stx.config.ts (site: { url: 'https://example.com' }), in site.config.ts,"," via --domain, or with SITE_URL in the build environment."].join(`
2
+ import{Bk as Fq,wk as Uq,xk as Iq}from"./chunk-p3p4d6g1.js";import{Ck as Eq,Dk as Tq,Ek as Dq}from"./chunk-c0n802j1.js";import{Zk as _q,al as zq,bl as Aq}from"./chunk-0vdng9sh.js";import"./chunk-9rc9gshn.js";import"./chunk-fvsefztj.js";import{Jp as P,Sm as Oq,io as l,lo as Hq,sr as n}from"./chunk-h2sbvyr4.js";import"./chunk-8y1ve8tc.js";import"./chunk-njx93b4z.js";import"./chunk-bqdfbwdf.js";import"./chunk-89mqnh67.js";import"./chunk-xvh1k1a4.js";import{it as Xq}from"./chunk-gnb15a8d.js";import{nt as d}from"./chunk-y2f41kaq.js";import"./chunk-xj6bcwqa.js";import"./chunk-qrhbqzz0.js";import"./chunk-vhta1be7.js";import{Ut as Bq}from"./chunk-tc4pj651.js";import"./chunk-san1eq0r.js";import"./chunk-avwywzsj.js";import"./chunk-wj21f1e0.js";import"./chunk-d86q27j8.js";import"./chunk-72x62kch.js";import{rw as Yq,sw as Vq,tw as jq,uw as Gq,vw as Lq,ww as Nq}from"./chunk-swvm1wgn.js";import"./chunk-9nrgjtah.js";import{Nw as y}from"./chunk-ejqpq0wc.js";import O from"fs";import j from"path";import{createHash as xq}from"crypto";function yq(q){try{return new Bun.Transpiler({loader:"js",minifyWhitespace:!0}).transformSync(q).replace(/\}(var |let |const |function )/g,"};$1").trim()}catch{return q}}var Pq=/<script\s+type="stx\/island"\s+data-stx-island="([^"]+)"([^>]*)>([\s\S]*?)<\/script>/g;function Cq(q){return Bun.hash(q).toString(16)}function Qq(q,Z={}){if(!q.includes('type="stx/island"'))return{html:q,chunks:[]};let $=new Map,Q=q.replace(Pq,(J,W,z,A)=>{let G=A.trim();if(!G)return J;if(/\bdata-stx-src=/.test(z))return J;if(Z.minify)G=yq(G);let X=Cq(G);if(!$.has(X))$.set(X,G);let B=Z.integrity?` data-stx-integrity="sha384-${xq("sha384").update(G).digest("base64")}"`:"";return`<script type="stx/island" data-stx-island="${W}" data-stx-src="/_stx/islands/${X}.js"${B}${z}></script>`}),K=Array.from($,([J,W])=>({hash:J,code:W}));return{html:Q,chunks:K}}var gq=new Set(["visible","idle"]);function Zq(q){if(!q.includes("data-stx-src="))return q;let Z=new Map,$=/data-stx-scope="([^"]+)"[^>]*?stx-hydrate="([^"]+)"/g,Q;while((Q=$.exec(q))!==null)Z.set(Q[1],Q[2]);let K=new Set,J=/data-stx-island="([^"]+)"[^>]*?data-stx-src="([^"]+)"/g,W;while((W=J.exec(q))!==null)if(gq.has(Z.get(W[1])||""))K.add(W[2]);let z=q.lastIndexOf("</head>");if(K.size===0||z===-1)return q;let A=Array.from(K).filter((G)=>!q.includes(`rel="prefetch" href="${G}"`)).map((G)=>`<link rel="prefetch" href="${G}" as="script">`).join("");if(!A)return q;return q.slice(0,z)+A+q.slice(z)}var fq=["STX_SITE_URL","SITE_URL","URL","VERCEL_URL","CF_PAGES_URL"];function h(q){let Z=q.trim();if(!Z)return null;let $=/^https?:\/\//i.test(Z)?Z:`https://${Z}`;try{return new URL($).origin+new URL($).pathname.replace(/\/$/,"")}catch{return null}}function mq(q){for(let Z of fq){let $=q[Z];if(!$)continue;let Q=h($);if(Q)return{url:Q,source:"env",via:Z}}return null}function $q(q){if(!q||typeof q!=="object")return null;let Z=q,$=Z.site?.url??Z.url??Z.app?.url;return typeof $==="string"?h($):null}async function dq(q){let{existsSync:Z}=await import("fs"),$=await import("path");for(let Q of["site.config.ts","site.config.js",".config/site.config.ts"]){let K=$.resolve(q,Q);if(!Z(K))continue;try{let J=await import(K),W=$q(J.default??J);if(W)return W}catch{}}return null}async function Kq(q={}){let Z=q.cwd??process.cwd(),$=q.env??process.env;if(q.explicit){let W=h(q.explicit);if(W)return{url:W,source:"option"}}let Q=$q(q.stxConfig);if(Q)return{url:Q,source:"stx.config",via:"site.url"};let K=await dq(Z);if(K)return{url:K,source:"site.config",via:"url"};let J=mq($);if(J)return J;return{url:"http://localhost",source:"fallback"}}function Wq(){return["No site URL configured \u2014 sitemap.xml and robots.txt will point at http://localhost."," Set one in stx.config.ts (site: { url: 'https://example.com' }), in site.config.ts,"," via --domain, or with SITE_URL in the build environment."].join(`
3
3
  `)}class wq{cacheDir;cache=new Map;loaded=!1;salt;constructor(q,Z=""){this.cacheDir=q,this.salt=Z}async load(){if(this.loaded)return;let q=j.join(this.cacheDir,"build-cache.json");if(await Bun.file(q).exists())try{let Z=JSON.parse(await Bun.file(q).text());for(let[$,Q]of Object.entries(Z))this.cache.set($,Q)}catch{}this.loaded=!0}async save(){await O.promises.mkdir(this.cacheDir,{recursive:!0});let q=j.join(this.cacheDir,"build-cache.json"),Z={};for(let[$,Q]of this.cache)Z[$]=Q;await Bun.write(q,JSON.stringify(Z))}getHash(q,Z){let $=O.statSync(q),Q=`${this.salt}|${q}:${$.mtimeMs}`;for(let J of Z)if(O.existsSync(J)){let W=O.statSync(J);Q+=`:${J}:${W.mtimeMs}`}let K=0;for(let J=0;J<Q.length;J++){let W=Q.charCodeAt(J);K=(K<<5)-K+W,K=K&K}return Math.abs(K).toString(36)}get(q,Z){let $=this.cache.get(q);if(!$)return null;let Q=this.getHash(Z,$.dependencies??[]);if($.hash!==Q)return this.cache.delete(q),null;return $.html}set(q,Z,$,Q){let K=this.getHash(Z,$);this.cache.set(q,{hash:K,html:Q,generatedAt:Date.now(),dependencies:$})}}class Rq{cacheDir;constructor(q){this.cacheDir=q}getFilePath(q){let Z=q.replace(/[^a-zA-Z0-9]/g,"_")+".json";return j.join(this.cacheDir,"isr",Z)}async get(q){let Z=this.getFilePath(q);if(!await Bun.file(Z).exists())return null;try{return JSON.parse(await Bun.file(Z).text())}catch{return null}}async set(q,Z){let $=this.getFilePath(q);await O.promises.mkdir(j.dirname($),{recursive:!0}),await Bun.write($,JSON.stringify(Z))}async invalidate(q){let Z=this.getFilePath(q);if(await Bun.file(Z).exists())await O.promises.unlink(Z)}async invalidatePattern(q){let Z=j.join(this.cacheDir,"isr");if(!O.existsSync(Z))return;let $=await O.promises.readdir(Z);for(let Q of $){let K=Q.replace(/_/g,"/").replace(".json","");if(q.test(K))await O.promises.unlink(j.join(Z,Q))}}async needsRevalidation(q){let Z=await this.get(q);if(!Z)return!0;return Date.now()>Z.revalidateAfter}}async function pq(q){try{let $=(await Bun.file(q).text()).match(/<script\s+server\b[^>]*>([\s\S]*?)<\/script>/i);if(!$)return null;let Q=$[1];if(!Q.includes("getStaticPaths"))return null;let K=Q.match(/(?:export\s+)?(?:async\s+)?function\s+getStaticPaths\s*\([^)]*\)\s*\{([\s\S]*?)\n[\t ]*\}/);if(!K)return null;return await Function(`
4
4
  return (async function getStaticPaths() {
5
5
  ${K[1]}
@@ -41,7 +41,7 @@ ${q.map((Q)=>{let K=new URL(Q.loc,Z).href,J=` <url>
41
41
  ${$.join(`
42
42
  `)}
43
43
  </channel>
44
- </rss>`}async function aq(q={}){let Z=Date.now();Aq();let $=await n(process.cwd()),Q=$.build||{},K=await Kq({explicit:q.domain||Q.domain,stxConfig:$});if(K.source==="fallback"&&(q.sitemap??Q.sitemap??!0))console.warn(Wq());let J={router:q.router??$?.router??{},pagesDir:q.pagesDir||Q.pagesDir||"pages",outputDir:q.outputDir||Q.outputDir||"dist",baseUrl:q.baseUrl||Q.baseUrl||"/",domain:K.url,revalidate:q.revalidate??Q.revalidate??!1,sitemap:q.sitemap??Q.sitemap??!0,sitemapExclude:q.sitemapExclude||Q.sitemapExclude||[],robots:q.robots??Q.robots??!0,rss:q.rss??Q.rss??!1,concurrency:q.concurrency||Q.concurrency||10,cache:q.cache??Q.cache??!0,cacheDir:q.cacheDir||Q.cacheDir||d(process.cwd(),"ssg-cache"),contentLoaders:q.contentLoaders||[],hooks:q.hooks||{},minify:q.minify??Q.minify??!0,generate404:q.generate404??Q.generate404??!0,publicDir:q.publicDir||Q.publicDir||"public",trailingSlash:q.trailingSlash??Q.trailingSlash??!1,cleanOutput:q.cleanOutput??Q.cleanOutput??!0,cacheSalt:q.cacheSalt??"",partialsDir:q.partialsDir||$?.partialsDir||"",componentsDir:q.componentsDir||$?.componentsDir||"",failOnIncludeError:q.failOnIncludeError??!0,chunkIslands:q.chunkIslands===!0,prefetchIslands:q.prefetchIslands===!0,integrityIslands:q.integrityIslands===!0},W={totalPages:0,successCount:0,failedCount:0,cachedCount:0,buildTime:0,pages:[],errors:[]},z=new Set,A=0,G={},X={};await J.hooks.onBuildStart?.();try{if(J.cleanOutput&&O.existsSync(J.outputDir))await O.promises.rm(J.outputDir,{recursive:!0});await O.promises.mkdir(J.outputDir,{recursive:!0});let L=J.cacheSalt;if(!L){let{resolveUserCssConfig:Y,fingerprintConfig:N}=await import("./chunk-jnbg3e4r.js");try{L=N(await Y(process.cwd()))}catch{}}let{prepareImageDelivery:_}=await import("./chunk-e631svvs.js"),F=await _(J.publicDir,J.outputDir);if(F.count>0)console.log(`Optimized ${F.count} image(s) for responsive delivery`),L=[L,F.fingerprint].filter(Boolean).join(":");let C=new wq(J.cacheDir,L);if(J.cache)await C.load();try{let{generateCss:Y}=await import("./chunk-jnbg3e4r.js");await Y('<div class="bg-black"></div>')}catch{}Yq();let kq=$?.routeMiddleware?.dir||"middleware";await jq(process.cwd(),kq);let p=Vq();if(p.length>0)console.log(`Loaded ${p.length} middleware: ${p.join(", ")}`);let bq=$?.routeMiddleware?.global||[];Fq($?.layoutsDir,d(process.cwd()));let i=l(".",{pagesDir:J.pagesDir,pagesDirs:$?._layerPageDirs});console.log(`Found ${i.length} routes`);let g=[];for(let Y of i){let N=await uq(Y);for(let H of N)g.push({route:Y,url:H.url,params:H.params,props:H.props,revalidate:H.revalidate??J.revalidate})}W.totalPages=g.length,console.log(`Generating ${W.totalPages} pages...`);let b=[],s=[];for(let Y=0;Y<g.length;Y+=J.concurrency)s.push(g.slice(Y,Y+J.concurrency));for(let Y of s)await Promise.all(Y.map(async(N)=>{let{route:H,url:V,params:S,props:Sq,revalidate:a}=N;try{await J.hooks.onPageStart?.(V);let w=await Bun.file(H.filePath).text(),R=JJ(w),vq=R?.middleware?Array.isArray(R.middleware)?R.middleware:[R.middleware]:[],t=[...bq,...vq];if(t.length>0){let D=Gq(V,S,R||{}),M=Lq(D,null,void 0,{isServer:!0}),E=await Nq(t,M);if(E.redirect){let U=typeof E.redirect==="string"?E.redirect:E.redirect.path||E.redirect,x=QJ(String(U),V),k=ZJ(V,J);await O.promises.mkdir(j.dirname(k),{recursive:!0}),await Bun.write(k,x),W.successCount++,W.pages.push({route:V,outputPath:k,size:Buffer.byteLength(x),cached:!1}),b.push({loc:V,lastmod:new Date().toISOString()}),console.log(` \u21B3 ${V} \u2192 redirect to ${U}`);return}if(E.abort){let U=typeof E.abort==="string"?E.abort:E.abort?.error?.message||"aborted";console.warn(` \u21B3 ${V} \u2014 middleware aborted: ${U}`);return}}let e=w.match(/definePageMeta\s*\(\s*\{[\s\S]*?validate\s*:\s*((?:\([^)]*\)|[^,}])*(?:\{[\s\S]*?\})?)/m);if(e)try{if(!await Function("route",`
44
+ </rss>`}async function aq(q={}){let Z=Date.now();Aq();let $=await n(process.cwd()),Q=$.build||{},K=await Kq({explicit:q.domain||Q.domain,stxConfig:$});if(K.source==="fallback"&&(q.sitemap??Q.sitemap??!0))console.warn(Wq());let J={router:q.router??$?.router??{},pagesDir:q.pagesDir||Q.pagesDir||"pages",outputDir:q.outputDir||Q.outputDir||"dist",baseUrl:q.baseUrl||Q.baseUrl||"/",domain:K.url,revalidate:q.revalidate??Q.revalidate??!1,sitemap:q.sitemap??Q.sitemap??!0,sitemapExclude:q.sitemapExclude||Q.sitemapExclude||[],robots:q.robots??Q.robots??!0,rss:q.rss??Q.rss??!1,concurrency:q.concurrency||Q.concurrency||10,cache:q.cache??Q.cache??!0,cacheDir:q.cacheDir||Q.cacheDir||d(process.cwd(),"ssg-cache"),contentLoaders:q.contentLoaders||[],hooks:q.hooks||{},minify:q.minify??Q.minify??!0,generate404:q.generate404??Q.generate404??!0,publicDir:q.publicDir||Q.publicDir||"public",trailingSlash:q.trailingSlash??Q.trailingSlash??!1,cleanOutput:q.cleanOutput??Q.cleanOutput??!0,cacheSalt:q.cacheSalt??"",partialsDir:q.partialsDir||$?.partialsDir||"",componentsDir:q.componentsDir||$?.componentsDir||"",failOnIncludeError:q.failOnIncludeError??!0,chunkIslands:q.chunkIslands===!0,prefetchIslands:q.prefetchIslands===!0,integrityIslands:q.integrityIslands===!0},W={totalPages:0,successCount:0,failedCount:0,cachedCount:0,buildTime:0,pages:[],errors:[]},z=new Set,A=0,G={},X={};await J.hooks.onBuildStart?.();try{if(J.cleanOutput&&O.existsSync(J.outputDir))await O.promises.rm(J.outputDir,{recursive:!0});await O.promises.mkdir(J.outputDir,{recursive:!0});let L=J.cacheSalt;if(!L){let{resolveUserCssConfig:Y,fingerprintConfig:N}=await import("./chunk-rd02ysw3.js");try{L=N(await Y(process.cwd()))}catch{}}let{prepareImageDelivery:_}=await import("./chunk-e631svvs.js"),F=await _(J.publicDir,J.outputDir);if(F.count>0)console.log(`Optimized ${F.count} image(s) for responsive delivery`),L=[L,F.fingerprint].filter(Boolean).join(":");let C=new wq(J.cacheDir,L);if(J.cache)await C.load();try{let{generateCss:Y}=await import("./chunk-rd02ysw3.js");await Y('<div class="bg-black"></div>')}catch{}Yq();let kq=$?.routeMiddleware?.dir||"middleware";await jq(process.cwd(),kq);let p=Vq();if(p.length>0)console.log(`Loaded ${p.length} middleware: ${p.join(", ")}`);let bq=$?.routeMiddleware?.global||[];Fq($?.layoutsDir,d(process.cwd()));let i=l(".",{pagesDir:J.pagesDir,pagesDirs:$?._layerPageDirs});console.log(`Found ${i.length} routes`);let g=[];for(let Y of i){let N=await uq(Y);for(let H of N)g.push({route:Y,url:H.url,params:H.params,props:H.props,revalidate:H.revalidate??J.revalidate})}W.totalPages=g.length,console.log(`Generating ${W.totalPages} pages...`);let b=[],s=[];for(let Y=0;Y<g.length;Y+=J.concurrency)s.push(g.slice(Y,Y+J.concurrency));for(let Y of s)await Promise.all(Y.map(async(N)=>{let{route:H,url:V,params:S,props:Sq,revalidate:a}=N;try{await J.hooks.onPageStart?.(V);let w=await Bun.file(H.filePath).text(),R=JJ(w),vq=R?.middleware?Array.isArray(R.middleware)?R.middleware:[R.middleware]:[],t=[...bq,...vq];if(t.length>0){let D=Gq(V,S,R||{}),M=Lq(D,null,void 0,{isServer:!0}),E=await Nq(t,M);if(E.redirect){let U=typeof E.redirect==="string"?E.redirect:E.redirect.path||E.redirect,x=QJ(String(U),V),k=ZJ(V,J);await O.promises.mkdir(j.dirname(k),{recursive:!0}),await Bun.write(k,x),W.successCount++,W.pages.push({route:V,outputPath:k,size:Buffer.byteLength(x),cached:!1}),b.push({loc:V,lastmod:new Date().toISOString()}),console.log(` \u21B3 ${V} \u2192 redirect to ${U}`);return}if(E.abort){let U=typeof E.abort==="string"?E.abort:E.abort?.error?.message||"aborted";console.warn(` \u21B3 ${V} \u2014 middleware aborted: ${U}`);return}}let e=w.match(/definePageMeta\s*\(\s*\{[\s\S]*?validate\s*:\s*((?:\([^)]*\)|[^,}])*(?:\{[\s\S]*?\})?)/m);if(e)try{if(!await Function("route",`
45
45
  const validate = ${e[1]};
46
46
  return validate(route);
47
47
  `)({params:S})){console.warn(`Validation failed for ${V}, skipping...`);return}}catch{}let I=null,qq=!1;if(J.cache){if(I=C.get(V,H.filePath),I)qq=!0,W.cachedCount++}if(!I){let D=await Mq(H,S,Sq,J,$);if(I=D.html,J.failOnIncludeError&&D.includeFailures.length>0)throw Error(`${D.includeFailures.length} include(s) failed to resolve, so this page would have shipped with an error banner where the markup should be:
@@ -62,7 +62,7 @@ export interface TemplateExpression {
62
62
  line: number;
63
63
  /** 1-based column of the expression's first character. */
64
64
  column: number;
65
- kind: 'interpolation' | 'directive';
65
+ kind: 'interpolation' | 'directive' | 'condition';
66
66
  /** For directives, the attribute name (`:if`, `@click`, `x-text`). */
67
67
  attribute?: string;
68
68
  /**
@@ -86,6 +86,15 @@ export interface MappedLine {
86
86
  * the start of the attribute.
87
87
  */
88
88
  prefixLength?: number;
89
+ /**
90
+ * Characters of `if (…) {` wrapper before the expression's own statement.
91
+ *
92
+ * A diagnostic inside this span is about the enclosing CONDITION, not about
93
+ * the expression this line carries - and the condition is checked on a line
94
+ * of its own, so reporting it here too would name innocent markup twice
95
+ * (stacksjs/stx#2013).
96
+ */
97
+ guardPrefixLength?: number;
89
98
  }
90
99
  export interface VirtualFile {
91
100
  text: string;
@@ -463,6 +472,12 @@ export interface ResolvedPosition {
463
472
  column: number;
464
473
  /** Set when the diagnostic came from a template expression. */
465
474
  expression?: TemplateExpression;
475
+ /**
476
+ * The hit landed in the `if (…) {` wrapper that markup guards put around an
477
+ * expression, so it is about the CONDITION rather than this expression. The
478
+ * condition has a line of its own (stacksjs/stx#2013).
479
+ */
480
+ inGuard?: boolean;
466
481
  }
467
482
  /**
468
483
  * Translate a position in a virtual buffer back to the `.stx` file.
@@ -173,6 +173,35 @@ export function extractTemplateExpressions(source) {
173
173
  kind: "interpolation"
174
174
  });
175
175
  }
176
+ const condition = /@(if|elseif|else\s+if|unless)\b\s*\(/g;
177
+ while ((m = condition.exec(masked)) !== null) {
178
+ const open = m.index + m[0].length - 1;
179
+ let depth = 0, index = open;
180
+ for (;index < masked.length; index++) {
181
+ const char = masked[index];
182
+ if (char === "(")
183
+ depth++;
184
+ else if (char === ")") {
185
+ depth--;
186
+ if (depth === 0)
187
+ break;
188
+ }
189
+ }
190
+ if (depth !== 0)
191
+ continue;
192
+ const raw = masked.slice(open + 1, index), code = raw.trim();
193
+ condition.lastIndex = index + 1;
194
+ if (!code)
195
+ continue;
196
+ const start = open + 1 + (raw.length - raw.trimStart().length);
197
+ found.push({
198
+ code,
199
+ line: lineAt(masked, start),
200
+ column: columnAt(masked, start),
201
+ offset: m.index,
202
+ kind: "condition"
203
+ });
204
+ }
176
205
  DIRECTIVE_ATTR_RE.lastIndex = 0;
177
206
  while ((m = DIRECTIVE_ATTR_RE.exec(masked)) !== null) {
178
207
  const name = m[1], value = m[3] ?? "", base = name.replace(/\..*$/, "").replace(/^([^:].*?):.*$/, "$1");
@@ -648,7 +677,8 @@ export function buildVirtualTypeScript(source, options = {}) {
648
677
  line: expression.line,
649
678
  column: expression.column,
650
679
  expression,
651
- prefixLength: statement.prefixLength + pad.length + guardPrefix.length
680
+ prefixLength: statement.prefixLength + pad.length + guardPrefix.length,
681
+ guardPrefixLength: pad.length + guardPrefix.length
652
682
  });
653
683
  }
654
684
  if (indent > 0)
@@ -689,6 +719,7 @@ export function resolvePosition(virtual, line, column) {
689
719
  return {
690
720
  line: mapped.line,
691
721
  column: mapped.column + within,
692
- expression: mapped.expression
722
+ expression: mapped.expression,
723
+ inGuard: column <= (mapped.guardPrefixLength ?? 0)
693
724
  };
694
725
  }
@@ -3,22 +3,26 @@ export const SEMANTIC_TOKENS = {
3
3
  "fg-strong": { light: "neutral-700", dark: "neutral-300", description: "Labels, secondary headings" },
4
4
  "fg-muted": { light: "neutral-600", dark: "neutral-400", description: "Supporting text" },
5
5
  "fg-soft": { light: "neutral-500", dark: "neutral-400", description: "De-emphasised text" },
6
- "fg-subtle": { light: "neutral-400", dark: "neutral-500", description: "Placeholders, disabled text" },
6
+ "fg-subtle": { light: "neutral-400", dark: "neutral-500", description: "Recessive, non-essential text" },
7
7
  surface: { light: "neutral-50", dark: "neutral-800", description: "Page and panel background" },
8
8
  "surface-raised": { light: "neutral-100", dark: "neutral-700", description: "Cards, popovers" },
9
9
  "surface-sunken": { light: "neutral-200", dark: "neutral-700", description: "Wells, track backgrounds" },
10
10
  panel: { light: "white", dark: "neutral-800", description: "Card, menu, dialog surface" },
11
11
  field: { light: "white", dark: "neutral-700", description: "Form control surface" },
12
+ page: { light: "neutral-50", dark: "neutral-900", description: "Page background, behind panels" },
13
+ content: { light: "white", dark: "neutral-900", description: "Content area under a chrome header - a table or list body" },
12
14
  "surface-hover": { light: "neutral-100", dark: "neutral-700", description: "Row or item highlight on a page or panel" },
13
15
  "surface-raised-hover": { light: "neutral-200", dark: "neutral-600", description: "Raised surface, hovered" },
14
16
  "surface-sunken-hover": { light: "neutral-300", dark: "neutral-600", description: "Sunken surface, hovered" },
15
17
  "field-hover": { light: "neutral-50", dark: "neutral-600", description: "Form control surface, hovered" },
18
+ link: { light: "blue-500", dark: "blue-400", description: "Hyperlink" },
19
+ "link-hover": { light: "blue-600", dark: "blue-300", description: "Hyperlink, hovered" },
16
20
  inverse: { light: "neutral-900", dark: "neutral-700", description: "Tooltip bubble, inverted surface" },
17
21
  "inverse-ink": { light: "white", dark: "white", description: "Text on an inverted surface" },
18
22
  line: { light: "neutral-200", dark: "neutral-700", description: "Default border, divider" },
19
23
  "line-strong": { light: "neutral-300", dark: "neutral-600", description: "Input border, focus ring track" },
20
24
  accent: { light: "blue-600", dark: "blue-400", description: "Primary action, selected state" },
21
- info: { light: "cyan-600", dark: "cyan-400", description: "Informational emphasis, links" },
25
+ info: { light: "cyan-600", dark: "cyan-400", description: "Informational emphasis" },
22
26
  danger: { light: "red-600", dark: "red-400", description: "Errors, destructive actions" },
23
27
  success: { light: "green-600", dark: "green-400", description: "Confirmation" },
24
28
  warning: { light: "yellow-600", dark: "yellow-400", description: "Caution" },
@@ -33,9 +37,9 @@ export const SEMANTIC_TOKENS = {
33
37
  "success-solid-hover": { light: "green-600", dark: "green-700", description: "Confirmation fill, hovered" },
34
38
  "warning-solid-hover": { light: "yellow-600", dark: "yellow-700", description: "Caution fill, hovered" },
35
39
  "accent-ink": { light: "white", dark: "white", description: "Text on a primary fill" },
36
- "info-ink": { light: "white", dark: "white", description: "Text on an informational fill" },
40
+ "info-ink": { light: "neutral-900", dark: "neutral-900", description: "Text on an informational fill" },
37
41
  "danger-ink": { light: "white", dark: "white", description: "Text on a destructive fill" },
38
- "success-ink": { light: "white", dark: "white", description: "Text on a confirmation fill" },
42
+ "success-ink": { light: "neutral-900", dark: "neutral-900", description: "Text on a confirmation fill" },
39
43
  "warning-ink": { light: "neutral-900", dark: "neutral-900", description: "Text on a caution fill" },
40
44
  "accent-soft": { light: "blue-100", dark: "blue-900", description: "Tinted primary fill" },
41
45
  "accent-soft-ink": { light: "blue-800", dark: "blue-200", description: "Text on a tinted primary fill" },
@@ -54,7 +58,7 @@ export const SEMANTIC_TOKENS = {
54
58
  "secondary-soft": { light: "purple-100", dark: "purple-900", description: "Tinted secondary fill" },
55
59
  "secondary-soft-ink": { light: "purple-800", dark: "purple-200", description: "Text on a tinted secondary fill" },
56
60
  "danger-fg": { light: "red-900", dark: "red-300", description: "Text in a field with an error" },
57
- "danger-fg-subtle": { light: "red-300", dark: "red-500", description: "Placeholder in a field with an error" },
61
+ "danger-fg-subtle": { light: "red-300", dark: "red-500", description: "Recessive text in a field with an error" },
58
62
  "danger-line": { light: "red-300", dark: "red-600", description: "Border of a field with an error" },
59
63
  "danger-focus": { light: "red-500", dark: "red-400", description: "Focus ring of a field with an error" }
60
64
  };
package/dist/typecheck.js CHANGED
@@ -305,6 +305,8 @@ export async function typecheckStxFiles(files, options = {}) {
305
305
  const at = resolvePosition(entry.virtual, line, column);
306
306
  if (!at?.expression)
307
307
  continue;
308
+ if (at.inGuard)
309
+ continue;
308
310
  if (Number(code) === 2774)
309
311
  continue;
310
312
  line = at.line;
@@ -708,6 +708,16 @@ export interface StxConfig {
708
708
  defaultLayout?: string;
709
709
  /** Plugins to load — npm packages, local paths, or [path, options] tuples */
710
710
  plugins?: Array<string | [string, Record<string, any>]>;
711
+ /**
712
+ * Installed packages whose components resolve by bare tag, with no plugin
713
+ * entry and no import.
714
+ *
715
+ * Each is loaded through its own `<pkg>/stx-plugin` export if the project has
716
+ * it installed, and is searched AFTER every project directory - so a
717
+ * component the app wrote still wins. Defaults to `['@stacksjs/components']`;
718
+ * set it to `[]` to opt out entirely (stacksjs/stx#2011).
719
+ */
720
+ componentPackages?: string[];
711
721
  /** Path to public/static assets directory, defaults to 'public' */
712
722
  publicDir?: string;
713
723
  /** Path to .env file, defaults to '.env' in project root (Bun loads this automatically) */