@openchamber/sdk 2.3.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCUMENTATION.md CHANGED
@@ -51,7 +51,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
51
51
  - `contributes.commands` is 1 to 8 entries (`GUEST_COMMANDS_MAX`) with a unique `name` matching `GUEST_COMMAND_NAME` (`/^[a-z][a-z0-9-]{0,23}$/`) and an optional `description` of 1 to `GUEST_COMMAND_DESCRIPTION_MAX` (80) characters; else `invalid-commands`. The host owns collisions: a name the composer already has (built-in, OpenCode command, skill, or an earlier guest) is dropped with a console warning. The public catalog row carries `commands` as declared.
52
52
  - `contributes.tools` is 1 to 16 entries (`GUEST_TOOLS_MAX`) that say how the extension's tool calls look in the chat, without code. `match` is the full tool name as OpenCode reports it, un-normalized (`mcp.jira.search`, `jira_search`), 1 to `GUEST_TOOL_MATCH_MAX` (128) characters of `[A-Za-z0-9_.:-]`, with one `*` allowed at the end as a suffix wildcard (`mcp.jira.*`; `*` alone is refused). Optional `name` (1 to `GUEST_TOOL_NAME_MAX`, 40) is the header title when `title` is absent or renders empty; `icon` is a Remixicon name or a package `.svg` path with the `panel.icon` rules (the chat draws the SVG as a `currentColor` mask at the glyph size through `resolveGuestToolIcon`, fetched as an authenticated asset under `/api/guests/<id>/<path>`; a Remixicon the sprite lacks leaves the host's own tool icon); `title` and `subtitle` are templates of up to `GUEST_TOOL_TEMPLATE_MAX` (200) characters with `{input.path}`, `{output.path}`, and `{metadata.path}` placeholders (dotted paths, array indexes allowed; a missing path is an empty string; a value is stringified and cut at `GUEST_TOOL_TEMPLATE_VALUE_MAX`, 200). `output` is `auto` (the host's own detection, the default), `text`, `json` (the JSON summary/tree/raw views), `markdown` (the assistant markdown renderer), `code` with `language` (1 to 32), or `table` with `columns` (1 to 16 dotted paths; rows are the output array or `output.items`). `language` outside `code` and `columns` outside `table` (or a `table` without `columns`) fail closed; anything else wrong is `invalid-tools`. The public catalog row carries `tools` as declared; the host applies them only for an active guest, an exact `match` beats a wildcard from any extension, and among equals the first extension in the catalog wins. The registry lives in `packages/ui/src/lib/guests/tool-presentation.ts` and is consulted by the existing chat tool renderers; VS Code and mobile keep the catalog empty, so nothing changes there.
53
53
  - Host → guest `resolve` is `{ id, payload: { command, args } }`; the guest answers `resolve-result` with the same `id` and `{ item }` (a clamped `AttachIssueRequest` or `null`) or `{ error }` (1 to `GUEST_RESOLVE_ERROR_MAX` characters). The host waits `GUEST_REQUEST_TIMEOUT_MS`, then treats silence as a failure and never sends a `result` for a `resolve-result`. `badge` is `{ count }` with `count` an integer 0..999 or `null`; the host schema drops anything else.
54
- - `ready` always carries `session` (`{ id, title, busy }` or `null`), `surface` (`panel` on the rail, `dialog` in the attach window, `page` full screen, `background` hidden, `status` in the Work Status panel, `file` as a Files view editor), `connection` (`{ connected, account }`), and `settings` (declared keys only). Title falls back to `id` when the host record has no title. `busy` is live `session_status` `busy` or `retry`. Missing status is `busy: false`, not an empty success that clears a previous snapshot. `model` is `providerID/id` when the host session record has both. `agent` is the session agent name when present. `surface` is which host chrome mounted the iframe. It is not `openSurface`. `item` is the attached item the surface was opened for (the user clicked that item's chip on the composer; the same `AttachIssueRequest` shape the guest handed to `attach`, with `text` filled from the chip's context) or `null` from the rail icon and the + menu. The host also pushes `item` when it changes while the frame is up; `onItem` replays the latest value. `ready.theme.tokens` is the host colour bag: surfaces (`background`, `elevated`, `mutedSurface`, `subtle`), text (`foreground`, `elevatedForeground`, `muted`), interaction (`border`, `hover`, `active`, `selection`, `selectionForeground`, `focus`), `primary` / `primaryForeground`, the four status colours, `font`, `mono`, and `radius`. `applyHostReady` writes it onto the iframe. The host also pushes `connection` and `settings` the same way as `session`. Access tokens never appear in `ready` or a result.
54
+ - `ready` always carries `session` (`{ id, title, busy }` or `null`), `surface` (`panel` on the rail, `dialog` in the attach window, `page` full screen, `background` hidden, `status` in the Work Status panel, `file` as a Files view editor), `connection` (`{ connected, account }`), and `settings` (declared keys only). Title falls back to `id` when the host record has no title. `busy` is live `session_status` `busy` or `retry`. Missing status is `busy: false`, not an empty success that clears a previous snapshot. `model` is `providerID/id` when the host session record has both. `agent` is the session agent name when present. `surface` is which host chrome mounted the iframe. It is not `openSurface`. `item` is the attached item the surface was opened for (the user clicked that item's chip on the composer; the same `AttachIssueRequest` shape the guest handed to `attach`, with `text` filled from the chip's context) or `null` from the rail icon and the + menu. The host also pushes `item` when it changes while the frame is up; `onItem` replays the latest value. `ready.theme.tokens` is the host colour bag: surfaces (`background`, `elevated`, `mutedSurface`, `subtle`), text (`foreground`, `elevatedForeground`, `muted`), interaction (`border`, `hover`, `active`, `selection`, `selectionForeground`, `focus`), `primary` / `primaryForeground`, the four status colours, `font`, `mono`, and `radius`. `background` is the page the frame sits on, not a fixed theme slot: a frame in a docked context or Work Status panel gets the panel's colour (the theme's muted surface in dark themes, halfway between it and the chat colour in light ones) and `mutedSurface` swaps to the chat colour so soft blocks stay a step off the page (`usePageColors` in `packages/ui`). `applyHostReady` writes it onto the iframe. The host also pushes `connection` and `settings` the same way as `session`. Access tokens never appear in `ready` or a result.
55
55
  - `writeClipboard` text is 1–32000 characters. `compose` text is 1–16000 after trim. Compose default mode is `append`. Compose writes the chat composer. It does not send.
56
56
  - `contributes.capabilities` is a list of `prompt`, `sessions`, `files`, `model`, and/or `shells` (`DECLARED_GUEST_CAPABILITIES`). `model` gates `generate`: `{ prompt, system?, maxOutputTokens? }` → `{ text }`, posted with a `GUEST_GENERATE_TIMEOUT_MS` (90s) timeout instead of the usual 20s because the model answers slowly; the host answers `NO_MODEL` when the small-model service resolves nothing (or only Claude Code) and `MODEL_FAILED` when the provider call fails. `requestedGuestCapabilities` adds `conversation`, `service`, `network`, `filesystem`, and `origins` from a session action with `payload: ["messages"]`, `contributes.service`, `contributes.integration`, `contributes.filesystem`, and `contributes.origins`; the user approves that whole list once at install (`PUT /api/guests/:id/capabilities`), and a package that later asks for more is re-approved. `hasGuestCapability` gates `prompt({ send })`, `startSession`, `request`, `serviceRequest`, the four file calls, and the three shell calls; a refusal is `NOT_GRANTED` (`NO_SERVICE` for the service).
57
57
  - `contributes.filesystem` is 1 to 16 globs, each 1 to 256 characters, starting with `/` or `~/`, with no `..`, empty segment, NUL, or backslash (`isGuestFilesystemPattern`; parse failure is `invalid-filesystem`). `**` spans directories, `*` and `?` stay inside one segment, and a pattern ending in `/**` also matches the directory itself. The public catalog row carries the list as `filesystem` so the approval dialog can show it.
package/dist/ui/style.js CHANGED
@@ -41,7 +41,6 @@ const hover = v('interactive-hover', 'transparent');
41
41
  const active = v('interactive-active', 'transparent');
42
42
  const selection = v('interactive-selection', 'transparent');
43
43
  const selectionFg = v('interactive-selection-foreground', 'inherit');
44
- const focus = v('interactive-focus-ring', 'currentColor');
45
44
  const primary = v('primary', 'currentColor');
46
45
  const primaryText = v('primary-text', 'inherit');
47
46
  const errorText = v('error-text', 'inherit');
@@ -52,7 +51,40 @@ const radius = v('radius', '9px');
52
51
  const controlRadius = `calc(${radius} - 2px)`;
53
52
  const smallRadius = `calc(${radius} - 4px)`;
54
53
  const mix = (color, pct, base = 'transparent') => (`color-mix(in srgb, ${color} ${pct}%, ${base})`);
55
- const focusRing = `box-shadow: 0 0 0 2px ${focus};`;
54
+ /** Keyboard focus draws no ring, like the app: the focused control takes the hover fill. */
55
+ const focusFill = `background-image: linear-gradient(${hover}, ${hover});`;
56
+ /**
57
+ * The app's `oc-field`, `oc-raised` and `oc-raised-button` light. Theme-dependent
58
+ * parts live in custom properties on the root, which `applyHostReady` marks with
59
+ * `data-oc-theme`: light themes press a field in and give a raised control the
60
+ * canvas colour; dark themes give fields, controls and tinted buttons one lit
61
+ * top line and one short shadow, and wash a field halfway toward the page so it
62
+ * sits a tone below the buttons beside it. Without the attribute (a preview
63
+ * outside the host) the light form applies.
64
+ */
65
+ const field = (edge) => `box-shadow: inset 0 0 0 1px ${edge}, var(--oc-sdk-field-lift);`;
66
+ const raised = 'box-shadow: inset 0 0 0 1px var(--oc-sdk-edge), var(--oc-sdk-raised-lift);';
67
+ const litButton = 'box-shadow: var(--oc-sdk-lit);';
68
+ const darkLift = `inset 0 1px 0 0 ${mix(fg, 1.5)}, 0 1px 1px 0 rgb(0 0 0 / 0.28), 0 2px 3px -2px rgb(0 0 0 / 0.2)`;
69
+ const THEME_VARS = `
70
+ :root {
71
+ --oc-sdk-edge: ${mix(fg, 10)};
72
+ --oc-sdk-field-bg: ${elevated};
73
+ --oc-sdk-raised-bg: ${bg};
74
+ --oc-sdk-field-lift: inset 0 1px 2px 0 rgb(0 0 0 / 0.04);
75
+ --oc-sdk-raised-lift: 0 1px 2px 0 rgb(0 0 0 / 0.06);
76
+ --oc-sdk-lit: inset 0 1px 0 0 rgb(255 255 255 / 0.45), 0 1px 2px 0 rgb(0 0 0 / 0.08);
77
+ --oc-sdk-float: inset 0 1px 0 0 rgb(255 255 255 / 0.8), inset 0 0 0 1px rgb(0 0 0 / 0.04), 0 0 0 1px rgb(0 0 0 / 0.1), 0 1px 2px -0.5px rgb(0 0 0 / 0.08), 0 4px 8px -2px rgb(0 0 0 / 0.08), 0 12px 20px -4px rgb(0 0 0 / 0.08);
78
+ }
79
+ :root[data-oc-theme="dark"] {
80
+ --oc-sdk-edge: ${mix(fg, 6)};
81
+ --oc-sdk-field-bg: ${mix(bg, 50, elevated)};
82
+ --oc-sdk-raised-bg: ${elevated};
83
+ --oc-sdk-field-lift: ${darkLift};
84
+ --oc-sdk-raised-lift: ${darkLift};
85
+ --oc-sdk-lit: inset 0 1px 0 0 ${mix(fg, 1.5)}, 0 1px 1px 0 rgb(0 0 0 / 0.28), 0 2px 3px -2px rgb(0 0 0 / 0.2);
86
+ --oc-sdk-float: inset 0 1px 0 0 rgb(255 255 255 / 0.06), inset 0 0 0 1px rgb(255 255 255 / 0.08), 0 0 0 1px rgb(0 0 0 / 0.36), 0 1px 1px -0.5px rgb(0 0 0 / 0.22), 0 3px 3px -1.5px rgb(0 0 0 / 0.2), 0 6px 6px -3px rgb(0 0 0 / 0.16);
87
+ }`;
56
88
  const tone = (name) => {
57
89
  const color = v(`status-${name}`, 'currentColor');
58
90
  return `
@@ -90,7 +122,7 @@ textarea.oc-sdk-input { height: auto; padding: 8px 10px; resize: vertical; }
90
122
  .oc-sdk-search[data-active="true"] .oc-sdk-search-clear { display: inline-flex; }
91
123
 
92
124
  .oc-sdk-select { position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
93
- .oc-sdk-trigger { display: inline-flex; align-items: center; gap: 6px; width: 100%; min-width: 0; height: 28px; padding: 0 10px; border: 1px solid transparent; font-size: 0.8125rem; text-align: left; }
125
+ .oc-sdk-trigger { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; width: auto; max-width: 100%; min-width: 0; height: 28px; padding: 0 10px; border: 1px solid transparent; font-size: 0.8125rem; text-align: left; }
94
126
  .oc-sdk-trigger-value { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
95
127
  .oc-sdk-trigger-chevron { flex: 0 0 auto; }
96
128
  .oc-sdk-popup { position: fixed; z-index: 50; display: flex; flex-direction: column; gap: 2px; min-width: 160px; max-width: calc(100vw - 16px); max-height: min(320px, calc(100vh - 16px)); overflow: auto; padding: 4px; border: 1px solid transparent; }
@@ -167,9 +199,10 @@ textarea.oc-sdk-input { height: auto; padding: 8px 10px; resize: vertical; }
167
199
  * follow the app's look without a rebuild. Selectors here keep the specificity
168
200
  * of the rules they override in older bundles.
169
201
  */
170
- export const GUEST_UI_LOOK_CSS = `
202
+ export const GUEST_UI_LOOK_CSS = `${THEME_VARS}
171
203
  .oc-sdk { color: ${fg}; font-family: ${font}; }
172
- .oc-sdk :focus-visible { outline: none; ${focusRing} }
204
+ .oc-sdk :focus-visible, .oc-sdk:focus-visible { outline: none; }
205
+ .oc-sdk :where(button, a[href], [role="button"], [role="tab"], [role="option"]):focus-visible, button.oc-sdk:focus-visible { ${focusFill} }
173
206
  .oc-sdk-mono { font-family: ${mono}; }
174
207
  .oc-sdk-muted { color: ${muted}; }
175
208
  ${tone('success')}${tone('warning')}${tone('error')}${tone('info')}
@@ -177,30 +210,29 @@ ${tone('success')}${tone('warning')}${tone('error')}${tone('info')}
177
210
 
178
211
  .oc-sdk-btn { border-radius: ${controlRadius}; transition: background 150ms ease-out, color 150ms ease-out; }
179
212
  .oc-sdk-btn[data-size="xs"] { border-radius: ${smallRadius}; }
180
- .oc-sdk-btn[data-variant="default"] { color: ${primaryText}; background: ${mix(primary, 10, bg)}; border-color: ${mix(primary, 12)}; }
213
+ .oc-sdk-btn[data-variant="default"] { color: ${primaryText}; background: ${mix(primary, 10, bg)}; border-color: ${mix(primary, 12)}; ${litButton} }
181
214
  .oc-sdk-btn[data-variant="default"]:hover { background: ${mix(primary, 16, bg)}; }
182
- .oc-sdk-btn[data-variant="default"]:active { background: ${mix(primary, 22, bg)}; }
215
+ .oc-sdk-btn[data-variant="default"]:active { background: ${mix(primary, 22, bg)}; box-shadow: none; }
183
216
  .oc-sdk-btn[data-variant="secondary"] { background: ${secondary}; color: var(--oc-fg); }
184
217
  .oc-sdk-btn[data-variant="secondary"]:hover { background-image: linear-gradient(${hover}, ${hover}); }
185
218
  .oc-sdk-btn[data-variant="secondary"]:active { background-image: linear-gradient(${active}, ${active}); }
186
- .oc-sdk-btn[data-variant="outline"] { background: ${elevated}; color: ${elevatedFg}; border-color: ${border}; }
219
+ .oc-sdk-btn[data-variant="outline"] { background: var(--oc-sdk-raised-bg); color: ${elevatedFg}; border-color: transparent; ${raised} }
187
220
  .oc-sdk-btn[data-variant="outline"]:hover { background-image: linear-gradient(${hover}, ${hover}); }
188
- .oc-sdk-btn[data-variant="outline"]:active { background-image: linear-gradient(${active}, ${active}); }
221
+ .oc-sdk-btn[data-variant="outline"]:active { background-image: linear-gradient(${active}, ${active}); box-shadow: inset 0 0 0 1px var(--oc-sdk-edge); }
189
222
  .oc-sdk-btn[data-variant="ghost"] { background: transparent; }
190
223
  .oc-sdk-btn[data-variant="ghost"]:hover { background: ${hover}; }
191
224
  .oc-sdk-btn[data-variant="ghost"]:active { background: ${active}; }
192
- .oc-sdk-btn[data-variant="destructive"] { --oc-sdk-tone: ${v('status-error', 'red')}; color: ${errorText}; background: ${mix('var(--oc-sdk-tone)', 7, bg)}; border-color: ${mix('var(--oc-sdk-tone)', 12)}; }
225
+ .oc-sdk-btn[data-variant="destructive"] { --oc-sdk-tone: ${v('status-error', 'red')}; color: ${errorText}; background: ${mix('var(--oc-sdk-tone)', 7, bg)}; border-color: ${mix('var(--oc-sdk-tone)', 12)}; ${litButton} }
193
226
  .oc-sdk-btn[data-variant="destructive"]:hover { background: ${mix('var(--oc-sdk-tone)', 9, bg)}; }
194
- .oc-sdk-btn[data-variant="destructive"]:active { background: ${mix('var(--oc-sdk-tone)', 11, bg)}; }
227
+ .oc-sdk-btn[data-variant="destructive"]:active { background: ${mix('var(--oc-sdk-tone)', 11, bg)}; box-shadow: none; }
195
228
 
196
229
  .oc-sdk-field-note { color: ${muted}; }
197
230
  .oc-sdk-field[data-invalid="true"] .oc-sdk-field-note { color: ${errorText}; }
198
- .oc-sdk-input { border-radius: ${controlRadius}; background: ${elevated}; color: ${elevatedFg}; box-shadow: inset 0 0 0 1px ${mix(border, 60)}; transition: background 150ms ease-out, box-shadow 150ms ease-out; }
231
+ .oc-sdk-input { border-radius: ${controlRadius}; background: var(--oc-sdk-field-bg); color: ${elevatedFg}; ${field('var(--oc-sdk-edge)')} transition: background 150ms ease-out; }
199
232
  .oc-sdk-input::placeholder { color: ${muted}; }
200
233
  .oc-sdk-input:hover:not(:focus) { background-image: linear-gradient(${hover}, ${hover}); }
201
- .oc-sdk-input:focus, .oc-sdk-input:focus-visible { box-shadow: inset 0 0 0 2px ${focus}; }
202
- .oc-sdk-field[data-invalid="true"] .oc-sdk-input { box-shadow: inset 0 0 0 1px ${v('status-error', 'red')}; }
203
- .oc-sdk-field[data-invalid="true"] .oc-sdk-input:focus { box-shadow: inset 0 0 0 2px ${v('status-error', 'red')}; }
234
+ .oc-sdk-input:focus, .oc-sdk-input:focus-visible { ${field(mix(fg, 26))} }
235
+ .oc-sdk-field[data-invalid="true"] .oc-sdk-input, .oc-sdk-field[data-invalid="true"] .oc-sdk-input:focus { ${field(v('status-error', 'red'))} }
204
236
  .oc-sdk-input[data-mono="true"] { font-family: ${mono}; }
205
237
 
206
238
  .oc-sdk-search-icon { color: ${muted}; }
@@ -208,12 +240,13 @@ ${tone('success')}${tone('warning')}${tone('error')}${tone('info')}
208
240
  .oc-sdk-search-clear { border-radius: ${smallRadius}; color: ${muted}; }
209
241
  .oc-sdk-search-clear:hover { background: ${hover}; color: ${fg}; }
210
242
 
211
- .oc-sdk-trigger { border-color: ${border}; border-radius: ${controlRadius}; background: ${elevated}; color: ${elevatedFg}; transition: background 150ms ease-out; }
243
+ .oc-sdk-trigger { border-color: transparent; border-radius: ${controlRadius}; background: var(--oc-sdk-raised-bg); color: ${elevatedFg}; ${raised} transition: background 150ms ease-out; }
212
244
  .oc-sdk-trigger:hover { background-image: linear-gradient(${hover}, ${hover}); }
213
245
  .oc-sdk-trigger[aria-expanded="true"] { background-image: linear-gradient(${active}, ${active}); }
214
246
  .oc-sdk-trigger-value[data-empty="true"] { color: ${muted}; }
215
247
  .oc-sdk-trigger-chevron { color: ${muted}; }
216
- .oc-sdk-popup { --surface-foreground: ${elevatedFg}; border-color: ${mix(border, 60)}; border-radius: 12px; background: ${elevated}; color: ${elevatedFg}; box-shadow: 0 8px 24px ${mix(fg, 12)}; }
248
+ /* The app's dropdown light: lit top, hairline rings and a layered shadow, no border. */
249
+ .oc-sdk-popup { --surface-foreground: ${elevatedFg}; border-color: transparent; border-radius: 12px; background: ${elevated}; color: ${elevatedFg}; box-shadow: var(--oc-sdk-float); }
217
250
  .oc-sdk-option { border-radius: 8px; }
218
251
  .oc-sdk-option[data-active="true"] { background: ${hover}; }
219
252
  /* A selected option shows a check mark in the text colour, like the app's own pickers. Options without the mark come
@@ -233,14 +266,15 @@ ${tone('success')}${tone('warning')}${tone('error')}${tone('info')}
233
266
  .oc-sdk-check[aria-checked="true"] .oc-sdk-check-thumb { background: ${primary}; }
234
267
  .oc-sdk-check[aria-checked="true"] .oc-sdk-check-thumb::after { transform: translateX(16px); }
235
268
  .oc-sdk-check:focus-visible { box-shadow: none; }
236
- .oc-sdk-check:focus-visible .oc-sdk-check-box, .oc-sdk-check:focus-visible .oc-sdk-check-thumb { ${focusRing} }
269
+ .oc-sdk-check:focus-visible .oc-sdk-check-box, .oc-sdk-check:focus-visible .oc-sdk-check-thumb { ${focusFill} }
237
270
  .oc-sdk-check-desc { color: ${muted}; }
238
271
 
239
272
  .oc-sdk-tabs { border-radius: 10px; }
240
- .oc-sdk-tabs[data-track="true"] { background: ${mix(fg, 4)}; }
273
+ .oc-sdk-tabs[data-track="true"] { background: ${mix(fg, 4)}; box-shadow: inset 0 1px 2px 0 rgb(0 0 0 / 0.16); }
241
274
  .oc-sdk-tab { border-radius: 8px; color: ${muted}; transition: color 150ms ease-out, background 150ms ease-out; }
242
275
  .oc-sdk-tab:hover { color: ${fg}; }
243
276
  .oc-sdk-tab[aria-selected="true"] { color: ${selectionFg}; background: ${selection}; border-color: ${border}; }
277
+ .oc-sdk-tabs[data-track="true"] .oc-sdk-tab[aria-selected="true"] { color: ${elevatedFg}; background: var(--oc-sdk-raised-bg); border-color: transparent; ${raised} }
244
278
  .oc-sdk-tab-count { color: ${muted}; }
245
279
 
246
280
  .oc-sdk-badge { border-radius: 9999px; background: ${hover}; color: ${muted}; }
@@ -260,7 +294,8 @@ ${tone('success')}${tone('warning')}${tone('error')}${tone('info')}
260
294
  .oc-sdk-spinner { color: ${muted}; }
261
295
  .oc-sdk-spinner-ring { border-color: ${border}; border-top-color: ${primary}; border-radius: 9999px; }
262
296
 
263
- .oc-sdk-banner { border-color: ${mix('var(--oc-sdk-tone)', 40)}; border-radius: 8px; background: ${mix('var(--oc-sdk-tone)', 10)}; }
297
+ /* Set apart by its tint alone, like the app's banners. */
298
+ .oc-sdk-banner { border-color: transparent; border-radius: 8px; background: ${mix('var(--oc-sdk-tone)', 10)}; }
264
299
  .oc-sdk-banner-title { color: var(--oc-sdk-tone-text, var(--oc-sdk-tone)); }
265
300
  .oc-sdk-banner-body { color: ${muted}; }
266
301
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchamber/sdk",
3
- "version": "2.3.2",
3
+ "version": "2.4.0",
4
4
  "description": "Contract for OpenChamber guests: manifest parse and iframe host bridge.",
5
5
  "private": false,
6
6
  "license": "MIT",