@threadlabs/looma 0.38.0 → 0.39.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.
@@ -25,6 +25,8 @@ const markdown = stringify(ast);
25
25
  const restoredEditorJson = expressmarkToEditorDocument(parse(markdown));
26
26
  ```
27
27
 
28
+ Editor Markdown import and export explicitly select Expressmark’s `compatible` syntax (CommonMark/GFM with expressive extensions), preserving the existing interchange format as Expressmark’s defaults evolve. Pass `syntax` in conversion options when selecting another supported reading; content outside the editor schema still raises a conversion error.
29
+
28
30
  The editor continues to save Tiptap JSON. Expressive Markdown is an interchange
29
31
  format; it does not replace your saved document, history or application metadata.
30
32
  See the [proposal and syntax](https://nextwebwg.org/expressive-markdown/) and
package/README.md CHANGED
@@ -67,7 +67,7 @@ The package includes the documentation generator and theme customizer used by Lo
67
67
  Install the optional documentation tools, then initialize your project:
68
68
 
69
69
  ```sh
70
- pnpm add -D markupress @nextwebwg/htmlkit@1.0.0-alpha.69 vite
70
+ pnpm add -D markupress @threadlabs/markupkit@0.2.0 vite
71
71
  pnpm exec looma-docs init
72
72
  pnpm exec looma-docs dev
73
73
  ```
package/docs/README.md CHANGED
@@ -12,12 +12,12 @@ In a project with a `package.json`, install Looma and its documentation tools:
12
12
 
13
13
  ```sh
14
14
  pnpm add @threadlabs/looma @nextwebwg/html-next@1.0.0-alpha.69
15
- pnpm add -D markupress@^0.9.1 @nextwebwg/htmlkit@1.0.0-alpha.69 vite
15
+ pnpm add -D 'markupress@>=0.10.0 <1' '@threadlabs/markupkit@>=0.2.0 <1' vite
16
16
  pnpm exec looma-docs init --title "The Fellowship"
17
17
  pnpm exec looma-docs dev
18
18
  ```
19
19
 
20
- Use the tool versions accepted by Looma's optional peer dependencies. Markupress 0.9 supplies the current documentation theme, including one appearance button that cycles Automatic, Light and Dark. Regular component imports do not load the documentation server. Vue and Tiptap remain optional; install them when your own authored documentation mounts the Vue editor.
20
+ Use the tool versions accepted by Looma's optional peer dependencies. Markupress 0.10 and later compatible 0.x releases supply the current documentation theme, including one appearance button that cycles Automatic, Light and Dark. Regular component imports do not load the documentation server. Vue and Tiptap remain optional; install them when your own authored documentation mounts the Vue editor.
21
21
 
22
22
  Initialization creates three project-owned files:
23
23
 
@@ -44,7 +44,7 @@ pnpm exec looma-docs build
44
44
  pnpm exec looma-docs preview
45
45
  ```
46
46
 
47
- `build` produces a static HTMLKit site in the configured output directory. `preview` serves that output without a repository writer. All commands accept `--root`; `dev` and `preview` also accept `--port`.
47
+ `build` produces a static MarkupKit site in the configured output directory. `preview` serves that output without a repository writer. All commands accept `--root`; `dev` and `preview` also accept `--port`.
48
48
 
49
49
  ## Name your local design system
50
50
 
package/docs/catalog.json CHANGED
@@ -458,7 +458,7 @@
458
458
  ],
459
459
  "schemaVersion": 1,
460
460
  "package": "@threadlabs/looma",
461
- "version": "0.38.0",
461
+ "version": "0.39.0",
462
462
  "groups": [
463
463
  {
464
464
  "id": "layout",
package/docs/cli.mjs CHANGED
@@ -6,7 +6,7 @@ try {
6
6
  const { values, positionals } = parseArgs({ allowPositionals: true, options: { root: { type: 'string' }, port: { type: 'string' }, title: { type: 'string' }, help: { type: 'boolean', short: 'h' } } });
7
7
  const [command = 'help'] = positionals;
8
8
  if (values.help || command === 'help') {
9
- console.log('looma-docs init | dev | build | preview\n\n --root <project> Project directory\n --port <number> Local server port (default 3000)\n --title <name> Documentation title for init\n\nInstall the optional documentation peers: markupress, @nextwebwg/htmlkit and vite.');
9
+ console.log('looma-docs init | dev | build | preview\n\n --root <project> Project directory\n --port <number> Local server port (default 3000)\n --title <name> Documentation title for init\n\nInstall the optional documentation peers: markupress, @threadlabs/markupkit and vite.');
10
10
  } else {
11
11
  const options = { ...(values.root ? { root: values.root } : {}), ...(values.title ? { title: values.title } : {}) };
12
12
  if (values.port) { const port = Number(values.port); if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('Port must be an integer from 0 to 65535.'); options.port = port; }
package/docs/commands.mjs CHANGED
@@ -47,11 +47,11 @@ export async function buildProjectDocumentation({ root = process.cwd(), config,
47
47
  }
48
48
  export async function previewProjectDocumentation({ root = process.cwd(), config, port = 3000 } = {}) {
49
49
  if (!config) ({ root, config } = await readConfiguration(root));
50
- const { previewApplication } = await tool('@nextwebwg/htmlkit', root);
50
+ const { previewApplication } = await tool('@threadlabs/markupkit', root);
51
51
  return previewApplication({ root, outDir: config.outDir ?? '.looma/site', base: config.base ?? '/', host: '127.0.0.1', port });
52
52
  }
53
53
 
54
- /** HTMLKit serves the build; this local-only gateway adds the constrained repository writer. */
54
+ /** MarkupKit serves the build; this local-only gateway adds the constrained repository writer. */
55
55
  export async function devProjectDocumentation({ root = process.cwd(), config, catalog, port = 3000, onError = console.error } = {}) {
56
56
  const configured = Boolean(config);
57
57
  if (!config) ({ root, config } = await readConfiguration(root));
package/docs/index.d.ts CHANGED
@@ -39,9 +39,9 @@ export function discoverComponents(input: DiscoveryRoot & { root: string }): Pro
39
39
  export function mergeCatalogs(catalogs: DocumentationCatalog[], options?: { categories?: Record<string, string> }): DocumentationCatalog;
40
40
  export function prepareDocumentation(input: DocumentationInput & { root: string }): Promise<DocumentationSite>;
41
41
  export function buildDocumentation(input?: DocumentationInput): Promise<DocumentationBuild>;
42
- /** Optional HTMLKit integration; server tooling peers are loaded only when invoked. */
43
- export function documentationOptions(site: DocumentationSite): Promise<import('@nextwebwg/htmlkit').ApplicationOptions>;
44
- export function documentationPlugin(site: DocumentationSite): import('@nextwebwg/htmlkit').HtmlKitPlugin;
42
+ /** Optional MarkupKit integration; server tooling peers are loaded only when invoked. */
43
+ export function documentationOptions(site: DocumentationSite): Promise<import('@threadlabs/markupkit').ApplicationOptions>;
44
+ export function documentationPlugin(site: DocumentationSite): import('@threadlabs/markupkit').MarkupKitPlugin;
45
45
  export function initializeDocumentation(input?: { root?: string; title?: string }): Promise<{ config: DocumentationConfiguration; files: string[]; applicationImport: string }>;
46
46
  export function readConfiguration(root?: string): Promise<{ root: string; config: DocumentationConfiguration }>;
47
47
  export function buildProjectDocumentation(input?: DocumentationInput): Promise<DocumentationBuild>;
@@ -17,7 +17,7 @@ async function body(request) {
17
17
  /** Local capabilities stay in the parent editor; opaque example frames receive only theme CSS. */
18
18
  export async function serveLocalDocumentation({ upstream, store, base = '/', port = 0, onSaved = async () => {} }) {
19
19
  const target = new URL(upstream);
20
- if (target.protocol !== 'http:' || target.hostname !== '127.0.0.1') throw new Error('The documentation upstream must be a loopback HTMLKit server.');
20
+ if (target.protocol !== 'http:' || target.hostname !== '127.0.0.1') throw new Error('The documentation upstream must be a loopback MarkupKit server.');
21
21
  const capability = randomBytes(32).toString('hex');
22
22
  let origin;
23
23
  const json = (response, status, data) => {
package/docs/site.mjs CHANGED
@@ -13,7 +13,7 @@ import { cacheDirectory, rejectSymlink, withBuildLock } from './workspace.mjs';
13
13
  const own = name => fileURLToPath(new URL(name, import.meta.url));
14
14
  /** Resolve import-only optional tooling peers in the consumer's ESM package scope. */
15
15
  export async function tool(name, root) {
16
- if (!['markupress', '@nextwebwg/htmlkit', 'vite'].includes(name)) throw new Error(`Unknown documentation tool ${name}.`);
16
+ if (!['markupress', '@threadlabs/markupkit', 'vite'].includes(name)) throw new Error(`Unknown documentation tool ${name}.`);
17
17
  const directory = await cacheDirectory(root, 'tooling');
18
18
  const file = path.join(directory, name.replaceAll('/', '-').replace('@', '') + '.mjs');
19
19
  await rejectSymlink(file);
@@ -89,7 +89,7 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
89
89
  for (const file of ['customizer.js', 'theme.mjs', 'theme-session.mjs', 'value-editors.mjs', 'palette.mjs', 'fonts.mjs']) await cp(own(file), path.join(generated, file));
90
90
  const write = async (file, source) => { await mkdir(path.dirname(file), { recursive: true }); await writeFile(file, source); };
91
91
  const examples = [];
92
- // HTMLKit route groups retain their labels/order in navigation without changing public URLs.
92
+ // MarkupKit route groups retain their labels/order in navigation without changing public URLs.
93
93
  const groupDirectories = new Map(combined.groups.map((group, index) => [group.id,
94
94
  `${String(index + 1).padStart(2, '0')}.(${group.label.replace(/[\\/()]/g, '-')})`]));
95
95
  const generatedReferences = new Set();
@@ -114,7 +114,7 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
114
114
  const setup = path.join(previews, key + '.js');
115
115
  const behavior = example.behavior;
116
116
  await write(setup, `import { connect as connectPreview } from '../preview.js';\n${behavior ? `import setupExample from ${JSON.stringify(behavior)};\n` : ''}export function connect(host) { const stopPreview = connectPreview(host); ${behavior ? 'const stopExample = setupExample(host.element);' : ''} return () => { ${behavior ? 'stopExample?.();' : ''} stopPreview(); }; }\n`);
117
- await write(file, ` ${links}\n<template component="looma-preview-${key}" module="./${key}.js"><defs><meta name="hk:navigation" content="hidden"><title>${escapeHtml(example.title)}</title></defs><div data-preview="${escapeHtml(example.id)}"${component.group === 'layout' ? ' data-preview-layout' : ''}>${example.markup}${anchors}</div></template>\n`);
117
+ await write(file, ` ${links}\n<template component="looma-preview-${key}" module="./${key}.js"><defs><meta name="mk:navigation" content="hidden"><title>${escapeHtml(example.title)}</title></defs><div data-preview="${escapeHtml(example.id)}"${component.group === 'layout' ? ' data-preview-layout' : ''}>${example.markup}${anchors}</div></template>\n`);
118
118
  examples.push({ ...example, file, component: component.id, path: base + previewPath(example).slice(1) });
119
119
  }
120
120
  }
@@ -155,7 +155,7 @@ export async function prepareDocumentation({ root, config = {}, catalog }) {
155
155
  return { root, config, base, catalog: combined, examples, generated, content, previews, redirects };
156
156
  }
157
157
 
158
- /** Bundle validated theme restoration into HTMLKit's before-paint head hook. */
158
+ /** Bundle validated theme restoration into MarkupKit's before-paint head hook. */
159
159
  async function themeBootstrap(site) {
160
160
  const { build } = await tool('vite', site.root);
161
161
  const entry = path.join(site.generated, 'theme-bootstrap.mjs');
@@ -210,11 +210,11 @@ async function portableThemeStyles(site) {
210
210
  return styles;
211
211
  }
212
212
 
213
- /** Build through HTMLKit, then make opaque preview scripts portable to static hosts without CORS. */
213
+ /** Build through MarkupKit, then make opaque preview scripts portable to static hosts without CORS. */
214
214
  export async function buildDocumentation({ root = process.cwd(), config = {}, catalog } = {}) {
215
215
  return withBuildLock(root, async () => {
216
216
  const site = await prepareDocumentation({ root, config, catalog });
217
- const { buildApplication } = await tool('@nextwebwg/htmlkit', site.root);
217
+ const { buildApplication } = await tool('@threadlabs/markupkit', site.root);
218
218
  const built = await buildApplication(await documentationOptions(site));
219
219
  return { ...built, site };
220
220
  });
@@ -109,7 +109,7 @@ function safeBackground(value, path) {
109
109
  value: "color",
110
110
  attributes: { style }
111
111
  }]
112
- })), (node) => typeof node.attributes?.style === "string")?.attributes?.style;
112
+ }), { syntax: "compatible" }), (node) => typeof node.attributes?.style === "string")?.attributes?.style;
113
113
  if (typeof accepted !== "string" || accepted.replace(/\s/g, "").replace(/;$/, "") !== style.replace(/\s/g, "")) fail(path, "unsafe or unsupported table background");
114
114
  }
115
115
  function safeNavigation(value, image, path) {
@@ -129,7 +129,7 @@ function safeNavigation(value, image, path) {
129
129
  value: "link"
130
130
  }]
131
131
  }]
132
- })), (child) => child.type === (image ? "image" : "link"))?.url !== value) fail(path, "unsafe or unsupported navigation URL");
132
+ }), { syntax: "compatible" }), (child) => child.type === (image ? "image" : "link"))?.url !== value) fail(path, "unsafe or unsupported navigation URL");
133
133
  }
134
134
  var MARKS = {
135
135
  bold: "strong",
@@ -605,6 +605,7 @@ function editorDocumentToExpressmark(document, options = {}) {
605
605
  return {
606
606
  type: "document",
607
607
  version: 1,
608
+ syntax: options.syntax ?? "compatible",
608
609
  body: (document.content ?? []).map((node, index) => {
609
610
  const child = toNode(node, `document.body[${index}]`, context);
610
611
  return child.type === "image" ? {
@@ -1007,7 +1008,11 @@ function expressmarkToEditorDocument(document) {
1007
1008
  * @failure Unsupported editor data raises EditorMarkdownConversionError before any input is changed.
1008
1009
  */
1009
1010
  function editorDocumentToMarkdown(document, options = {}) {
1010
- return stringify(editorDocumentToExpressmark(document, options), options);
1011
+ const conversion = {
1012
+ syntax: "compatible",
1013
+ ...options
1014
+ };
1015
+ return stringify(editorDocumentToExpressmark(document, conversion), conversion);
1011
1016
  }
1012
1017
  /**
1013
1018
  * Serializes the current editor, including its current display cache and concrete TOC entries.
@@ -1024,7 +1029,10 @@ function editorToMarkdown(editor, options = {}) {
1024
1029
  * @failure Unsupported content or root metadata stops conversion instead of being silently flattened.
1025
1030
  */
1026
1031
  function markdownToEditorDocument(markdown, options = {}) {
1027
- return expressmarkToEditorDocument(parse(markdown, options));
1032
+ return expressmarkToEditorDocument(parse(markdown, {
1033
+ syntax: "compatible",
1034
+ ...options
1035
+ }));
1028
1036
  }
1029
1037
  //#endregion
1030
1038
  export { EditorMarkdownConversionError, editorDocumentToExpressmark, editorDocumentToMarkdown, editorToMarkdown, expressmarkToEditorDocument, markdownToEditorDocument };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -119,7 +119,7 @@
119
119
  "lowlight": "^3.3.0",
120
120
  "markdown-it": "14.3.1",
121
121
  "lucide": "~0.554.0",
122
- "expressmark": "0.1.6"
122
+ "expressmark": ">=0.2.0 <1"
123
123
  },
124
124
  "peerDependencies": {
125
125
  "@nextwebwg/html-next": "1.0.0-alpha.69",
@@ -127,9 +127,9 @@
127
127
  "@tiptap/pm": ">=2 <3",
128
128
  "@tiptap/vue-3": ">=2 <3",
129
129
  "vue": "^3.5.43",
130
- "markupress": "^0.9.1",
131
- "@nextwebwg/htmlkit": "1.0.0-alpha.69",
132
- "vite": "^8.3.4"
130
+ "markupress": ">=0.10.0 <1",
131
+ "@threadlabs/markupkit": ">=0.2.0 <1",
132
+ "vite": ">=7.2.7 <9"
133
133
  },
134
134
  "peerDependenciesMeta": {
135
135
  "@tiptap/core": {
@@ -147,7 +147,7 @@
147
147
  "markupress": {
148
148
  "optional": true
149
149
  },
150
- "@nextwebwg/htmlkit": {
150
+ "@threadlabs/markupkit": {
151
151
  "optional": true
152
152
  },
153
153
  "vite": {