@hale-bopp/valentino-engine 1.0.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/AGENTS.md +49 -0
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/bin/commands/audit.d.ts +2 -0
- package/dist/bin/commands/audit.d.ts.map +1 -0
- package/dist/bin/commands/audit.js +24 -0
- package/dist/bin/commands/audit.js.map +1 -0
- package/dist/bin/commands/catalog.d.ts +2 -0
- package/dist/bin/commands/catalog.d.ts.map +1 -0
- package/dist/bin/commands/catalog.js +21 -0
- package/dist/bin/commands/catalog.js.map +1 -0
- package/dist/bin/commands/contrast.d.ts +2 -0
- package/dist/bin/commands/contrast.d.ts.map +1 -0
- package/dist/bin/commands/contrast.js +22 -0
- package/dist/bin/commands/contrast.js.map +1 -0
- package/dist/bin/commands/guardrails.d.ts +2 -0
- package/dist/bin/commands/guardrails.d.ts.map +1 -0
- package/dist/bin/commands/guardrails.js +18 -0
- package/dist/bin/commands/guardrails.js.map +1 -0
- package/dist/bin/commands/manifest.d.ts +2 -0
- package/dist/bin/commands/manifest.d.ts.map +1 -0
- package/dist/bin/commands/manifest.js +21 -0
- package/dist/bin/commands/manifest.js.map +1 -0
- package/dist/bin/commands/probe.d.ts +2 -0
- package/dist/bin/commands/probe.d.ts.map +1 -0
- package/dist/bin/commands/probe.js +59 -0
- package/dist/bin/commands/probe.js.map +1 -0
- package/dist/bin/commands/validate.d.ts +2 -0
- package/dist/bin/commands/validate.d.ts.map +1 -0
- package/dist/bin/commands/validate.js +19 -0
- package/dist/bin/commands/validate.js.map +1 -0
- package/dist/bin/valentino.d.ts +7 -0
- package/dist/bin/valentino.d.ts.map +1 -0
- package/dist/bin/valentino.js +62 -0
- package/dist/bin/valentino.js.map +1 -0
- package/dist/core/catalog.d.ts +13 -0
- package/dist/core/catalog.d.ts.map +1 -0
- package/dist/core/catalog.js +63 -0
- package/dist/core/catalog.js.map +1 -0
- package/dist/core/catalog.test.d.ts +2 -0
- package/dist/core/catalog.test.d.ts.map +1 -0
- package/dist/core/catalog.test.js +115 -0
- package/dist/core/catalog.test.js.map +1 -0
- package/dist/core/contrast.d.ts +42 -0
- package/dist/core/contrast.d.ts.map +1 -0
- package/dist/core/contrast.js +98 -0
- package/dist/core/contrast.js.map +1 -0
- package/dist/core/contrast.test.d.ts +2 -0
- package/dist/core/contrast.test.d.ts.map +1 -0
- package/dist/core/contrast.test.js +82 -0
- package/dist/core/contrast.test.js.map +1 -0
- package/dist/core/css-named-colors.d.ts +7 -0
- package/dist/core/css-named-colors.d.ts.map +1 -0
- package/dist/core/css-named-colors.js +45 -0
- package/dist/core/css-named-colors.js.map +1 -0
- package/dist/core/guardrails.d.ts +16 -0
- package/dist/core/guardrails.d.ts.map +1 -0
- package/dist/core/guardrails.js +55 -0
- package/dist/core/guardrails.js.map +1 -0
- package/dist/core/guardrails.test.d.ts +2 -0
- package/dist/core/guardrails.test.d.ts.map +1 -0
- package/dist/core/guardrails.test.js +88 -0
- package/dist/core/guardrails.test.js.map +1 -0
- package/dist/core/hero-contract.d.ts +21 -0
- package/dist/core/hero-contract.d.ts.map +1 -0
- package/dist/core/hero-contract.js +70 -0
- package/dist/core/hero-contract.js.map +1 -0
- package/dist/core/hero-contract.test.d.ts +2 -0
- package/dist/core/hero-contract.test.d.ts.map +1 -0
- package/dist/core/hero-contract.test.js +71 -0
- package/dist/core/hero-contract.test.js.map +1 -0
- package/dist/core/manifest.d.ts +8 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +17 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/manifest.test.d.ts +2 -0
- package/dist/core/manifest.test.d.ts.map +1 -0
- package/dist/core/manifest.test.js +45 -0
- package/dist/core/manifest.test.js.map +1 -0
- package/dist/core/page-spec.d.ts +14 -0
- package/dist/core/page-spec.d.ts.map +1 -0
- package/dist/core/page-spec.js +20 -0
- package/dist/core/page-spec.js.map +1 -0
- package/dist/core/page-spec.test.d.ts +2 -0
- package/dist/core/page-spec.test.d.ts.map +1 -0
- package/dist/core/page-spec.test.js +38 -0
- package/dist/core/page-spec.test.js.map +1 -0
- package/dist/core/presentation.d.ts +9 -0
- package/dist/core/presentation.d.ts.map +1 -0
- package/dist/core/presentation.js +65 -0
- package/dist/core/presentation.js.map +1 -0
- package/dist/core/presentation.test.d.ts +2 -0
- package/dist/core/presentation.test.d.ts.map +1 -0
- package/dist/core/presentation.test.js +63 -0
- package/dist/core/presentation.test.js.map +1 -0
- package/dist/core/rhythm.d.ts +25 -0
- package/dist/core/rhythm.d.ts.map +1 -0
- package/dist/core/rhythm.js +67 -0
- package/dist/core/rhythm.js.map +1 -0
- package/dist/core/rhythm.test.d.ts +2 -0
- package/dist/core/rhythm.test.d.ts.map +1 -0
- package/dist/core/rhythm.test.js +69 -0
- package/dist/core/rhythm.test.js.map +1 -0
- package/dist/core/section-integrity.d.ts +30 -0
- package/dist/core/section-integrity.d.ts.map +1 -0
- package/dist/core/section-integrity.js +72 -0
- package/dist/core/section-integrity.js.map +1 -0
- package/dist/core/section-integrity.test.d.ts +2 -0
- package/dist/core/section-integrity.test.d.ts.map +1 -0
- package/dist/core/section-integrity.test.js +83 -0
- package/dist/core/section-integrity.test.js.map +1 -0
- package/dist/core/types.d.ts +456 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +126 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/skills/design-guidelines.d.ts +6 -0
- package/dist/skills/design-guidelines.d.ts.map +1 -0
- package/dist/skills/design-guidelines.js +11 -0
- package/dist/skills/design-guidelines.js.map +1 -0
- package/dist/skills/index.d.ts +7 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +7 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/premium-design.d.ts +6 -0
- package/dist/skills/premium-design.d.ts.map +1 -0
- package/dist/skills/premium-design.js +11 -0
- package/dist/skills/premium-design.js.map +1 -0
- package/dist/skills/web-guardrails.d.ts +6 -0
- package/dist/skills/web-guardrails.d.ts.map +1 -0
- package/dist/skills/web-guardrails.js +11 -0
- package/dist/skills/web-guardrails.js.map +1 -0
- package/package.json +69 -0
- package/skills/valentino-premium-design.md +10 -0
- package/skills/valentino-web-guardrails.md +9 -0
- package/skills/web-design-guidelines.md +9 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { inferRhythmProfile, resolvePresentation, DEFAULT_PRESENTATION } from './presentation.js';
|
|
3
|
+
describe('inferRhythmProfile', () => {
|
|
4
|
+
it('returns "hero" for hero sections', () => {
|
|
5
|
+
expect(inferRhythmProfile({ type: 'hero', titleKey: 'h' })).toBe('hero');
|
|
6
|
+
});
|
|
7
|
+
it('returns "transition" for cta sections', () => {
|
|
8
|
+
expect(inferRhythmProfile({ type: 'cta', titleKey: 't' })).toBe('transition');
|
|
9
|
+
});
|
|
10
|
+
it('returns "transition" for spacer sections', () => {
|
|
11
|
+
expect(inferRhythmProfile({ type: 'spacer' })).toBe('transition');
|
|
12
|
+
});
|
|
13
|
+
it('returns "feature" for cards sections', () => {
|
|
14
|
+
expect(inferRhythmProfile({ type: 'cards', variant: 'catalog', items: [] })).toBe('feature');
|
|
15
|
+
});
|
|
16
|
+
it('returns "reading" for manifesto sections', () => {
|
|
17
|
+
expect(inferRhythmProfile({ type: 'manifesto' })).toBe('reading');
|
|
18
|
+
});
|
|
19
|
+
it('returns "metrics" for stats sections', () => {
|
|
20
|
+
expect(inferRhythmProfile({ type: 'stats', items: [] })).toBe('metrics');
|
|
21
|
+
});
|
|
22
|
+
it('returns "ops" for agent-dashboard sections', () => {
|
|
23
|
+
expect(inferRhythmProfile({ type: 'agent-dashboard' })).toBe('ops');
|
|
24
|
+
});
|
|
25
|
+
it('returns "ops" for data-list sections', () => {
|
|
26
|
+
expect(inferRhythmProfile({ type: 'data-list', dataUrl: '/api', columns: [] })).toBe('ops');
|
|
27
|
+
});
|
|
28
|
+
it('returns "feature" for how-it-works sections', () => {
|
|
29
|
+
expect(inferRhythmProfile({ type: 'how-it-works', steps: [] })).toBe('feature');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('resolvePresentation', () => {
|
|
33
|
+
it('returns all defaults for a section with no presentation', () => {
|
|
34
|
+
const result = resolvePresentation({ type: 'cards', variant: 'catalog', items: [] });
|
|
35
|
+
expect(result.surface).toBe('default');
|
|
36
|
+
expect(result.rhythmProfile).toBe('feature'); // inferred
|
|
37
|
+
});
|
|
38
|
+
it('preserves explicit presentation values', () => {
|
|
39
|
+
const result = resolvePresentation({
|
|
40
|
+
type: 'hero',
|
|
41
|
+
titleKey: 'h',
|
|
42
|
+
presentation: { surface: 'shell-dark', tone: 'immersive' },
|
|
43
|
+
});
|
|
44
|
+
expect(result.surface).toBe('shell-dark');
|
|
45
|
+
expect(result.tone).toBe('immersive');
|
|
46
|
+
expect(result.rhythmProfile).toBe('hero'); // inferred from type
|
|
47
|
+
});
|
|
48
|
+
it('explicit rhythmProfile overrides inferred', () => {
|
|
49
|
+
const result = resolvePresentation({
|
|
50
|
+
type: 'hero',
|
|
51
|
+
titleKey: 'h',
|
|
52
|
+
presentation: { rhythmProfile: 'reading' },
|
|
53
|
+
});
|
|
54
|
+
expect(result.rhythmProfile).toBe('reading');
|
|
55
|
+
});
|
|
56
|
+
it('returns all keys from DEFAULT_PRESENTATION', () => {
|
|
57
|
+
const result = resolvePresentation({ type: 'spacer' });
|
|
58
|
+
for (const key of Object.keys(DEFAULT_PRESENTATION)) {
|
|
59
|
+
expect(result).toHaveProperty(key);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=presentation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation.test.js","sourceRoot":"","sources":["../../src/core/presentation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rhythm Probe — validates section sequence against Valentino rhythm rules.
|
|
3
|
+
* Pure function, no DOM.
|
|
4
|
+
*/
|
|
5
|
+
import type { PageSpecV1, SectionSpec } from './types.js';
|
|
6
|
+
export type RhythmWarning = {
|
|
7
|
+
index: number;
|
|
8
|
+
sectionType: SectionSpec['type'];
|
|
9
|
+
rule: string;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export type RhythmProbeResult = {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
warnings: RhythmWarning[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Probe a PageSpecV1 for rhythm rule violations.
|
|
18
|
+
*
|
|
19
|
+
* Rules:
|
|
20
|
+
* 1. Hero must be the first section (if present)
|
|
21
|
+
* 2. No two consecutive sections with the same rhythmProfile
|
|
22
|
+
* 3. Spacer should appear between sections with the same surface
|
|
23
|
+
*/
|
|
24
|
+
export declare function probeRhythm(spec: PageSpecV1): RhythmProbeResult;
|
|
25
|
+
//# sourceMappingURL=rhythm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rhythm.d.ts","sourceRoot":"","sources":["../../src/core/rhythm.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG1D,MAAM,MAAM,aAAa,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC7B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CA2D/D"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rhythm Probe — validates section sequence against Valentino rhythm rules.
|
|
3
|
+
* Pure function, no DOM.
|
|
4
|
+
*/
|
|
5
|
+
import { inferRhythmProfile } from './presentation.js';
|
|
6
|
+
/**
|
|
7
|
+
* Probe a PageSpecV1 for rhythm rule violations.
|
|
8
|
+
*
|
|
9
|
+
* Rules:
|
|
10
|
+
* 1. Hero must be the first section (if present)
|
|
11
|
+
* 2. No two consecutive sections with the same rhythmProfile
|
|
12
|
+
* 3. Spacer should appear between sections with the same surface
|
|
13
|
+
*/
|
|
14
|
+
export function probeRhythm(spec) {
|
|
15
|
+
const warnings = [];
|
|
16
|
+
const sections = spec.sections;
|
|
17
|
+
if (sections.length === 0) {
|
|
18
|
+
return { valid: true, warnings };
|
|
19
|
+
}
|
|
20
|
+
// Rule 1: Hero must be first
|
|
21
|
+
const heroIndex = sections.findIndex(s => s.type === 'hero');
|
|
22
|
+
if (heroIndex > 0) {
|
|
23
|
+
warnings.push({
|
|
24
|
+
index: heroIndex,
|
|
25
|
+
sectionType: 'hero',
|
|
26
|
+
rule: 'hero-first',
|
|
27
|
+
message: `Hero section found at index ${heroIndex} — should be first (index 0).`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// Rule 2: No consecutive same rhythmProfile
|
|
31
|
+
for (let i = 1; i < sections.length; i++) {
|
|
32
|
+
const prev = sections[i - 1];
|
|
33
|
+
const curr = sections[i];
|
|
34
|
+
const prevRhythm = prev.presentation?.rhythmProfile || inferRhythmProfile(prev);
|
|
35
|
+
const currRhythm = curr.presentation?.rhythmProfile || inferRhythmProfile(curr);
|
|
36
|
+
if (prevRhythm === currRhythm && currRhythm !== 'transition') {
|
|
37
|
+
warnings.push({
|
|
38
|
+
index: i,
|
|
39
|
+
sectionType: curr.type,
|
|
40
|
+
rule: 'no-consecutive-rhythm',
|
|
41
|
+
message: `Sections at index ${i - 1} (${prev.type}) and ${i} (${curr.type}) share rhythmProfile "${currRhythm}" — consider a spacer or different rhythm.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Rule 3: Same surface without spacer
|
|
46
|
+
for (let i = 1; i < sections.length; i++) {
|
|
47
|
+
const prev = sections[i - 1];
|
|
48
|
+
const curr = sections[i];
|
|
49
|
+
if (curr.type === 'spacer' || prev.type === 'spacer')
|
|
50
|
+
continue;
|
|
51
|
+
const prevSurface = prev.presentation?.surface || 'default';
|
|
52
|
+
const currSurface = curr.presentation?.surface || 'default';
|
|
53
|
+
if (prevSurface === currSurface && prevSurface !== 'default') {
|
|
54
|
+
warnings.push({
|
|
55
|
+
index: i,
|
|
56
|
+
sectionType: curr.type,
|
|
57
|
+
rule: 'spacer-between-same-surface',
|
|
58
|
+
message: `Sections at index ${i - 1} and ${i} share surface "${currSurface}" without a spacer — consider adding visual separation.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
valid: warnings.length === 0,
|
|
64
|
+
warnings,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=rhythm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rhythm.js","sourceRoot":"","sources":["../../src/core/rhythm.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAcvD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAgB;IACxC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAE/B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAC7D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAChB,QAAQ,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,SAAS;YAChB,WAAW,EAAE,MAAM;YACnB,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,+BAA+B,SAAS,+BAA+B;SACnF,CAAC,CAAC;IACP,CAAC;IAED,4CAA4C;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAChF,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAEhF,IAAI,UAAU,KAAK,UAAU,IAAI,UAAU,KAAK,YAAY,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,CAAC;gBACR,WAAW,EAAE,IAAI,CAAC,IAAI;gBACtB,IAAI,EAAE,uBAAuB;gBAC7B,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,SAAS,CAAC,KAAK,IAAI,CAAC,IAAI,0BAA0B,UAAU,4CAA4C;aAC5J,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,sCAAsC;IACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS;QAE/D,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,IAAI,SAAS,CAAC;QAC5D,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,EAAE,OAAO,IAAI,SAAS,CAAC;QAE5D,IAAI,WAAW,KAAK,WAAW,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,CAAC;gBACR,WAAW,EAAE,IAAI,CAAC,IAAI;gBACtB,IAAI,EAAE,6BAA6B;gBACnC,OAAO,EAAE,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,WAAW,yDAAyD;aACtI,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,OAAO;QACH,KAAK,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC5B,QAAQ;KACX,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rhythm.test.d.ts","sourceRoot":"","sources":["../../src/core/rhythm.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { probeRhythm } from './rhythm.js';
|
|
3
|
+
function makeSpec(sections) {
|
|
4
|
+
return { version: '1', id: 'test', sections };
|
|
5
|
+
}
|
|
6
|
+
describe('probeRhythm', () => {
|
|
7
|
+
it('returns valid for empty sections', () => {
|
|
8
|
+
const result = probeRhythm(makeSpec([]));
|
|
9
|
+
expect(result.valid).toBe(true);
|
|
10
|
+
expect(result.warnings).toHaveLength(0);
|
|
11
|
+
});
|
|
12
|
+
it('returns valid for hero-first sequence', () => {
|
|
13
|
+
const result = probeRhythm(makeSpec([
|
|
14
|
+
{ type: 'hero', titleKey: 'h' },
|
|
15
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
16
|
+
{ type: 'cta', titleKey: 'c' },
|
|
17
|
+
]));
|
|
18
|
+
expect(result.valid).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('warns when hero is not first', () => {
|
|
21
|
+
const result = probeRhythm(makeSpec([
|
|
22
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
23
|
+
{ type: 'hero', titleKey: 'h' },
|
|
24
|
+
]));
|
|
25
|
+
expect(result.valid).toBe(false);
|
|
26
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'hero-first' }));
|
|
27
|
+
});
|
|
28
|
+
it('warns on consecutive same rhythmProfile', () => {
|
|
29
|
+
const result = probeRhythm(makeSpec([
|
|
30
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
31
|
+
{ type: 'how-it-works', steps: [] },
|
|
32
|
+
]));
|
|
33
|
+
// Both infer "feature" rhythmProfile
|
|
34
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'no-consecutive-rhythm' }));
|
|
35
|
+
});
|
|
36
|
+
it('allows consecutive transition sections (cta + spacer)', () => {
|
|
37
|
+
const result = probeRhythm(makeSpec([
|
|
38
|
+
{ type: 'cta', titleKey: 'c' },
|
|
39
|
+
{ type: 'spacer' },
|
|
40
|
+
]));
|
|
41
|
+
const transitionWarnings = result.warnings.filter(w => w.rule === 'no-consecutive-rhythm');
|
|
42
|
+
expect(transitionWarnings).toHaveLength(0);
|
|
43
|
+
});
|
|
44
|
+
it('warns on same non-default surface without spacer', () => {
|
|
45
|
+
const result = probeRhythm(makeSpec([
|
|
46
|
+
{ type: 'hero', titleKey: 'h', presentation: { surface: 'accent' } },
|
|
47
|
+
{ type: 'cards', variant: 'catalog', items: [], presentation: { surface: 'accent' } },
|
|
48
|
+
]));
|
|
49
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'spacer-between-same-surface' }));
|
|
50
|
+
});
|
|
51
|
+
it('does not warn on same default surface', () => {
|
|
52
|
+
const result = probeRhythm(makeSpec([
|
|
53
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
54
|
+
{ type: 'comparison', titleKey: 't', left: { titleKey: 'l', itemsKeys: [] }, right: { titleKey: 'r', itemsKeys: [] } },
|
|
55
|
+
]));
|
|
56
|
+
const surfaceWarnings = result.warnings.filter(w => w.rule === 'spacer-between-same-surface');
|
|
57
|
+
expect(surfaceWarnings).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
it('does not warn when spacer separates same surface', () => {
|
|
60
|
+
const result = probeRhythm(makeSpec([
|
|
61
|
+
{ type: 'cards', variant: 'catalog', items: [], presentation: { surface: 'muted' } },
|
|
62
|
+
{ type: 'spacer' },
|
|
63
|
+
{ type: 'cards', variant: 'catalog', items: [], presentation: { surface: 'muted' } },
|
|
64
|
+
]));
|
|
65
|
+
const surfaceWarnings = result.warnings.filter(w => w.rule === 'spacer-between-same-surface');
|
|
66
|
+
expect(surfaceWarnings).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=rhythm.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rhythm.test.js","sourceRoot":"","sources":["../../src/core/rhythm.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Integrity Checker — per-type structural validation.
|
|
3
|
+
* Pure function, no DOM.
|
|
4
|
+
*/
|
|
5
|
+
import type { SectionSpec } from './types.js';
|
|
6
|
+
export type IntegrityWarning = {
|
|
7
|
+
index: number;
|
|
8
|
+
sectionType: SectionSpec['type'];
|
|
9
|
+
rule: string;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export type IntegrityResult = {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
warnings: IntegrityWarning[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Validate structural integrity of each section in the array.
|
|
18
|
+
*
|
|
19
|
+
* Rules per type:
|
|
20
|
+
* - cards: items must be non-empty
|
|
21
|
+
* - form: fields must be non-empty, submitKey required
|
|
22
|
+
* - comparison: left and right must have itemsKeys
|
|
23
|
+
* - how-it-works: steps must be non-empty
|
|
24
|
+
* - advisor: prompts must be non-empty
|
|
25
|
+
* - stats: items must be non-empty
|
|
26
|
+
* - data-list: columns must be non-empty, dataUrl required
|
|
27
|
+
* - action-form: fields must be non-empty, submitUrl required
|
|
28
|
+
*/
|
|
29
|
+
export declare function probeSectionIntegrity(sections: SectionSpec[]): IntegrityResult;
|
|
30
|
+
//# sourceMappingURL=section-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-integrity.d.ts","sourceRoot":"","sources":["../../src/core/section-integrity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,gBAAgB,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,eAAe,CA8D9E"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Integrity Checker — per-type structural validation.
|
|
3
|
+
* Pure function, no DOM.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Validate structural integrity of each section in the array.
|
|
7
|
+
*
|
|
8
|
+
* Rules per type:
|
|
9
|
+
* - cards: items must be non-empty
|
|
10
|
+
* - form: fields must be non-empty, submitKey required
|
|
11
|
+
* - comparison: left and right must have itemsKeys
|
|
12
|
+
* - how-it-works: steps must be non-empty
|
|
13
|
+
* - advisor: prompts must be non-empty
|
|
14
|
+
* - stats: items must be non-empty
|
|
15
|
+
* - data-list: columns must be non-empty, dataUrl required
|
|
16
|
+
* - action-form: fields must be non-empty, submitUrl required
|
|
17
|
+
*/
|
|
18
|
+
export function probeSectionIntegrity(sections) {
|
|
19
|
+
const warnings = [];
|
|
20
|
+
sections.forEach((section, index) => {
|
|
21
|
+
switch (section.type) {
|
|
22
|
+
case 'cards':
|
|
23
|
+
if (!section.items || section.items.length === 0) {
|
|
24
|
+
warnings.push({ index, sectionType: 'cards', rule: 'cards-items-required', message: 'Cards section has no items.' });
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'form':
|
|
28
|
+
if (!section.fields || section.fields.length === 0) {
|
|
29
|
+
warnings.push({ index, sectionType: 'form', rule: 'form-fields-required', message: 'Form section has no fields.' });
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case 'comparison':
|
|
33
|
+
if (!section.left?.itemsKeys?.length) {
|
|
34
|
+
warnings.push({ index, sectionType: 'comparison', rule: 'comparison-left-required', message: 'Comparison section has empty left.itemsKeys.' });
|
|
35
|
+
}
|
|
36
|
+
if (!section.right?.itemsKeys?.length) {
|
|
37
|
+
warnings.push({ index, sectionType: 'comparison', rule: 'comparison-right-required', message: 'Comparison section has empty right.itemsKeys.' });
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case 'how-it-works':
|
|
41
|
+
if (!section.steps || section.steps.length === 0) {
|
|
42
|
+
warnings.push({ index, sectionType: 'how-it-works', rule: 'how-it-works-steps-required', message: 'How-it-works section has no steps.' });
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
case 'advisor':
|
|
46
|
+
if (!section.prompts || section.prompts.length === 0) {
|
|
47
|
+
warnings.push({ index, sectionType: 'advisor', rule: 'advisor-prompts-required', message: 'Advisor section has no prompts.' });
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case 'stats':
|
|
51
|
+
if (!section.items || section.items.length === 0) {
|
|
52
|
+
warnings.push({ index, sectionType: 'stats', rule: 'stats-items-required', message: 'Stats section has no items.' });
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case 'data-list':
|
|
56
|
+
if (!section.columns || section.columns.length === 0) {
|
|
57
|
+
warnings.push({ index, sectionType: 'data-list', rule: 'data-list-columns-required', message: 'Data-list section has no columns.' });
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case 'action-form':
|
|
61
|
+
if (!section.fields || section.fields.length === 0) {
|
|
62
|
+
warnings.push({ index, sectionType: 'action-form', rule: 'action-form-fields-required', message: 'Action-form section has no fields.' });
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
valid: warnings.length === 0,
|
|
69
|
+
warnings,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=section-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-integrity.js","sourceRoot":"","sources":["../../src/core/section-integrity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-integrity.test.d.ts","sourceRoot":"","sources":["../../src/core/section-integrity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { probeSectionIntegrity } from './section-integrity.js';
|
|
3
|
+
describe('probeSectionIntegrity', () => {
|
|
4
|
+
it('returns valid for empty sections', () => {
|
|
5
|
+
expect(probeSectionIntegrity([]).valid).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it('passes for valid cards', () => {
|
|
8
|
+
const result = probeSectionIntegrity([
|
|
9
|
+
{ type: 'cards', variant: 'catalog', items: [{ titleKey: 't' }] },
|
|
10
|
+
]);
|
|
11
|
+
expect(result.valid).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it('warns on empty cards items', () => {
|
|
14
|
+
const result = probeSectionIntegrity([
|
|
15
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
16
|
+
]);
|
|
17
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'cards-items-required' }));
|
|
18
|
+
});
|
|
19
|
+
it('warns on empty form fields', () => {
|
|
20
|
+
const result = probeSectionIntegrity([
|
|
21
|
+
{ type: 'form', titleKey: 't', fields: [], submitKey: 's' },
|
|
22
|
+
]);
|
|
23
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'form-fields-required' }));
|
|
24
|
+
});
|
|
25
|
+
it('warns on empty comparison left', () => {
|
|
26
|
+
const result = probeSectionIntegrity([
|
|
27
|
+
{ type: 'comparison', titleKey: 't', left: { titleKey: 'l', itemsKeys: [] }, right: { titleKey: 'r', itemsKeys: ['a'] } },
|
|
28
|
+
]);
|
|
29
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'comparison-left-required' }));
|
|
30
|
+
});
|
|
31
|
+
it('warns on empty comparison right', () => {
|
|
32
|
+
const result = probeSectionIntegrity([
|
|
33
|
+
{ type: 'comparison', titleKey: 't', left: { titleKey: 'l', itemsKeys: ['a'] }, right: { titleKey: 'r', itemsKeys: [] } },
|
|
34
|
+
]);
|
|
35
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'comparison-right-required' }));
|
|
36
|
+
});
|
|
37
|
+
it('warns on empty how-it-works steps', () => {
|
|
38
|
+
const result = probeSectionIntegrity([
|
|
39
|
+
{ type: 'how-it-works', steps: [] },
|
|
40
|
+
]);
|
|
41
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'how-it-works-steps-required' }));
|
|
42
|
+
});
|
|
43
|
+
it('warns on empty advisor prompts', () => {
|
|
44
|
+
const result = probeSectionIntegrity([
|
|
45
|
+
{ type: 'advisor', titleKey: 't', submitKey: 's', fallbackTitleKey: 'f', fallbackBodyKey: 'fb', prompts: [] },
|
|
46
|
+
]);
|
|
47
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'advisor-prompts-required' }));
|
|
48
|
+
});
|
|
49
|
+
it('warns on empty stats items', () => {
|
|
50
|
+
const result = probeSectionIntegrity([
|
|
51
|
+
{ type: 'stats', items: [] },
|
|
52
|
+
]);
|
|
53
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'stats-items-required' }));
|
|
54
|
+
});
|
|
55
|
+
it('warns on empty data-list columns', () => {
|
|
56
|
+
const result = probeSectionIntegrity([
|
|
57
|
+
{ type: 'data-list', dataUrl: '/api', columns: [] },
|
|
58
|
+
]);
|
|
59
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'data-list-columns-required' }));
|
|
60
|
+
});
|
|
61
|
+
it('warns on empty action-form fields', () => {
|
|
62
|
+
const result = probeSectionIntegrity([
|
|
63
|
+
{ type: 'action-form', titleKey: 't', submitUrl: '/api', submitKey: 's', successKey: 'ok', fields: [] },
|
|
64
|
+
]);
|
|
65
|
+
expect(result.warnings).toContainEqual(expect.objectContaining({ rule: 'action-form-fields-required' }));
|
|
66
|
+
});
|
|
67
|
+
it('reports correct index', () => {
|
|
68
|
+
const result = probeSectionIntegrity([
|
|
69
|
+
{ type: 'hero', titleKey: 'h' },
|
|
70
|
+
{ type: 'cards', variant: 'catalog', items: [] },
|
|
71
|
+
]);
|
|
72
|
+
expect(result.warnings[0].index).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
it('skips types without integrity rules (hero, spacer, cta)', () => {
|
|
75
|
+
const result = probeSectionIntegrity([
|
|
76
|
+
{ type: 'hero', titleKey: 'h' },
|
|
77
|
+
{ type: 'spacer' },
|
|
78
|
+
{ type: 'cta', titleKey: 'c' },
|
|
79
|
+
]);
|
|
80
|
+
expect(result.valid).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=section-integrity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-integrity.test.js","sourceRoot":"","sources":["../../src/core/section-integrity.test.ts"],"names":[],"mappings":"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"}
|