@apex-stack/core 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,12 +7,13 @@ import {
7
7
  renderPage,
8
8
  resolveApexConfig,
9
9
  scanPages
10
- } from "./chunk-EVFABT7B.js";
10
+ } from "./chunk-S3HCE23H.js";
11
11
  import "./chunk-PMLGY6Z3.js";
12
12
 
13
13
  // src/commands/build.ts
14
14
  import { cpSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync3, rmSync, writeFileSync } from "fs";
15
15
  import { dirname, join as join3, resolve } from "path";
16
+ import { renderFragment } from "@apex-stack/kit";
16
17
  import { apex as apex3 } from "@apex-stack/vite";
17
18
  import { defineCommand } from "citty";
18
19
  import { createServer as createViteServer } from "vite";
@@ -57,11 +58,20 @@ async function buildClient(root, routes, outDir, base = "/") {
57
58
  return [
58
59
  ...appCssRel ? [`import ${JSON.stringify(`/${appCssRel}`)}`] : [],
59
60
  `import Alpine from 'alpinejs'`,
61
+ `import { installNav } from '@apex-stack/core/client'`,
60
62
  ...storeIds.map((sid, i) => `import __s${i} from ${JSON.stringify(sid)}`),
63
+ // Registers this page's Alpine factory as a side effect. During client-side
64
+ // navigation this bundle is dynamic-imported again for the target page —
65
+ // the import runs (registering the new page), the boot guard below skips
66
+ // the one-time Alpine.start()/installNav so nothing double-initialises.
61
67
  `import ${JSON.stringify(pageId)}`,
62
- "window.Alpine = Alpine",
63
- ...storeIds.map((_, i) => `Alpine.store(__s${i}.name, __s${i}.factory())`),
64
- "Alpine.start()"
68
+ "if (!window.__apexBooted) {",
69
+ " window.__apexBooted = true",
70
+ " window.Alpine = Alpine",
71
+ ...storeIds.map((_, i) => ` Alpine.store(__s${i}.name, __s${i}.factory())`),
72
+ " Alpine.start()",
73
+ " installNav()",
74
+ "}"
65
75
  ].join("\n");
66
76
  }
67
77
  }
@@ -222,12 +232,18 @@ var buildCommand = defineCommand({
222
232
  (id) => vite.ssrLoadModule(id)
223
233
  );
224
234
  const stores = await loadStores(root, (id) => vite.ssrLoadModule(id));
225
- const { runtimeConfig, publicConfig } = await resolveApexConfig(
235
+ const { config, runtimeConfig, publicConfig } = await resolveApexConfig(
226
236
  root,
227
237
  (id) => vite.ssrLoadModule(id)
228
238
  );
239
+ const clientNav = config.clientNav !== false;
229
240
  const layoutsDir = join3(root, "layouts");
230
241
  const layouts = existsSync3(layoutsDir) ? readdirSync3(layoutsDir).filter((f) => f.endsWith(".alpine")).map((f) => f.replace(/\.alpine$/, "")) : [];
242
+ let loadingHtml;
243
+ if (!args.islands && clientNav && existsSync3(join3(root, "pages", "loading.alpine"))) {
244
+ const l = await vite.ssrLoadModule("/pages/loading.alpine");
245
+ loadingHtml = renderFragment(l.template, {}, l.scopeId, registry);
246
+ }
231
247
  for (const route of staticRoutes) {
232
248
  const common = {
233
249
  loadModule: (id) => vite.ssrLoadModule(id),
@@ -241,7 +257,13 @@ var buildCommand = defineCommand({
241
257
  publicConfig
242
258
  };
243
259
  const assets = hrefs.get(route.pageId);
244
- const html = args.islands ? await renderIslandsPage(common) : await renderPage({ ...common, clientHref: assets?.js, clientCss: assets?.css });
260
+ const html = args.islands ? await renderIslandsPage(common) : await renderPage({
261
+ ...common,
262
+ clientHref: assets?.js,
263
+ clientCss: assets?.css,
264
+ clientNav,
265
+ loadingHtml
266
+ });
245
267
  const dest = join3(outDir, outFile(route.pattern));
246
268
  mkdirSync(dirname(dest), { recursive: true });
247
269
  writeFileSync(dest, html);
@@ -268,6 +290,8 @@ async function buildServerTarget(root, outDir, outLabel, routes) {
268
290
  const clientHrefs = await buildClient(root, routes, outDir);
269
291
  const server = await buildServer(root, routes, outDir);
270
292
  let runtimeConfig = { public: {} };
293
+ let clientNav = true;
294
+ let loadingHtml;
271
295
  const cfgVite = await createViteServer({
272
296
  root,
273
297
  appType: "custom",
@@ -278,6 +302,12 @@ async function buildServerTarget(root, outDir, outLabel, routes) {
278
302
  const resolved = await resolveApexConfig(root, (id) => cfgVite.ssrLoadModule(id));
279
303
  runtimeConfig = { public: {}, ...resolved.config.runtimeConfig ?? {} };
280
304
  if (!runtimeConfig.public) runtimeConfig.public = {};
305
+ clientNav = resolved.config.clientNav !== false;
306
+ if (clientNav && existsSync3(join3(root, "pages", "loading.alpine"))) {
307
+ const { registry } = await loadComponents(root, (id) => cfgVite.ssrLoadModule(id));
308
+ const l = await cfgVite.ssrLoadModule("/pages/loading.alpine");
309
+ loadingHtml = renderFragment(l.template, {}, l.scopeId, registry);
310
+ }
281
311
  } finally {
282
312
  await cfgVite.close();
283
313
  }
@@ -316,7 +346,9 @@ async function buildServerTarget(root, outDir, outLabel, routes) {
316
346
  components,
317
347
  api,
318
348
  middleware,
319
- runtimeConfig
349
+ runtimeConfig,
350
+ clientNav,
351
+ loadingHtml
320
352
  };
321
353
  writeFileSync(join3(outDir, "apex-manifest.json"), JSON.stringify(manifest, null, 2));
322
354
  const pub = join3(root, "public");
@@ -135,12 +135,12 @@ function renderHead(head) {
135
135
  const parts = [`<title>${head?.title ? escAttr(head.title) : "Apex JS"}</title>`];
136
136
  for (const m of head?.meta ?? []) {
137
137
  parts.push(
138
- `<meta ${Object.entries(m).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} />`
138
+ `<meta ${Object.entries(m).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} data-apex-head />`
139
139
  );
140
140
  }
141
141
  for (const l of head?.link ?? []) {
142
142
  parts.push(
143
- `<link ${Object.entries(l).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} />`
143
+ `<link ${Object.entries(l).map(([k, v]) => `${k}="${escAttr(v)}"`).join(" ")} data-apex-head />`
144
144
  );
145
145
  }
146
146
  return parts.join("\n ");
@@ -207,7 +207,11 @@ async function renderPage(opts) {
207
207
  appCss: opts.appCss,
208
208
  clientCss: opts.clientCss,
209
209
  headTags: renderHead(head),
210
- configScript: clientConfigScript(opts.publicConfig ?? {})
210
+ configScript: clientConfigScript(opts.publicConfig ?? {}),
211
+ // dev imports the page module by path; prod imports its hashed bundle.
212
+ moduleUrl: opts.clientHref ?? opts.pageId,
213
+ clientNav: opts.clientNav !== false,
214
+ loadingHtml: opts.loadingHtml
211
215
  });
212
216
  return opts.transformHtml ? opts.transformHtml(opts.url, doc) : doc;
213
217
  }
@@ -221,31 +225,44 @@ function shell({
221
225
  appCss,
222
226
  clientCss = [],
223
227
  headTags = "<title>Apex JS</title>",
224
- configScript = ""
228
+ configScript = "",
229
+ moduleUrl,
230
+ clientNav = true,
231
+ loadingHtml
225
232
  }) {
226
233
  const storeImports = storeIds.map((id, i) => ` import __s${i} from ${JSON.stringify(id)}`).join("\n");
227
234
  const storeRegs = storeIds.map((_, i) => ` Alpine.store(__s${i}.name, __s${i}.factory())`).join("\n");
235
+ const navImport = clientNav ? ` import { installNav } from '@apex-stack/core/client'
236
+ ` : "";
237
+ const navInstall = clientNav ? ` installNav()
238
+ ` : "";
228
239
  const clientScript = clientHref ? `<script type="module" src="${clientHref}"></script>` : `<script type="module">
229
240
  import Alpine from 'alpinejs'
230
- ${storeImports ? `${storeImports}
241
+ ${navImport}${storeImports ? `${storeImports}
231
242
  ` : ""} import ${JSON.stringify(pageId)}
232
243
  window.Alpine = Alpine
233
244
  ${storeRegs ? `${storeRegs}
234
245
  ` : ""} Alpine.start()
235
- </script>`;
246
+ ${navInstall}</script>`;
236
247
  const cssLinks = [...appCss ? [appCss] : [], ...clientCss].map((href) => `<link rel="stylesheet" href="${href}" />`).join("\n ");
248
+ const moduleMeta = moduleUrl ? `
249
+ <meta name="apex:page-module" content="${escAttr(moduleUrl)}" />` : "";
250
+ const loadingTpl = loadingHtml ? `
251
+ <template data-apex-loading>${loadingHtml}</template>` : "";
237
252
  return `<!DOCTYPE html>
238
253
  <html lang="en">
239
254
  <head>
240
255
  <meta charset="utf-8" />
241
256
  <meta name="viewport" content="width=device-width, initial-scale=1" />
242
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
257
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />${moduleMeta}
243
258
  ${cssLinks ? `${cssLinks}
244
259
  ` : ""}${headTags}
245
260
  <style>${css}</style>
246
261
  </head>
247
262
  <body>
263
+ <div id="__apex" data-apex-root>
248
264
  ${body}
265
+ </div>${loadingTpl}
249
266
  ${island}
250
267
  ${configScript}
251
268
  ${clientScript}
@@ -364,7 +381,8 @@ function walkAlpine(dir) {
364
381
  function scanPages(root) {
365
382
  const dir = join3(root, "pages");
366
383
  if (!existsSync3(dir)) return [];
367
- const routes = walkAlpine(dir).map((abs) => {
384
+ const RESERVED = /* @__PURE__ */ new Set(["error.alpine", "loading.alpine"]);
385
+ const routes = walkAlpine(dir).filter((abs) => !RESERVED.has(relative(dir, abs).split(sep).join("/"))).map((abs) => {
368
386
  const rel = relative(dir, abs).split(sep).join("/");
369
387
  const pageId = `/pages/${rel}`;
370
388
  const parts = rel.replace(/\.alpine$/, "").split("/");
@@ -90,6 +90,8 @@ async function offerExtension(choice) {
90
90
  }
91
91
 
92
92
  export {
93
+ cmpVersion,
93
94
  openInEditor,
95
+ promptYesNo,
94
96
  offerExtension
95
97
  };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  offerExtension
4
- } from "./chunk-P6KQSPAV.js";
4
+ } from "./chunk-TWAGSGFN.js";
5
5
  import {
6
6
  VERSION,
7
7
  banner,
@@ -147,13 +147,13 @@ var main = defineCommand2({
147
147
  },
148
148
  subCommands: {
149
149
  new: newCommand,
150
- dev: () => import("./dev-SXOWAX4N.js").then((m) => m.devCommand),
151
- build: () => import("./build-C3FW7BTM.js").then((m) => m.buildCommand),
152
- start: () => import("./start-VBGP3HFC.js").then((m) => m.startCommand),
150
+ dev: () => import("./dev-3EVUK364.js").then((m) => m.devCommand),
151
+ build: () => import("./build-WKK4BCQI.js").then((m) => m.buildCommand),
152
+ start: () => import("./start-FIRWZ55Y.js").then((m) => m.startCommand),
153
153
  make: () => import("./make-VAYO5GWA.js").then((m) => m.makeCommand),
154
154
  add: () => import("./add-M3YLIFF5.js").then((m) => m.addCommand),
155
155
  theme: () => import("./theme-UUOIV44V.js").then((m) => m.themeCommand),
156
- upgrade: () => import("./upgrade-KGUW3C3O.js").then((m) => m.upgradeCommand),
156
+ upgrade: () => import("./upgrade-YZN6TVNI.js").then((m) => m.upgradeCommand),
157
157
  migrate: () => import("./migrate-X6LIHMIE.js").then((m) => m.migrateCommand),
158
158
  mcp: () => import("./mcp-CH7L4GF3.js").then((m) => m.mcpCommand)
159
159
  },
package/dist/client.d.ts CHANGED
@@ -1 +1 @@
1
- export { ActionOptions, ActionState, createAction, registerApexComponent } from '@apex-stack/kit/client';
1
+ export { ActionOptions, ActionState, NavOptions, createAction, installNav, registerApexComponent } from '@apex-stack/kit/client';
package/dist/client.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/client.ts
2
- import { createAction, registerApexComponent } from "@apex-stack/kit/client";
2
+ import { createAction, installNav, registerApexComponent } from "@apex-stack/kit/client";
3
3
  export {
4
4
  createAction,
5
+ installNav,
5
6
  registerApexComponent
6
7
  };
@@ -24,7 +24,7 @@ var devCommand = defineCommand({
24
24
  process.stdout.write(banner());
25
25
  const sp = spinner(`Starting dev server${args.islands ? " (islands mode)" : ""}\u2026`);
26
26
  try {
27
- const { startDevServer } = await import("./server-AV533LKD.js");
27
+ const { startDevServer } = await import("./server-UVY6DFK2.js");
28
28
  const { port: actual } = await startDevServer({ root, port, islands: Boolean(args.islands) });
29
29
  sp.succeed("Dev server ready");
30
30
  ready([
package/dist/index.d.ts CHANGED
@@ -14,6 +14,11 @@ interface ApexConfig {
14
14
  * `APEX_PUBLIC_<KEY>` for `public` keys (camelCase ↔ SCREAMING_SNAKE).
15
15
  */
16
16
  runtimeConfig?: RuntimeConfig;
17
+ /**
18
+ * Client-side navigation (SPA link nav + prefetch + progress bar). On by
19
+ * default; set `false` to fall back to full-page loads.
20
+ */
21
+ clientNav?: boolean;
17
22
  [key: string]: unknown;
18
23
  }
19
24
  /** Author an `apex.config.ts`. Identity function — exists for types + discoverability. */
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  openInEditor
3
- } from "./chunk-P6KQSPAV.js";
3
+ } from "./chunk-TWAGSGFN.js";
4
4
  import {
5
5
  loadComponents
6
6
  } from "./chunk-4VG3CZ6H.js";
@@ -19,7 +19,7 @@ import {
19
19
  renderPage,
20
20
  resolveApexConfig,
21
21
  scanPages
22
- } from "./chunk-EVFABT7B.js";
22
+ } from "./chunk-S3HCE23H.js";
23
23
  import "./chunk-PMLGY6Z3.js";
24
24
 
25
25
  // src/dev/server.ts
@@ -28,6 +28,7 @@ import { createServer as createHttpServer } from "http";
28
28
  import { createRequire } from "module";
29
29
  import { join as join2, resolve } from "path";
30
30
  import { fileURLToPath, pathToFileURL } from "url";
31
+ import { renderFragment } from "@apex-stack/kit";
31
32
  import { apex } from "@apex-stack/vite";
32
33
  import {
33
34
  createApp,
@@ -388,10 +389,11 @@ async function startDevServer(options) {
388
389
  const resolved = id[0] === "/" && !id.startsWith(options.root) ? join2(options.root, id).replace(/\\/g, "/") : id;
389
390
  return vite.ssrLoadModule(resolved);
390
391
  };
391
- const { runtimeConfig, publicConfig } = await resolveApexConfig(
392
+ const { config, runtimeConfig, publicConfig } = await resolveApexConfig(
392
393
  options.root,
393
394
  (id) => ssrLoad(id)
394
395
  );
396
+ const clientNav = config.clientNav !== false;
395
397
  const app = createApp();
396
398
  app.use(fromNodeMiddleware(vite.middlewares));
397
399
  app.use(
@@ -463,6 +465,11 @@ async function startDevServer(options) {
463
465
  const stores = await loadStores(options.root, (id) => ssrLoad(id));
464
466
  const layoutsDir = join2(options.root, "layouts");
465
467
  const layouts = existsSync2(layoutsDir) ? readdirSync2(layoutsDir).filter((f) => f.endsWith(".alpine")).map((f) => f.replace(/\.alpine$/, "")) : [];
468
+ let loadingHtml;
469
+ if (!options.islands && clientNav && existsSync2(join2(options.root, "pages", "loading.alpine"))) {
470
+ const l = await ssrLoad("/pages/loading.alpine");
471
+ loadingHtml = renderFragment(l.template, {}, l.scopeId, registry);
472
+ }
466
473
  const render = options.islands ? renderIslandsPage : renderPage;
467
474
  const html = await render({
468
475
  loadModule: (id) => ssrLoad(id),
@@ -476,6 +483,8 @@ async function startDevServer(options) {
476
483
  layouts,
477
484
  runtimeConfig,
478
485
  publicConfig,
486
+ clientNav,
487
+ loadingHtml,
479
488
  locals: event.context.apexLocals ?? {},
480
489
  errorPageId: existsSync2(join2(options.root, "pages", "error.alpine")) ? "/pages/error.alpine" : void 0,
481
490
  transformHtml: (u, doc) => vite.transformIndexHtml(u, doc)
@@ -11,7 +11,7 @@ import {
11
11
  matchRoute,
12
12
  renderIslandsPage,
13
13
  renderPage
14
- } from "./chunk-EVFABT7B.js";
14
+ } from "./chunk-S3HCE23H.js";
15
15
  import "./chunk-PMLGY6Z3.js";
16
16
 
17
17
  // src/commands/start.ts
@@ -133,6 +133,8 @@ async function startProdServer(options) {
133
133
  clientCss: route?.clientCss,
134
134
  runtimeConfig,
135
135
  publicConfig,
136
+ clientNav: manifest.clientNav !== false,
137
+ loadingHtml: manifest.loadingHtml,
136
138
  locals: event.context.apexLocals ?? {},
137
139
  errorPageId
138
140
  });
@@ -1,6 +1,8 @@
1
1
  import {
2
- offerExtension
3
- } from "./chunk-P6KQSPAV.js";
2
+ cmpVersion,
3
+ offerExtension,
4
+ promptYesNo
5
+ } from "./chunk-TWAGSGFN.js";
4
6
  import {
5
7
  VERSION,
6
8
  banner,
@@ -8,12 +10,69 @@ import {
8
10
  } from "./chunk-QIXJSQLW.js";
9
11
 
10
12
  // src/commands/upgrade.ts
11
- import { spawnSync } from "child_process";
13
+ import { spawnSync as spawnSync2 } from "child_process";
12
14
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
13
- import { basename, dirname, join, relative, resolve } from "path";
14
- import { fileURLToPath } from "url";
15
+ import { basename, dirname, join, relative, resolve as resolve2 } from "path";
16
+ import { fileURLToPath as fileURLToPath2 } from "url";
15
17
  import { defineCommand } from "citty";
16
- var TEMPLATE_DIR = fileURLToPath(new URL("../templates/default", import.meta.url));
18
+
19
+ // src/selfUpdate.ts
20
+ import { spawnSync } from "child_process";
21
+ import { resolve, sep } from "path";
22
+ import { fileURLToPath } from "url";
23
+ var PKG = "@apex-stack/core";
24
+ var WIN = process.platform === "win32";
25
+ function latestPublished() {
26
+ try {
27
+ const r = spawnSync("npm", ["view", PKG, "version"], {
28
+ encoding: "utf8",
29
+ timeout: 4e3,
30
+ shell: WIN
31
+ });
32
+ if (r.status !== 0 || !r.stdout) return null;
33
+ const v = r.stdout.trim();
34
+ return /^\d+\.\d+\.\d+/.test(v) ? v : null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function isGlobalInstall() {
40
+ const self = fileURLToPath(import.meta.url);
41
+ const localRoot = resolve(process.cwd(), "node_modules") + sep;
42
+ return !self.startsWith(localRoot);
43
+ }
44
+ function installGlobalLatest() {
45
+ return spawnSync("npm", ["install", "-g", `${PKG}@latest`], { stdio: "inherit", shell: WIN }).status === 0;
46
+ }
47
+ async function maybeSelfUpdate(reexecArgv, choice) {
48
+ if (choice === false) return false;
49
+ if (!isGlobalInstall()) return false;
50
+ const latest = latestPublished();
51
+ if (!latest || cmpVersion(latest, VERSION) <= 0) return false;
52
+ const log = console.log;
53
+ log(
54
+ `
55
+ ${color.cyan("\u2191")} A newer Apex CLI is available: ${color.gray(VERSION)} \u2192 ${color.green(latest)}`
56
+ );
57
+ const yes = choice ?? (process.stdin.isTTY ? await promptYesNo(` Update the global CLI now?`) : false);
58
+ if (!yes) {
59
+ log(` ${color.gray("Skipped. Update later with")} ${color.cyan(`npm i -g ${PKG}@latest`)}
60
+ `);
61
+ return false;
62
+ }
63
+ if (!installGlobalLatest()) {
64
+ log(` ${color.red("\u2717")} Global update failed \u2014 run ${color.cyan(`npm i -g ${PKG}@latest`)}
65
+ `);
66
+ return false;
67
+ }
68
+ log(` ${color.green("\u2713")} CLI updated to ${latest} \u2014 re-running on the new engine\u2026
69
+ `);
70
+ const r = spawnSync("apex", reexecArgv, { stdio: "inherit", shell: WIN });
71
+ process.exit(r.status ?? 0);
72
+ }
73
+
74
+ // src/commands/upgrade.ts
75
+ var TEMPLATE_DIR = fileURLToPath2(new URL("../templates/default", import.meta.url));
17
76
  var PROTECTED = /* @__PURE__ */ new Set(["package.json"]);
18
77
  function projectName(root) {
19
78
  try {
@@ -79,10 +138,14 @@ var upgradeCommand = defineCommand({
79
138
  vscode: {
80
139
  type: "boolean",
81
140
  description: "Install the Apex VS Code extension (skip the prompt)"
141
+ },
142
+ self: {
143
+ type: "boolean",
144
+ description: "Update the global Apex CLI first if a newer one is published (default: ask)"
82
145
  }
83
146
  },
84
147
  async run({ args }) {
85
- const root = resolve(process.cwd(), String(args.root));
148
+ const root = resolve2(process.cwd(), String(args.root));
86
149
  const log = console.log;
87
150
  if (!existsSync(join(root, "package.json"))) {
88
151
  console.error(`
@@ -90,6 +153,15 @@ var upgradeCommand = defineCommand({
90
153
  `);
91
154
  process.exit(1);
92
155
  }
156
+ const reexecArgv = [
157
+ "upgrade",
158
+ String(args.root),
159
+ ...args.force ? ["--force"] : [],
160
+ ...args.install === false ? ["--no-install"] : [],
161
+ ...args.vscode === true ? ["--vscode"] : args.vscode === false ? ["--no-vscode"] : [],
162
+ "--no-self"
163
+ ];
164
+ if (await maybeSelfUpdate(reexecArgv, args.self)) return;
93
165
  const name = projectName(root);
94
166
  process.stdout.write(banner());
95
167
  const added = [];
@@ -141,7 +213,7 @@ var upgradeCommand = defineCommand({
141
213
  const pm = detectPm();
142
214
  log(`
143
215
  ${color.gray(`Installing with ${pm}\u2026`)}`);
144
- const ok = spawnSync(pm, ["install"], {
216
+ const ok = spawnSync2(pm, ["install"], {
145
217
  cwd: root,
146
218
  stdio: "inherit",
147
219
  shell: process.platform === "win32"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apex-stack/core",
3
- "version": "0.7.7",
3
+ "version": "0.8.0",
4
4
  "description": "The full-stack meta-framework for Alpine.js — CLI and runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,8 +47,8 @@
47
47
  "h3": "^1.13.0",
48
48
  "vite": "^6.0.7",
49
49
  "zod": "^4.4.3",
50
- "@apex-stack/kit": "0.3.0",
51
- "@apex-stack/vite": "0.1.7",
50
+ "@apex-stack/kit": "0.4.0",
51
+ "@apex-stack/vite": "0.1.8",
52
52
  "@apex-stack/theme": "0.3.0"
53
53
  },
54
54
  "peerDependencies": {
@@ -0,0 +1,13 @@
1
+ <!--
2
+ pages/loading.alpine — the client-side navigation loading boundary.
3
+
4
+ Apex renders this once and shows it in the page region during a navigation that
5
+ takes longer than a moment (e.g. a slow loader). It's plain, presentational
6
+ HTML — no loader, no interactivity needed.
7
+ -->
8
+ <template x-data>
9
+ <div class="flex flex-col items-center justify-center gap-3 py-24 text-center">
10
+ <div class="size-8 animate-spin rounded-full border-2 border-outline border-t-primary dark:border-outline-dark dark:border-t-primary-dark"></div>
11
+ <p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">Loading…</p>
12
+ </div>
13
+ </template>