Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, '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" + ' Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, '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('^' + ".*" + ' Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, '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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Adopt platform-keyed conventions across schema, engine, CLI and plugin · Issue #372 · DirectedEdges/specs · GitHub
Skip to content

Adopt platform-keyed conventions across schema, engine, CLI and plugin #372

Description

@nathanacurtis

Problem

Conventions describe one library — the Figma file — and nothing else. A code generator reading a spec has no way to know which of a design system's own components means "text", "glyph", or "container", so it emits host elements: a <span> where the design system requires DsText. Compositions are built almost entirely out of these primitives, so the gap is most of what a composition is.

The same shape has no room for the answer, because it is namespaced figma rather than by platform — and the answer differs per implementation: DsText in React, <ds-text> in Web Components, Text in SwiftUI. Figma is itself one of those implementations, since specs are rendered back onto the canvas.

Two smaller gaps ride along: a spec records every platform a workspace configures rather than the one that produced it, and nothing states the width a component should be shown at when its root resizes to fill a parent it does not have.

Solution

Conventions become platform-keyed, with figma as one implementation among react, web-components and swiftui. Each platform declares which of its components implements each spec primitive, and how the spec's concepts reach that component's props. Each platform is authored as its own file in config/conventions/, named for the platform id.

The spec contract itself does not change: an element stays type: text, and each generator resolves it to its own component at emit time, so one spec serves every implementation.

Acceptance criteria

  • A workspace declares conventions as one file per platform in config/conventions/, the filename carrying the platform id
  • A workspace still holding a single config/conventions.yaml gets an error naming the directory it should become, not a silent fall back to defaults
  • specs generate reads the figma entry and produces specs unchanged from today
  • A generated React artifact emits the declared component for a text element, and routes unmapped styling to the declared stylesProp
  • A container binds to one component, or to a Row/Column/Box trio selected by layout direction
  • A spec's metadata.conventions records only the platform that produced it
  • A component whose root resizes to fill its parent renders at the declared width; fixed and hugging roots are unaffected

Problems

  • Conventions have no platform axis, so there is nowhere to put a spec-to-code binding
  • A generator emits host elements instead of the design system's own components
  • Every platform's conventions share one file, with one owner per section and unrelated review cadences
  • A spec's metadata carries platforms that had no part in producing it
  • A fill-width root has no declared width when shown standalone

Impacted code

  • specspackages/schema (landed), packages/cli (loader, templates, migration, ~24 read sites), site/ docs
  • specs-from-figma — reads the conventions object handed to Component; ~13 source files, ~44 test fixtures
  • figma-from-specsElements, KeyReversal, CodeOnlyProps
  • react-from-specs / webcomponents-from-specs — implement primitive resolution
  • specs-plugin-2 — one translation point in SpecsController

Related Docs

Notes

ADRs 073–079 and 081, on adr/primitive-composition (PR #363).

Order of implementation is schema → specs-from-figma → CLI, since the CLI depends on the engine. Schema is landed; everything downstream is outstanding, including ADR-078's loader, which has no schema-package surface at all.


Implementation details are tracked internally.

Metadata

Metadata

Assignees

Labels

clispecs-cli and MCP serverpluginFigma pluginschemaspecs-schema types and JSON schemaspecs-from-figma

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions