') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); finding(registry): component deprecation is not declared anywhere machine-readable — the only statements of it are a console.warn literal and a human label, so no gate can ask "is this type deprecated?" · Issue #6674 · objectstack-ai/objectui · GitHub
Skip to content

finding(registry): component deprecation is not declared anywhere machine-readable — the only statements of it are a console.warn literal and a human label, so no gate can ask "is this type deprecated?" #6674

Description

@os-sales

Found while measuring #3965 (the div residue in examples/schema-catalog). Filed unassigned, recording only — not graded, no type. domain:* and grading are triage's to produce; the lane label is carried only because the measurement was taken here.

Measured on origin/mainf55d6661.

The fact

Nothing stops the 49th authored div. Both gates that touch component types ask only whether the type resolves, never whether it is deprecated:

  1. examples/schema-catalog/test/catalog-gallery-render.test.tsx asserts ComponentRegistry.get(type) is truthy and that no OBJUI-001 "Unknown component type" panel paints. divis registered and does render, so it stays green.
    • ⭐ Demonstrated rather than argued: that suite passes 583/583 with 85 authored div nodes present in the corpus. The green is the finding.
  2. scripts/check-doc-component-types.mjs asks the same existence question, and is scoped to content/docs/** — it does not cover examples/** at all.

Why this is worse than the usual shape

This lane's common defect is declared but enforced nowhere. This one is a layer below that: the deprecation is not declared anywhere machine-readable to begin with.

RegistryComponentMetaExtras carries tier, namespace, skipFallback and labelAssociation. It has no deprecated field. So the only two statements that div is deprecated are:

  • a console.warnstring literal inside div.tsx, and
  • the human-readable label Container (Deprecated).

A string literal in a renderer and a word in a label are not a contract. No gate, no test and no type can consult either one, which is why a deprecated type can be authored 85 times across 27 shipped exemplars with every check green.

Suggested disposition (not a decision)

Add a deprecated flag to the registry component metadata, and have the catalog render test fail on any authored node whose type declares it.

That is one field plus one assertion, and it converts "somebody remembers to grep for it" into "the corpus cannot carry one". ⭐ It also makes #3965's option A self-enforcing: if a neutral box type is added and div marked deprecated in metadata, a new example authoring div goes red on arrival instead of waiting for the next manual census.

⚠️ Whether the flag should also drive anything at author time (the objectui validate path, the save gate) is a separate and larger question — ⛔ not proposed here. The bounded version is registry metadata + the one test that already renders the whole corpus.

Related

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions