can1357/oh-my-pi · error · Error

Composer style "${id}" is already registered

Error message

Composer style "${id}" is already registered

What it means

registerComposerStyle() rejects duplicate registrations: if an extension style with the same id is already present in the extension map, it throws instead of overwriting. Built-in ids are rejected separately (a different error).

Source

Thrown at packages/tui/src/components/composer/registry.ts:48

 *
 * Extensions registered before `filledSurface` existed received undecorated
 * row text, so an omitted flag remains filled for extension-owned ids.
 */
export function isFilledComposerStyle(style: ComposerStyle): boolean {
	return style.filledSurface ?? !isBuiltinComposerStyle(style.id);
}

/**
 * Register one extension-owned composer style for this process.
 *
 * Built-in ids and duplicate extension ids are rejected. The returned disposer
 * removes only this registration.
 */
export function registerComposerStyle(style: ComposerStyle): () => void {
	const id = style.id.trim();
	if (id.length === 0 || id !== style.id) throw new TypeError("Composer style id must be a non-empty trimmed string");
	if (isBuiltinComposerStyle(id)) throw new Error(`Cannot replace built-in composer style "${id}"`);
	if (extensionComposerStyles.has(id)) throw new Error(`Composer style "${id}" is already registered`);
	extensionComposerStyles.set(id, style);
	return () => {
		if (extensionComposerStyles.get(id) === style) extensionComposerStyles.delete(id);
	};
}

/** Style object for a composer shape; unknown ids fall back to `box`. */
export function getComposerStyle(id: EditorBorderStyle): ComposerStyle {
	return extensionComposerStyles.get(id) ?? BUILTIN_COMPOSER_STYLES[id] ?? boxComposerStyle;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Call the disposer returned by the first registerComposerStyle call before re-registering
  2. Guard registration so it runs only once per id
  3. Use a unique id per extension/feature

Example fix

// before
registerComposerStyle(style)
registerComposerStyle(style) // throws
// after
const dispose = registerComposerStyle(style)
// ...
dispose() // then re-register safely
registerComposerStyle(style)
Defensive patterns

Strategy: try-catch

Validate before calling

// track your own registrations to avoid duplicates
const registered = new Set<string>();
function safeRegister(style: ComposerStyle) {
  if (registered.has(style.id)) return;
  registered.add(style.id);
  disposers.set(style.id, registerComposerStyle(style));
}

Try / catch

const disposers = new Map<string, () => void>();
function registerOnce(style: ComposerStyle) {
  disposers.get(style.id)?.();
  disposers.set(style.id, registerComposerStyle(style));
}

Prevention

When it happens

Trigger: Calling registerComposerStyle twice with the same extension style id without disposing the first registration.

Common situations: Hot-reload re-running an extension's registration code; a plugin loaded twice; forgetting to call the disposer returned by a previous registration before re-registering.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a65fb59714065ec1. Report an issue: GitHub.