@moku-labs/web 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2028,11 +2028,18 @@ type Config = {
2028
2028
  *
2029
2029
  * @example
2030
2030
  * ```ts
2031
- * { articles: new Map() }
2031
+ * { articles: new Map(), loadedAll: null }
2032
2032
  * ```
2033
2033
  */
2034
2034
  type State = {
2035
2035
  /** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
2036
+ /**
2037
+ * Memoized full `loadAll()` result, or `null` when not yet loaded / invalidated. List-route
2038
+ * loaders call `loadAll()` once PER PAGE, so without this every page re-reads + re-renders
2039
+ * every article (the dev-loop killer). The memo makes repeated calls O(1); `invalidate()`
2040
+ * clears it so a dev rebuild reloads (re-resolving only the changed slugs). Starts `null`.
2041
+ */
2042
+ loadedAll: Map<string, Article[]> | null;
2036
2043
  };
2037
2044
  /**
2038
2045
  * Notification-only events emitted by the content plugin.
@@ -2082,11 +2089,13 @@ type ContentApiContext = {
2082
2089
  */
2083
2090
  type LoadAllOptions = {
2084
2091
  /**
2085
- * Reuse already-cached articles for slugs NOT dropped by a preceding `invalidate()`,
2086
- * re-reading + re-rendering (Shiki) ONLY the invalidated (dirty) articles. The
2087
- * post-sort `contentId` ordinals are always recomputed across the full set, so order +
2088
- * ids match a full load. Default `false` (a full load that re-reads every article).
2089
- * Used by dev incremental rebuilds; a fresh process / production build never reuses.
2092
+ * Reuse the per-build memo + per-slug cache (re-resolving only slugs a preceding
2093
+ * `invalidate()` dropped). Default `true` this is what keeps repeated `loadAll()` calls
2094
+ * (a list route's loader runs once per page) cheap, and makes a dev rebuild re-render only
2095
+ * changed articles. Set `false` to force a FRESH full reload (cold build / an
2096
+ * unclassifiable change), which re-reads + re-renders every article and rebuilds the memo.
2097
+ * The post-sort `contentId` ordinals are always recomputed across the full set, so order +
2098
+ * ids match a full load either way.
2090
2099
  */
2091
2100
  reuse?: boolean;
2092
2101
  };
@@ -2100,10 +2109,13 @@ type LoadAllOptions = {
2100
2109
  */
2101
2110
  type Api = {
2102
2111
  /**
2103
- * Load every article across every active locale, returning a locale-keyed
2104
- * map of date-descending Article arrays. Emits content:ready.
2112
+ * Load every article across every active locale, returning a locale-keyed map of
2113
+ * date-descending Article arrays. Emits content:ready (once per actual load). Cache-first
2114
+ * + memoized: repeated calls (e.g. a list route's loader on every page) return the SAME
2115
+ * cached result with no re-read — so treat the result as READ-ONLY (do not sort/mutate it
2116
+ * in place; slice/copy first). Pass `{ reuse: false }` to force a fresh full reload.
2105
2117
  *
2106
- * @param options - Optional load behaviour ({@link LoadAllOptions}); omit for a full load.
2118
+ * @param options - Optional load behaviour ({@link LoadAllOptions}); default reuses the cache.
2107
2119
  */
2108
2120
  loadAll(options?: LoadAllOptions): Promise<Map<string, Article[]>>;
2109
2121
  /**
package/dist/browser.mjs CHANGED
@@ -4409,12 +4409,15 @@ function createContentApi(ctx) {
4409
4409
  * Load every article across every active locale (locale fallback, production
4410
4410
  * draft exclusion, date sort, `contentId` after sort), cache them, emit `content:ready`.
4411
4411
  *
4412
- * With `{ reuse: true }` (dev incremental rebuild) cached articles are reused for
4413
- * every slug a preceding `invalidate()` did not drop, so only the dirty articles
4414
- * re-read + re-run the Markdown/Shiki pipeline; the `contentId` ordinals are still
4415
- * recomputed across the FULL sorted set, so ids + order match a full load.
4412
+ * Cache-first by default: repeated calls return the per-build memo (list-route loaders
4413
+ * call this once PER PAGE without the memo every page would re-read + re-render every
4414
+ * article, the dev-loop killer), and a rebuild after `invalidate()` re-resolves only the
4415
+ * dropped slugs while reusing the cached articles for the rest (`contentId` ordinals are
4416
+ * still recomputed across the FULL sorted set, so ids + order match a full load). Pass
4417
+ * `{ reuse: false }` to force a FRESH full reload (cold build / an unclassifiable change
4418
+ * where the caller cannot pinpoint what changed) — this bypasses the memo + per-slug cache.
4416
4419
  *
4417
- * @param options - Optional load behaviour (`reuse`); omit for a full load.
4420
+ * @param options - Optional load behaviour (`reuse`, default `true`).
4418
4421
  * @returns A locale-keyed map of date-descending articles.
4419
4422
  * @example
4420
4423
  * ```ts
@@ -4422,7 +4425,9 @@ function createContentApi(ctx) {
4422
4425
  * ```
4423
4426
  */
4424
4427
  async loadAll(options) {
4425
- const reuse = options?.reuse === true;
4428
+ const reuse = options?.reuse !== false;
4429
+ const memo = ctx.state.loadedAll;
4430
+ if (reuse && memo !== null) return memo;
4426
4431
  const slugs = await ctx.provider.slugs();
4427
4432
  const locales = ctx.locales();
4428
4433
  const result = /* @__PURE__ */ new Map();
@@ -4440,6 +4445,7 @@ function createContentApi(ctx) {
4440
4445
  result.set(locale, present);
4441
4446
  total += present.length;
4442
4447
  }
4448
+ ctx.state.loadedAll = result;
4443
4449
  ctx.emit("content:ready", {
4444
4450
  locales,
4445
4451
  articleCount: total
@@ -4503,6 +4509,7 @@ function createContentApi(ctx) {
4503
4509
  if (slug === void 0) continue;
4504
4510
  for (const cache of ctx.state.articles.values()) cache.delete(slug);
4505
4511
  }
4512
+ if (accepted.length > 0) ctx.state.loadedAll = null;
4506
4513
  ctx.emit("content:invalidated", { paths: accepted });
4507
4514
  },
4508
4515
  /**
@@ -4572,14 +4579,17 @@ const contentEvents = (register) => ({
4572
4579
  * @param _ctx - Minimal context with global and config.
4573
4580
  * @param _ctx.global - Global plugin registry.
4574
4581
  * @param _ctx.config - Resolved plugin configuration.
4575
- * @returns Fresh content shell state: an empty article cache.
4582
+ * @returns Fresh content shell state: an empty article cache + an empty loadAll memo.
4576
4583
  * @example
4577
4584
  * ```ts
4578
4585
  * const state = createContentState({ global: {}, config: { providers: [] } });
4579
4586
  * ```
4580
4587
  */
4581
4588
  function createContentState(_ctx) {
4582
- return { articles: /* @__PURE__ */ new Map() };
4589
+ return {
4590
+ articles: /* @__PURE__ */ new Map(),
4591
+ loadedAll: null
4592
+ };
4583
4593
  }
4584
4594
  //#endregion
4585
4595
  //#region src/plugins/content/validate.ts
package/dist/index.cjs CHANGED
@@ -1349,12 +1349,15 @@ function createContentApi(ctx) {
1349
1349
  * Load every article across every active locale (locale fallback, production
1350
1350
  * draft exclusion, date sort, `contentId` after sort), cache them, emit `content:ready`.
1351
1351
  *
1352
- * With `{ reuse: true }` (dev incremental rebuild) cached articles are reused for
1353
- * every slug a preceding `invalidate()` did not drop, so only the dirty articles
1354
- * re-read + re-run the Markdown/Shiki pipeline; the `contentId` ordinals are still
1355
- * recomputed across the FULL sorted set, so ids + order match a full load.
1352
+ * Cache-first by default: repeated calls return the per-build memo (list-route loaders
1353
+ * call this once PER PAGE without the memo every page would re-read + re-render every
1354
+ * article, the dev-loop killer), and a rebuild after `invalidate()` re-resolves only the
1355
+ * dropped slugs while reusing the cached articles for the rest (`contentId` ordinals are
1356
+ * still recomputed across the FULL sorted set, so ids + order match a full load). Pass
1357
+ * `{ reuse: false }` to force a FRESH full reload (cold build / an unclassifiable change
1358
+ * where the caller cannot pinpoint what changed) — this bypasses the memo + per-slug cache.
1356
1359
  *
1357
- * @param options - Optional load behaviour (`reuse`); omit for a full load.
1360
+ * @param options - Optional load behaviour (`reuse`, default `true`).
1358
1361
  * @returns A locale-keyed map of date-descending articles.
1359
1362
  * @example
1360
1363
  * ```ts
@@ -1362,7 +1365,9 @@ function createContentApi(ctx) {
1362
1365
  * ```
1363
1366
  */
1364
1367
  async loadAll(options) {
1365
- const reuse = options?.reuse === true;
1368
+ const reuse = options?.reuse !== false;
1369
+ const memo = ctx.state.loadedAll;
1370
+ if (reuse && memo !== null) return memo;
1366
1371
  const slugs = await ctx.provider.slugs();
1367
1372
  const locales = ctx.locales();
1368
1373
  const result = /* @__PURE__ */ new Map();
@@ -1380,6 +1385,7 @@ function createContentApi(ctx) {
1380
1385
  result.set(locale, present);
1381
1386
  total += present.length;
1382
1387
  }
1388
+ ctx.state.loadedAll = result;
1383
1389
  ctx.emit("content:ready", {
1384
1390
  locales,
1385
1391
  articleCount: total
@@ -1443,6 +1449,7 @@ function createContentApi(ctx) {
1443
1449
  if (slug === void 0) continue;
1444
1450
  for (const cache of ctx.state.articles.values()) cache.delete(slug);
1445
1451
  }
1452
+ if (accepted.length > 0) ctx.state.loadedAll = null;
1446
1453
  ctx.emit("content:invalidated", { paths: accepted });
1447
1454
  },
1448
1455
  /**
@@ -1512,14 +1519,17 @@ const contentEvents = (register) => ({
1512
1519
  * @param _ctx - Minimal context with global and config.
1513
1520
  * @param _ctx.global - Global plugin registry.
1514
1521
  * @param _ctx.config - Resolved plugin configuration.
1515
- * @returns Fresh content shell state: an empty article cache.
1522
+ * @returns Fresh content shell state: an empty article cache + an empty loadAll memo.
1516
1523
  * @example
1517
1524
  * ```ts
1518
1525
  * const state = createContentState({ global: {}, config: { providers: [] } });
1519
1526
  * ```
1520
1527
  */
1521
1528
  function createContentState(_ctx) {
1522
- return { articles: /* @__PURE__ */ new Map() };
1529
+ return {
1530
+ articles: /* @__PURE__ */ new Map(),
1531
+ loadedAll: null
1532
+ };
1523
1533
  }
1524
1534
  //#endregion
1525
1535
  //#region src/plugins/content/validate.ts
@@ -7515,23 +7525,23 @@ function createDevHandler(ctx, hub) {
7515
7525
  }
7516
7526
  /**
7517
7527
  * Build the per-run {@link BuildRunOverrides} for a dev build from the session feature
7518
- * opt-ins: minification is always off in dev (no benefit, slower), and each expensive
7519
- * output stays off unless its flag re-enables it (`ogImage: false` disables OG generation
7520
- * regardless of the persisted config). The persisted plugin config is never mutated — the
7521
- * overrides apply to the dev run only.
7528
+ * opt-ins: minification is always off in dev (no benefit, slower), and each expensive,
7529
+ * NON-navigational output stays off unless its flag re-enables it (`ogImage: false`
7530
+ * disables OG generation regardless of the persisted config). Locale-redirects are NOT
7531
+ * overridden they produce navigable pages (the bare `/` → `/{defaultLocale}/` redirect),
7532
+ * so they follow the app's own config. The persisted plugin config is never mutated.
7522
7533
  *
7523
7534
  * @param features - The resolved per-session dev feature opt-ins.
7524
7535
  * @returns The config overrides merged into the dev build run.
7525
7536
  * @example
7526
- * devBuildOverrides({ og: false, sitemap: false, feeds: false, localeRedirects: false });
7537
+ * devBuildOverrides({ og: false, sitemap: false, feeds: false });
7527
7538
  */
7528
7539
  function devBuildOverrides(features) {
7529
7540
  return {
7530
7541
  minify: false,
7531
7542
  ...features.feeds ? {} : { feeds: false },
7532
7543
  ...features.sitemap ? {} : { sitemap: false },
7533
- ...features.og ? {} : { ogImage: false },
7534
- ...features.localeRedirects ? {} : { localeRedirects: false }
7544
+ ...features.og ? {} : { ogImage: false }
7535
7545
  };
7536
7546
  }
7537
7547
  /**
@@ -7891,9 +7901,10 @@ function createApi$1(ctx) {
7891
7901
  /**
7892
7902
  * Dev loop: build once, serve `dist/` in-process (live-reload injected), watch
7893
7903
  * `watchDirs`, debounced + incremental rebuild + reload. For a fast rebuild the dev
7894
- * build disables minification + expensive, preview-irrelevant outputs (feeds /
7895
- * sitemap / og-images / locale-redirects); pass `og`/`sitemap`/`feeds`/
7896
- * `localeRedirects` to re-enable any of them for the session. Resolves on SIGINT/SIGTERM.
7904
+ * build disables minification + expensive, NON-navigational outputs (feeds / sitemap /
7905
+ * og-images); pass `og`/`sitemap`/`feeds` to re-enable any of them for the session.
7906
+ * Locale-redirects are always built per the app config (they emit the navigable bare-path
7907
+ * `/` → `/{defaultLocale}/` redirect). Resolves on SIGINT/SIGTERM.
7897
7908
  *
7898
7909
  * @param options - Optional port override + per-session dev feature opt-ins.
7899
7910
  * @returns Resolves once the server has been torn down.
@@ -7906,8 +7917,7 @@ function createApi$1(ctx) {
7906
7917
  return runDevServer(ctx, port, {
7907
7918
  og: options.og ?? false,
7908
7919
  sitemap: options.sitemap ?? false,
7909
- feeds: options.feeds ?? false,
7910
- localeRedirects: options.localeRedirects ?? false
7920
+ feeds: options.feeds ?? false
7911
7921
  });
7912
7922
  },
7913
7923
  /**
package/dist/index.d.cts CHANGED
@@ -2460,8 +2460,7 @@ type ServeOptions = {
2460
2460
  */
2461
2461
  og?: boolean; /** Re-enable `sitemap.xml` + `robots.txt` for this dev session (maps to `--sitemap`). Defaults to `false`. */
2462
2462
  sitemap?: boolean; /** Re-enable RSS/Atom/JSON feeds for this dev session (maps to `--feeds`). Defaults to `false`. */
2463
- feeds?: boolean; /** Re-enable i18n locale-redirect pages for this dev session (maps to `--locale-redirects`). Defaults to `false`. */
2464
- localeRedirects?: boolean;
2463
+ feeds?: boolean;
2465
2464
  };
2466
2465
  /**
2467
2466
  * Options for `cli.preview()`.
@@ -2736,11 +2735,18 @@ type Config = {
2736
2735
  *
2737
2736
  * @example
2738
2737
  * ```ts
2739
- * { articles: new Map() }
2738
+ * { articles: new Map(), loadedAll: null }
2740
2739
  * ```
2741
2740
  */
2742
2741
  type State = {
2743
2742
  /** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
2743
+ /**
2744
+ * Memoized full `loadAll()` result, or `null` when not yet loaded / invalidated. List-route
2745
+ * loaders call `loadAll()` once PER PAGE, so without this every page re-reads + re-renders
2746
+ * every article (the dev-loop killer). The memo makes repeated calls O(1); `invalidate()`
2747
+ * clears it so a dev rebuild reloads (re-resolving only the changed slugs). Starts `null`.
2748
+ */
2749
+ loadedAll: Map<string, Article[]> | null;
2744
2750
  };
2745
2751
  /**
2746
2752
  * Notification-only events emitted by the content plugin.
@@ -2790,11 +2796,13 @@ type ContentApiContext = {
2790
2796
  */
2791
2797
  type LoadAllOptions = {
2792
2798
  /**
2793
- * Reuse already-cached articles for slugs NOT dropped by a preceding `invalidate()`,
2794
- * re-reading + re-rendering (Shiki) ONLY the invalidated (dirty) articles. The
2795
- * post-sort `contentId` ordinals are always recomputed across the full set, so order +
2796
- * ids match a full load. Default `false` (a full load that re-reads every article).
2797
- * Used by dev incremental rebuilds; a fresh process / production build never reuses.
2799
+ * Reuse the per-build memo + per-slug cache (re-resolving only slugs a preceding
2800
+ * `invalidate()` dropped). Default `true` this is what keeps repeated `loadAll()` calls
2801
+ * (a list route's loader runs once per page) cheap, and makes a dev rebuild re-render only
2802
+ * changed articles. Set `false` to force a FRESH full reload (cold build / an
2803
+ * unclassifiable change), which re-reads + re-renders every article and rebuilds the memo.
2804
+ * The post-sort `contentId` ordinals are always recomputed across the full set, so order +
2805
+ * ids match a full load either way.
2798
2806
  */
2799
2807
  reuse?: boolean;
2800
2808
  };
@@ -2808,10 +2816,13 @@ type LoadAllOptions = {
2808
2816
  */
2809
2817
  type Api = {
2810
2818
  /**
2811
- * Load every article across every active locale, returning a locale-keyed
2812
- * map of date-descending Article arrays. Emits content:ready.
2819
+ * Load every article across every active locale, returning a locale-keyed map of
2820
+ * date-descending Article arrays. Emits content:ready (once per actual load). Cache-first
2821
+ * + memoized: repeated calls (e.g. a list route's loader on every page) return the SAME
2822
+ * cached result with no re-read — so treat the result as READ-ONLY (do not sort/mutate it
2823
+ * in place; slice/copy first). Pass `{ reuse: false }` to force a fresh full reload.
2813
2824
  *
2814
- * @param options - Optional load behaviour ({@link LoadAllOptions}); omit for a full load.
2825
+ * @param options - Optional load behaviour ({@link LoadAllOptions}); default reuses the cache.
2815
2826
  */
2816
2827
  loadAll(options?: LoadAllOptions): Promise<Map<string, Article[]>>;
2817
2828
  /**
package/dist/index.d.mts CHANGED
@@ -2460,8 +2460,7 @@ type ServeOptions = {
2460
2460
  */
2461
2461
  og?: boolean; /** Re-enable `sitemap.xml` + `robots.txt` for this dev session (maps to `--sitemap`). Defaults to `false`. */
2462
2462
  sitemap?: boolean; /** Re-enable RSS/Atom/JSON feeds for this dev session (maps to `--feeds`). Defaults to `false`. */
2463
- feeds?: boolean; /** Re-enable i18n locale-redirect pages for this dev session (maps to `--locale-redirects`). Defaults to `false`. */
2464
- localeRedirects?: boolean;
2463
+ feeds?: boolean;
2465
2464
  };
2466
2465
  /**
2467
2466
  * Options for `cli.preview()`.
@@ -2736,11 +2735,18 @@ type Config = {
2736
2735
  *
2737
2736
  * @example
2738
2737
  * ```ts
2739
- * { articles: new Map() }
2738
+ * { articles: new Map(), loadedAll: null }
2740
2739
  * ```
2741
2740
  */
2742
2741
  type State = {
2743
2742
  /** Article cache keyed locale -> (slug -> Article). Starts empty. */articles: Map<string, Map<string, Article>>;
2743
+ /**
2744
+ * Memoized full `loadAll()` result, or `null` when not yet loaded / invalidated. List-route
2745
+ * loaders call `loadAll()` once PER PAGE, so without this every page re-reads + re-renders
2746
+ * every article (the dev-loop killer). The memo makes repeated calls O(1); `invalidate()`
2747
+ * clears it so a dev rebuild reloads (re-resolving only the changed slugs). Starts `null`.
2748
+ */
2749
+ loadedAll: Map<string, Article[]> | null;
2744
2750
  };
2745
2751
  /**
2746
2752
  * Notification-only events emitted by the content plugin.
@@ -2790,11 +2796,13 @@ type ContentApiContext = {
2790
2796
  */
2791
2797
  type LoadAllOptions = {
2792
2798
  /**
2793
- * Reuse already-cached articles for slugs NOT dropped by a preceding `invalidate()`,
2794
- * re-reading + re-rendering (Shiki) ONLY the invalidated (dirty) articles. The
2795
- * post-sort `contentId` ordinals are always recomputed across the full set, so order +
2796
- * ids match a full load. Default `false` (a full load that re-reads every article).
2797
- * Used by dev incremental rebuilds; a fresh process / production build never reuses.
2799
+ * Reuse the per-build memo + per-slug cache (re-resolving only slugs a preceding
2800
+ * `invalidate()` dropped). Default `true` this is what keeps repeated `loadAll()` calls
2801
+ * (a list route's loader runs once per page) cheap, and makes a dev rebuild re-render only
2802
+ * changed articles. Set `false` to force a FRESH full reload (cold build / an
2803
+ * unclassifiable change), which re-reads + re-renders every article and rebuilds the memo.
2804
+ * The post-sort `contentId` ordinals are always recomputed across the full set, so order +
2805
+ * ids match a full load either way.
2798
2806
  */
2799
2807
  reuse?: boolean;
2800
2808
  };
@@ -2808,10 +2816,13 @@ type LoadAllOptions = {
2808
2816
  */
2809
2817
  type Api = {
2810
2818
  /**
2811
- * Load every article across every active locale, returning a locale-keyed
2812
- * map of date-descending Article arrays. Emits content:ready.
2819
+ * Load every article across every active locale, returning a locale-keyed map of
2820
+ * date-descending Article arrays. Emits content:ready (once per actual load). Cache-first
2821
+ * + memoized: repeated calls (e.g. a list route's loader on every page) return the SAME
2822
+ * cached result with no re-read — so treat the result as READ-ONLY (do not sort/mutate it
2823
+ * in place; slice/copy first). Pass `{ reuse: false }` to force a fresh full reload.
2813
2824
  *
2814
- * @param options - Optional load behaviour ({@link LoadAllOptions}); omit for a full load.
2825
+ * @param options - Optional load behaviour ({@link LoadAllOptions}); default reuses the cache.
2815
2826
  */
2816
2827
  loadAll(options?: LoadAllOptions): Promise<Map<string, Article[]>>;
2817
2828
  /**
package/dist/index.mjs CHANGED
@@ -1336,12 +1336,15 @@ function createContentApi(ctx) {
1336
1336
  * Load every article across every active locale (locale fallback, production
1337
1337
  * draft exclusion, date sort, `contentId` after sort), cache them, emit `content:ready`.
1338
1338
  *
1339
- * With `{ reuse: true }` (dev incremental rebuild) cached articles are reused for
1340
- * every slug a preceding `invalidate()` did not drop, so only the dirty articles
1341
- * re-read + re-run the Markdown/Shiki pipeline; the `contentId` ordinals are still
1342
- * recomputed across the FULL sorted set, so ids + order match a full load.
1339
+ * Cache-first by default: repeated calls return the per-build memo (list-route loaders
1340
+ * call this once PER PAGE without the memo every page would re-read + re-render every
1341
+ * article, the dev-loop killer), and a rebuild after `invalidate()` re-resolves only the
1342
+ * dropped slugs while reusing the cached articles for the rest (`contentId` ordinals are
1343
+ * still recomputed across the FULL sorted set, so ids + order match a full load). Pass
1344
+ * `{ reuse: false }` to force a FRESH full reload (cold build / an unclassifiable change
1345
+ * where the caller cannot pinpoint what changed) — this bypasses the memo + per-slug cache.
1343
1346
  *
1344
- * @param options - Optional load behaviour (`reuse`); omit for a full load.
1347
+ * @param options - Optional load behaviour (`reuse`, default `true`).
1345
1348
  * @returns A locale-keyed map of date-descending articles.
1346
1349
  * @example
1347
1350
  * ```ts
@@ -1349,7 +1352,9 @@ function createContentApi(ctx) {
1349
1352
  * ```
1350
1353
  */
1351
1354
  async loadAll(options) {
1352
- const reuse = options?.reuse === true;
1355
+ const reuse = options?.reuse !== false;
1356
+ const memo = ctx.state.loadedAll;
1357
+ if (reuse && memo !== null) return memo;
1353
1358
  const slugs = await ctx.provider.slugs();
1354
1359
  const locales = ctx.locales();
1355
1360
  const result = /* @__PURE__ */ new Map();
@@ -1367,6 +1372,7 @@ function createContentApi(ctx) {
1367
1372
  result.set(locale, present);
1368
1373
  total += present.length;
1369
1374
  }
1375
+ ctx.state.loadedAll = result;
1370
1376
  ctx.emit("content:ready", {
1371
1377
  locales,
1372
1378
  articleCount: total
@@ -1430,6 +1436,7 @@ function createContentApi(ctx) {
1430
1436
  if (slug === void 0) continue;
1431
1437
  for (const cache of ctx.state.articles.values()) cache.delete(slug);
1432
1438
  }
1439
+ if (accepted.length > 0) ctx.state.loadedAll = null;
1433
1440
  ctx.emit("content:invalidated", { paths: accepted });
1434
1441
  },
1435
1442
  /**
@@ -1499,14 +1506,17 @@ const contentEvents = (register) => ({
1499
1506
  * @param _ctx - Minimal context with global and config.
1500
1507
  * @param _ctx.global - Global plugin registry.
1501
1508
  * @param _ctx.config - Resolved plugin configuration.
1502
- * @returns Fresh content shell state: an empty article cache.
1509
+ * @returns Fresh content shell state: an empty article cache + an empty loadAll memo.
1503
1510
  * @example
1504
1511
  * ```ts
1505
1512
  * const state = createContentState({ global: {}, config: { providers: [] } });
1506
1513
  * ```
1507
1514
  */
1508
1515
  function createContentState(_ctx) {
1509
- return { articles: /* @__PURE__ */ new Map() };
1516
+ return {
1517
+ articles: /* @__PURE__ */ new Map(),
1518
+ loadedAll: null
1519
+ };
1510
1520
  }
1511
1521
  //#endregion
1512
1522
  //#region src/plugins/content/validate.ts
@@ -7502,23 +7512,23 @@ function createDevHandler(ctx, hub) {
7502
7512
  }
7503
7513
  /**
7504
7514
  * Build the per-run {@link BuildRunOverrides} for a dev build from the session feature
7505
- * opt-ins: minification is always off in dev (no benefit, slower), and each expensive
7506
- * output stays off unless its flag re-enables it (`ogImage: false` disables OG generation
7507
- * regardless of the persisted config). The persisted plugin config is never mutated — the
7508
- * overrides apply to the dev run only.
7515
+ * opt-ins: minification is always off in dev (no benefit, slower), and each expensive,
7516
+ * NON-navigational output stays off unless its flag re-enables it (`ogImage: false`
7517
+ * disables OG generation regardless of the persisted config). Locale-redirects are NOT
7518
+ * overridden they produce navigable pages (the bare `/` → `/{defaultLocale}/` redirect),
7519
+ * so they follow the app's own config. The persisted plugin config is never mutated.
7509
7520
  *
7510
7521
  * @param features - The resolved per-session dev feature opt-ins.
7511
7522
  * @returns The config overrides merged into the dev build run.
7512
7523
  * @example
7513
- * devBuildOverrides({ og: false, sitemap: false, feeds: false, localeRedirects: false });
7524
+ * devBuildOverrides({ og: false, sitemap: false, feeds: false });
7514
7525
  */
7515
7526
  function devBuildOverrides(features) {
7516
7527
  return {
7517
7528
  minify: false,
7518
7529
  ...features.feeds ? {} : { feeds: false },
7519
7530
  ...features.sitemap ? {} : { sitemap: false },
7520
- ...features.og ? {} : { ogImage: false },
7521
- ...features.localeRedirects ? {} : { localeRedirects: false }
7531
+ ...features.og ? {} : { ogImage: false }
7522
7532
  };
7523
7533
  }
7524
7534
  /**
@@ -7878,9 +7888,10 @@ function createApi$1(ctx) {
7878
7888
  /**
7879
7889
  * Dev loop: build once, serve `dist/` in-process (live-reload injected), watch
7880
7890
  * `watchDirs`, debounced + incremental rebuild + reload. For a fast rebuild the dev
7881
- * build disables minification + expensive, preview-irrelevant outputs (feeds /
7882
- * sitemap / og-images / locale-redirects); pass `og`/`sitemap`/`feeds`/
7883
- * `localeRedirects` to re-enable any of them for the session. Resolves on SIGINT/SIGTERM.
7891
+ * build disables minification + expensive, NON-navigational outputs (feeds / sitemap /
7892
+ * og-images); pass `og`/`sitemap`/`feeds` to re-enable any of them for the session.
7893
+ * Locale-redirects are always built per the app config (they emit the navigable bare-path
7894
+ * `/` → `/{defaultLocale}/` redirect). Resolves on SIGINT/SIGTERM.
7884
7895
  *
7885
7896
  * @param options - Optional port override + per-session dev feature opt-ins.
7886
7897
  * @returns Resolves once the server has been torn down.
@@ -7893,8 +7904,7 @@ function createApi$1(ctx) {
7893
7904
  return runDevServer(ctx, port, {
7894
7905
  og: options.og ?? false,
7895
7906
  sitemap: options.sitemap ?? false,
7896
- feeds: options.feeds ?? false,
7897
- localeRedirects: options.localeRedirects ?? false
7907
+ feeds: options.feeds ?? false
7898
7908
  });
7899
7909
  },
7900
7910
  /**
package/package.json CHANGED
@@ -58,7 +58,7 @@
58
58
  "bun": ">=1.3.14"
59
59
  },
60
60
  "dependencies": {
61
- "@moku-labs/core": "0.1.0-alpha.6",
61
+ "@moku-labs/core": "0.1.1",
62
62
  "@resvg/resvg-js": "2.6.2",
63
63
  "@shikijs/rehype": "3.22.0",
64
64
  "feed": "5.2.0",
@@ -113,5 +113,5 @@
113
113
  "test:cli-e2e": "bun test src/plugins/cli/__tests__/e2e/",
114
114
  "test:coverage": "vitest run --project unit --project integration --coverage"
115
115
  },
116
- "version": "1.3.0"
116
+ "version": "1.3.1"
117
117
  }