This project uses data-component-id as the stable DOM hook for extensibility, plugin integration, and automation.
- Plugins need predictable anchors to extend UI safely.
- E2E and integration tests need selectors that are stable across style/layout changes.
- We avoid global
idcollisions in repeated UI (lists, rows, nested panels).
- Use
data-component-idfor component hooks. - Use HTML
idonly when strictly needed for native semantics (labels, anchors, etc.).
Use lowercase, kebab-case identifiers.
- Static nodes:
section-element- Example:
character-sidebar-list
- Example:
- Dynamic entities:
<entity-prefix>-<entity-id>-<element>- Example:
ch-abc123-list-item - Example:
ch-abc123-pin-toggle
- Example:
- Plugin boundaries:
plugin-panel-hostplugin-<pluginName>-panel-root
Use helpers from src/lib/dom-ids.ts:
componentId(...parts)- Sanitizes and joins parts to kebab-case.
fragmentComponentId(fragment, suffix)- Produces stable fragment IDs using fragment type/prefix and fragment id.
story-sidebarsidebar-section-story-infosidebar-section-characterssidebar-section-guidelinessidebar-section-knowledgesidebar-section-mediasidebar-section-agent-activitysidebar-section-settingsdetail-panel-rootdetail-panel-contentdetail-panel-section-<section>
character-sidebar-listguideline-sidebar-listknowledge-sidebar-listmedia-sidebar-list- Dynamic rows and actions:
ch-<id>-list-itemch-<id>-selectch-<id>-pin-togglech-<id>-drag-handle
The same pattern applies to other prefixes (
gl,kn,pr,im,ic).
prose-chain-rootprose-chain-scrollprose-<fragmentId>-blockprose-<fragmentId>-regenerateprose-<fragmentId>-refineinline-generation-rootinline-generation-inputinline-generation-submitdebug-panel-rootdebug-log-<logId>-item
stories-pagestory-liststory-<storyId>-cardstory-editor-rootmain-prose-paneoverlay-story-wizardoverlay-debug-paneloverlay-provider-paneloverlay-fragment-editor-<mode>
When adding or changing UI:
- Add
data-component-idto extension boundaries and repeated item rows. - Prefer
componentId/fragmentComponentIdover hardcoded string concatenation. - Keep names semantic to feature + role (not style or visual position).
- Preserve existing IDs unless there is a migration reason.
- Read anchors from the host app by
data-component-id. - Mount plugin UI inside
plugin-<pluginName>-panel-rootwhen applicable. - Do not depend on Tailwind class names for integration.
- Prefer selectors like
[data-component-id="character-sidebar-list"]. - For dynamic rows, build selectors from known entity IDs.
- Avoid selectors tied to text content or visual class names when stable hooks exist.