@uxuissk/design-system-core 3.1.2 → 3.4.2

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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * DS 3.0 Vibecode Contract — public API
3
+ *
4
+ * Used by ds3-mcp (and any other vibecode template generator) to verify that
5
+ * generated HTML output conforms to DS 3.0 standards.
6
+ *
7
+ * @example
8
+ * import { runContract } from "@uxuissk/design-system-core/test-utils/vibecode-contract";
9
+ *
10
+ * const result = runContract(html, { templateName: "feature-page", brand: "ccs3" });
11
+ * if (result.violations.length > 0) {
12
+ * throw new Error(`Template violates DS 3.0:\n${result.violations.map(v => v.message).join("\n")}`);
13
+ * }
14
+ */
15
+ export declare const ALLOWED_BRANDS: readonly ["patona", "ccs3", "oc2plus"];
16
+ export type Brand = (typeof ALLOWED_BRANDS)[number];
17
+ export interface ContractMeta {
18
+ /** Logical template name, e.g. "feature-page", "product-list". */
19
+ templateName: string;
20
+ /** Brand the template was generated for. */
21
+ brand: string;
22
+ }
23
+ export interface Violation {
24
+ /** Rule identifier (1–8). */
25
+ rule: number;
26
+ /** Short rule label. */
27
+ ruleLabel: string;
28
+ /** Human-readable message. */
29
+ message: string;
30
+ /** Snippet of the offending content (if applicable). */
31
+ snippet?: string;
32
+ }
33
+ export interface ContractResult {
34
+ /** Whether the template passes ALL contract rules. */
35
+ ok: boolean;
36
+ /** Empty array when ok=true. */
37
+ violations: Violation[];
38
+ /** Echo of input meta for traceability. */
39
+ meta: ContractMeta;
40
+ }
41
+ /**
42
+ * Validate a vibecode template HTML against DS 3.0 contract rules.
43
+ *
44
+ * @param html Generated HTML output (full document or fragment).
45
+ * @param meta { templateName, brand } — brand must be in the allowed set.
46
+ * @returns { ok, violations, meta } — ok=true means zero violations.
47
+ */
48
+ export declare function runContract(html: string, meta: ContractMeta): ContractResult;
49
+ /**
50
+ * Brand-switching contract — verify two templates have identical structure
51
+ * apart from the `brand` attribute on the provider. This guarantees that
52
+ * brand switching at runtime does not require structural changes.
53
+ */
54
+ export declare function compareBrandSwitching(htmlA: string, htmlB: string): {
55
+ ok: boolean;
56
+ reason?: string;
57
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * DS 3.0 Vibecode Contract — public API
3
+ *
4
+ * Used by ds3-mcp (and any other vibecode template generator) to verify that
5
+ * generated HTML output conforms to DS 3.0 standards.
6
+ *
7
+ * @example
8
+ * import { runContract } from "@uxuissk/design-system-core/test-utils/vibecode-contract";
9
+ *
10
+ * const result = runContract(html, { templateName: "feature-page", brand: "ccs3" });
11
+ * if (result.violations.length > 0) {
12
+ * throw new Error(`Template violates DS 3.0:\n${result.violations.map(v => v.message).join("\n")}`);
13
+ * }
14
+ */
15
+ export const ALLOWED_BRANDS = ["patona", "ccs3", "oc2plus"];
16
+ // ── Helpers ──────────────────────────────────────────────────────────────────
17
+ /** Strip HTML comments — they're documentation, not enforced output. */
18
+ function stripComments(html) {
19
+ return html.replace(/<!--[\s\S]*?-->/g, "");
20
+ }
21
+ /** Strip everything inside `var(...)` so token references don't count as hex. */
22
+ function stripVarCalls(html) {
23
+ return html.replace(/var\([^)]*\)/g, "var(_)");
24
+ }
25
+ // ── Rule implementations ────────────────────────────────────────────────────
26
+ function rule1_rootIsAppShellProvider(body) {
27
+ const trimmed = body.trim();
28
+ if (!trimmed.startsWith("<ssk-app-shell-provider")) {
29
+ return {
30
+ rule: 1,
31
+ ruleLabel: "root-app-shell-provider",
32
+ message: "Root element must be <ssk-app-shell-provider>. Got: " +
33
+ trimmed.slice(0, 80) +
34
+ "…",
35
+ };
36
+ }
37
+ return null;
38
+ }
39
+ function rule2_brandWhitelistAndMatch(body, meta) {
40
+ const violations = [];
41
+ const m = body.match(/<ssk-app-shell-provider\s+brand="([^"]+)"/);
42
+ if (!m) {
43
+ violations.push({
44
+ rule: 2,
45
+ ruleLabel: "brand-attribute-required",
46
+ message: "<ssk-app-shell-provider> must declare brand attribute",
47
+ });
48
+ return violations;
49
+ }
50
+ const brand = m[1];
51
+ if (!ALLOWED_BRANDS.includes(brand)) {
52
+ violations.push({
53
+ rule: 2,
54
+ ruleLabel: "brand-whitelist",
55
+ message: `brand="${brand}" must be one of ${ALLOWED_BRANDS.join("|")}`,
56
+ snippet: brand,
57
+ });
58
+ }
59
+ if (brand !== meta.brand) {
60
+ violations.push({
61
+ rule: 2,
62
+ ruleLabel: "brand-mismatch",
63
+ message: `brand attribute "${brand}" does not match expected "${meta.brand}"`,
64
+ snippet: brand,
65
+ });
66
+ }
67
+ return violations;
68
+ }
69
+ function rule3_noHexLiteralsOutsideVar(bodyNoVar) {
70
+ const hex = bodyNoVar.match(/#[0-9a-fA-F]{3,8}\b/g) ?? [];
71
+ return hex.map((h) => ({
72
+ rule: 3,
73
+ ruleLabel: "no-hex-color",
74
+ message: `Hardcoded hex color ${h} — use semantic tokens via var(--bg-*, --fg-*, --text-*)`,
75
+ snippet: h,
76
+ }));
77
+ }
78
+ function rule4_noFontSizeBelow18(body) {
79
+ const matches = [...body.matchAll(/font-size\s*:\s*(\d+(?:\.\d+)?)px/g)];
80
+ return matches
81
+ .filter((m) => parseFloat(m[1]) < 18)
82
+ .map((m) => ({
83
+ rule: 4,
84
+ ruleLabel: "min-font-size-18px",
85
+ message: `font-size: ${m[1]}px violates DS 3.0 minimum 18px — use var(--font-size-caption) (18px) or larger`,
86
+ snippet: m[0],
87
+ }));
88
+ }
89
+ function rule5_customTagsMustBeSskPrefix(body) {
90
+ const tags = [...body.matchAll(/<([a-z][a-z0-9]*-[a-z0-9-]*)\b/g)];
91
+ const offenders = new Set();
92
+ for (const m of tags) {
93
+ if (!m[1].startsWith("ssk-"))
94
+ offenders.add(m[1]);
95
+ }
96
+ return [...offenders].map((tag) => ({
97
+ rule: 5,
98
+ ruleLabel: "ssk-tag-prefix",
99
+ message: `Custom-element tag <${tag}> must use ssk-* prefix`,
100
+ snippet: tag,
101
+ }));
102
+ }
103
+ function rule6_noTailwindForbiddenSize(body) {
104
+ const found = body.match(/\b(text-xs|text-sm)\b/g) ?? [];
105
+ return [...new Set(found)].map((klass) => ({
106
+ rule: 6,
107
+ ruleLabel: "no-forbidden-tailwind-size",
108
+ message: `Tailwind class '${klass}' (${klass === "text-xs" ? "12px" : "14px"}) violates DS 3.0 minimum 18px`,
109
+ snippet: klass,
110
+ }));
111
+ }
112
+ function rule7_buttonUsesVariantTone(body) {
113
+ const violations = [];
114
+ const buttons = [...body.matchAll(/<ssk-button\b[^>]*>/gi)].map((m) => m[0]);
115
+ // Match camelCase, kebab-case, lowercase variants — HTML attrs are case-insensitive
116
+ const LEGACY = /\b(themecolor|theme-color|backgroundcolor|background-color|fontsize|font-size)=/i;
117
+ for (const btn of buttons) {
118
+ if (LEGACY.test(btn)) {
119
+ violations.push({
120
+ rule: 7,
121
+ ruleLabel: "button-no-legacy-props",
122
+ message: "<ssk-button> must use variant+tone, not legacy themeColor/backgroundColor/fontSize",
123
+ snippet: btn,
124
+ });
125
+ }
126
+ if (!/\bvariant=/i.test(btn)) {
127
+ violations.push({
128
+ rule: 7,
129
+ ruleLabel: "button-explicit-variant",
130
+ message: '<ssk-button> should declare explicit variant="solid|outline|ghost|solid-light"',
131
+ snippet: btn,
132
+ });
133
+ }
134
+ }
135
+ return violations;
136
+ }
137
+ // ── Public API ───────────────────────────────────────────────────────────────
138
+ /**
139
+ * Validate a vibecode template HTML against DS 3.0 contract rules.
140
+ *
141
+ * @param html Generated HTML output (full document or fragment).
142
+ * @param meta { templateName, brand } — brand must be in the allowed set.
143
+ * @returns { ok, violations, meta } — ok=true means zero violations.
144
+ */
145
+ export function runContract(html, meta) {
146
+ const body = stripComments(html);
147
+ const bodyNoVar = stripVarCalls(body);
148
+ const violations = [];
149
+ const r1 = rule1_rootIsAppShellProvider(body);
150
+ if (r1)
151
+ violations.push(r1);
152
+ violations.push(...rule2_brandWhitelistAndMatch(body, meta));
153
+ violations.push(...rule3_noHexLiteralsOutsideVar(bodyNoVar));
154
+ violations.push(...rule4_noFontSizeBelow18(body));
155
+ violations.push(...rule5_customTagsMustBeSskPrefix(body));
156
+ violations.push(...rule6_noTailwindForbiddenSize(body));
157
+ violations.push(...rule7_buttonUsesVariantTone(body));
158
+ return {
159
+ ok: violations.length === 0,
160
+ violations,
161
+ meta,
162
+ };
163
+ }
164
+ /**
165
+ * Brand-switching contract — verify two templates have identical structure
166
+ * apart from the `brand` attribute on the provider. This guarantees that
167
+ * brand switching at runtime does not require structural changes.
168
+ */
169
+ export function compareBrandSwitching(htmlA, htmlB) {
170
+ const normalize = (h) => stripComments(h).replace(/(<ssk-app-shell-provider\s+brand=)"[^"]+"/, '$1"_"');
171
+ const a = normalize(htmlA);
172
+ const b = normalize(htmlB);
173
+ if (a === b)
174
+ return { ok: true };
175
+ return {
176
+ ok: false,
177
+ reason: "Templates differ beyond just the brand attribute. Brand-switching " +
178
+ "requires structurally identical output across brands.",
179
+ };
180
+ }
package/package.json CHANGED
@@ -1,78 +1,83 @@
1
- {
2
- "name": "@uxuissk/design-system-core",
3
- "version": "3.1.2",
4
- "publishConfig": {
5
- "access": "public"
6
- },
7
- "type": "module",
8
- "files": [
9
- "dist"
10
- ],
11
- "main": "dist/sellsuki-components.umd.cjs",
12
- "module": "dist/sellsuki-components.js",
13
- "types": "dist/main.d.ts",
14
- "exports": {
15
- ".": {
16
- "import": "./dist/sellsuki-components.js",
17
- "require": "./dist/sellsuki-components.umd.cjs",
18
- "types": "./dist/main.d.ts"
19
- },
20
- "./style.css": "./dist/style.css"
21
- },
22
- "scripts": {
23
- "dev": "vite",
24
- "build": "cross-env NODE_ENV=production tsc && vite build",
25
- "preview": "vite preview",
26
- "storybook": "storybook dev -p 6006",
27
- "build-storybook": "storybook build",
28
- "test": "vitest",
29
- "generate:icon": "node scripts/generate-icons/index.js",
30
- "generate:font": "node scripts/generate-fonts/index.js",
31
- "generate:country": "node scripts/generate-country/index.js",
32
- "chromatic": "npx chromatic",
33
- "prepublishOnly": "npm run build"
34
- },
35
- "dependencies": {
36
- "@lit/context": "^1.1.3",
37
- "change-case": "^5.4.1",
38
- "croppie": "^2.6.5",
39
- "date-fns": "^3.6.0",
40
- "date-fns-tz": "^3.1.3",
41
- "glob": "^10.3.3",
42
- "gridstack": "^12.2.2",
43
- "idb": "^7.1.1",
44
- "iso-3166-1": "^2.1.1",
45
- "lit": "^3.2.1",
46
- "mustache": "^4.2.0",
47
- "prismjs": "^1.29.0",
48
- "shiki": "^4.0.2"
49
- },
50
- "devDependencies": {
51
- "@open-wc/lit-helpers": "^0.7.0",
52
- "@storybook/addon-designs": "^8.0.0",
53
- "@storybook/addon-essentials": "^8.6.18",
54
- "@storybook/addon-links": "^8.6.18",
55
- "@storybook/blocks": "^8.6.18",
56
- "@storybook/preview-api": "^8.6.18",
57
- "@storybook/web-components": "^8.6.18",
58
- "@storybook/web-components-vite": "^8.6.18",
59
- "@types/croppie": "^2.6.4",
60
- "@types/mustache": "^4.2.2",
61
- "@types/node": "^25.6.0",
62
- "@types/prismjs": "^1.26.3",
63
- "@types/react": "^19.2.14",
64
- "@types/react-dom": "^19.2.3",
65
- "chromatic": "^6.24.1",
66
- "cross-env": "^10.1.0",
67
- "dayjs": "^1.11.13",
68
- "ejs": "^3.1.9",
69
- "fake-indexeddb": "^4.0.2",
70
- "react": "^18.2.0",
71
- "react-dom": "^18.2.0",
72
- "storybook": "^8.6.18",
73
- "typescript": "^5.0.2",
74
- "vite": "^6.4.2",
75
- "vite-plugin-dts": "^4.5.4",
76
- "vitest": "^2.1.9"
77
- }
78
- }
1
+ {
2
+ "name": "@uxuissk/design-system-core",
3
+ "version": "3.4.2",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "main": "dist/sellsuki-components.umd.cjs",
12
+ "module": "dist/sellsuki-components.js",
13
+ "types": "dist/main.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/sellsuki-components.js",
17
+ "require": "./dist/sellsuki-components.umd.cjs",
18
+ "types": "./dist/main.d.ts"
19
+ },
20
+ "./style.css": "./dist/style.css",
21
+ "./test-utils/vibecode-contract": {
22
+ "import": "./dist/test-utils/vibecode-contract.js",
23
+ "types": "./dist/test-utils/vibecode-contract.d.ts"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "dev": "vite",
28
+ "build": "cross-env NODE_ENV=production tsc && vite build && npm run build:test-utils",
29
+ "build:test-utils": "tsc -p tsconfig.test-utils.json",
30
+ "preview": "vite preview",
31
+ "storybook": "storybook dev -p 6006",
32
+ "build-storybook": "storybook build",
33
+ "test": "vitest",
34
+ "generate:icon": "node scripts/generate-icons/index.js",
35
+ "generate:font": "node scripts/generate-fonts/index.js",
36
+ "generate:country": "node scripts/generate-country/index.js",
37
+ "chromatic": "npx chromatic",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "dependencies": {
41
+ "@lit/context": "^1.1.3",
42
+ "change-case": "^5.4.1",
43
+ "croppie": "^2.6.5",
44
+ "date-fns": "^3.6.0",
45
+ "date-fns-tz": "^3.1.3",
46
+ "glob": "^10.3.3",
47
+ "gridstack": "^12.2.2",
48
+ "idb": "^7.1.1",
49
+ "iso-3166-1": "^2.1.1",
50
+ "lit": "^3.2.1",
51
+ "mustache": "^4.2.0",
52
+ "prismjs": "^1.29.0",
53
+ "shiki": "^4.0.2"
54
+ },
55
+ "devDependencies": {
56
+ "@open-wc/lit-helpers": "^0.7.0",
57
+ "@storybook/addon-designs": "^8.0.0",
58
+ "@storybook/addon-essentials": "^8.6.18",
59
+ "@storybook/addon-links": "^8.6.18",
60
+ "@storybook/blocks": "^8.6.18",
61
+ "@storybook/preview-api": "^8.6.18",
62
+ "@storybook/web-components": "^8.6.18",
63
+ "@storybook/web-components-vite": "^8.6.18",
64
+ "@types/croppie": "^2.6.4",
65
+ "@types/mustache": "^4.2.2",
66
+ "@types/node": "^25.6.0",
67
+ "@types/prismjs": "^1.26.3",
68
+ "@types/react": "^19.2.14",
69
+ "@types/react-dom": "^19.2.3",
70
+ "chromatic": "^6.24.1",
71
+ "cross-env": "^10.1.0",
72
+ "dayjs": "^1.11.13",
73
+ "ejs": "^3.1.9",
74
+ "fake-indexeddb": "^4.0.2",
75
+ "react": "^18.2.0",
76
+ "react-dom": "^18.2.0",
77
+ "storybook": "^8.6.18",
78
+ "typescript": "^5.0.2",
79
+ "vite": "^6.4.2",
80
+ "vite-plugin-dts": "^4.5.4",
81
+ "vitest": "^2.1.9"
82
+ }
83
+ }