@avocadostudio-ai/shared 0.1.0 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,7 +1,16 @@
1
1
  export { EDITOR_PROTOCOL_VERSION } from "./protocol.js";
2
2
  export { getConfiguredDraftSecret, getSafeInternalRedirectPath, validateDraftSecret } from "./draft-mode.js";
3
3
  export { isImagePath, toAltPath, setPropAtPath } from "./editable-path.js";
4
- export { blockDefinitionSchema, blockManifestSchema, jsonSchemaLikeSchema, validateByJsonSchemaLike, validateManifestDefaultProps, deriveFieldMetaFromSchema } from "./block-manifest.js";
4
+ /*
5
+ * The rich-text grammar lives in `@avocadostudio-ai/richtext`, which owns the
6
+ * parser and every CMS converter and has no dependencies of its own. It is
7
+ * re-exported here because the renderers, the overlay and the Puck adapter all
8
+ * already import it from `shared` — moving the source should not move every
9
+ * call site. New code that only needs the grammar should depend on the
10
+ * richtext package directly.
11
+ */
12
+ export { parseInline, parseRichText, parseRichTextBlocks, normalizeRichTextBody, resolveRichTextHeadingLevel, clampMarkdownHeadings, unescapeMarkdownText, isRichTextDoc, fromMarkdown, toMarkdown, mergeRichTextDoc, NODE, MARK } from "@avocadostudio-ai/richtext";
13
+ export { blockDefinitionSchema, blockManifestSchema, buildBlockManifest, jsonSchemaLikeSchema, validateByJsonSchemaLike, findManifestSchemaIssue, validateManifestDefaultProps, deriveFieldMetaFromSchema, resolveManifestFieldMeta, isProseMirrorDocSchema } from "./block-manifest.js";
5
14
  export {
6
15
  // Constants & helpers
7
16
  IMAGE_PLACEHOLDER, isImagePlaceholder,
@@ -9,11 +18,13 @@ IMAGE_PLACEHOLDER, isImagePlaceholder,
9
18
  registerBlock, getBlockMeta, getAllBlockMeta, getImageFields, getListImageFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes,
10
19
  // Backwards-compatible exports
11
20
  blockSchemas, allowedBlockTypes,
21
+ // The catalogue a site actually renders — see `declareBlockCatalogue`
22
+ declareBlockCatalogue, getBlockCatalogue, isInBlockCatalogue, catalogueBlockTypes, undeclaredBlockTypes,
12
23
  // Utility functions
13
24
  getPropDisplayName, defaultListItemForBlock,
14
25
  // Schemas & validation
15
26
  blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.js";
16
- export { defaultPropsForType, resolveHeadingTag, resolveItemHeadingTag, DEFAULT_HEADING_LEVELS, } from "./blocks/index.js";
27
+ export { defaultPropsForType, declaredDefaultPropsForType, resolveHeadingTag, resolveItemHeadingTag, DEFAULT_HEADING_LEVELS, } from "./blocks/index.js";
17
28
  export { blockTypeToCamel, camelToBlockType, blockTypeToLower, lowerToBlockType, } from "./block-names.js";
18
29
  export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, } from "./ops/builders.js";
19
30
  export { THEME_TOKEN_TO_CSS_VARS, themeTokenKeys, semanticThemeTokensSchema, mapSemanticThemeTokens, } from "./ops/theme-tokens.js";
@@ -12,9 +12,14 @@ export type MakeAddBlockOptions = {
12
12
  /**
13
13
  * Build a canonical `add_block` op from a block type and partial props.
14
14
  *
15
- * The returned op's `block` merges `defaultPropsForType(type)` with `partialProps`
16
- * (partial wins) and carries a generated id, so it is valid against the strict
17
- * operation schema and ready to send to the orchestrator.
15
+ * The returned op's `block` merges the type's declared defaults with
16
+ * `partialProps` (partial wins) and carries a generated id, so it is valid
17
+ * against the strict operation schema and ready to send to the orchestrator.
18
+ *
19
+ * A type that declares no defaults — every block a site registers itself —
20
+ * contributes nothing, so the op carries exactly the props the caller passed.
21
+ * The alternative is worse than empty: the generic fallback would add a
22
+ * `title`, `description` and CTA the site's block has no field for.
18
23
  *
19
24
  * @example
20
25
  * makeAddBlock("/", "Hero", { heading: "Welcome" })
@@ -8,20 +8,25 @@
8
8
  // must mint an id and supply a full prop set.
9
9
  //
10
10
  // `makeAddBlock` closes that gap without denormalizing the op: it generates a
11
- // collision-resistant id and fills `defaultPropsForType(type)` under any
11
+ // collision-resistant id and fills the type's declared defaults under any
12
12
  // partial props you pass, returning a canonical, schema-valid `add_block`.
13
13
  //
14
14
  // External integrators: prefer this over hand-constructing the op (and never
15
15
  // hand-write the `Operation` union — import it from this package, or validate
16
16
  // against `contract/operation.schema.json`).
17
17
  import { getBlockMeta } from "./../blocks/_registry.js";
18
- import { defaultPropsForType } from "../blocks/index.js";
18
+ import { declaredDefaultPropsForType } from "../blocks/index.js";
19
19
  /**
20
20
  * Build a canonical `add_block` op from a block type and partial props.
21
21
  *
22
- * The returned op's `block` merges `defaultPropsForType(type)` with `partialProps`
23
- * (partial wins) and carries a generated id, so it is valid against the strict
24
- * operation schema and ready to send to the orchestrator.
22
+ * The returned op's `block` merges the type's declared defaults with
23
+ * `partialProps` (partial wins) and carries a generated id, so it is valid
24
+ * against the strict operation schema and ready to send to the orchestrator.
25
+ *
26
+ * A type that declares no defaults — every block a site registers itself —
27
+ * contributes nothing, so the op carries exactly the props the caller passed.
28
+ * The alternative is worse than empty: the generic fallback would add a
29
+ * `title`, `description` and CTA the site's block has no field for.
25
30
  *
26
31
  * @example
27
32
  * makeAddBlock("/", "Hero", { heading: "Welcome" })
@@ -31,7 +36,7 @@ export function makeAddBlock(pageSlug, type, partialProps = {}, options = {}) {
31
36
  const block = {
32
37
  id: options.id ?? generateBlockId(type),
33
38
  type,
34
- props: { ...defaultPropsForType(type), ...partialProps },
39
+ props: { ...declaredDefaultPropsForType(type), ...partialProps },
35
40
  };
36
41
  return options.afterBlockId
37
42
  ? { op: "add_block", pageSlug, afterBlockId: options.afterBlockId, block }
package/dist/schemas.d.ts CHANGED
@@ -15,6 +15,8 @@ export type PageMeta = {
15
15
  title?: string;
16
16
  description?: string;
17
17
  ogImage?: string;
18
+ /** The public URL path, when it differs from the slug. See `pageMetaSchema`. */
19
+ path?: string;
18
20
  };
19
21
  export type PageDoc = {
20
22
  id: string;
@@ -28,6 +30,7 @@ export declare const pageMetaSchema: z.ZodObject<{
28
30
  title: z.ZodOptional<z.ZodString>;
29
31
  description: z.ZodOptional<z.ZodString>;
30
32
  ogImage: z.ZodOptional<z.ZodString>;
33
+ path: z.ZodOptional<z.ZodString>;
31
34
  }, z.core.$strip>;
32
35
  /** Lenient — accepts any block type (for ingesting content from sites with custom blocks). */
33
36
  export declare const pageDocSchemaLenient: z.ZodType<PageDoc>;
@@ -133,7 +136,7 @@ export declare const operationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
133
136
  title: z.ZodOptional<z.ZodString>;
134
137
  description: z.ZodOptional<z.ZodString>;
135
138
  ogImage: z.ZodOptional<z.ZodString>;
136
- }, z.core.$strip>;
139
+ }, z.core.$strict>;
137
140
  }, z.core.$strip>, z.ZodObject<{
138
141
  op: z.ZodLiteral<"update_site_config">;
139
142
  patch: z.ZodObject<{
@@ -267,7 +270,7 @@ export declare const editPlanSchema: z.ZodObject<{
267
270
  title: z.ZodOptional<z.ZodString>;
268
271
  description: z.ZodOptional<z.ZodString>;
269
272
  ogImage: z.ZodOptional<z.ZodString>;
270
- }, z.core.$strip>;
273
+ }, z.core.$strict>;
271
274
  }, z.core.$strip>, z.ZodObject<{
272
275
  op: z.ZodLiteral<"update_site_config">;
273
276
  patch: z.ZodObject<{
package/dist/schemas.js CHANGED
@@ -18,7 +18,22 @@ export const siteConfigSchema = z.object({
18
18
  export const pageMetaSchema = z.object({
19
19
  title: z.string().optional(),
20
20
  description: z.string().optional(),
21
- ogImage: z.string().optional()
21
+ ogImage: z.string().optional(),
22
+ /*
23
+ * The public path this page is served at, when that differs from its slug.
24
+ *
25
+ * Avocado has always treated the slug as the URL, which holds only while a
26
+ * site's routing is Avocado's. It is not for anything with a locale prefix,
27
+ * a basePath, or a CMS that owns its own routes: Paintball Arena Bern keeps
28
+ * one editable page per (document x language), so `/de/events` is a page
29
+ * identity, while the site serves German at the root and answers 404 for
30
+ * that path. An agent given the slug and told to open it goes to a 404 and
31
+ * has no way to know it was handed an id rather than a URL.
32
+ *
33
+ * Optional, and absent means "the slug is the path" — which stays true for
34
+ * every site whose routing Avocado does own.
35
+ */
36
+ path: z.string().optional()
22
37
  });
23
38
  const pageDocFields = {
24
39
  id: z.string().min(1),
@@ -165,13 +180,42 @@ const duplicatePageSchema = z.object({
165
180
  newTitle: z.string().min(1).optional(),
166
181
  afterPageSlug: z.string().min(1).optional()
167
182
  });
183
+ const UPDATE_PAGE_META_KEYS = ["title", "description", "ogImage"];
184
+ // The patch is strict, and carries its own wording, because stripping is what
185
+ // made this unreadable. An agent that sent `path` here had the key dropped by
186
+ // Zod before validation returned; the engine then reported a patch with no
187
+ // defined keys and listed the three fields the caller had *not* sent, so the
188
+ // only conclusion available was "path is unsettable for some other reason".
189
+ //
190
+ // `path` stays unsettable on purpose — it is the adapter's report of where the
191
+ // site already serves a page, and Avocado cannot move a route it does not own
192
+ // (see docs/ideas/page-identity-punch-list.md, PI-03/PI-07). What changes here
193
+ // is that the refusal says so.
194
+ //
195
+ // The message must keep the word "invalid" and must avoid "not found",
196
+ // "ambiguous", "unclear" and "no effective … change": classifyGuardrailError
197
+ // (ops-engine.ts) matches on those keywords, and Zod's own "Unrecognized key"
198
+ // text classifies as internal_error, which the planner's repair pass does not
199
+ // retry. Only unrecognized keys are reworded — every other issue keeps Zod's
200
+ // default, so a non-object patch still reads as a type error.
168
201
  const updatePageMetaSchema = z.object({
169
202
  op: z.literal("update_page_meta"),
170
203
  pageSlug: z.string().min(1),
171
- patch: z.object({
204
+ patch: z.strictObject({
172
205
  title: z.string().optional(),
173
206
  description: z.string().optional(),
174
207
  ogImage: z.string().optional()
208
+ }, {
209
+ error: (issue) => {
210
+ if (issue.code !== "unrecognized_keys")
211
+ return undefined;
212
+ const keys = issue.keys;
213
+ const named = keys.map((key) => `\`${key}\``).join(", ");
214
+ const why = keys.includes("path")
215
+ ? " `path` reports where the site already serves this page and is supplied by the site's adapter, so Avocado cannot reassign it here."
216
+ : "";
217
+ return `Invalid update_page_meta patch: unrecognized key ${named}. This patch accepts ${UPDATE_PAGE_META_KEYS.join(", ")}.${why}`;
218
+ }
175
219
  })
176
220
  });
177
221
  const updateSiteConfigSchema = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/shared",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,8 @@
18
18
  "contract"
19
19
  ],
20
20
  "dependencies": {
21
- "zod": "^4.3.6"
21
+ "zod": "^4.3.6",
22
+ "@avocadostudio-ai/richtext": "0.2.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "tsx": "^4.21.0",
@@ -33,7 +34,8 @@
33
34
  "exports": {
34
35
  ".": {
35
36
  "types": "./dist/index.d.ts",
36
- "import": "./dist/index.js"
37
+ "import": "./dist/index.js",
38
+ "default": "./dist/index.js"
37
39
  },
38
40
  "./contract/*": "./contract/*",
39
41
  "./package.json": "./package.json"
@@ -41,7 +43,7 @@
41
43
  "scripts": {
42
44
  "build": "tsc -p tsconfig.build.json",
43
45
  "typecheck": "tsc --noEmit",
44
- "test": "NODE_ENV=test tsx --test src/**/*.test.ts",
46
+ "test": "NODE_ENV=test node ../../scripts/run-tests.mjs",
45
47
  "gen:contract": "tsx scripts/gen-contract.ts",
46
48
  "gen:contract:check": "tsx scripts/gen-contract.ts --check"
47
49
  }