mikser-io 7.12.0 → 7.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Built for Node.js around a strict lifecycle and a composable plugin system. Every document, asset, and template flows through the same pipeline; plugins hook in at any phase. The same engine runs a single markdown blog and a multi-language publishing platform with PDF / email / AI-augmented asset pipelines — same lifecycle, more plugins.
12
12
 
13
- It's MIT-licensed, runs on Node 18+, has zero hosted dependencies, and the entire content tree it manages is a folder of `.md` and `.yml` files you can copy, diff, and version-control. **The portability promise is the architecture, not a feature.**
13
+ It's MIT-licensed, runs on Node, has zero hosted dependencies, and the entire content tree it manages is a folder of `.md` and `.yml` files you can copy, diff, and version-control. **The portability promise is the architecture, not a feature.**
14
14
 
15
15
  > **New to mikser?** Read the [Architecture Overview](./documentation/overview.md) — one document, end-to-end walkthrough of how a file becomes a deployed page across all twenty lifecycle phases. It's the doc most projects need first.
16
16
 
@@ -84,7 +84,7 @@ Add `--mcp` to your mikser command and any MCP-speaking client — Claude Deskto
84
84
  - read every entity in the catalog
85
85
  - write new content files (markdown, layouts, configuration) — writes land on disk and the next cycle picks them up
86
86
  - render any layout for preview without touching the output folder
87
- - **surface rendered UI inline in the conversation** — `mikser_preview_ui` runs an entity through a layout that declares `mcpUi` frontmatter and returns the HTML as a UI block the host can show; an approve/reject button on that block sends the result straight back to the agent
87
+ - **surface interactive UI inline in the conversation** — you author the UI as a normal mikser layout with YAML frontmatter (`mcpUi: { mode, actions }`). The agent reads `mikser://mcp-ui/modes` to discover what UIs your project supports, calls `mikser_preview_ui` to render one against an entity, and the host displays the result as a sandboxed iframe in the chat. Buttons in the UI `postMessage` their click back as the tool result no separate UI framework, no glue code; layouts are still just layouts
88
88
  - watch every build log as it streams past
89
89
  - introspect engine state — current lifecycle phase, effective config, recent log buffer
90
90
 
@@ -103,7 +103,7 @@ When an AI agent edits ten files, the next question is: *did it do what I asked?
103
103
  - **"Did I update every article that needed it?"** — semantic search finds anything that still matches the old tone or phrasing the agent was supposed to change.
104
104
  - **"What else mentions this person, product, or topic?"** — mikser knows how content references content. "Show me every page that mentions Dick" returns the list instantly, no full-tree scan.
105
105
  - **"Did anything break?"** — if a reference points at something that no longer exists, mikser surfaces it as a warning. The build either completes cleanly or doesn't.
106
- - **"Can I see what this looks like before publishing?"** — render any single page or section on demand, no full rebuild, no staging deploy.
106
+ - **"Can I see what this looks like before publishing?"** — render any single page or section on demand, no full rebuild, no staging deploy. With an `mcpUi` layout, the agent surfaces the rendered preview *inside the chat* with approve/reject controls; one click sends the result back as the tool response.
107
107
  - **"What changed since I last looked?"** — `git diff`. The catalog is plain files, so the audit trail is the same one your engineers already use for code.
108
108
  - **"Roll back this batch?"** — `git checkout`. Atomic. No database migration to undo, no version-history-feature to learn.
109
109
 
@@ -153,7 +153,7 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
153
153
 
154
154
  ## Client SDKs
155
155
 
156
- The `api`, `vector`, and `schemas` plugins are paired with client-side SDKs so a frontend (or another Node app) can talk to a running mikser server without rolling its own `fetch` glue or type contracts. Zero dependencies, runs in browsers / Node 18+ / Deno / Bun / Workers.
156
+ The `api`, `vector`, and `schemas` plugins are paired with client-side SDKs so a frontend (or another Node app) can talk to a running mikser server without rolling its own `fetch` glue or type contracts. Zero dependencies, runs in browsers / Node / Deno / Bun / Workers.
157
157
 
158
158
  **Transport-level:**
159
159
 
@@ -190,7 +190,7 @@ For a working starter — config with a real plugin set, sample `documents/`, ex
190
190
 
191
191
  - **Lifecycle** — Processing runs through fixed phases: initialize → load → import → process → persist → render → finalize. Plugins hook into any phase.
192
192
  - **Entities** — Everything is an entity (document, file, layout, asset). Entities flow through the journal and are tracked in the catalog.
193
- - **References between entities** — Front-matter keys starting with `$` (e.g. `$author: /authors/dick`) are references. The engine projects them to plain keys (`meta.author`) for templates and SDK consumers; the schemas plugin auto-validates them; the api can `expand` them inline for one-trip graph fetches. The engine also maintains an inverse-reference index at `runtime.refs.*` (graph queries, rename cascade, live-expand subscriptions, MCP tools `mikser_refs_inbound` / `_outbound` / `_broken` / `_rename`). See [ADR-0007](./documentation/decisions/0007-references-declaration-and-expansion.md).
193
+ - **References between entities** — A front-matter key starting with `$` (e.g. `$author: /authors/dick`) points at another entity. The engine knows the whole graph: templates can follow the links, the schemas plugin checks they resolve, and a single query can pull referenced entities along inline instead of one round trip per link. See [ADR-0007](./documentation/decisions/0007-references-declaration-and-expansion.md).
194
194
  - **Plugins** — Functionality is delivered via plugins. Built-in plugins handle common sources (documents, files, layouts, assets). Custom plugins can be added to any project.
195
195
  - **Runtime Singleton** — A plain module-level object holds all global state and coordinates the lifecycle. The ES module cache guarantees every importer gets the same instance.
196
196
  - **Watch Mode** — In watch mode, file changes trigger incremental re-processing without restarting.
@@ -221,7 +221,7 @@ What you get from how this project is built:
221
221
 
222
222
  The earliest version of mikser was inspired by [DocPad](https://github.com/docpad/docpad) (Benjamin Lupton, with Michael Duane Mooring and Rob Loach). DocPad's "freeway, not a box" philosophy — files on disk, any pre-processor or template engine, plugin-by-convention extension — shaped how mikser started.
223
223
 
224
- Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/almero-digital-marketing/mikser) (last release 2022) introduced the real-time SSG model the current engine still carries forward. The redesign dropped MongoDB (the catalog lives in-process now, not in a database), modernized to Node 18+ ESM with a structured 20-phase lifecycle, added the live SSE channel that powers the framework SDKs, and replaced cluster-based rendering with an async worker pool. Same intent — content as files, real-time previews, multi-format output at scale — clearer foundations.
224
+ Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/almero-digital-marketing/mikser) (last release 2022) introduced the real-time SSG model the current engine still carries forward. The redesign dropped MongoDB (the catalog lives in-process now, not in a database), modernized to Node ESM with a structured 20-phase lifecycle, added the live SSE channel that powers the framework SDKs, and replaced cluster-based rendering with an async worker pool. Same intent — content as files, real-time previews, multi-format output at scale — clearer foundations.
225
225
 
226
226
  ## Documentation Index
227
227
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "7.12.0",
3
+ "version": "7.12.1",
4
4
  "description": "<p align=\"center\"> <img src=\"mikser-lockup-stacked.svg\" alt=\"mikser\" width=\"198\" /> </p>",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -403,7 +403,12 @@ export default ({
403
403
 
404
404
  if (data?.pages) {
405
405
  if (!_.endsWith(entity.name, entity.format)) {
406
- for (let page = 0; page < data.pages - 1; page++) {
406
+ // Loop bound is `< data.pages` (not `data.pages - 1`).
407
+ // With 4 pages and the old bound, iteration only ran
408
+ // page=0,1,2 and the 4th page was silently dropped —
409
+ // the sitemap claimed "Page X of 4" but the destination
410
+ // for page 4 was never produced.
411
+ for (let page = 0; page < data.pages; page++) {
407
412
  const pageEntity = _.cloneDeep(entity)
408
413
  pageEntity.pages = data.pages
409
414
  if (page) {
@@ -322,7 +322,18 @@ export default ({
322
322
 
323
323
  // Force the chosen layout — bypass autoLayouts /
324
324
  // layouts.match resolution that onProcessed would do.
325
- const renderEntity = { ...entity, layout: matched }
325
+ // Both `entity.layout` AND `entity.meta.layout` need
326
+ // to be set: the layouts plugin's onProcessed
327
+ // re-resolves entity.layout from entity.meta.layout
328
+ // on every cycle, and previewRender goes through
329
+ // the full lifecycle. Without overriding meta.layout
330
+ // the agent's `mcp-ui/post-approval` choice gets
331
+ // silently replaced by the production `post` layout.
332
+ const renderEntity = {
333
+ ...entity,
334
+ layout: matched,
335
+ meta: { ...(entity.meta || {}), layout: matched.name },
336
+ }
326
337
  const { output } = await previewRender(renderEntity, {
327
338
  save: false,
328
339
  catalog: false,
@@ -13,4 +13,33 @@ export function load({ runtime }) {
13
13
  runtime.glob = (pattern, options = {}) => {
14
14
  return globby.sync(pattern, options)
15
15
  }
16
- }
16
+
17
+ // Stringify an arbitrary value as a JSON literal. Use with the
18
+ // triple-stash form ({{{json …}}}) when embedding inside a
19
+ // <script> block or HTML attribute — Handlebars's HTML-escape
20
+ // would otherwise turn quotes into &quot; and break the literal.
21
+ //
22
+ // <script>const id = {{{json document.id}}};</script>
23
+ //
24
+ // No SafeString wrap on purpose — that would silently make
25
+ // double-stash also output raw, which is the foot-gun when the
26
+ // template author thought they were getting escaping.
27
+ runtime.json = (value) => JSON.stringify(value)
28
+
29
+ // Build an array from positional args. Lets a template construct
30
+ // an empty array ({{array}}) or a literal list ({{array 1 2 3}})
31
+ // without a custom helper. Handy as the fallback value in
32
+ // expressions like (default document.meta.tags (array)).
33
+ //
34
+ // Handlebars passes a trailing `options` object to every helper
35
+ // call; we strip it so the array doesn't end up with a stray
36
+ // hash/data/fn object as its last element.
37
+ runtime.array = (...args) => {
38
+ if (args.length && typeof args[args.length - 1] === 'object'
39
+ && args[args.length - 1] !== null
40
+ && 'hash' in args[args.length - 1]) {
41
+ args = args.slice(0, -1)
42
+ }
43
+ return args
44
+ }
45
+ }