amruthpillai/reactive-resume · error · Error

A compound selector can contain only one type selector.

Error message

A compound selector can contain only one type selector.

What it means

validateCompound inspects the list of compiled simple selectors that form one compound selector and counts how many have type 'type'. A compound selector (no combinator) may contain at most one type selector (an element/tag). If two or more are present the compiler throws, because such a selector is meaningless in CSS (e.g. two tags adjoined).

Source

Thrown at packages/resume/src/stylesheet/selector.ts:122

	return type ? new Set(SEMANTIC_REGISTRY_V1[type].roles) : knownRoles;
}

function roleValueIsKnown(matcher: AttributeMatcher | null, value: string | null, roles: ReadonlySet<string>): boolean {
	if (!matcher || value === null) return roles.size > 0;
	if (matcher === "~=") return roles.has(value);
	if (matcher === "=") {
		const tokens = value.split(/\s+/).filter(Boolean);
		return tokens.length > 0 && tokens.every((token) => roles.has(token));
	}

	return [...roles].some((role) => matchesAttribute(role, matcher, value));
}

function validateCompound(selectors: readonly CompiledSimpleSelector[]): void {
	const types = selectors.filter(
		(selector): selector is Extract<CompiledSimpleSelector, { type: "type" }> => selector.type === "type",
	);
	if (types.length > 1) throw new Error("A compound selector can contain only one type selector.");

	const type = types[0]?.name ?? null;
	for (const selector of selectors) {
		if (selector.type !== "attribute") continue;

		if (selector.name === "id") continue;
		if (selector.name === "role") {
			if (!roleValueIsKnown(selector.matcher, selector.value, allowedRoles(type))) {
				throw new Error("Selector uses an unknown role.");
			}
			continue;
		}

		if (type && !(SEMANTIC_REGISTRY_V1[type].attributes as readonly string[]).includes(selector.name)) {
			throw new Error(`Attribute ${selector.name} is not available on ${type}.`);
		}
	}
}

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Insert the intended combinator: descendant (' '), child ('>'), etc., so the two type selectors are not in the same compound.
  2. If you meant an attribute/role restriction on a single type, keep only one type selector and express the rest as attributes (e.g. div[role='x']).
  3. Re-run the selector through the compiler in isolation to see the compiled simple-selector list and confirm where the second type came from.

Example fix

/* before: two types in one compound (invalid) */
divspan { ... }
/* after */
div span { ... }   /* descendant */
/* or */
div[role='card'] { ... }
Defensive patterns

Strategy: validation

Validate before calling

function countTypeSelectors(selector: string): number {
  // rough heuristic: count bare tag tokens not separated by a combinator
  const types = selector.match(/(^|\s)[a-zA-Z][\w-]*(?=$|\s|\.|#|\[|:)/g) ?? [];
  return types.length;
}

Type guard

function isSingleCompoundType(selectors: CompiledSimpleSelector[]): boolean {
  return selectors.filter(s => s.type === 'type').length <= 1;
}

Try / catch

try { compileSelector(selector); }
catch (e) { if (/only one type selector/.test(String((e as Error).message))) { reportInvalidSelector(selector); return; } throw e; }

Prevention

When it happens

Trigger: A selector string the compiler decomposed into multiple type selectors inside one compound — typically a malformed selector without a combinator between two tag names, or a custom syntax the compiler mis-tokenized into adjacent types.

Common situations: User/AI-authored selector like 'divspan' or 'div p' parsed incorrectly; a templating layer concatenating two type tokens without a combinator; misuse of the semantic type registry producing two type matches.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/2159b54d37b99ce6. Report an issue: GitHub.