@neatico/visual-core 0.14.3 → 0.14.64
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/candidateRanking.d.ts +31 -0
- package/dist/candidateRanking.js +55 -0
- package/dist/cjs/candidateRanking.js +61 -0
- package/dist/cjs/composition.js +62 -36
- package/dist/cjs/compositionNumbers.js +111 -0
- package/dist/cjs/compositionScenes.js +91 -38
- package/dist/cjs/compositionUseCases.js +145 -0
- package/dist/cjs/content.js +574 -120
- package/dist/cjs/contentBrief.js +82 -0
- package/dist/cjs/designBlueprint.js +94 -19
- package/dist/cjs/designSystem.js +536 -77
- package/dist/cjs/editorial.js +76 -20
- package/dist/cjs/fontCatalog.js +81 -23
- package/dist/cjs/formalCompositions.js +120 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/layoutContracts.js +57 -11
- package/dist/cjs/layouts.js +5 -92
- package/dist/cjs/nativeSocialParameters.js +145 -0
- package/dist/cjs/pagination.js +27 -7
- package/dist/cjs/parameterDefaults.js +20 -0
- package/dist/cjs/recipeParameters.js +304 -0
- package/dist/cjs/socialLayouts.js +127 -387
- package/dist/cjs/styleManifest.js +107 -0
- package/dist/cjs/text.js +12 -2
- package/dist/cjs/textPresentation.js +68 -0
- package/dist/composition.d.ts +4 -4
- package/dist/composition.js +62 -36
- package/dist/compositionNumbers.d.ts +4 -0
- package/dist/compositionNumbers.js +108 -0
- package/dist/compositionScenes.d.ts +2 -2
- package/dist/compositionScenes.js +91 -38
- package/dist/compositionUseCases.d.ts +1 -0
- package/dist/compositionUseCases.js +142 -0
- package/dist/content.d.ts +65 -20
- package/dist/content.js +575 -121
- package/dist/contentBrief.d.ts +16 -0
- package/dist/contentBrief.js +78 -0
- package/dist/designBlueprint.d.ts +4 -4
- package/dist/designBlueprint.js +95 -20
- package/dist/designSystem.d.ts +11 -7
- package/dist/designSystem.js +537 -79
- package/dist/editorial.d.ts +1 -1
- package/dist/editorial.js +77 -21
- package/dist/fontCatalog.d.ts +1 -1
- package/dist/fontCatalog.js +81 -23
- package/dist/formalCompositions.d.ts +26 -0
- package/dist/formalCompositions.js +116 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/layoutContracts.d.ts +3 -3
- package/dist/layoutContracts.js +58 -12
- package/dist/layouts.d.ts +2 -2
- package/dist/layouts.js +5 -92
- package/dist/nativeSocialParameters.d.ts +38 -0
- package/dist/nativeSocialParameters.js +139 -0
- package/dist/pagination.js +27 -7
- package/dist/parameterDefaults.d.ts +2 -0
- package/dist/parameterDefaults.js +17 -0
- package/dist/recipeParameters.d.ts +34 -0
- package/dist/recipeParameters.js +300 -0
- package/dist/socialLayouts.d.ts +40 -232
- package/dist/socialLayouts.js +127 -387
- package/dist/styleManifest.d.ts +20 -0
- package/dist/styleManifest.js +103 -0
- package/dist/text.js +12 -2
- package/dist/textPresentation.d.ts +29 -0
- package/dist/textPresentation.js +64 -0
- package/package.json +1 -1
package/dist/layouts.js
CHANGED
|
@@ -1,100 +1,13 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Content intents for structured presentations; not a separate renderer catalog. */
|
|
2
2
|
export const PRESENTATION_LAYOUT_IDS = [
|
|
3
3
|
"cover",
|
|
4
|
-
"agenda",
|
|
5
4
|
"section",
|
|
6
5
|
"statement",
|
|
7
|
-
"bullets",
|
|
8
|
-
"two-column",
|
|
9
|
-
"comparison",
|
|
10
|
-
"three-column",
|
|
11
|
-
"process",
|
|
12
|
-
"timeline",
|
|
13
|
-
"metrics",
|
|
14
|
-
"quote",
|
|
15
|
-
"image-left",
|
|
16
|
-
"image-right",
|
|
17
|
-
"image-full",
|
|
18
|
-
"closing",
|
|
19
6
|
"cards",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"team",
|
|
23
|
-
"pricing",
|
|
24
|
-
"faq",
|
|
25
|
-
"swot",
|
|
26
|
-
"scorecard",
|
|
7
|
+
"process",
|
|
8
|
+
"comparison",
|
|
27
9
|
"bar-chart",
|
|
28
|
-
"
|
|
10
|
+
"quote",
|
|
29
11
|
"table",
|
|
30
|
-
"
|
|
31
|
-
"case-study",
|
|
32
|
-
"architecture",
|
|
33
|
-
"risk",
|
|
34
|
-
"action-plan",
|
|
35
|
-
"usage-breakdown",
|
|
36
|
-
"organization",
|
|
37
|
-
"editorial-quote",
|
|
38
|
-
"research-highlight",
|
|
39
|
-
"product-showcase",
|
|
40
|
-
"split-bar-compare",
|
|
41
|
-
"benchmark-bars",
|
|
42
|
-
"block-banner",
|
|
43
|
-
"progress-roadmap",
|
|
44
|
-
"fan-deck",
|
|
45
|
-
"contents-index",
|
|
46
|
-
"contents-chapters",
|
|
47
|
-
"contents-grid",
|
|
48
|
-
"definition-card",
|
|
49
|
-
"topic-branches",
|
|
50
|
-
"radial-mindmap",
|
|
51
|
-
"tips-card",
|
|
52
|
-
"code-snippet",
|
|
53
|
-
"series-carousel",
|
|
54
|
-
"reflection-card",
|
|
55
|
-
"video-cover",
|
|
56
|
-
"digest-card",
|
|
57
|
-
"chat-preview",
|
|
58
|
-
"recommendation-feed",
|
|
59
|
-
"before-after",
|
|
60
|
-
"course-syllabus",
|
|
61
|
-
"concept-explainer",
|
|
62
|
-
"persona-question",
|
|
63
|
-
"nutrient-symptom-grid",
|
|
64
|
-
"milestone-history",
|
|
65
|
-
"release-notes",
|
|
66
|
-
"product-listing",
|
|
67
|
-
"flash-sale",
|
|
68
|
-
"event-banner",
|
|
69
|
-
"startup-journey",
|
|
70
|
-
"vision-goals",
|
|
71
|
-
"tips-grid",
|
|
72
|
-
"experiment-report",
|
|
73
|
-
"leaderboard",
|
|
74
|
-
"win-journey",
|
|
75
|
-
"device-price-promo",
|
|
76
|
-
"mega-sale-typographic",
|
|
77
|
-
"illustration-process",
|
|
78
|
-
"glass-neon-sale",
|
|
79
|
-
"free-trial-hero",
|
|
80
|
-
"feature-chip-hero",
|
|
81
|
-
"event-speaker-card",
|
|
82
|
-
"corporate-hero",
|
|
83
|
-
"release-version-hero",
|
|
84
|
-
"og-cover",
|
|
85
|
-
"gradient-cover",
|
|
86
|
-
"bold-diagonal-cover",
|
|
87
|
-
"split-cover",
|
|
88
|
-
"article-feed-card",
|
|
89
|
-
"article-summary-card",
|
|
90
|
-
"paragraph-summary-card",
|
|
91
|
-
"summary-card-stack",
|
|
92
|
-
"article-repost-card",
|
|
93
|
-
"article-core-digest",
|
|
94
|
-
"product-thread-mockup",
|
|
95
|
-
"agent-notification",
|
|
96
|
-
"angled-ui-launch",
|
|
97
|
-
"repo-card",
|
|
98
|
-
"magazine-pull-quote",
|
|
99
|
-
"question-answer-quote"
|
|
12
|
+
"closing",
|
|
100
13
|
];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type TextPresentation } from './textPresentation.js';
|
|
2
|
+
import type { ParameterSchema } from './recipeParameters.js';
|
|
3
|
+
/** Native slots keep renderer-specific content separate from semantic page projection. */
|
|
4
|
+
export interface NativeSocialParameters {
|
|
5
|
+
textPresentation?: TextPresentation[];
|
|
6
|
+
decorationEmoji?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
body: string;
|
|
9
|
+
eyebrow?: string;
|
|
10
|
+
emphasis?: string[];
|
|
11
|
+
titleLines?: string[];
|
|
12
|
+
cta?: string;
|
|
13
|
+
value?: string;
|
|
14
|
+
attribution?: string;
|
|
15
|
+
imageAssetId?: string;
|
|
16
|
+
comparisonLabels?: [string, string];
|
|
17
|
+
items?: {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
assetId?: string;
|
|
21
|
+
illustration?: 'document' | 'collaboration' | 'review' | 'feedback' | 'sequence' | 'comparison' | 'structure';
|
|
22
|
+
primaryValue?: number;
|
|
23
|
+
secondaryValue?: number;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
export interface NativeSocialContract {
|
|
27
|
+
parameterModel: 'native-social';
|
|
28
|
+
parameterSchema: ParameterSchema;
|
|
29
|
+
importance: Readonly<Record<string, number>>;
|
|
30
|
+
generationPolicy: Readonly<Record<string, readonly string[]>>;
|
|
31
|
+
}
|
|
32
|
+
export declare const COMPOSITION_ILLUSTRATIONS: readonly ["document", "collaboration", "review", "feedback", "sequence", "comparison", "structure"];
|
|
33
|
+
/** Every field corresponds to an authored visible slot; no scene-based eligibility filter. */
|
|
34
|
+
export declare function nativeSocialParameterContract(id: string): NativeSocialContract;
|
|
35
|
+
/** Apply the small closed schema vocabulary used by native slots on every local render. */
|
|
36
|
+
export declare function validateNativeSocialParameters(id: string, raw: unknown): NativeSocialParameters;
|
|
37
|
+
/** Numeric and image identities remain available when a native design becomes editable content. */
|
|
38
|
+
export declare function nativeSocialContentPage(values: NativeSocialParameters, original: import('./content.js').ContentPage): import('./content.js').ContentPage;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { compositionParameterDefaults } from './parameterDefaults.js';
|
|
2
|
+
import { textPresentationSchema, validateTextPresentations } from './textPresentation.js';
|
|
3
|
+
import { SOCIAL_LAYOUT_CATALOG } from './socialLayouts.js';
|
|
4
|
+
const slots = {
|
|
5
|
+
'note-question': {}, 'note-marker': {}, 'note-editorial': {}, 'note-checklist': { items: 3 }, 'note-photo': { image: true }, 'note-news': { value: true },
|
|
6
|
+
'chat-messenger': { items: 3 }, 'chat-imessage': { items: 3 }, 'chat-telegram': { items: 3 }, 'chat-workplace': { items: 3 },
|
|
7
|
+
'time-notice': { value: true }, 'news-focus': {}, manifesto: {}, launch: { image: true }, bento: { items: 3 }, spotlight: { items: 3, value: true, image: true },
|
|
8
|
+
ticket: { items: 2, value: true }, webinar: { value: true, image: true }, quote: { attribution: true }, testimonial: { attribution: true }, metric: { value: true },
|
|
9
|
+
report: { items: 3 }, checklist: { items: 3 }, steps: { items: 3 }, comparison: { items: 2 }, myth: { items: 2 }, pricing: { items: 3, value: true }, offer: { value: true, image: true },
|
|
10
|
+
editorial: { image: true }, story: { image: true }, hiring: { items: 3 }, team: { items: 3, itemImages: true }, course: { items: 3 }, 'case-study': { items: 3 }, notice: {}, community: { value: true },
|
|
11
|
+
menu: { items: 3 }, specification: { items: 3 }, itinerary: { items: 3 }, schedule: { items: 3 }, safety: { items: 3 }, faq: { items: 3 },
|
|
12
|
+
'split-bar-compare': { items: 3, quantities: 'comparison' }, 'benchmark-bars': { items: 3, quantities: 'benchmark' }, 'usage-breakdown': { items: 3, quantities: 'share' },
|
|
13
|
+
organization: { items: 3 }, 'editorial-quote': { attribution: true }, 'research-highlight': { items: 2 }, 'product-showcase': { items: 3, image: true },
|
|
14
|
+
};
|
|
15
|
+
const copy = (maxLength, description, minLength = 1) => ({ type: 'string', minLength, maxLength, description });
|
|
16
|
+
const object = (properties) => ({ type: 'object', additionalProperties: false, properties, required: Object.keys(properties) });
|
|
17
|
+
const asset = () => copy(160, 'An existing supplied asset ID. Use neatico-editorial-placeholder when no matching source image exists.');
|
|
18
|
+
export const COMPOSITION_ILLUSTRATIONS = ['document', 'collaboration', 'review', 'feedback', 'sequence', 'comparison', 'structure'];
|
|
19
|
+
const illustratedSlots = new Set(['bento', 'course', 'case-study', 'report', 'product-showcase', 'comparison', 'research-highlight']);
|
|
20
|
+
const provenance = ['user-fact', 'derived', 'creative', 'simulated'];
|
|
21
|
+
// These authored slots have been measured with complete provider sentences at
|
|
22
|
+
// square, landscape, and portrait ratios. Compact labels keep their own limits.
|
|
23
|
+
const sentenceBodySlots = new Set(['note-question', 'note-marker', 'note-editorial', 'chat-imessage', 'chat-telegram', 'news-focus', 'manifesto', 'launch', 'spotlight', 'comparison', 'myth', 'editorial', 'story', 'notice']);
|
|
24
|
+
const supportingItemSlots = new Set(['team', 'course', 'specification', 'schedule', 'research-highlight']);
|
|
25
|
+
/** Every field corresponds to an authored visible slot; no scene-based eligibility filter. */
|
|
26
|
+
export function nativeSocialParameterContract(id) {
|
|
27
|
+
const spec = slots[id];
|
|
28
|
+
if (!spec || !SOCIAL_LAYOUT_CATALOG.some(item => item.id === id))
|
|
29
|
+
throw new Error('COMPOSITION_UNKNOWN_NATIVE_SOCIAL');
|
|
30
|
+
const properties = { title: copy(id === 'note-question' ? 44 : 40, 'Concise audience-facing headline.'), body: copy(id === 'organization' ? 32 : id === 'case-study' ? 64 : ['quote', 'testimonial', 'editorial-quote'].includes(id) ? 140 : sentenceBodySlots.has(id) ? 96 : 80, id === 'organization' ? 'A short label for the parent or coordinating role; never invent a real person.' : 'One supporting sentence, or the quoted passage for a quote composition.') };
|
|
31
|
+
if (!id.startsWith('chat-') && !['note-photo', 'note-checklist'].includes(id))
|
|
32
|
+
properties.eyebrow = { ...copy(20, 'Optional concise section label. Use an empty string when no label improves the message.', 0), default: '' };
|
|
33
|
+
if (!id.startsWith('chat-') && !['note-photo', 'note-checklist'].includes(id))
|
|
34
|
+
properties.cta = { ...copy(24, 'Optional short call to action appropriate to the input. Use an empty string when this is purely informational.', 0), default: '' };
|
|
35
|
+
if (['note-marker', 'note-editorial', 'note-question'].includes(id))
|
|
36
|
+
properties.titleLines = { type: 'array', minItems: 0, maxItems: 4, default: [], items: copy(40, 'One complete phrase from the title, preserving its original language.'), description: 'Preferred title lines: one item for a single line, two to four for a multiline heading, or empty for automatic fitting. Concatenating the phrases must preserve the complete title and word boundaries. The renderer may reflow only when the requested lines cannot fit legibly.' };
|
|
37
|
+
if (id === 'note-marker')
|
|
38
|
+
properties.emphasis = { type: 'array', minItems: 0, maxItems: 2, default: [], items: copy(40, 'An exact contiguous phrase from title to highlight.'), description: 'Choose one or two important exact title phrases. Empty means the renderer highlights the final title line.' };
|
|
39
|
+
if (spec.value)
|
|
40
|
+
properties.value = copy(24, 'The focal quantity, price, time, or short value statement. Label fictional quantities as simulated.');
|
|
41
|
+
if (spec.attribution)
|
|
42
|
+
properties.attribution = copy(48, 'Exact supplied attribution, or an empty string. Never invent an endorsement.', 0);
|
|
43
|
+
if (spec.image)
|
|
44
|
+
properties.imageAssetId = asset();
|
|
45
|
+
if (spec.items) {
|
|
46
|
+
const item = { title: copy(id === 'faq' ? 40 : id === 'case-study' ? 18 : 20, id === 'faq' ? 'A concise audience question ending in a question mark.' : 'A short item label.'), description: copy(id === 'faq' ? 64 : spec.quantities || id === 'case-study' ? 32 : ['comparison', 'myth'].includes(id) ? 80 : supportingItemSlots.has(id) ? 48 : 40, 'Concise supporting copy.', spec.quantities ? 0 : 1) };
|
|
47
|
+
if (id === 'faq')
|
|
48
|
+
item.title.pattern = '^.+[??]$';
|
|
49
|
+
if (illustratedSlots.has(id))
|
|
50
|
+
item.illustration = { type: 'string', enum: COMPOSITION_ILLUSTRATIONS, default: 'structure', description: 'An editable native illustration that explains this item. Choose document for material, review for checking, collaboration for people, feedback for revision, sequence for ordered actions, comparison for alternatives, or structure for organization. Never omit the visual slot.' };
|
|
51
|
+
if (spec.itemImages)
|
|
52
|
+
item.assetId = asset();
|
|
53
|
+
if (spec.quantities) {
|
|
54
|
+
item.primaryValue = { type: 'number', minimum: 0, maximum: 100, description: spec.quantities === 'comparison' ? 'A percentage from zero to one hundred. primaryValue plus secondaryValue must equal exactly 100 in this item.' : 'A percentage from zero to one hundred. Never encode numbers inside descriptive copy.' };
|
|
55
|
+
if (spec.quantities !== 'share')
|
|
56
|
+
item.secondaryValue = { type: 'number', minimum: 0, maximum: 100, description: 'The comparison or benchmark percentage.' };
|
|
57
|
+
if (spec.quantities === 'share')
|
|
58
|
+
item.description = { const: '' };
|
|
59
|
+
}
|
|
60
|
+
properties.items = { type: 'array', minItems: spec.items, maxItems: spec.items, items: object(item), ...(spec.quantities === 'share' ? { description: 'Exactly three nonnegative percentage shares totaling at most one hundred.' } : {}) };
|
|
61
|
+
}
|
|
62
|
+
if (spec.quantities === 'comparison' || spec.quantities === 'benchmark')
|
|
63
|
+
properties.comparisonLabels = { type: 'array', minItems: 2, maxItems: 2, items: copy(20, 'A supplied or explicitly illustrative series label.') };
|
|
64
|
+
if (['note-question', 'note-marker', 'note-editorial'].includes(id))
|
|
65
|
+
properties.decorationEmoji = { type: 'string', enum: ['', '✨', '💡', '🌱', '💬', '🎯', '📚'], default: '', description: 'Optional built-in decorative emoji for informal social notes. Choose one relevant symbol, or empty for the authored illustration. Never use it as factual evidence.' };
|
|
66
|
+
properties.textPresentation = textPresentationSchema();
|
|
67
|
+
const contentKeys = Object.keys(properties).filter(key => !['textPresentation', 'decorationEmoji'].includes(key));
|
|
68
|
+
const importance = Object.fromEntries(contentKeys.map(key => [key, key === 'title' ? 3 : key === 'items' ? 7 : ['eyebrow', 'cta'].includes(key) ? .5 : 2]));
|
|
69
|
+
return { parameterModel: 'native-social', parameterSchema: object(properties), importance, generationPolicy: Object.fromEntries(contentKeys.map(key => [key, provenance])) };
|
|
70
|
+
}
|
|
71
|
+
/** Apply the small closed schema vocabulary used by native slots on every local render. */
|
|
72
|
+
export function validateNativeSocialParameters(id, raw) {
|
|
73
|
+
const check = (value, schema) => {
|
|
74
|
+
if (Object.hasOwn(schema, 'const'))
|
|
75
|
+
return value === schema.const;
|
|
76
|
+
if (Array.isArray(schema.enum))
|
|
77
|
+
return schema.enum.includes(value);
|
|
78
|
+
if (schema.type === 'string')
|
|
79
|
+
return typeof value === 'string' && Array.from(value).length >= Number(schema.minLength || 0) && Array.from(value).length <= Number(schema.maxLength) && (!schema.pattern || new RegExp(String(schema.pattern), 'u').test(value));
|
|
80
|
+
if (schema.type === 'boolean')
|
|
81
|
+
return typeof value === 'boolean';
|
|
82
|
+
if (schema.type === 'number')
|
|
83
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= Number(schema.minimum) && value <= Number(schema.maximum);
|
|
84
|
+
if (schema.type === 'array')
|
|
85
|
+
return Array.isArray(value) && value.length >= Number(schema.minItems) && value.length <= Number(schema.maxItems) && value.every(item => check(item, schema.items));
|
|
86
|
+
if (schema.type === 'object') {
|
|
87
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
88
|
+
return false;
|
|
89
|
+
const properties = schema.properties, object = value;
|
|
90
|
+
return Object.keys(object).every(key => Object.hasOwn(properties, key) && check(object[key], properties[key])) && schema.required.every(key => Object.hasOwn(object, key) && check(object[key], properties[key]));
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
};
|
|
94
|
+
const schema = nativeSocialParameterContract(id).parameterSchema;
|
|
95
|
+
raw = compositionParameterDefaults(schema, raw);
|
|
96
|
+
if (!check(raw, schema))
|
|
97
|
+
throw new Error('CANDIDATE_INVALID_PARAMETERS');
|
|
98
|
+
const values = raw;
|
|
99
|
+
validateTextPresentations(values.textPresentation, values);
|
|
100
|
+
if (values.titleLines?.length) {
|
|
101
|
+
const normalize = (copy) => copy.normalize('NFC').replace(/\s+/gu, ' ').trim();
|
|
102
|
+
const title = normalize(values.title);
|
|
103
|
+
let cursor = 0;
|
|
104
|
+
for (const phrase of values.titleLines) {
|
|
105
|
+
const line = normalize(phrase), hadSpace = title[cursor] === ' ';
|
|
106
|
+
if (hadSpace)
|
|
107
|
+
cursor++;
|
|
108
|
+
// CJK phrases may meet without spaces, but Latin words cannot be split.
|
|
109
|
+
const brokenWord = cursor > 0 && !hadSpace && /[\p{Script=Latin}\p{Mark}\d’'-]/u.test(title[cursor - 1]) && /^[\p{Script=Latin}\p{Mark}\d’'-]/u.test(line);
|
|
110
|
+
if (!line || brokenWord || !title.startsWith(line, cursor))
|
|
111
|
+
throw new Error('CANDIDATE_INVALID_TITLE_LINES');
|
|
112
|
+
cursor += line.length;
|
|
113
|
+
}
|
|
114
|
+
if (cursor !== title.length)
|
|
115
|
+
throw new Error('CANDIDATE_INVALID_TITLE_LINES');
|
|
116
|
+
}
|
|
117
|
+
if (values.emphasis?.some(phrase => !values.title.includes(phrase)))
|
|
118
|
+
throw new Error('CANDIDATE_INVALID_EMPHASIS');
|
|
119
|
+
if (id === 'usage-breakdown' && values.items.reduce((sum, item) => sum + item.primaryValue, 0) > 100.0001)
|
|
120
|
+
throw new Error('CANDIDATE_INVALID_PERCENTAGES');
|
|
121
|
+
if (id === 'split-bar-compare' && values.items.some(item => Math.abs(item.primaryValue + item.secondaryValue - 100) > .001))
|
|
122
|
+
throw new Error('CANDIDATE_INVALID_PERCENTAGES');
|
|
123
|
+
return JSON.parse(JSON.stringify(values));
|
|
124
|
+
}
|
|
125
|
+
/** Numeric and image identities remain available when a native design becomes editable content. */
|
|
126
|
+
export function nativeSocialContentPage(values, original) {
|
|
127
|
+
const proof = original.evidence, blocks = [{ id: 'native-body', kind: 'paragraph', text: values.body, evidence: proof }];
|
|
128
|
+
if (values.items?.length)
|
|
129
|
+
blocks.push({ id: 'native-items', kind: 'features', evidence: proof, entries: values.items.map(item => ({ title: item.title, ...(item.illustration ? { illustration: item.illustration } : {}), description: [item.description, ...[item.primaryValue, item.secondaryValue].filter(value => value !== undefined).map(value => `${value}%`)].filter(Boolean).join(' · ') })) });
|
|
130
|
+
for (const field of ['value', 'attribution', 'eyebrow', 'cta'])
|
|
131
|
+
if (values[field])
|
|
132
|
+
blocks.push({ id: `native-${field}`, kind: 'paragraph', text: values[field], evidence: proof });
|
|
133
|
+
if (values.comparisonLabels)
|
|
134
|
+
blocks.push({ id: 'native-series', kind: 'paragraph', evidence: proof, text: values.comparisonLabels.join(' / ') });
|
|
135
|
+
const images = [values.imageAssetId, ...(values.items || []).map(item => item.assetId)].filter((id) => !!id);
|
|
136
|
+
for (const [index, assetId] of images.entries())
|
|
137
|
+
blocks.push({ id: `native-image-${index}`, kind: 'image', evidence: [{ sourceId: assetId }], sourceId: assetId, assetId, alt: values.title, treatment: assetId === 'neatico-editorial-placeholder' ? 'diagram' : 'photograph', status: 'available' });
|
|
138
|
+
return { ...original, title: values.title, blocks };
|
|
139
|
+
}
|
package/dist/pagination.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
/** Split only at explicit word/sentence boundaries. Never cut identifiers,
|
|
2
2
|
* decimal values, or unbroken URLs to force them onto a smaller canvas. */
|
|
3
3
|
export function splitCopy(copy) {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
let at;
|
|
5
|
+
let distance = Infinity;
|
|
6
|
+
const midpoint = copy.length / 2;
|
|
7
|
+
for (const match of copy.matchAll(/\s+|[。!?;]|[.!?;](?=\s|$)/gu)) {
|
|
8
|
+
const index = match.index + match[0].length;
|
|
9
|
+
if (index <= 0 || index >= copy.length)
|
|
10
|
+
continue;
|
|
11
|
+
const candidateDistance = Math.abs(index - midpoint);
|
|
12
|
+
if (candidateDistance < distance) {
|
|
13
|
+
at = index;
|
|
14
|
+
distance = candidateDistance;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (at === undefined)
|
|
6
18
|
return;
|
|
7
|
-
const at = breaks.sort((a, b) => Math.abs(a - copy.length / 2) - Math.abs(b - copy.length / 2))[0];
|
|
8
19
|
return [copy.slice(0, at), copy.slice(at)];
|
|
9
20
|
}
|
|
10
21
|
/** No paraphrasing, invented continuation labels or removed constraints. The
|
|
@@ -15,18 +26,27 @@ export function splitContent(source) {
|
|
|
15
26
|
const items = source.items || [];
|
|
16
27
|
if (items.length > 1) {
|
|
17
28
|
const middle = Math.ceil(items.length / 2);
|
|
18
|
-
return [
|
|
29
|
+
return [
|
|
30
|
+
{ ...base, items: items.slice(0, middle) },
|
|
31
|
+
{ ...base, body: "", items: items.slice(middle) },
|
|
32
|
+
];
|
|
19
33
|
}
|
|
20
34
|
if (items.length && source.body?.trim())
|
|
21
|
-
return [
|
|
35
|
+
return [
|
|
36
|
+
{ ...base, items: [] },
|
|
37
|
+
{ ...base, body: "", items },
|
|
38
|
+
];
|
|
22
39
|
if (source.body) {
|
|
23
40
|
const parts = splitCopy(source.body);
|
|
24
41
|
if (parts)
|
|
25
|
-
return parts.map(body => ({ ...base, body }));
|
|
42
|
+
return parts.map((body) => ({ ...base, body }));
|
|
26
43
|
}
|
|
27
44
|
if (items.length === 1) {
|
|
28
45
|
const parts = splitCopy(items[0].description);
|
|
29
46
|
if (parts)
|
|
30
|
-
return parts.map(description => ({
|
|
47
|
+
return parts.map((description) => ({
|
|
48
|
+
...base,
|
|
49
|
+
items: [{ ...items[0], description }],
|
|
50
|
+
}));
|
|
31
51
|
}
|
|
32
52
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Apply only renderer-authored defaults. Never rewrite supplied copy or values. */
|
|
2
|
+
export function compositionParameterDefaults(schema, value) {
|
|
3
|
+
if (value === undefined && Object.hasOwn(schema, 'default'))
|
|
4
|
+
return JSON.parse(JSON.stringify(schema.default));
|
|
5
|
+
if (schema.type === 'object' && value && typeof value === 'object' && !Array.isArray(value)) {
|
|
6
|
+
const next = { ...value };
|
|
7
|
+
for (const [key, child] of Object.entries(schema.properties || {})) {
|
|
8
|
+
const resolved = compositionParameterDefaults(child, next[key]);
|
|
9
|
+
if (resolved !== undefined)
|
|
10
|
+
next[key] = resolved;
|
|
11
|
+
}
|
|
12
|
+
return next;
|
|
13
|
+
}
|
|
14
|
+
if (schema.type === 'array' && Array.isArray(value))
|
|
15
|
+
return value.map((item, index) => compositionParameterDefaults((Array.isArray(schema.items) ? schema.items[index] : schema.items) || {}, item));
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type DesignRecipe } from "./designSystem.js";
|
|
2
|
+
export type ParameterSchema = Readonly<Record<string, unknown>>;
|
|
3
|
+
export type CompositionKind = "presentation" | "social" | "article-cover" | "article-illustration";
|
|
4
|
+
export interface RecipeParameterContract {
|
|
5
|
+
parameterModel?: "semantic" | "native-social";
|
|
6
|
+
id: string;
|
|
7
|
+
schemaVersion: 1;
|
|
8
|
+
supportedKinds: readonly CompositionKind[];
|
|
9
|
+
semanticDescription: string;
|
|
10
|
+
scenes: readonly string[];
|
|
11
|
+
rendererKey: string;
|
|
12
|
+
capacity: {
|
|
13
|
+
minEntries: number;
|
|
14
|
+
maxEntries: number;
|
|
15
|
+
requiresImage: boolean;
|
|
16
|
+
};
|
|
17
|
+
parameterSchema: ParameterSchema;
|
|
18
|
+
importance: Readonly<Record<string, number>>;
|
|
19
|
+
generationPolicy: Readonly<Record<string, readonly string[]>>;
|
|
20
|
+
themeBindings: readonly string[];
|
|
21
|
+
fallbackPolicy: {
|
|
22
|
+
missingContent: "derive-or-labeled-example";
|
|
23
|
+
missingAsset: "search-or-native-placeholder";
|
|
24
|
+
omitCandidate: false;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Fixed renderer parameters, not executable layout trees. The complete schema
|
|
28
|
+
* travels with each formal generation batch; summaries are scheduling aids only.
|
|
29
|
+
*/
|
|
30
|
+
export declare function recipeParameterContract(recipe: DesignRecipe, supportedKinds?: RecipeParameterContract["supportedKinds"]): RecipeParameterContract;
|
|
31
|
+
/** Shared recipes are one part of the formal catalog; host-specific renderer
|
|
32
|
+
* definitions register through the same contract constructor during migration.
|
|
33
|
+
*/
|
|
34
|
+
export declare const RECIPE_PARAMETER_CONTRACTS: readonly RecipeParameterContract[];
|