@mtdt/observeops-ds-spec 0.1.5 → 0.1.6

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.
@@ -124,9 +124,27 @@ const MEASURE = () => {
124
124
  const v = parseFloat(c[p]); if (v > 0) spaces.push({ prop: p, val: Math.round(v * 10) / 10 })
125
125
  }
126
126
  }
127
- const ctrlSel = 'button,input,select,textarea,a[href],[role=button],[role=checkbox],[role=switch],[role=radio],[role=tab]'
128
- const controls = [...document.querySelectorAll(ctrlSel)].map((el) => ({ tag: el.tagName.toLowerCase(), cls: (el.getAttribute('class') || ''), ce: el.tagName.includes('-') }))
129
- return { colors, spaces, controls, bodyBg: getComputedStyle(document.body).backgroundColor }
127
+ // ── component fidelity ─────────────────────────────────────────────────────
128
+ // A DS component (obs-*) renders its real control in SHADOW DOM, so any RAW interactive element in the
129
+ // light DOM is a non-DS control that should be a DS component. And a styled <span>/<div> chip that isn't
130
+ // a DS component is a fabricated look-alike (should be obs-tag / obs-severity).
131
+ const DS = ['obs-button','obs-input','obs-select','obs-switch','obs-checkbox','obs-radio','obs-link','obs-tag','obs-severity','obs-tags','obs-tooltip','obs-date-time-picker','obs-filters','obs-selected-pills']
132
+ const SUGGEST = { button:'obs-button', input:'obs-input', textarea:'obs-input', select:'obs-select', a:'obs-link' }
133
+ const insideCE = (el) => { let n = el.parentElement; while (n) { if (n.tagName.includes('-')) return true; n = n.parentElement } return false }
134
+ const dsInteractive = document.querySelectorAll('obs-button,obs-input,obs-select,obs-switch,obs-checkbox,obs-radio,obs-link').length
135
+ const rawControls = [...document.querySelectorAll('button,input:not([type=hidden]),select,textarea,a[href],[role=button],[role=switch],[role=checkbox],[role=radio],[role=tab]')]
136
+ .filter((el) => !el.tagName.includes('-') && !insideCE(el))
137
+ .map((el) => { const t = el.tagName.toLowerCase(); const role = el.getAttribute('role'); return { tag: t, role, suggest: SUGGEST[t] || (role ? 'obs-' + role.replace('checkbox','checkbox').replace('button','button') : 'a DS component'), text: (el.textContent || el.getAttribute('placeholder') || '').trim().slice(0, 30) } })
138
+ // fabricated chips: light-DOM, non-custom element with a bg tint + radius + short text (a Tag/Badge look-alike)
139
+ const chips = [...document.querySelectorAll('span,div,i')].filter((el) => {
140
+ if (el.tagName.includes('-') || insideCE(el)) return false
141
+ const c = getComputedStyle(el); const r = el.getBoundingClientRect()
142
+ const bg = c.backgroundColor; const hasBg = bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent'
143
+ const rad = parseFloat(c.borderTopLeftRadius) || 0
144
+ const txt = (el.textContent || '').trim()
145
+ return hasBg && rad >= 4 && r.width > 0 && r.width < 160 && r.height < 40 && txt.length >= 1 && txt.length <= 24 && el.children.length <= 1
146
+ }).map((el) => (el.textContent || '').trim().slice(0, 24))
147
+ return { colors, spaces, dsInteractive, rawControls, chips: [...new Set(chips)], bodyBg: getComputedStyle(document.body).backgroundColor }
130
148
  }
131
149
 
132
150
  ;(async () => {
@@ -200,26 +218,29 @@ const MEASURE = () => {
200
218
  if (brandMisuse.length) { philosophyScore -= 20; violations.philosophy.push({ rule: 'brand-navy', detail: `off-token saturated blue used (${brandMisuse.map((b) => b.value).join(', ')}) — brand must be --primary navy, not blue/cyan` }) }
201
219
  philosophyScore = Math.max(0, philosophyScore)
202
220
 
203
- // 4. component adherence (advisory): interactive controls should carry a DS signature
204
- const DS_SIG = /\b(ant-|obs-|m-|floto|tag-|squared-button|sel|trig)/i
205
- let cOk = 0
206
- for (const ctrl of m.controls) { if (ctrl.ce || DS_SIG.test(ctrl.cls) || ctrl.tag === 'a') cOk++ }
207
- const componentScore = m.controls.length ? Math.round((cOk / m.controls.length) * 100) : 100
208
- if (m.controls.length && cOk < m.controls.length) violations.component.push({ detail: `${m.controls.length - cOk} of ${m.controls.length} interactive controls have no DS component/class signature (raw element?)` })
221
+ // 4. COMPONENT FIDELITY every interactive control must be a real DS component (not a raw element),
222
+ // and no fabricated Tag/Badge look-alikes. A DS page renders controls via obs-* (shadow DOM), so any
223
+ // raw light-DOM control is a non-DS element. This is the "use DS components, not look-alikes" check.
224
+ const rawN = m.rawControls.length
225
+ const denom = m.dsInteractive + rawN
226
+ const componentScore = denom === 0 ? 100 : Math.round((m.dsInteractive / denom) * 100)
227
+ for (const rc of m.rawControls) violations.component.push({ detail: `raw <${rc.tag}${rc.role ? ' role=' + rc.role : ''}>${rc.text ? ` "${rc.text}"` : ''} — use ${rc.suggest}, not a raw element` })
228
+ if (m.chips.length) violations.component.push({ advisory: true, detail: `possible fabricated chip(s) — use obs-tag / obs-severity: ${m.chips.slice(0, 6).join(', ')}` })
209
229
 
210
- const overall = Math.round(tokenScore * 0.45 + layoutScore * 0.15 + philosophyScore * 0.25 + componentScore * 0.15)
211
- const result = { target, theme, overall, dimensions: { token: tokenScore, layout: layoutScore, philosophy: philosophyScore, component: componentScore },
212
- measured: { colors: tTot, spacings: lTot, controls: m.controls.length }, violations }
230
+ // Component fidelity is weighted heavily the point of the check is DS components, not just DS colours.
231
+ const overall = Math.round(tokenScore * 0.35 + componentScore * 0.30 + philosophyScore * 0.20 + layoutScore * 0.15)
232
+ const result = { target, theme, overall, dimensions: { token: tokenScore, component: componentScore, philosophy: philosophyScore, layout: layoutScore },
233
+ measured: { colors: tTot, spacings: lTot, dsComponents: m.dsInteractive, rawControls: rawN, fabricatedChips: m.chips.length }, violations }
213
234
 
214
235
  if (jsonOut) fs.writeFileSync(jsonOut, JSON.stringify(result, null, 2))
215
236
  if (!QUIET) {
216
237
  console.log(`\n=== DS conformance — ${target} (theme=${theme}) ===`)
217
- console.log(` OVERALL: ${overall}/100 · token ${tokenScore} layout ${layoutScore} philosophy ${philosophyScore} component ${componentScore}`)
218
- console.log(` measured: ${tTot} colours · ${lTot} spacings · ${m.controls.length} controls`)
238
+ console.log(` OVERALL: ${overall}/100 · token ${tokenScore} component ${componentScore} philosophy ${philosophyScore} layout ${layoutScore}`)
239
+ console.log(` measured: ${tTot} colours · ${lTot} spacings · ${m.dsInteractive} DS components · ${rawN} raw controls · ${m.chips.length} fabricated chip(s)`)
240
+ if (violations.component.length) { console.log('\n COMPONENT fidelity (use DS components, not raw/look-alikes):'); for (const v of violations.component.slice(0, 10)) console.log(` ✗ ${v.detail}`) }
219
241
  if (violations.token.length) { console.log('\n off-token colours (top):'); for (const v of violations.token.slice(0, 8)) console.log(` ✗ ${v.value} (${v.prop}, ×${v.count}) → nearest DS token ${v.nearestToken} (${v.nearestVal}, Δ${v.delta})`) }
220
242
  if (violations.layout.length) { console.log('\n off-scale spacing/radii:'); for (const v of violations.layout.slice(0, 6)) console.log(` ~ ${v.value} (×${v.count})`) }
221
243
  if (violations.philosophy.length) { console.log('\n philosophy:'); for (const v of violations.philosophy) console.log(` ✗ [${v.rule}] ${v.detail}`) }
222
- if (violations.component.length) { console.log('\n component:'); for (const v of violations.component) console.log(` ~ ${v.detail}`) }
223
244
  console.log('')
224
245
  }
225
246
  process.exit(overall >= 80 ? 0 : 1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtdt/observeops-ds-spec",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Machine-readable spec and AI operating contract for the Motadata ObserveOps design system \u2014 components, tokens, page recipes, layout structure, and the rules an AI tool follows to build ObserveOps UI faithfully.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "index.js",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mtdt/observeops-ds-spec",
3
- "version": "0.1.5",
4
- "generated": "2026-07-05T05:55:32.800Z",
3
+ "version": "0.1.6",
4
+ "generated": "2026-07-05T06:45:18.241Z",
5
5
  "entry": "llms.txt",
6
6
  "contract": "AGENTS.md",
7
7
  "index": "components/index.json",
@@ -360,8 +360,8 @@
360
360
  },
361
361
  {
362
362
  "path": "conformance/ds-conformance.mjs",
363
- "bytes": 14797,
364
- "sha256": "6c0ab765d85ffbb63f37bbecb31f4c1d0dc53f95dbce01044d0e6fb9baf5fd03"
363
+ "bytes": 17517,
364
+ "sha256": "2ab84ecc68575abf122b85beb3f9a9d4eccde9ffd629f71ea1cae2af50cfd3ac"
365
365
  },
366
366
  {
367
367
  "path": "foundation/README.md",
@@ -411,7 +411,7 @@
411
411
  {
412
412
  "path": "package.json",
413
413
  "bytes": 1072,
414
- "sha256": "a498b96846cd6731099d56e25658eedd15b1c3af8ee49bd969b5dcd2de9876f5"
414
+ "sha256": "3e5a5e13ada2e15ca887ec4d40f280a0fac033f4dbaba8ac601d6efd46011d2f"
415
415
  },
416
416
  {
417
417
  "path": "tokens/README.md",