@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.
Files changed (144) hide show
  1. package/AGENTS.md +49 -0
  2. package/CHANGELOG.md +22 -0
  3. package/LICENSE +21 -0
  4. package/README.md +108 -0
  5. package/dist/bin/commands/audit.d.ts +2 -0
  6. package/dist/bin/commands/audit.d.ts.map +1 -0
  7. package/dist/bin/commands/audit.js +24 -0
  8. package/dist/bin/commands/audit.js.map +1 -0
  9. package/dist/bin/commands/catalog.d.ts +2 -0
  10. package/dist/bin/commands/catalog.d.ts.map +1 -0
  11. package/dist/bin/commands/catalog.js +21 -0
  12. package/dist/bin/commands/catalog.js.map +1 -0
  13. package/dist/bin/commands/contrast.d.ts +2 -0
  14. package/dist/bin/commands/contrast.d.ts.map +1 -0
  15. package/dist/bin/commands/contrast.js +22 -0
  16. package/dist/bin/commands/contrast.js.map +1 -0
  17. package/dist/bin/commands/guardrails.d.ts +2 -0
  18. package/dist/bin/commands/guardrails.d.ts.map +1 -0
  19. package/dist/bin/commands/guardrails.js +18 -0
  20. package/dist/bin/commands/guardrails.js.map +1 -0
  21. package/dist/bin/commands/manifest.d.ts +2 -0
  22. package/dist/bin/commands/manifest.d.ts.map +1 -0
  23. package/dist/bin/commands/manifest.js +21 -0
  24. package/dist/bin/commands/manifest.js.map +1 -0
  25. package/dist/bin/commands/probe.d.ts +2 -0
  26. package/dist/bin/commands/probe.d.ts.map +1 -0
  27. package/dist/bin/commands/probe.js +59 -0
  28. package/dist/bin/commands/probe.js.map +1 -0
  29. package/dist/bin/commands/validate.d.ts +2 -0
  30. package/dist/bin/commands/validate.d.ts.map +1 -0
  31. package/dist/bin/commands/validate.js +19 -0
  32. package/dist/bin/commands/validate.js.map +1 -0
  33. package/dist/bin/valentino.d.ts +7 -0
  34. package/dist/bin/valentino.d.ts.map +1 -0
  35. package/dist/bin/valentino.js +62 -0
  36. package/dist/bin/valentino.js.map +1 -0
  37. package/dist/core/catalog.d.ts +13 -0
  38. package/dist/core/catalog.d.ts.map +1 -0
  39. package/dist/core/catalog.js +63 -0
  40. package/dist/core/catalog.js.map +1 -0
  41. package/dist/core/catalog.test.d.ts +2 -0
  42. package/dist/core/catalog.test.d.ts.map +1 -0
  43. package/dist/core/catalog.test.js +115 -0
  44. package/dist/core/catalog.test.js.map +1 -0
  45. package/dist/core/contrast.d.ts +42 -0
  46. package/dist/core/contrast.d.ts.map +1 -0
  47. package/dist/core/contrast.js +98 -0
  48. package/dist/core/contrast.js.map +1 -0
  49. package/dist/core/contrast.test.d.ts +2 -0
  50. package/dist/core/contrast.test.d.ts.map +1 -0
  51. package/dist/core/contrast.test.js +82 -0
  52. package/dist/core/contrast.test.js.map +1 -0
  53. package/dist/core/css-named-colors.d.ts +7 -0
  54. package/dist/core/css-named-colors.d.ts.map +1 -0
  55. package/dist/core/css-named-colors.js +45 -0
  56. package/dist/core/css-named-colors.js.map +1 -0
  57. package/dist/core/guardrails.d.ts +16 -0
  58. package/dist/core/guardrails.d.ts.map +1 -0
  59. package/dist/core/guardrails.js +55 -0
  60. package/dist/core/guardrails.js.map +1 -0
  61. package/dist/core/guardrails.test.d.ts +2 -0
  62. package/dist/core/guardrails.test.d.ts.map +1 -0
  63. package/dist/core/guardrails.test.js +88 -0
  64. package/dist/core/guardrails.test.js.map +1 -0
  65. package/dist/core/hero-contract.d.ts +21 -0
  66. package/dist/core/hero-contract.d.ts.map +1 -0
  67. package/dist/core/hero-contract.js +70 -0
  68. package/dist/core/hero-contract.js.map +1 -0
  69. package/dist/core/hero-contract.test.d.ts +2 -0
  70. package/dist/core/hero-contract.test.d.ts.map +1 -0
  71. package/dist/core/hero-contract.test.js +71 -0
  72. package/dist/core/hero-contract.test.js.map +1 -0
  73. package/dist/core/manifest.d.ts +8 -0
  74. package/dist/core/manifest.d.ts.map +1 -0
  75. package/dist/core/manifest.js +17 -0
  76. package/dist/core/manifest.js.map +1 -0
  77. package/dist/core/manifest.test.d.ts +2 -0
  78. package/dist/core/manifest.test.d.ts.map +1 -0
  79. package/dist/core/manifest.test.js +45 -0
  80. package/dist/core/manifest.test.js.map +1 -0
  81. package/dist/core/page-spec.d.ts +14 -0
  82. package/dist/core/page-spec.d.ts.map +1 -0
  83. package/dist/core/page-spec.js +20 -0
  84. package/dist/core/page-spec.js.map +1 -0
  85. package/dist/core/page-spec.test.d.ts +2 -0
  86. package/dist/core/page-spec.test.d.ts.map +1 -0
  87. package/dist/core/page-spec.test.js +38 -0
  88. package/dist/core/page-spec.test.js.map +1 -0
  89. package/dist/core/presentation.d.ts +9 -0
  90. package/dist/core/presentation.d.ts.map +1 -0
  91. package/dist/core/presentation.js +65 -0
  92. package/dist/core/presentation.js.map +1 -0
  93. package/dist/core/presentation.test.d.ts +2 -0
  94. package/dist/core/presentation.test.d.ts.map +1 -0
  95. package/dist/core/presentation.test.js +63 -0
  96. package/dist/core/presentation.test.js.map +1 -0
  97. package/dist/core/rhythm.d.ts +25 -0
  98. package/dist/core/rhythm.d.ts.map +1 -0
  99. package/dist/core/rhythm.js +67 -0
  100. package/dist/core/rhythm.js.map +1 -0
  101. package/dist/core/rhythm.test.d.ts +2 -0
  102. package/dist/core/rhythm.test.d.ts.map +1 -0
  103. package/dist/core/rhythm.test.js +69 -0
  104. package/dist/core/rhythm.test.js.map +1 -0
  105. package/dist/core/section-integrity.d.ts +30 -0
  106. package/dist/core/section-integrity.d.ts.map +1 -0
  107. package/dist/core/section-integrity.js +72 -0
  108. package/dist/core/section-integrity.js.map +1 -0
  109. package/dist/core/section-integrity.test.d.ts +2 -0
  110. package/dist/core/section-integrity.test.d.ts.map +1 -0
  111. package/dist/core/section-integrity.test.js +83 -0
  112. package/dist/core/section-integrity.test.js.map +1 -0
  113. package/dist/core/types.d.ts +456 -0
  114. package/dist/core/types.d.ts.map +1 -0
  115. package/dist/core/types.js +7 -0
  116. package/dist/core/types.js.map +1 -0
  117. package/dist/index.d.ts +21 -0
  118. package/dist/index.d.ts.map +1 -0
  119. package/dist/index.js +23 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/mcp/index.d.ts +2 -0
  122. package/dist/mcp/index.d.ts.map +1 -0
  123. package/dist/mcp/index.js +126 -0
  124. package/dist/mcp/index.js.map +1 -0
  125. package/dist/skills/design-guidelines.d.ts +6 -0
  126. package/dist/skills/design-guidelines.d.ts.map +1 -0
  127. package/dist/skills/design-guidelines.js +11 -0
  128. package/dist/skills/design-guidelines.js.map +1 -0
  129. package/dist/skills/index.d.ts +7 -0
  130. package/dist/skills/index.d.ts.map +1 -0
  131. package/dist/skills/index.js +7 -0
  132. package/dist/skills/index.js.map +1 -0
  133. package/dist/skills/premium-design.d.ts +6 -0
  134. package/dist/skills/premium-design.d.ts.map +1 -0
  135. package/dist/skills/premium-design.js +11 -0
  136. package/dist/skills/premium-design.js.map +1 -0
  137. package/dist/skills/web-guardrails.d.ts +6 -0
  138. package/dist/skills/web-guardrails.d.ts.map +1 -0
  139. package/dist/skills/web-guardrails.js +11 -0
  140. package/dist/skills/web-guardrails.js.map +1 -0
  141. package/package.json +69 -0
  142. package/skills/valentino-premium-design.md +10 -0
  143. package/skills/valentino-web-guardrails.md +9 -0
  144. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rhythm.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=section-integrity.test.d.ts.map
@@ -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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"}