Strict styling requirements
Extensions must follow the ISA Warden design system as closely as possible.
14.0 Extension styling platform
ISA Warden now exposes a small extension styling platform for iframe content:
- shared starter stylesheet:
extensions/shared/isa-extension-theme.css - shared theme helper:
extensions/shared/isa-extension-theme.js - shared notification stylesheet:
extensions/shared/isa-extension-notifications.css - shared notification state and error helpers:
extensions/shared/isa-extension-notifications.js - host theme event:
theme:changed - bridge helpers:
window.isaExtensionBridge.getTheme(),window.isaExtensionBridge.onThemeChanged(callback), andwindow.isaExtensionBridge.applyHostThemeClasses(themeState)
Recommended extension entry setup:
html
<link rel="stylesheet" href="../shared/isa-extension-theme.css" />
<link rel="stylesheet" href="../shared/isa-extension-notifications.css" />
<script type="module">
import { initHostThemeBridge } from '../shared/isa-extension-theme.js';
initHostThemeBridge();
</script>Packaged extensions may copy these shared files into their package if the relative path differs. Keep the filenames and public API unchanged when possible so examples remain portable.
The host sends this theme payload to extension iframes when the iframe loads and when the host theme changes:
json
{
"theme": "dark",
"effectiveTheme": "dark",
"systemPrefersDark": true,
"designTokenVersion": 1,
"designTokens": {
"--isa-extension-panel-bg": "rgb(17 24 34 / 0.96)",
"--isa-extension-panel-text": "rgb(248 250 252 / 0.97)"
}
}Extension code should treat designTokens as the stable styling contract. The token names are extension-facing compatibility names, even when their values are sourced from host-internal CSS variables. Payload values are self-contained: a value may be concrete CSS or reference another --isa-extension-* token, but it must never reference host-private variables such as --brand-* or --color-*. The shared theme helper rejects non-portable values and removes previously applied inline tokens when a later theme payload omits them, allowing the stylesheet fallback for the active theme to take effect again.
Use these starter classes before adding local primitives:
isa-extension-shellisa-extension-panelisa-extension-cardisa-extension-headingisa-extension-mutedisa-extension-button,isa-extension-button--primary,isa-extension-button--danger,isa-extension-icon-buttonisa-extension-input,isa-extension-textarea,isa-extension-select,isa-extension-labelisa-extension-modal-backdrop,isa-extension-modalisa-extension-notice,isa-extension-notice--success,isa-extension-notice--warning,isa-extension-notice--errorisa-extension-badgeisa-extension-tableisa-extension-prose
Extension-local CSS should consume --isa-extension-* variables for shell, panel, card, text, border, focus, action, radius, and shadow roles. Domain-specific colors may still be added, but they must be defined as local tokens and remain isolated to domain-specific UI.
14.1 Design-system-first rule
Before adding local styling, extension authors must:
- reuse an existing semantic class from
chat-ui-3/src/app.css - reuse an existing token from
chat-ui-3/src/app.css - extend a nearby semantic pattern
- only then add extension-local styling
Custom styling is the exception, not the default.
14.2 Required surface rules
Use these host surface patterns where possible:
app-shell-panelfor main panel surfacesapp-shell-cardfor card surfaces
Do not create a separate panel/card visual system when these fit the use case.
14.3 Border radius rules
Use host radius tokens and semantic utilities:
Rules:
- panels, cards, and modal shells must use panel radius semantics
- inputs, buttons, icon buttons, selectors, and similar controls must use control radius semantics
- hardcoded radius values for standard components are forbidden unless a documented exception exists
14.4 Button rules
Use shared button styles where applicable:
Do not create a second generic button system for standard actions.
14.5 Form and control rules
Use shared control patterns, including:
form-select- shared control border/radius/focus treatment near
chat-ui-3/src/app.css
Inputs, selects, and textareas must align with host focus, disabled, error, and success treatment.
14.6 Selector and flyout rules
Use the selector system where the control is selector-like:
Do not create a separate dropdown/flyout visual system when the selector system fits.
14.7 Modal rules
Use shared modal shells and surfaces where applicable:
Do not hand-roll modal shells when host modal primitives fit the use case.
14.8 Color rules
Rules:
- use semantic tokens and shared class colors from
chat-ui-3/src/app.css - do not hardcode standard text, surface, border, or button colors when shared tokens/classes already cover the need
- brand-specific colors are only allowed where there is a genuine product or domain reason and accessibility remains valid
14.9 Dark and light mode requirements
Every extension UI must support both themes.
Verify:
- text contrast
- icon contrast
- border visibility
- hover and focus states
- selected and disabled states
14.10 Prohibited styling patterns
Extensions must not:
- create a parallel generic button system when host buttons fit
- create a separate dropdown/flyout system when selector primitives fit
- hardcode standard border radii
- hardcode standard surface/text colors without need
- create a modal shell from scratch when shared modal primitives fit
- assume only one theme exists
14.11 Notifications and transient messages
Every extension must use the framework-neutral shared notification contract:
- state and error helpers:
extensions/shared/isa-extension-notifications.js - canonical layout and appearance:
extensions/shared/isa-extension-notifications.css
Import the stylesheet after isa-extension-theme.css and mount exactly one notification centre at the extension application root. A modal, route, panel, or individual workflow must publish to that root-owned centre; it must never mount a second local stack. The shared stylesheet fixes the stack to the bottom-right, accounts for safe-area insets, switches to a responsive mobile inset, bounds and scrolls long stacks, and supplies semantic tone, countdown, focus, and reduced-motion styles.
The shared JavaScript module exports:
js
MAX_NOTIFICATION_COUNT;
NOTIFICATION_TIMEOUT_MS;
NOTIFICATION_EXIT_DURATION_MS;
NOTIFICATION_DISMISS_DELAY_MS;
getNotificationTone(kind, message);
appendNotification(notifications, candidate, options);
dismissNotification(notifications, notificationId);
dismissNotificationsByKey(notifications, notificationKey);
normalizeExtensionError(error, { fallback });Notification renderers use this complete, framework-neutral record:
js
const notification = {
id,
key,
tone,
title,
message,
detail,
action,
actionLabel,
createdAt,
timeoutStartedAt
};When id, createdAt, or timeoutStartedAt is omitted, appendNotification generates it. title remains empty when omitted so the renderer can supply a localized title for the semantic tone.
appendNotification caps the list at 20 by default, suppresses an adjacent unkeyed duplicate, and updates a keyed notification in place. Use a stable key for durable operational conditions such as a remote project update. Restart the dismiss timer when a keyed record receives a new timeoutStartedAt. Begin exit styling after NOTIFICATION_DISMISS_DELAY_MS, then remove the record after NOTIFICATION_EXIT_DURATION_MS.
Use the shared classes as the renderer contract:
.isa-extension-notification-center.isa-extension-notification-header.isa-extension-notification-clear.isa-extension-notification-list.isa-extension-notificationwithdata-tone="success|warning|error|info".isa-extension-notification-icon.isa-extension-notification-content.isa-extension-notification-title.isa-extension-notification-message.isa-extension-notification-detail.isa-extension-notification-action.isa-extension-notification-dismiss.isa-extension-notification-timer
Set data-transition-managed="true" when the renderer supplies its own enter and exit transition; otherwise the shared stylesheet supplies the default enter animation.
Set role="alert" and an assertive live region only for errors. Use role="status" and polite announcements for all other tones. Notifications must not steal focus. The clear control clears the complete stack; dismiss controls remove one record. Recovery actions stay inside the relevant notification and are executed by the extension renderer.
Call normalizeExtensionError before presenting caught host or bridge errors. It recursively unwraps JSON serialized into Error.message and returns only { code, message, retryable }. Translate known error codes into concise, extension-local copy and pass translated fallback copy:
js
const normalized = normalizeExtensionError(error, {
fallback: translate('errors.unexpected')
});
const message = translateKnownErrorCode(normalized.code) || normalized.message;
notifications = appendNotification(notifications, {
tone: 'error',
title: translate('notifications.error'),
message
});Never display a serialized error object as the notification message. Unknown technical detail may be placed in detail when it helps recovery, but the primary message must remain readable.
Keep field validation, typed-confirmation errors, loading/progress, empty states, and durable sync or storage-health status next to the control or surface they describe. These are contextual states, not transient notifications. A general operation failure may also create a notification, but do not duplicate the same text both inline and in the notification centre.
Operational outcomes such as save, delete, import, export, archive, restore, recording, transcription, and analysis success or failure belong in the root notification centre. Contextual state that remains true without a recent operation belongs inline. Do not show the same message both inline and as a notification.
Use only shared --isa-extension-* tokens for notification styling. The implementation in isa-match-rsx-to-quote remains the Svelte reference, but new extensions must consume the shared framework-neutral files instead of copying its local implementation.
14.12 Host styling with mod
The iframe design-token contract remains the required styling path for normal extensions. Without the explicit mod permission, extension CSS is isolated to its iframe and must not style or add UI to the parent application.
A mod may apply application-wide styling only from its separately declared modEntry after the host warning has been accepted. Prefer context.appendStyle(cssText) and context.appendRoot('appShell' | 'extensionToolbar') so contributions are tagged, tracked, and removed on disposal. Use the named mount points rather than locating incidental component internals, and reuse host semantic classes and tokens when the mounted control can do so.
Every direct host mutation must have explicit cleanup through context.onDispose or the function returned from activate. Event listeners should use context.addEventListener, and helper-created styles and roots must not be moved out of their tracked owner. Test cleanup after close and reload as well as light, dark, hover, focus, active, and disabled states.
The permission warning is not styling guidance and does not make host CSS stable or sandboxed. Avoid broad resets, fragile selectors, inaccessible contrast, or changes that obscure the host-owned active-mod indicator and dialogs.