@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.
- package/conformance/ds-conformance.mjs +36 -15
- package/package.json +1 -1
- package/spec.manifest.json +5 -5
|
@@ -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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
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}
|
|
218
|
-
console.log(` measured: ${tTot} colours · ${lTot} spacings · ${m.controls.length}
|
|
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.
|
|
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",
|
package/spec.manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mtdt/observeops-ds-spec",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"generated": "2026-07-
|
|
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":
|
|
364
|
-
"sha256": "
|
|
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": "
|
|
414
|
+
"sha256": "3e5a5e13ada2e15ca887ec4d40f280a0fac033f4dbaba8ac601d6efd46011d2f"
|
|
415
415
|
},
|
|
416
416
|
{
|
|
417
417
|
"path": "tokens/README.md",
|