# extendify/trunk/src/AutoLaunch/templates/schema.js

Extendify, version trunk. 82 lines.

- Page: https://pluginprobe.com/plugins/extendify/trunk/code/src/AutoLaunch/templates/schema.js
- Raw: https://pluginprobe.com/plugins/extendify/trunk/raw/src/AutoLaunch/templates/schema.js
- Modified: 2026-09-09T18:23:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/extendify/trunk/code/src/AutoLaunch/templates/schema.js#L10-L20`.

```javascript
/**
 * JSON Schema describes the value; the x- keywords describe what it has no
 * opinion about — a key's widget, its grouping and the panel's own labels.
 */
const CONTROL_KEYS = {
	'x-card': 'card',
	'x-label': 'label',
	'x-control': 'type',
	'x-feeds': 'feeds',
	'x-options': 'options',
	'x-unlocks': 'unlocks',
	'x-lockedBy': 'lockedBy',
	'x-inherits': 'inherits',
	'x-placeholder': 'placeholder',
	'x-note': 'note',
	'x-unit': 'unit',
};

// A length is `12px` at rest and 12 in the input, so the bounds describe the number.
const CSS_UNIT = { length: 'px', em: 'em' };

const withUnit = (number, unit) =>
	unit && number !== undefined ? `${number}${unit}` : number;

const withoutUnit = (value, unit) =>
	unit && typeof value === 'string' && value.endsWith(unit)
		? value.slice(0, -unit.length)
		: value;

// A value failing its schema becomes the bound or the default, never the page.
export const coerce = (shape, value) => {
	if (value === undefined || value === null || value === '') return value;
	if (shape.enum && !shape.enum.includes(value)) return shape.default;
	if (shape.type === 'number') {
		const unit = CSS_UNIT[shape['x-control']];
		const number = Number(withoutUnit(value, unit));
		if (Number.isNaN(number)) return withUnit(shape.default, unit);
		if (shape.minimum !== undefined && number < shape.minimum) {
			return withUnit(shape.minimum, unit);
		}
		if (shape.maximum !== undefined && number > shape.maximum) {
			return withUnit(shape.maximum, unit);
		}
		return withUnit(number, unit);
	}
	if (shape.type === 'string' && typeof value !== 'string')
		return shape.default;
	return value;
};

export const controlsFrom = (manifest) =>
	Object.entries(manifest.properties ?? {}).map(([key, shape]) => {
		const control = { key };
		if (key.startsWith('--')) control.cssVar = key;
		for (const [source, target] of Object.entries(CONTROL_KEYS)) {
			if (shape[source] !== undefined) control[target] = shape[source];
		}
		if (shape.default !== undefined) control.defaultValue = shape.default;
		if (shape.enum !== undefined) control.enum = shape.enum;
		if (shape.minimum !== undefined) control.min = shape.minimum;
		if (shape.maximum !== undefined) control.max = shape.maximum;
		if (shape.multipleOf !== undefined) control.step = shape.multipleOf;
		control.coerce = (value) => coerce(shape, value);
		return control;
	});

export const idOf = (manifest) => manifest.$id.split('/').pop();

/**
 * Global, then the page, then the template. A level restates only what it
 * changes — a different default still inherits the type and the bounds.
 */
export const mergeManifests = (manifests) => {
	const properties = {};
	for (const manifest of manifests) {
		for (const [key, shape] of Object.entries(manifest.properties ?? {})) {
			properties[key] = { ...properties[key], ...shape };
		}
	}
	return { properties };
};

```
