mechanica 2.0.0 → 2.0.2

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
@@ -30,6 +30,37 @@ npm run export
30
30
 
31
31
  Prefer Bun? `bun create mechanica my-site`, then `bun install` and `bun run dev`.
32
32
 
33
+ ### With an AI coding agent
34
+
35
+ Claude Code, Codex and similar agents can set the site up and build it for you. Open an empty folder in the agent and paste:
36
+
37
+ ```text
38
+ Create a Mechanica site in this folder.
39
+
40
+ 1. Check for Node.js 20.19+ (`node -v`) or Bun (`bun -v`). If neither is
41
+ installed, ask me before installing Bun from https://bun.sh.
42
+ 2. Run `npm create mechanica@latest .` (or `bun create mechanica .`), install
43
+ the dependencies, and start the dev server in the background.
44
+ 3. Read AGENTS.md in the project and follow it for everything else.
45
+
46
+ Then build: <describe your site>
47
+ ```
48
+
49
+ To skip the pasting, install the skill once. It does the same setup and also handles a machine with no Node.js.
50
+
51
+ Claude Code:
52
+
53
+ ```bash
54
+ claude plugin marketplace add den59k/mechanica
55
+ claude plugin install mechanica@mechanica
56
+ ```
57
+
58
+ Codex: save [SKILL.md](https://github.com/den59k/mechanica/blob/main/plugins/mechanica/skills/create-mechanica-site/SKILL.md) as `~/.agents/skills/create-mechanica-site/SKILL.md`.
59
+
60
+ Then ask the agent to create a Mechanica site.
61
+
62
+ The starter's `AGENTS.md` teaches the agent how to write blocks and pages and how to check its work with screenshots, and points it at the documentation of the installed version. Run the agent on your own machine: the editor opens in your browser from the local dev server.
63
+
33
64
  ## A Mechanica project
34
65
 
35
66
  ```text
@@ -45,7 +76,7 @@ src/composer.ts optional: your design system for the Block Composer
45
76
  .mech/data.json site-wide data values
46
77
  ```
47
78
 
48
- `.mech/` is content — commit it. Scaffolded projects also include a `CLAUDE.md` that teaches AI coding assistants to author blocks and pages in this format.
79
+ `.mech/` is content — commit it. Scaffolded projects also include an `AGENTS.md` that teaches AI coding assistants (Claude Code, Codex and others) to author blocks and pages in this format.
49
80
 
50
81
  ### Adding Mechanica to an existing Vite + Vue project
51
82
 
@@ -139,14 +170,29 @@ data:
139
170
 
140
171
  ::: hero
141
172
  title: About us
173
+ cta: { url: /contacts, title: Write to us }
142
174
  @subtitle
143
175
  We build fast websites
144
176
  for people who care about the details.
145
177
  :::
178
+
179
+ ::: columns
180
+ gap: lg
181
+ ::: card slot=left
182
+ title: Fast
183
+ :::
184
+ ::: card slot=right
185
+ title: Readable
186
+ :::
187
+ ::: /columns
146
188
  ```
147
189
 
148
190
  - The YAML frontmatter holds the editor label (`name`), page-scoped data (`data`) and optional `layout`, `draft: true` (hidden from listings and the export) and `meta` (`noindex: true`, `lastmod`).
149
- - Each block is a fence: `::: <blockId>`, then its props as YAML, then `@field` regions for long text, closed by `:::`. A region fills a `text` prop with its text as written, and a `richText` prop with its Markdown turned into rich text. Blocks nest by placing fences inside a container's fence.
191
+ - Each block is a fence: `::: <blockId>`, then its props as YAML, then `@field` regions for long text, then any child blocks, closed by `:::`. Keep that order: once a region has started, a `key: value` line is part of its text.
192
+ - A region runs until the next `@field`, child block or `:::`, and is taken as written, blank lines and Markdown included. It fills a `text` prop with that text, and a `richText` prop with its Markdown turned into rich text.
193
+ - Blocks nest by placing fences inside a container's fence, and `slot=<name>` puts a child into a named slot (a container's children go either all to the default slot or all to named ones). Nothing is indented: every `:::` and `@field` starts at the beginning of its line, and a block is a child of whichever fence is still open. A container may close with `::: /<blockId>`, which reports a missing `:::` at the line where the fences stop pairing up.
194
+ - Props are YAML, so quote a value that starts with `#` or contains `: `, as in `href: "#pricing"`. Inside a region nothing needs quoting; only a line that starts with `:::`, or consists of just `@name`, takes a leading backslash (`\:::`), and not even that inside a code fence.
195
+ - An `image` prop points at an uploaded file: `photo: { src: /@mechanica/assets/team.jpg, alt: "Our team" }`.
150
196
  - Props left out take their schema defaults, so a hand-written page renders exactly like one made in the editor.
151
197
 
152
198
  With `npm run dev` running, edits you make to these files show up in the editor live, and the editor won't overwrite a file that changed on disk underneath it.
package/bin/mechanica.js CHANGED
File without changes
@@ -6,7 +6,7 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
6
6
  return target;
7
7
  };
8
8
  //#endregion
9
- //#region \0svg-glob:C:\Users\Den\Desktop\CURVES\mechanica\packages\mechanica\src\editor\icons
9
+ //#region \0svg-glob:/home/runner/work/mechanica/mechanica/packages/mechanica/src/editor/icons
10
10
  var contents = {
11
11
  "align-center": `<path d="M4 6h16M7 12h10M6 18h12"/>`,
12
12
  "align-left": `<path d="M4 6h16M4 12h10M4 18h13"/>`,
@@ -1,4 +1,4 @@
1
- import { a as renderCrop, c as useDialog, d as _plugin_vue_export_helper_default, i as readImageSize, l as VIcon_default, n as VDialog_default, r as analyzeImageFile, s as dialogKey, t as ImagePickerDialog_default } from "./ImagePickerDialog-6skSAzJh.js";
1
+ import { a as renderCrop, c as useDialog, d as _plugin_vue_export_helper_default, i as readImageSize, l as VIcon_default, n as VDialog_default, r as analyzeImageFile, s as dialogKey, t as ImagePickerDialog_default } from "./ImagePickerDialog-DJGhtW3r.js";
2
2
  import { Fragment, Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, inject, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, openBlock, provide, reactive, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, unref, vShow, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
3
3
  import { deepEqual, getDefaultValue } from "mechanica-shared";
4
4
  import { unfoldSchema } from "compact-json-schema";
@@ -975,6 +975,12 @@ function resolveChunkAssets(manifest, key, into = {
975
975
  * Build the `<link>` tags for one page: stylesheets first (they must apply
976
976
  * before first paint — the server-rendered blocks are already in the HTML),
977
977
  * then `modulepreload` for the block chunks and their shared imports.
978
+ *
979
+ * Every tag carries `crossorigin`, like the ones Vite writes into the built
980
+ * index: a later `import()` of the same chunk always fetches in CORS mode, and
981
+ * on a cross-origin CDN a copy cached by a no-CORS hint has no
982
+ * `Access-Control-Allow-Origin`, so the browser would block the import. The
983
+ * attribute is a no-op for same-origin builds.
978
984
  */
979
985
  function blockAssetLinks(options) {
980
986
  const keyByFile = /* @__PURE__ */ new Map();
@@ -991,7 +997,7 @@ function blockAssetLinks(options) {
991
997
  }
992
998
  const wanted = (file) => !options.alreadyLinked?.(file);
993
999
  const href = (file) => `${options.base ?? ""}/${file}`;
994
- return [...assets.css.filter(wanted).map((file) => `<link rel="stylesheet" href="${href(file)}">`), ...assets.js.filter(wanted).map((file) => `<link rel="modulepreload" href="${href(file)}">`)];
1000
+ return [...assets.css.filter(wanted).map((file) => `<link rel="stylesheet" crossorigin href="${href(file)}">`), ...assets.js.filter(wanted).map((file) => `<link rel="modulepreload" crossorigin href="${href(file)}">`)];
995
1001
  }
996
1002
  //#endregion
997
1003
  export { renamePage as A, readImageManifest as B, getPagePath as C, pageUrlOf as D, movePage as E, translationsOf as F, wasRecentlyMutated as G, saveUpload as H, UPLOADS_PREFIX as I, hasSharp as J, writeFileAtomic as K, assetFileOf as L, setPageBlocks as M, setPageCodec as N, pageVersion as O, setPageDraft as P, isDerivedAsset as R, fillImageMeta as S, listPages as T, updateImageManifest as U, saveDerivedAsset as V, markMutated as W, isRasterImage as Y, createTranslation as _, toBlockMeta as a, duplicatePage as b, mergeLocaleFolderData as c, readFolderData as d, readFoldersData as f, createPage as g, PageExistsError as h, composedBlockMeta as i, savePage as j, readPage as k, mergeLocaleSiteData as l, readSiteLocaleOverride as m, blockAssetLinks as n, folderOf as o, readSiteData as p, analyzeImageBuffer as q, buildRichTextCodec as r, mergeFolderData as s, BLOCKS_MANIFEST_FILE as t, mergeSiteData as u, deletePage as v, listFolders as w, fillContentDefaults as x, deleteTranslation as y, listImages as z };
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B as readImageManifest, C as getPagePath, I as UPLOADS_PREFIX, J as hasSharp, L as assetFileOf, N as setPageCodec, R as isDerivedAsset, T as listPages, U as updateImageManifest, Y as isRasterImage, a as toBlockMeta, d as readFolderData, f as readFoldersData, i as composedBlockMeta, m as readSiteLocaleOverride, n as blockAssetLinks, p as readSiteData, q as analyzeImageBuffer, r as buildRichTextCodec, t as BLOCKS_MANIFEST_FILE } from "./chunks/page-assets-Bp6mxPSy.js";
1
+ import { B as readImageManifest, C as getPagePath, I as UPLOADS_PREFIX, J as hasSharp, L as assetFileOf, N as setPageCodec, R as isDerivedAsset, T as listPages, U as updateImageManifest, Y as isRasterImage, a as toBlockMeta, d as readFolderData, f as readFoldersData, i as composedBlockMeta, m as readSiteLocaleOverride, n as blockAssetLinks, p as readSiteData, q as analyzeImageBuffer, r as buildRichTextCodec, t as BLOCKS_MANIFEST_FILE } from "./chunks/page-assets-CM_MCuoa.js";
2
2
  import fs from "node:fs";
3
3
  import { dirname, join, parse, relative } from "node:path";
4
4
  import { applySeoTags, auditPageHtml, buildRobotsTxt, buildSitemap, findUnknownBlocks, generateProject, isPaginatedQuery, localePath, mergeTranslation, migrateContent, paginationVariantPath, registerFieldSchemas, resolveQueryKey, templateBlockIds, validateLinks, walkSchema, walkTree } from "mechanica-shared";
package/dist/composer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './editor.css';
2
2
  import { a as renderBlocks, l as ANCHOR_H, t as createMechanica, u as ANCHOR_V } from "./chunks/create-mechanica-CYaKXuQd.js";
3
- import { C as placeBlock, D as registerBuiltinFieldEditors, E as uid, M as SmartLinkField_default, R as VSelect_default, S as moveBlock, T as removeBlock, _ as duplicateBlock, b as findParentList, c as shortcutChar, g as cloneBlock, h as humanize, i as normalizeTemplate, m as toBlockMeta, n as createSaveQueue, o as SchemaField_default, s as FieldControl_default, t as SaveConflictError, v as ensureSlotList, w as relocateBlock, x as findParentSlot, y as findBlock, z as VPopover_default } from "./chunks/editor-B0r4EOIM.js";
4
- import { d as _plugin_vue_export_helper_default, l as VIcon_default, u as contents } from "./chunks/ImagePickerDialog-6skSAzJh.js";
3
+ import { C as placeBlock, D as registerBuiltinFieldEditors, E as uid, M as SmartLinkField_default, R as VSelect_default, S as moveBlock, T as removeBlock, _ as duplicateBlock, b as findParentList, c as shortcutChar, g as cloneBlock, h as humanize, i as normalizeTemplate, m as toBlockMeta, n as createSaveQueue, o as SchemaField_default, s as FieldControl_default, t as SaveConflictError, v as ensureSlotList, w as relocateBlock, x as findParentSlot, y as findBlock, z as VPopover_default } from "./chunks/editor-JV0Nhfw4.js";
4
+ import { d as _plugin_vue_export_helper_default, l as VIcon_default, u as contents } from "./chunks/ImagePickerDialog-DJGhtW3r.js";
5
5
  import { Fragment, Teleport, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, renderSlot, resolveComponent, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
6
6
  import { buildPreviewData, getDefaultValue, isBinding, lookupBinding, registerFieldSchemas, resolveBindings, walkTree } from "mechanica-shared";
7
7
  import { unfoldSchema } from "compact-json-schema";
package/dist/editor.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import './editor.css';
2
2
  import { _ as mechanicaKey, a as renderBlocks, c as registerElements, i as createComposedComponent, m as createRouter, n as pushStateUpdate, r as runtimeLayoutNames } from "./chunks/create-mechanica-CYaKXuQd.js";
3
3
  import { i as getDataEntries, t as buildPreviewContent } from "./chunks/preview-CbUxh2gL.js";
4
- import { A as createContextMenu, C as placeBlock, D as registerBuiltinFieldEditors, E as uid, F as groupPagesByFolder, I as pageThumbUrl, L as pathFromName, N as fetchPages, O as registerRichTextWidgets, P as filterPages, R as VSelect_default, S as moveBlock, T as removeBlock, _ as duplicateBlock, a as SchemaForm_default, c as shortcutChar, d as compareBlocks, f as createContentBlock, g as cloneBlock, h as humanize, j as VSegmented_default, k as contextMenuKey, l as blockAvailableForLayout, m as toBlockMeta, n as createSaveQueue, p as isUnderFolder, r as createRootFrame, t as SaveConflictError, u as blockAvailableIn, w as relocateBlock, x as findParentSlot, y as findBlock, z as VPopover_default } from "./chunks/editor-B0r4EOIM.js";
5
- import { c as useDialog, d as _plugin_vue_export_helper_default, l as VIcon_default, n as VDialog_default, o as createDialogStore, s as dialogKey } from "./chunks/ImagePickerDialog-6skSAzJh.js";
4
+ import { A as createContextMenu, C as placeBlock, D as registerBuiltinFieldEditors, E as uid, F as groupPagesByFolder, I as pageThumbUrl, L as pathFromName, N as fetchPages, O as registerRichTextWidgets, P as filterPages, R as VSelect_default, S as moveBlock, T as removeBlock, _ as duplicateBlock, a as SchemaForm_default, c as shortcutChar, d as compareBlocks, f as createContentBlock, g as cloneBlock, h as humanize, j as VSegmented_default, k as contextMenuKey, l as blockAvailableForLayout, m as toBlockMeta, n as createSaveQueue, p as isUnderFolder, r as createRootFrame, t as SaveConflictError, u as blockAvailableIn, w as relocateBlock, x as findParentSlot, y as findBlock, z as VPopover_default } from "./chunks/editor-JV0Nhfw4.js";
5
+ import { c as useDialog, d as _plugin_vue_export_helper_default, l as VIcon_default, n as VDialog_default, o as createDialogStore, s as dialogKey } from "./chunks/ImagePickerDialog-DJGhtW3r.js";
6
6
  import { Fragment, Teleport, Transition, TransitionGroup, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, markRaw, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, resolveComponent, resolveDynamicComponent, shallowRef, toDisplayString, unref, useTemplateRef, vModelCheckbox, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
7
7
  import { getDefaultValue, localeLabel, localePath, parseLocalePath, registerFieldSchemas } from "mechanica-shared";
8
8
  import { blocksList } from "virtual:mechanica/blocks";
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as renamePage, D as pageUrlOf, E as movePage, F as translationsOf, G as wasRecentlyMutated, H as saveUpload, K as writeFileAtomic, M as setPageBlocks, N as setPageCodec, O as pageVersion, P as setPageDraft, S as fillImageMeta, T as listPages, V as saveDerivedAsset, W as markMutated, _ as createTranslation, a as toBlockMeta, b as duplicatePage, c as mergeLocaleFolderData, d as readFolderData, g as createPage, h as PageExistsError, i as composedBlockMeta, j as savePage, k as readPage, l as mergeLocaleSiteData, o as folderOf, p as readSiteData, r as buildRichTextCodec, s as mergeFolderData, t as BLOCKS_MANIFEST_FILE, u as mergeSiteData, v as deletePage, w as listFolders, x as fillContentDefaults, y as deleteTranslation, z as listImages } from "./chunks/page-assets-Bp6mxPSy.js";
1
+ import { A as renamePage, D as pageUrlOf, E as movePage, F as translationsOf, G as wasRecentlyMutated, H as saveUpload, K as writeFileAtomic, M as setPageBlocks, N as setPageCodec, O as pageVersion, P as setPageDraft, S as fillImageMeta, T as listPages, V as saveDerivedAsset, W as markMutated, _ as createTranslation, a as toBlockMeta, b as duplicatePage, c as mergeLocaleFolderData, d as readFolderData, g as createPage, h as PageExistsError, i as composedBlockMeta, j as savePage, k as readPage, l as mergeLocaleSiteData, o as folderOf, p as readSiteData, r as buildRichTextCodec, s as mergeFolderData, t as BLOCKS_MANIFEST_FILE, u as mergeSiteData, v as deletePage, w as listFolders, x as fillContentDefaults, y as deleteTranslation, z as listImages } from "./chunks/page-assets-CM_MCuoa.js";
2
2
  import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { basename, join, relative, resolve, sep } from "node:path";
4
4
  import { parseVueRequest } from "@vitejs/plugin-vue";
@@ -68,5 +68,11 @@ export interface BlockAssetLinkOptions {
68
68
  * Build the `<link>` tags for one page: stylesheets first (they must apply
69
69
  * before first paint — the server-rendered blocks are already in the HTML),
70
70
  * then `modulepreload` for the block chunks and their shared imports.
71
+ *
72
+ * Every tag carries `crossorigin`, like the ones Vite writes into the built
73
+ * index: a later `import()` of the same chunk always fetches in CORS mode, and
74
+ * on a cross-origin CDN a copy cached by a no-CORS hint has no
75
+ * `Access-Control-Allow-Origin`, so the browser would block the import. The
76
+ * attribute is a no-op for same-origin builds.
71
77
  */
72
78
  export declare function blockAssetLinks(options: BlockAssetLinkOptions): string[];
package/dist/widgets.js CHANGED
@@ -1,4 +1,4 @@
1
- import { s as dialogKey, t as ImagePickerDialog_default } from "./chunks/ImagePickerDialog-6skSAzJh.js";
1
+ import { s as dialogKey, t as ImagePickerDialog_default } from "./chunks/ImagePickerDialog-DJGhtW3r.js";
2
2
  import { inject } from "vue";
3
3
  //#region src/editor/widget-api.ts
4
4
  /**
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "mechanica",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Build Vue 3 websites with a visual block editor",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://gitlab.letary.ru/den59k/mechanica.git"
8
+ "url": "git+https://github.com/den59k/mechanica.git",
9
+ "directory": "packages/mechanica"
9
10
  },
10
11
  "keywords": [
11
12
  "vue",
@@ -54,10 +55,10 @@
54
55
  "test": "vitest run",
55
56
  "test:watch": "vitest",
56
57
  "typecheck": "tsc --noEmit",
57
- "prepublishOnly": "bun run build"
58
+ "prepublishOnly": "bun ../../scripts/ensure-bun-publish.js && bun run build"
58
59
  },
59
60
  "dependencies": {
60
- "mechanica-shared": "workspace:*",
61
+ "mechanica-shared": "2.0.2",
61
62
  "@vue/compiler-sfc": "^3.5.38",
62
63
  "compact-json-schema": "^0.1.5",
63
64
  "estree-walker": "^3.0.3",