Framework contract

Component Spec

componentManager + componentInspector

The current source of truth for how DOM Studio discovers, decorates, documents, and exposes components to Studio.

Purpose

One contract, multiple consumers

A component should be discoverable from its folder, then decorated by inspecting the Vue component. The docs site, sidebar, generated pages, Studio palette, and future component database should all be able to use the same shape.

The names below document what exists now. They are intentionally precise so that future refactors from __doc, __studio, and _edit can be made against a known contract.

Adding a component

Put an El*.vue file under components, forms, or visual. It should appear in the generated docs and Studio automatically unless hidden.

Auto-documenter

Take the docs engine with the components

Optional when adopting DOM Studio

When someone copies, vendors, or builds on the components, they can also bring the auto-documenter. It turns local Vue component metadata into living docs inside their own app, so the component library does not need a separate Storybook-style setup to stay inspectable.

DiscoveryFinds local El*.vue files in configured component folders with Vite glob imports.
InspectionReads __doc, __studio, Vue prop definitions, slots, and events from the component object.
Generated pageRenders a playground, props table, slots, and events when no bespoke Index.vue exists.
NavigationAdds the component to the sidebar unless __doc.hidden or __doc.nav.hidden is set.
StudioMakes the component available to visual editing unless __doc.studio.hidden is set.
// 1. Add or copy a component into a discovered folder.
src/pages/forms/my-input/DomMyInput.vue

// 2. Give the component lightweight docs metadata.
defineOptions({
	__doc: {
		name: 'My input',
		tag: '<DomMyInput>',
		description: 'A labelled input for project-specific values.',
		studio: { group: 'Project forms' },
	},
});

// 3. Export it when the app needs package-style imports.
export { default as DomMyInput } from '../../forms/my-input/DomMyInput.vue';

// 4. The docs route is created automatically if there is no Index.vue.
/forms/my-input

Progressive docs

Start plain, then refine

The happy path

The core workflow is deliberately light. Focus on making the component first. Once it lands in a discovered folder, it shows up in the docs automatically. Later, when the generated page reveals what needs better naming, examples, or inspector controls, add only the metadata or custom page you actually need.

Make the componentBuild a normal Vue SFC in a discovered folder. No docs ceremony is required for the first pass.
It appears in docsThe route, sidebar item, generated playground, and props reference are inferred from the folder, export name, and defineProps.
Tweak metadataAdd __doc when the default label, description, nav order, Studio group, slots, or events need product language.
Tune the inspectorAdd _edit on props when the generated playground should use a richer editor or constrained options.
Override the pageAdd Index.vue beside the component only when the generated docs are no longer enough.

Generated inspector

Status pill

Auto

String / Text input

default
neutral

Inferred from defineProps. Add _edit.options later to turn this into a select-style control.

String / Text input

default
Draft

Seeded from the prop default and bound to the live component preview.

<!-- src/pages/components/status-pill/DomStatusPill.vue -->
<script setup>
const props = defineProps({
	tone: { type: String, default: 'neutral' },
	label: { type: String, default: 'Draft' },
});
</script>

<template>
	<span class="rounded-full border border-border px-2 py-1 text-xs">
		{{ label }}
	</span>
</template>

<!-- Result: /components/status-pill appears automatically.
The generated page uses the folder/export name, prop definitions, and playground. -->
<!-- Tweak the generated docs without writing a page. -->
<script setup>
defineOptions({
	__doc: {
		name: 'Status pill',
		tag: '<DomStatusPill>',
		description: 'A compact status label for workflow state.',
		order: 40,
		studio: {
			group: 'Feedback',
			icon: 'Circle',
		},
	},
});

const props = defineProps({
	tone: {
		type: String,
		default: 'neutral',
		_edit: {
			options: ['neutral', 'success', 'warning', 'danger'],
			description: 'Visual state shown by the pill.',
		},
	},
	label: { type: String, default: 'Draft' },
});
</script>

<!-- Override the whole docs page only when needed:
src/pages/components/status-pill/Index.vue -->

Adoption

What a consuming app chooses

StepWhy it matters
Copy the engineBring componentManager.js, componentInspector.js, GeneratedComponentPage.vue, and the docs helpers used by that page into the app.
Point discovery at the appChange the import.meta.glob patterns to match the app component folders that should be documented.
Add component metadataUse defineOptions({ __doc }) for name, tag, description, slots, events, nav, and Studio hints.
Choose the page styleOmit Index.vue for generated docs, or add Index.vue when the component needs authored examples and deeper guidance.
Keep examples nearbyStore examples beside the component so authored pages and future generated examples can stay close to the source.

Discovery

Component row

The component row is intentionally plain. It is the database-like record of what exists and where it lives. It does not decide labels, props, visibility, icons, or Studio behaviour.

// Discovered by componentManager.js from:
// src/pages/components/*/El*.vue
// src/pages/forms/*/El*.vue
// src/pages/visual/*/El*.vue

{
	"id": "forms/text-input",
	"path": "../forms/text-input/DomTextInput.vue",
	"section": "forms",
	"slug": "text-input",
	"exportName": "DomTextInput",
	"route": "/forms/text-input",
	"component": "DomTextInput"
}

Inspection

Decorated component row

The decorated row is created by inspecting the loaded Vue component. This is the shape consumers usually want when rendering docs, navigation, or Studio UI.

// Returned by inspecting the discovered row and Vue component.
{
	"id": "forms/text-input",
	"path": "../forms/text-input/DomTextInput.vue",
	"section": "forms",
	"slug": "text-input",
	"exportName": "DomTextInput",
	"route": "/forms/text-input",
	"component": "DomTextInput",
	"doc": {
		"name": "Text input",
		"tag": "<DomTextInput>",
		"description": "A labelled single-line text field."
	},
	"studio": {
		"group": "Forms",
		"hidden": false
	},
	"label": "Text input",
	"badge": null,
	"icon": "M5 7h14M12 7v10M8 17h8",
	"order": 100,
	"hidden": false,
	"navHidden": false,
	"studioHidden": false,
	"props": {
		"modelValue": { "type": "String", "default": "" },
		"label": { "type": "String", "default": "" }
	}
}

Metadata

Current __doc contract

FieldCurrent meaning
nameHuman label used by docs and navigation. Falls back to a label from the folder slug.
tagDisplay tag shown at the top of docs, such as <DomTextInput>.
descriptionShort documentation summary. Used by generated docs.
iconSVG path data. This is the preferred component icon location.
orderNumeric sort order inside the inferred group. Defaults to 100.
badgeGeneral badge. nav.badge is more specific for sidebar usage.
hiddenHide the component from generated docs/navigation/studio discovery.
navNavigation-specific decoration: badge, hidden, icon.
studioStudio-specific decoration: group, icon, hidden, defaults, accepts, hints.
slotsArray of slot docs: name, payload, description.
eventsArray of event docs: name, payload, description, and optional details for exact payload shapes or examples.
<script setup>
defineOptions({
	__doc: {
		name: 'Text input',
		tag: '<DomTextInput>',
		description: 'A labelled single-line text field.',
		icon: 'M5 7h14M12 7v10M8 17h8',
		order: 100,
		badge: 'New',
		nav: {
			badge: 'New',
			hidden: false,
			icon: 'M5 7h14M12 7v10M8 17h8',
		},
		studio: {
			group: 'Forms',
			icon: 'T',
			hidden: false,
		},
		slots: [
			{ name: 'prefix', description: 'Rendered before the input.' },
		],
		events: [
			{ name: 'update:modelValue', payload: 'string', description: 'Fired when text changes.' },
		],
	},
});
</script>

Inspector

Current prop _edit contract

_edit lives on Vue prop definitions. Studio's inspector reads it to choose the editor component and pass editor-specific props. This is not required for ordinary app usage.

componentName of the inspector editor component. Example: DomTextInput, DomJsonInput, DomJsonListInput.
descriptionHelper copy shown by the inspector field.
optionsOption values passed to the default editor when useful.
propsProps passed to the editor component. Use this for compact, rows, schema, addLabel, and similar editor-specific props.
type commentsUse JSDoc @typedef and @type comments near the component props when array/object shapes need richer documentation.
labelOptional label override when the editor uses a different visible label.
const props = defineProps({
	options: {
		type: Array,
		default: () => [],
		_edit: {
			component: 'DomJsonListInput',
			description: 'Options shown in the picker.',
			props: {
				compact: true,
				addLabel: '+ Add option',
				schema: [
					{ key: 'label', label: 'Label', default: (index) => `Option ${index + 1}` },
					{ key: 'value', label: 'Value', default: (index) => `option-${index + 1}` },
				],
			},
		},
	},
});

Studio

Studio renderer spec

The Studio renderer spec is a saved UI tree. It references components by name and stores props and children. It is not the same thing as the component row, but it depends on the component registry being able to resolve names back to components.

// Studio renderer spec. This is different from the component database row.
{
	"id": "email-field",
	"label": "Email field",
	"component": "DomTextInput",
	"props": {
		"label": "Email address",
		"placeholder": "you@example.com"
	},
	"children": []
}

Responsibilities

Where logic belongs today

PartResponsibility
Component managerDiscovers component files and derives path facts only: section, slug, exportName, route, id, and loader/component.
Component inspectorDecorates a discovered row by reading the loaded Vue component: __doc, __studio, props, labels, icons, hidden flags, and ordering.
Docs routesIf a component folder has an Index.vue, that page is used. If it has no Index.vue, GeneratedComponentPage.vue creates a reference page from inspected metadata.
SidebarUses discovered and inspected rows, but owns display ordering and fallback icons as presentation concerns.
StudioUses discovered and inspected rows to build the palette. It also infers defaults from props and local sample data.

Convention

Folder shape

A custom docs page is optional. If there is no Index.vue, the generated docs page uses component metadata and runtime props. Examples are still recommended because they show intended composition.

src/pages/forms/my-input/
	ElMyInput.vue
	Index.vue
	examples/
		Basic.vue