spark-ssr 0.3.3 → 0.3.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +67 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spark-ssr",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Zero-config SSR for spark-html on Bun. The HTML template infers everything: filesystem routing, layouts, <spark-ssr> declarative data (SQL, URLs, globs, modules), auto CRUD with validation, guards, no-JS forms, schema + seeds, live updates, SEO. No build step.",
5
5
  "homepage": "https://wilkinnovo.github.io/spark-html",
6
6
  "type": "module",
package/src/server.js CHANGED
@@ -172,11 +172,18 @@ function pageData(page, cache, pagesDir) {
172
172
  const pageP = parsed[parsed.length - 1];
173
173
 
174
174
  // Compose bodies innermost-out: the page replaces each layout's <slot>.
175
+ // Comments are masked first so a literal <slot> written inside a layout
176
+ // comment (the template's own explainer text does this) isn't mistaken for
177
+ // the real slot — which would inject the whole page inside the comment.
175
178
  let body = pageP.body;
176
179
  for (let i = parsed.length - 2; i >= 0; i--) {
177
180
  const lay = parsed[i].body;
178
181
  const SLOT = /<slot\b[^>]*>(?:\s*<\/slot>)?/i;
179
- body = SLOT.test(lay) ? lay.replace(SLOT, () => body) : lay + body;
182
+ const { masked, restore } = maskComments(lay);
183
+ const m = masked.match(SLOT);
184
+ body = m
185
+ ? restore(masked.slice(0, m.index)) + body + restore(masked.slice(m.index + m[0].length))
186
+ : lay + body;
180
187
  }
181
188
 
182
189
  const blocks = parsed.flatMap((p) => p.blocks);
@@ -1098,15 +1105,67 @@ export async function serve(options = {}) {
1098
1105
  });
1099
1106
  }
1100
1107
 
1101
- const STATUS_TEXT = { 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not found' };
1108
+ // Human copy for the built-in default error screen (used when the app ships
1109
+ // no <status>.html of its own).
1110
+ const STATUS_INFO = {
1111
+ 400: ['Bad request', 'That request could not be understood.'],
1112
+ 401: ['Sign in required', 'You need to sign in to view this page.'],
1113
+ 403: ['Forbidden', "You don't have access to this page."],
1114
+ 404: ['Page not found', "The page you're looking for doesn't exist — it may have moved."],
1115
+ 500: ['Server error', 'Something went wrong on our end. Try again in a moment.'],
1116
+ };
1117
+
1118
+ // Zero-config error screen: a styled, self-contained page in the Spark design
1119
+ // system (dark default, gold ⚡, monospace) — no dependency on the app's
1120
+ // layout or data, so it renders even when those are what failed. Apps override
1121
+ // it by dropping a <status>.html in pages/ (or the project root).
1122
+ function defaultErrorPage(status) {
1123
+ const [title, blurb] = STATUS_INFO[status] || ['Error', 'Something went wrong.'];
1124
+ const body = `<!doctype html>
1125
+ <html lang="en"><head><meta charset="utf-8">
1126
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1127
+ <meta name="robots" content="noindex">
1128
+ <title>${status} · ${escapeHtml(title)}</title>
1129
+ <style>
1130
+ :root{color-scheme:dark light}
1131
+ *{box-sizing:border-box}
1132
+ body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
1133
+ background:#000;color:#fff;text-align:center;padding:2rem;
1134
+ font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.6}
1135
+ @media(prefers-color-scheme:light){body{background:#fff;color:#1a1a1a}}
1136
+ .bolt{font-size:2.25rem;filter:drop-shadow(0 0 16px rgba(255,210,74,.45))}
1137
+ .code{font-size:clamp(3.5rem,14vw,6rem);font-weight:800;letter-spacing:-.04em;margin:.25rem 0 0;
1138
+ background:linear-gradient(110deg,currentColor,#ffd24a);-webkit-background-clip:text;background-clip:text;color:transparent}
1139
+ h1{font-size:1.15rem;font-weight:700;margin:.25rem 0 .5rem}
1140
+ p{color:#888;max-width:32rem;margin:0 auto 1.5rem;font-size:.95rem}
1141
+ @media(prefers-color-scheme:light){p{color:#666}}
1142
+ a{display:inline-block;color:#000;background:#ffd24a;text-decoration:none;font-weight:700;
1143
+ padding:.6rem 1.2rem;border-radius:8px;font-size:.9rem}
1144
+ a:active{transform:scale(.97)}
1145
+ </style></head>
1146
+ <body><main>
1147
+ <div class="bolt">⚡</div>
1148
+ <div class="code">${status}</div>
1149
+ <h1>${escapeHtml(title)}</h1>
1150
+ <p>${escapeHtml(blurb)}</p>
1151
+ <a href="/">← Back home</a>
1152
+ </main></body></html>`;
1153
+ return body;
1154
+ }
1155
+
1102
1156
  function errorPage(status) {
1103
- const file = join(root, `${status}.html`);
1104
- if (existsSync(file)) {
1105
- // The reload client rides along so fixing the page un-sticks the browser.
1106
- const body = readFileSync(file, 'utf8') + (live ? '\n' + RELOAD_CLIENT : '');
1107
- return new Response(body, { status, headers: { 'content-type': 'text/html; charset=utf-8' } });
1157
+ // Override precedence: pages/<status>.html (filesystem convention)
1158
+ // <root>/<status>.html (back-compat) → the built-in default.
1159
+ for (const dir of new Set([pagesDir, root])) {
1160
+ const file = join(dir, `${status}.html`);
1161
+ if (existsSync(file)) {
1162
+ // The reload client rides along so fixing the page un-sticks the browser.
1163
+ const custom = readFileSync(file, 'utf8') + (live ? '\n' + RELOAD_CLIENT : '');
1164
+ return new Response(custom, { status, headers: { 'content-type': 'text/html; charset=utf-8' } });
1165
+ }
1108
1166
  }
1109
- return new Response(STATUS_TEXT[status] || 'Server error', { status });
1167
+ const body = defaultErrorPage(status) + (live ? '\n' + RELOAD_CLIENT : '');
1168
+ return new Response(body, { status, headers: { 'content-type': 'text/html; charset=utf-8' } });
1110
1169
  }
1111
1170
 
1112
1171
  // Dev-only error overlay (§4): the real error — SQL, file, line — on the