@avocadostudio-ai/shared 0.4.0 → 0.5.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/blocks/_registry.d.ts +21 -0
- package/dist/editable-coverage.js +27 -1
- package/dist/editor-block-meta.d.ts +70 -0
- package/dist/editor-block-meta.js +131 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/panel-coverage.d.ts +90 -0
- package/dist/panel-coverage.js +0 -0
- package/package.json +2 -2
|
@@ -76,6 +76,27 @@ export type FieldMeta = {
|
|
|
76
76
|
}[];
|
|
77
77
|
/** Whether the field is required. Auto-derived from Zod schema at registration. */
|
|
78
78
|
required?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* This field is edited in the property panel and nowhere else, by design.
|
|
81
|
+
*
|
|
82
|
+
* Not every editable prop is *drawn* as an element. A `sectionId` becomes an
|
|
83
|
+
* HTML `id`; a video's `poster` and an input's `placeholder` are attributes;
|
|
84
|
+
* a form field's `key` and `type` are configuration. None of them has an
|
|
85
|
+
* element that could carry `data-editable-target`, so the preview cannot
|
|
86
|
+
* offer them and the panel is the whole of their UI.
|
|
87
|
+
*
|
|
88
|
+
* `editableCoverage` takes this as "no marker expected", which is what makes
|
|
89
|
+
* the number mean something: without it a correct integration reports 88%
|
|
90
|
+
* and the integrator gating on it has to hard-code a magic threshold, at
|
|
91
|
+
* which point a real regression that drops one marker while a new field adds
|
|
92
|
+
* another slips through. With it, 100% is reachable and anything less is
|
|
93
|
+
* actionable.
|
|
94
|
+
*
|
|
95
|
+
* `inlineEditable: false` is a *different* statement, and narrower: it says a
|
|
96
|
+
* text field is not typed into on the page, and it says nothing about images.
|
|
97
|
+
* Use this one when there is no element at all.
|
|
98
|
+
*/
|
|
99
|
+
panelOnly?: boolean;
|
|
79
100
|
};
|
|
80
101
|
/** Metadata for list-type props (features, items, cards). */
|
|
81
102
|
export type ListFieldMeta = {
|
|
@@ -49,6 +49,10 @@ const DRAWN_KINDS = new Set(["text", "richtext", "image"]);
|
|
|
49
49
|
* button, and the panel offering one is the site saying the image is editable.
|
|
50
50
|
*/
|
|
51
51
|
function needsMarker(meta) {
|
|
52
|
+
// The site has said outright that nothing on the page draws this — a
|
|
53
|
+
// `sectionId`, a `<video poster>`, an input's `placeholder`. See `panelOnly`.
|
|
54
|
+
if (meta.panelOnly)
|
|
55
|
+
return false;
|
|
52
56
|
if (!DRAWN_KINDS.has(meta.kind))
|
|
53
57
|
return false;
|
|
54
58
|
if (meta.kind === "image")
|
|
@@ -122,6 +126,28 @@ function hasContent(value) {
|
|
|
122
126
|
function generalizeIndex(path) {
|
|
123
127
|
return path.replace(/\[\d+\]/g, "[]");
|
|
124
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Is this field marked — at its own path, or at a finer one inside it?
|
|
131
|
+
*
|
|
132
|
+
* A prop the manifest calls `buttons` is not always drawn as one element. A
|
|
133
|
+
* renderer that maps over it and marks `buttons[].label` on each button has
|
|
134
|
+
* instrumented that field *better* than a single marker on the container would,
|
|
135
|
+
* and reporting it as missing is the kind of unclosable finding that gets a
|
|
136
|
+
* checker ignored. Villa hit exactly this, and it was one of the six gaps that
|
|
137
|
+
* kept its report off 100%.
|
|
138
|
+
*
|
|
139
|
+
* The prefix test is on `key[` and `key.` rather than on `key`, so `title` is
|
|
140
|
+
* not counted as covering `titleColor`.
|
|
141
|
+
*/
|
|
142
|
+
function isCovered(key, paths) {
|
|
143
|
+
if (paths.has(key))
|
|
144
|
+
return true;
|
|
145
|
+
for (const path of paths) {
|
|
146
|
+
if (path.startsWith(`${key}[`) || path.startsWith(`${key}.`))
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
125
151
|
/**
|
|
126
152
|
* The item fields a list is expected to have markers for, given what is in it.
|
|
127
153
|
*
|
|
@@ -260,7 +286,7 @@ export function editableCoverage(manifest, blocks) {
|
|
|
260
286
|
if (knowsContent && !filled.has(key))
|
|
261
287
|
continue;
|
|
262
288
|
expected += 1;
|
|
263
|
-
if (!
|
|
289
|
+
if (!isCovered(key, paths)) {
|
|
264
290
|
missing.push(key);
|
|
265
291
|
}
|
|
266
292
|
else if (meta.kind === "image" && voids.has(key)) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the property panel should render for one block type.
|
|
3
|
+
*
|
|
4
|
+
* There are two sources and they disagree. The **manifest** is what the site
|
|
5
|
+
* serves over HTTP: its own schema, its own labels, its own list metadata. The
|
|
6
|
+
* **registry** is whatever is registered in the process doing the rendering —
|
|
7
|
+
* and in the editor that is its *own* bundled copy of `@avocadostudio-ai/blocks`,
|
|
8
|
+
* which registers the built-in types on import. A site that calls
|
|
9
|
+
* `registerBlocks` fills the registry inside the *orchestrator*; the editor's
|
|
10
|
+
* in-browser registry is a third source of truth that nobody registered into.
|
|
11
|
+
*
|
|
12
|
+
* This used to be inline in `PropertyPanel`, with the rule "the manifest decides
|
|
13
|
+
* *which* fields exist; the registry supplies richer metadata for any field in
|
|
14
|
+
* both". That is right when the site's `Hero` really is Avocado's `Hero`, and
|
|
15
|
+
* exactly wrong when the site re-registered that name with its own shape — which
|
|
16
|
+
* is the common case, because these are the names blocks *have*. A real
|
|
17
|
+
* integration collided on seven of its eight types and got a property panel
|
|
18
|
+
* describing Avocado's blocks: its `Left column` list was labelled
|
|
19
|
+
* `Left column items`, its `Variant` became `Style variant`.
|
|
20
|
+
*
|
|
21
|
+
* So the rule is now about *who said it*, not *who has it*:
|
|
22
|
+
*
|
|
23
|
+
* - The manifest **declared** the field (it is in `definition.fields`, not merely
|
|
24
|
+
* derived from the JSON schema) → the site said this out loud, and wins.
|
|
25
|
+
* - The manifest only **derived** it → the site said nothing beyond the schema,
|
|
26
|
+
* and the registry's entry is a better answer than an inference, because it
|
|
27
|
+
* carries `imageSpec`, `options`, `inlineEditable` and a human label.
|
|
28
|
+
*
|
|
29
|
+
* It also carries `discriminator` and `itemFieldsByType` through, which the old
|
|
30
|
+
* merge dropped by constructing a two-key object. That was pure data loss: the
|
|
31
|
+
* manifest carries both, and without them the panel narrows nothing, so every
|
|
32
|
+
* row of a polymorphic list is measured against the merged union of all
|
|
33
|
+
* branches. Rows whose branch has no text field then fall back to `Item 4`,
|
|
34
|
+
* `Item 5` — in the case that found this, a bullet list and a pair of CTAs, both
|
|
35
|
+
* fully described in the manifest the panel had in hand.
|
|
36
|
+
*/
|
|
37
|
+
import type { BlockMeta, FieldMeta, ListFieldMeta } from "./blocks/_registry.ts";
|
|
38
|
+
/** The shape this needs from a manifest entry — a structural subset of `BlockDefinition`. */
|
|
39
|
+
export type EditorBlockDefinition = {
|
|
40
|
+
type: string;
|
|
41
|
+
displayName?: string;
|
|
42
|
+
propsSchema: Record<string, unknown>;
|
|
43
|
+
fields?: Record<string, unknown>;
|
|
44
|
+
listFields?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Merge one manifest block definition with whatever the local registry knows
|
|
48
|
+
* about the same type.
|
|
49
|
+
*
|
|
50
|
+
* `registryMeta` is `undefined` for a type the registry has never heard of — a
|
|
51
|
+
* net-new block — and then the manifest is simply the answer.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveEditorBlockMeta(definition: EditorBlockDefinition, registryMeta: BlockMeta | undefined): BlockMeta;
|
|
54
|
+
/**
|
|
55
|
+
* The fields one list row is actually edited with.
|
|
56
|
+
*
|
|
57
|
+
* Polymorphic lists narrow to the branch named by the row's own discriminant
|
|
58
|
+
* value; anything else — no discriminator, or a value with no branch — falls
|
|
59
|
+
* back to the union in `itemFields`, which is the pre-narrowing behaviour and
|
|
60
|
+
* the right answer when there is nothing to narrow by.
|
|
61
|
+
*
|
|
62
|
+
* Exported because the panel and the editing-surface check must agree exactly.
|
|
63
|
+
* A checker that approximates this reports gaps the panel does not have, and
|
|
64
|
+
* misses the ones it does.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveListItemFields(listField: ListFieldMeta, item: Record<string, unknown>): {
|
|
67
|
+
fields: Record<string, FieldMeta>;
|
|
68
|
+
discriminantValue: string;
|
|
69
|
+
matchedBranch: boolean;
|
|
70
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the property panel should render for one block type.
|
|
3
|
+
*
|
|
4
|
+
* There are two sources and they disagree. The **manifest** is what the site
|
|
5
|
+
* serves over HTTP: its own schema, its own labels, its own list metadata. The
|
|
6
|
+
* **registry** is whatever is registered in the process doing the rendering —
|
|
7
|
+
* and in the editor that is its *own* bundled copy of `@avocadostudio-ai/blocks`,
|
|
8
|
+
* which registers the built-in types on import. A site that calls
|
|
9
|
+
* `registerBlocks` fills the registry inside the *orchestrator*; the editor's
|
|
10
|
+
* in-browser registry is a third source of truth that nobody registered into.
|
|
11
|
+
*
|
|
12
|
+
* This used to be inline in `PropertyPanel`, with the rule "the manifest decides
|
|
13
|
+
* *which* fields exist; the registry supplies richer metadata for any field in
|
|
14
|
+
* both". That is right when the site's `Hero` really is Avocado's `Hero`, and
|
|
15
|
+
* exactly wrong when the site re-registered that name with its own shape — which
|
|
16
|
+
* is the common case, because these are the names blocks *have*. A real
|
|
17
|
+
* integration collided on seven of its eight types and got a property panel
|
|
18
|
+
* describing Avocado's blocks: its `Left column` list was labelled
|
|
19
|
+
* `Left column items`, its `Variant` became `Style variant`.
|
|
20
|
+
*
|
|
21
|
+
* So the rule is now about *who said it*, not *who has it*:
|
|
22
|
+
*
|
|
23
|
+
* - The manifest **declared** the field (it is in `definition.fields`, not merely
|
|
24
|
+
* derived from the JSON schema) → the site said this out loud, and wins.
|
|
25
|
+
* - The manifest only **derived** it → the site said nothing beyond the schema,
|
|
26
|
+
* and the registry's entry is a better answer than an inference, because it
|
|
27
|
+
* carries `imageSpec`, `options`, `inlineEditable` and a human label.
|
|
28
|
+
*
|
|
29
|
+
* It also carries `discriminator` and `itemFieldsByType` through, which the old
|
|
30
|
+
* merge dropped by constructing a two-key object. That was pure data loss: the
|
|
31
|
+
* manifest carries both, and without them the panel narrows nothing, so every
|
|
32
|
+
* row of a polymorphic list is measured against the merged union of all
|
|
33
|
+
* branches. Rows whose branch has no text field then fall back to `Item 4`,
|
|
34
|
+
* `Item 5` — in the case that found this, a bullet list and a pair of CTAs, both
|
|
35
|
+
* fully described in the manifest the panel had in hand.
|
|
36
|
+
*/
|
|
37
|
+
import { resolveManifestFieldMeta } from "./block-manifest.js";
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
/** Keys the definition stated for itself, as opposed to keys inferred from its schema. */
|
|
42
|
+
function declaredKeys(source) {
|
|
43
|
+
return isRecord(source) ? new Set(Object.keys(source)) : new Set();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Merge one manifest block definition with whatever the local registry knows
|
|
47
|
+
* about the same type.
|
|
48
|
+
*
|
|
49
|
+
* `registryMeta` is `undefined` for a type the registry has never heard of — a
|
|
50
|
+
* net-new block — and then the manifest is simply the answer.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveEditorBlockMeta(definition, registryMeta) {
|
|
53
|
+
const derived = resolveManifestFieldMeta(definition);
|
|
54
|
+
if (!registryMeta) {
|
|
55
|
+
return {
|
|
56
|
+
displayName: definition.displayName ?? definition.type,
|
|
57
|
+
fields: derived.fields,
|
|
58
|
+
...(Object.keys(derived.listFields).length > 0 ? { listFields: derived.listFields } : {})
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const declaredFields = declaredKeys(definition.fields);
|
|
62
|
+
const fields = {};
|
|
63
|
+
for (const [key, fromManifest] of Object.entries(derived.fields)) {
|
|
64
|
+
const fromRegistry = registryMeta.fields[key];
|
|
65
|
+
// The site said it out loud, or the registry has nothing to add.
|
|
66
|
+
fields[key] = declaredFields.has(key) || !fromRegistry ? fromManifest : fromRegistry;
|
|
67
|
+
}
|
|
68
|
+
const declaredLists = declaredKeys(definition.listFields);
|
|
69
|
+
const listFields = {};
|
|
70
|
+
for (const [key, fromManifest] of Object.entries(derived.listFields)) {
|
|
71
|
+
const fromRegistry = registryMeta.listFields?.[key];
|
|
72
|
+
if (!fromRegistry || declaredLists.has(key)) {
|
|
73
|
+
listFields[key] = fromManifest;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const declaredItems = declaredKeys(isRecord(definition.listFields?.[key])
|
|
77
|
+
? definition.listFields[key].itemFields
|
|
78
|
+
: undefined);
|
|
79
|
+
const itemFields = {};
|
|
80
|
+
for (const [itemKey, itemFromManifest] of Object.entries(fromManifest.itemFields)) {
|
|
81
|
+
const itemFromRegistry = fromRegistry.itemFields[itemKey];
|
|
82
|
+
itemFields[itemKey] =
|
|
83
|
+
declaredItems.has(itemKey) || !itemFromRegistry ? itemFromManifest : itemFromRegistry;
|
|
84
|
+
}
|
|
85
|
+
listFields[key] = {
|
|
86
|
+
...fromManifest,
|
|
87
|
+
...(fromManifest.label ?? fromRegistry.label
|
|
88
|
+
? { label: fromManifest.label ?? fromRegistry.label }
|
|
89
|
+
: {}),
|
|
90
|
+
itemFields,
|
|
91
|
+
// Never synthesised, never dropped: whichever source has a branch map, keep it.
|
|
92
|
+
...(fromManifest.discriminator ?? fromRegistry.discriminator
|
|
93
|
+
? { discriminator: fromManifest.discriminator ?? fromRegistry.discriminator }
|
|
94
|
+
: {}),
|
|
95
|
+
...(fromManifest.itemFieldsByType ?? fromRegistry.itemFieldsByType
|
|
96
|
+
? { itemFieldsByType: fromManifest.itemFieldsByType ?? fromRegistry.itemFieldsByType }
|
|
97
|
+
: {})
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
...registryMeta,
|
|
102
|
+
displayName: definition.displayName ?? registryMeta.displayName,
|
|
103
|
+
fields,
|
|
104
|
+
...(Object.keys(listFields).length > 0 ? { listFields } : {})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The fields one list row is actually edited with.
|
|
109
|
+
*
|
|
110
|
+
* Polymorphic lists narrow to the branch named by the row's own discriminant
|
|
111
|
+
* value; anything else — no discriminator, or a value with no branch — falls
|
|
112
|
+
* back to the union in `itemFields`, which is the pre-narrowing behaviour and
|
|
113
|
+
* the right answer when there is nothing to narrow by.
|
|
114
|
+
*
|
|
115
|
+
* Exported because the panel and the editing-surface check must agree exactly.
|
|
116
|
+
* A checker that approximates this reports gaps the panel does not have, and
|
|
117
|
+
* misses the ones it does.
|
|
118
|
+
*/
|
|
119
|
+
export function resolveListItemFields(listField, item) {
|
|
120
|
+
if (!listField.discriminator) {
|
|
121
|
+
return { fields: listField.itemFields, discriminantValue: "", matchedBranch: false };
|
|
122
|
+
}
|
|
123
|
+
const raw = item[listField.discriminator];
|
|
124
|
+
const discriminantValue = raw === undefined || raw === null ? "" : String(raw);
|
|
125
|
+
const branch = listField.itemFieldsByType?.[discriminantValue];
|
|
126
|
+
return {
|
|
127
|
+
fields: branch ?? listField.itemFields,
|
|
128
|
+
discriminantValue,
|
|
129
|
+
matchedBranch: Boolean(branch)
|
|
130
|
+
};
|
|
131
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export type { FieldDiffKind, FieldDiff, BlockDiffStatus, BlockDiff, PageDiffStat
|
|
|
4
4
|
export { isImagePath, toAltPath, isAltPath, toImagePath, setPropAtPath } from "./editable-path.ts";
|
|
5
5
|
export { extractMarkedBlocks, editableCoverage, formatEditableCoverage } from "./editable-coverage.ts";
|
|
6
6
|
export type { MarkedBlock, BlockCoverageGap, EditableCoverage } from "./editable-coverage.ts";
|
|
7
|
+
export { panelCoverage, formatPanelCoverage, deriveRowLabel } from "./panel-coverage.ts";
|
|
8
|
+
export type { PanelCoverage, PanelFinding, PanelFindingCode } from "./panel-coverage.ts";
|
|
9
|
+
export { resolveEditorBlockMeta, resolveListItemFields } from "./editor-block-meta.ts";
|
|
10
|
+
export type { EditorBlockDefinition } from "./editor-block-meta.ts";
|
|
7
11
|
export { parseLink, resolveLink, normalizeLinkPath, isKnownRoute, internalPathForUrl, rankLinkTargets, rankFileTargets, suggestLinkTargets, scoreLinkCandidate, suggestLinkTarget, newTabKeyFor, linkAttrs, isFilePath, knownFileExtensions, linksInRichText, type LinkKind, type ParsedLink, type ResolvedLink, type LinkPageOption, type LinkFileOption, type LinkSuggestions, } from "./links.ts";
|
|
8
12
|
export { parseInline, parseRichText, parseRichTextBlocks, normalizeRichTextBody, resolveRichTextHeadingLevel, clampMarkdownHeadings, unescapeMarkdownText, isRichTextDoc, fromMarkdown, toMarkdown, mergeRichTextDoc, NODE, MARK, type InlineToken, type RichTextBlock, type RichTextList, type RichTextListItem, type RichTextDoc, type RichTextNode, type RichTextMark } from "@avocadostudio-ai/richtext";
|
|
9
13
|
export { blockDefinitionSchema, blockManifestSchema, buildBlockManifest, jsonSchemaLikeSchema, validateByJsonSchemaLike, findManifestSchemaIssue, type ManifestSchemaIssue, validateManifestDefaultProps, deriveFieldMetaFromSchema, resolveManifestFieldMeta, isProseMirrorDocSchema, type BlockDefinition, type BlockManifest } from "./block-manifest.ts";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ export { EDITOR_PROTOCOL_VERSION } from "./protocol.js";
|
|
|
2
2
|
export { getConfiguredDraftSecret, getSafeInternalRedirectPath, validateDraftSecret } from "./draft-mode.js";
|
|
3
3
|
export { isImagePath, toAltPath, isAltPath, toImagePath, setPropAtPath } from "./editable-path.js";
|
|
4
4
|
export { extractMarkedBlocks, editableCoverage, formatEditableCoverage } from "./editable-coverage.js";
|
|
5
|
+
export { panelCoverage, formatPanelCoverage, deriveRowLabel } from "./panel-coverage.js";
|
|
6
|
+
export { resolveEditorBlockMeta, resolveListItemFields } from "./editor-block-meta.js";
|
|
5
7
|
export { parseLink, resolveLink, normalizeLinkPath, isKnownRoute, internalPathForUrl, rankLinkTargets, rankFileTargets, suggestLinkTargets, scoreLinkCandidate, suggestLinkTarget, newTabKeyFor, linkAttrs, isFilePath, knownFileExtensions, linksInRichText, } from "./links.js";
|
|
6
8
|
/*
|
|
7
9
|
* The rich-text grammar lives in `@avocadostudio-ai/richtext`, which owns the
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Can a human actually edit this site in the property panel?
|
|
3
|
+
*
|
|
4
|
+
* `editableCoverage` answers the same question for the *preview*: which fields
|
|
5
|
+
* carry a marker the overlay can find. This is the panel's half, and it was the
|
|
6
|
+
* half with no check at all — which is why a real integration shipped a panel
|
|
7
|
+
* whose list rows read `Item 4`, `Item 5`, whose labels came from somebody
|
|
8
|
+
* else's block, and whose polymorphic branches were never narrowed. Every one of
|
|
9
|
+
* those was visible in the data the whole time. Nobody was asking.
|
|
10
|
+
*
|
|
11
|
+
* The check needs no browser, no screenshot and no model. It has the manifest
|
|
12
|
+
* (what the panel will render) and the site's own pages (what the rows really
|
|
13
|
+
* contain), and every finding below is a disagreement between the two.
|
|
14
|
+
*
|
|
15
|
+
* It resolves metadata through `resolveEditorBlockMeta` and rows through
|
|
16
|
+
* `resolveListItemFields` — the same functions the panel itself uses — so its
|
|
17
|
+
* findings are the panel's behaviour rather than a model of it. A checker that
|
|
18
|
+
* approximates the panel reports gaps the panel does not have and misses the
|
|
19
|
+
* ones it does, and gets switched off within a week.
|
|
20
|
+
*
|
|
21
|
+
* Pass `builtinTypes` (the registry the *editor* will run with) to get the
|
|
22
|
+
* collision findings. Without it, collisions are simply not reported — an
|
|
23
|
+
* absent input is never evidence.
|
|
24
|
+
*/
|
|
25
|
+
import { type EditorBlockDefinition } from "./editor-block-meta.ts";
|
|
26
|
+
import type { BlockMeta, FieldMeta } from "./blocks/_registry.ts";
|
|
27
|
+
export type PanelFindingCode =
|
|
28
|
+
/** A list row the panel labels `Item N` — present in the content, unidentifiable in the panel. */
|
|
29
|
+
"unlabelled_row"
|
|
30
|
+
/** A row whose discriminant value has no branch, so it is edited against the union of all branches. */
|
|
31
|
+
| "unmatched_branch"
|
|
32
|
+
/** A list declares a discriminator and no branch map, or vice versa. */
|
|
33
|
+
| "incomplete_polymorphism"
|
|
34
|
+
/** A prop the content holds that no field or list describes — uneditable, and invisible. */
|
|
35
|
+
| "orphan_prop"
|
|
36
|
+
/** A field declared for rows that no row ever has. Panel noise. */
|
|
37
|
+
| "phantom_field"
|
|
38
|
+
/** The type name exists in the editor's own registry with a different shape. */
|
|
39
|
+
| "colliding_type"
|
|
40
|
+
/** An image row labelled by its filename while a populated alt field sits beside it. */
|
|
41
|
+
| "filename_row_label";
|
|
42
|
+
export type PanelFinding = {
|
|
43
|
+
code: PanelFindingCode;
|
|
44
|
+
blockType: string;
|
|
45
|
+
/** Where the problem is, in the same path grammar operations use. */
|
|
46
|
+
path?: string;
|
|
47
|
+
/** One page slug that exhibits it, so the report points somewhere. */
|
|
48
|
+
exampleSlug?: string;
|
|
49
|
+
/** How many occurrences across everything examined. */
|
|
50
|
+
count: number;
|
|
51
|
+
detail: string;
|
|
52
|
+
};
|
|
53
|
+
export type PanelCoverage = {
|
|
54
|
+
/** List rows examined across every page. */
|
|
55
|
+
rowsExamined: number;
|
|
56
|
+
/** Rows the panel can label from their own content. */
|
|
57
|
+
rowsLabelled: number;
|
|
58
|
+
findings: PanelFinding[];
|
|
59
|
+
/** Block types on a page that the manifest does not describe. Not our business, but worth saying. */
|
|
60
|
+
unknownBlockTypes: string[];
|
|
61
|
+
};
|
|
62
|
+
type PageLike = {
|
|
63
|
+
slug?: string;
|
|
64
|
+
blocks?: Array<{
|
|
65
|
+
type?: string;
|
|
66
|
+
props?: Record<string, unknown>;
|
|
67
|
+
} | null | undefined>;
|
|
68
|
+
};
|
|
69
|
+
type ManifestLike = {
|
|
70
|
+
blocks: EditorBlockDefinition[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The label the panel puts on a collapsed list row.
|
|
74
|
+
*
|
|
75
|
+
* Mirrors `PropertyPanel`'s own derivation exactly, including its fallbacks: the
|
|
76
|
+
* first text-ish field with a value, else the filename of the first image, else
|
|
77
|
+
* `Item N`. Kept here so the two cannot drift — the panel imports this.
|
|
78
|
+
*/
|
|
79
|
+
export declare function deriveRowLabel(fields: Record<string, FieldMeta>, item: Record<string, unknown>, index: number, options?: {
|
|
80
|
+
discriminator?: string;
|
|
81
|
+
}): {
|
|
82
|
+
label: string;
|
|
83
|
+
source: "text" | "filename" | "fallback";
|
|
84
|
+
};
|
|
85
|
+
export declare function panelCoverage(manifest: ManifestLike, pages: PageLike[], options?: {
|
|
86
|
+
builtinTypes?: Record<string, BlockMeta>;
|
|
87
|
+
}): PanelCoverage;
|
|
88
|
+
/** A human-readable report, in the shape `formatEditableCoverage` uses. */
|
|
89
|
+
export declare function formatPanelCoverage(report: PanelCoverage): string;
|
|
90
|
+
export {};
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"zod": "^4.3.6",
|
|
22
|
-
"@avocadostudio-ai/richtext": "^0.
|
|
22
|
+
"@avocadostudio-ai/richtext": "^0.5.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"tsx": "^4.21.0",
|