designlang 10.4.0 → 10.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [10.5.0] — 2026-04-22
4
+
5
+ **The states LLMs always botch.**
6
+
7
+ ### Added
8
+
9
+ - **`src/extractors/form-states.js`** — surfaces forms (input count + style families), modal/dialog/sheet containers, skeleton and spinner loading indicators, empty-state and error-state placeholders, and detects which toast library is on the page (Sonner, react-hot-toast, react-toastify, Radix Toast, Chakra Toast, Notistack).
10
+ - New output: `*-form-states.json`.
11
+
12
+ Closes the v10.x minor-release series started in v10.1. Everything the
13
+ v10 spec deferred for v11 is now shipped as minor releases — with no
14
+ breaking changes along the way.
15
+
3
16
  ## [10.4.0] — 2026-04-22
4
17
 
5
18
  **Identification trio: icon system, background patterns, stack intel.**
@@ -376,6 +376,9 @@ program
376
376
  if (design.stackIntel) {
377
377
  files.push({ name: `${prefix}-stack-intel.json`, content: JSON.stringify(design.stackIntel, null, 2), label: 'Stack Intel (CMS/analytics/experimentation)' });
378
378
  }
379
+ if (design.formStates) {
380
+ files.push({ name: `${prefix}-form-states.json`, content: JSON.stringify(design.formStates, null, 2), label: 'Forms + States' });
381
+ }
379
382
  if (merged.prompts !== false) {
380
383
  const pack = buildPromptPack(design);
381
384
  const promptsDir = join(outDir, `${prefix}-prompts`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "designlang",
3
- "version": "10.4.0",
3
+ "version": "10.5.0",
4
4
  "description": "Extract the complete design language from any website — colors, typography, spacing, shadows, motion, component anatomy, brand voice, page intent, section roles, material language, component library, imagery style, and logo. Outputs AI-optimized markdown, W3C design tokens, motion tokens, typed component stubs, Tailwind config, and ready-to-paste v0 / Lovable / Cursor / Claude-Artifacts prompts.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,109 @@
1
+ // v10.5 — Form & State Capture
2
+ //
3
+ // The states LLM agents always botch when rebuilding: form fields (styling
4
+ // per type), validation hints, modal containers (backdrop + panel geometry),
5
+ // empty / loading / error placeholders, skeleton shapes, and which toast
6
+ // library (if any) is on the page. Pure function — reads the crawler's
7
+ // existing computedStyles + sections + componentCandidates.
8
+
9
+ const TOAST_LIBS = [
10
+ { id: 'sonner', re: /\bsonner\b|sonner-toast/i },
11
+ { id: 'react-hot-toast', re: /react-hot-toast/i },
12
+ { id: 'react-toastify', re: /react-toastify/i },
13
+ { id: 'radix-toast', re: /radix-toast|data-radix-toast/i },
14
+ { id: 'chakra-toast', re: /chakra-toast/i },
15
+ { id: 'notistack', re: /notistack/i },
16
+ ];
17
+
18
+ const SKELETON_CLASS_RE = /\b(skeleton|placeholder-loading|shimmer|pulse-loading|animate-pulse)\b/i;
19
+ const SPINNER_CLASS_RE = /\b(spinner|loading-indicator|loader)\b/i;
20
+ const EMPTY_STATE_RE = /\b(empty-state|no-results|no-data|nothing-here)\b/i;
21
+ const ERROR_STATE_RE = /\b(error-state|error-message|alert-error|form-error|invalid)\b/i;
22
+
23
+ function summarizeInputs(styles = []) {
24
+ const types = {};
25
+ for (const s of styles) {
26
+ if (!s.tag || !/^(input|textarea|select)$/i.test(s.tag)) continue;
27
+ const t = (s.type || s.inputType || s.tag).toLowerCase();
28
+ types[t] = (types[t] || 0) + 1;
29
+ }
30
+ return types;
31
+ }
32
+
33
+ function detectToastLib(stack = {}) {
34
+ const haystack = [
35
+ ...(stack.scripts || []),
36
+ ...(stack.classNameSample || []),
37
+ ].join(' ');
38
+ return TOAST_LIBS.filter(t => t.re.test(haystack)).map(t => t.id);
39
+ }
40
+
41
+ function detectModals(sections = []) {
42
+ return sections.filter(s => {
43
+ const blob = `${s.className || ''} ${s.role || ''}`.toLowerCase();
44
+ return /\bmodal\b|dialog|overlay|drawer|sheet/.test(blob) || s.role === 'dialog';
45
+ }).map(s => ({
46
+ role: s.role || null,
47
+ className: (s.className || '').slice(0, 80),
48
+ bounds: s.bounds || null,
49
+ }));
50
+ }
51
+
52
+ function classBasedScan(classSample = []) {
53
+ let skeleton = 0, spinner = 0, emptyState = 0, errorState = 0;
54
+ for (const c of classSample) {
55
+ if (SKELETON_CLASS_RE.test(c)) skeleton++;
56
+ if (SPINNER_CLASS_RE.test(c)) spinner++;
57
+ if (EMPTY_STATE_RE.test(c)) emptyState++;
58
+ if (ERROR_STATE_RE.test(c)) errorState++;
59
+ }
60
+ return { skeleton, spinner, emptyState, errorState };
61
+ }
62
+
63
+ function summarizeFormFields(candidates = []) {
64
+ const inputs = candidates.filter(c => c.kind === 'input');
65
+ if (!inputs.length) return { count: 0, families: {} };
66
+ const families = {};
67
+ for (const inp of inputs) {
68
+ const key = [
69
+ inp.css?.borderRadius || '',
70
+ inp.css?.padding || '',
71
+ inp.css?.border || '',
72
+ ].join('|');
73
+ families[key] = (families[key] || 0) + 1;
74
+ }
75
+ return { count: inputs.length, families: Object.values(families).slice(0, 6) };
76
+ }
77
+
78
+ export function extractFormStates(rawData = {}, design = {}) {
79
+ const light = rawData.light || {};
80
+ const stack = light.stack || {};
81
+ const sections = light.sections || [];
82
+ const candidates = light.componentCandidates || [];
83
+
84
+ const toastLibs = detectToastLib(stack);
85
+ const modals = detectModals(sections);
86
+ const classScan = classBasedScan(stack.classNameSample || []);
87
+ const form = summarizeFormFields(candidates);
88
+ const inputTypes = summarizeInputs(light.computedStyles || []);
89
+
90
+ const flags = [];
91
+ if (classScan.skeleton) flags.push('skeleton-loading');
92
+ if (classScan.spinner) flags.push('spinner-loading');
93
+ if (classScan.emptyState) flags.push('empty-state');
94
+ if (classScan.errorState) flags.push('error-state');
95
+ if (modals.length) flags.push('modal');
96
+ if (toastLibs.length) flags.push('toast-library');
97
+ if (form.count) flags.push('forms');
98
+
99
+ return {
100
+ flags,
101
+ forms: form,
102
+ inputTypesSeen: inputTypes,
103
+ modals,
104
+ toastLibraries: toastLibs,
105
+ loading: { skeleton: classScan.skeleton, spinner: classScan.spinner },
106
+ empty: { count: classScan.emptyState },
107
+ error: { count: classScan.errorState },
108
+ };
109
+ }
package/src/index.js CHANGED
@@ -37,6 +37,7 @@ import { extractSeo } from './extractors/seo.js';
37
37
  import { extractIconSystem } from './extractors/icon-system.js';
38
38
  import { extractBackgroundPatterns } from './extractors/background-patterns.js';
39
39
  import { extractStackIntel } from './extractors/stack-intel.js';
40
+ import { extractFormStates } from './extractors/form-states.js';
40
41
  import { formatDtcgTokens } from './formatters/dtcg-tokens.js';
41
42
  import { formatMotionTokens } from './formatters/motion-tokens.js';
42
43
 
@@ -145,6 +146,7 @@ export async function extractDesignLanguage(url, options = {}) {
145
146
  design.iconSystem = safeExtract(extractIconSystem, rawData.light?.icons || []) || { library: 'unknown', confidence: 0, stats: {}, signals: [], icons: [] };
146
147
  design.backgroundPatterns = safeExtract(extractBackgroundPatterns, rawData) || { labels: ['plain'], counts: {}, gradientTotals: {}, samples: [] };
147
148
  design.stackIntel = safeExtract(extractStackIntel, rawData.light?.stack || {}) || { cms: [], analytics: [], experimentation: [] };
149
+ design.formStates = safeExtract(extractFormStates, rawData, design) || { flags: [], forms: { count: 0, families: [] }, modals: [], toastLibraries: [] };
148
150
  // Stash raw crawler output so downstream orchestration (multipage, smart)
149
151
  // can rebuild the digest without re-crawling.
150
152
  design._raw = rawData;
@@ -218,6 +220,7 @@ export { extractSeo } from './extractors/seo.js';
218
220
  export { extractIconSystem } from './extractors/icon-system.js';
219
221
  export { extractBackgroundPatterns } from './extractors/background-patterns.js';
220
222
  export { extractStackIntel } from './extractors/stack-intel.js';
223
+ export { extractFormStates } from './extractors/form-states.js';
221
224
  export { refineWithSmart } from './classifiers/smart.js';
222
225
  export { crawlCanonicalPages, computeCrossPageConsistency, discoverCanonicalPages } from './multipage.js';
223
226
  export { buildPromptPack, formatV0Prompt, formatLovablePrompt, formatCursorPrompt, formatClaudeArtifactPrompt } from './formatters/prompt-pack.js';