@runtypelabs/persona 4.17.1 → 4.18.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/dist/chunk-3I2S3I5P.js +1 -0
- package/dist/chunk-76IELU7F.js +1 -0
- package/dist/chunk-EFJBUH5F.js +1 -0
- package/dist/chunk-PDPVORUF.js +1 -0
- package/dist/chunk-Q735PSZL.js +1 -0
- package/dist/chunk-VK6JOIYJ.js +1 -0
- package/dist/codegen.cjs +4 -4
- package/dist/codegen.js +4 -4
- package/dist/{context-mentions-BTSTIBFC.js → context-mentions-MJL6S6P2.js} +1 -1
- package/dist/context-mentions.cjs +9 -8
- package/dist/context-mentions.d.cts +1212 -14
- package/dist/context-mentions.d.ts +1212 -14
- package/dist/context-mentions.js +9 -8
- package/dist/event-stream-view-4EZ7BGFK.js +1 -0
- package/dist/event-stream-view.cjs +129 -0
- package/dist/event-stream-view.d.cts +8384 -0
- package/dist/event-stream-view.d.ts +8384 -0
- package/dist/event-stream-view.js +129 -0
- package/dist/history-view-BD6OXLNA.js +898 -0
- package/dist/history-view.cjs +898 -0
- package/dist/history-view.d.cts +1461 -0
- package/dist/history-view.d.ts +1461 -0
- package/dist/history-view.js +898 -0
- package/dist/index.cjs +77 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1987 -17
- package/dist/index.d.ts +1987 -17
- package/dist/index.global.js +67 -66
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +77 -77
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +5 -4
- package/dist/launcher.global.js.map +1 -1
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +0 -8
- package/dist/plugin-kit.d.ts +0 -8
- package/dist/plugin-kit.js +1 -1
- package/dist/smart-dom-reader.d.cts +1212 -14
- package/dist/smart-dom-reader.d.ts +1212 -14
- package/dist/theme-editor-preview.cjs +970 -73
- package/dist/theme-editor-preview.d.cts +1331 -15
- package/dist/theme-editor-preview.d.ts +1331 -15
- package/dist/theme-editor-preview.js +81 -81
- package/dist/theme-editor.cjs +7 -7
- package/dist/theme-editor.d.cts +1215 -15
- package/dist/theme-editor.d.ts +1215 -15
- package/dist/theme-editor.js +9 -9
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +226 -7
- package/dist/theme-reference.d.ts +226 -7
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +16 -4
- package/src/client-history-rest.test.ts +1034 -0
- package/src/client-visitor-history.test.ts +596 -0
- package/src/client.ts +1040 -24
- package/src/components/artifact-inline.ts +8 -1
- package/src/components/artifact-pane.test.ts +278 -1
- package/src/components/artifact-pane.ts +135 -67
- package/src/components/artifact-preview.test.ts +14 -1
- package/src/components/artifact-preview.ts +9 -0
- package/src/components/header-builder.ts +3 -0
- package/src/components/header-layouts.test.ts +171 -7
- package/src/components/header-layouts.ts +83 -53
- package/src/components/header-parts.test.ts +154 -1
- package/src/components/header-parts.ts +266 -138
- package/src/components/history-confirm-dialog.ts +177 -0
- package/src/components/history-shell-copy.ts +99 -0
- package/src/components/history-view/announcer.ts +40 -0
- package/src/components/history-view/copy.ts +105 -0
- package/src/components/history-view/css.ts +1078 -0
- package/src/components/history-view/grouping.test.ts +142 -0
- package/src/components/history-view/grouping.ts +142 -0
- package/src/components/history-view/icons.ts +73 -0
- package/src/components/history-view/rows.ts +341 -0
- package/src/components/history-view/state.ts +309 -0
- package/src/components/history-view.test.ts +2660 -0
- package/src/components/history-view.ts +1891 -0
- package/src/components/launcher-surface.test.ts +133 -0
- package/src/components/launcher.test.ts +18 -0
- package/src/components/launcher.ts +7 -7
- package/src/components/panel.ts +17 -7
- package/src/components/welcome.ts +72 -2
- package/src/defaults.ts +36 -3
- package/src/event-stream-view-bundle.test.ts +69 -0
- package/src/event-stream-view-entry.ts +19 -0
- package/src/event-stream-view-loader.ts +31 -0
- package/src/event-stream-view.ts +19 -0
- package/src/generated/runtype-openapi-contract.ts +1 -0
- package/src/history-render.ts +325 -0
- package/src/history-view-bundle.test.ts +78 -0
- package/src/history-view-entry.ts +41 -0
- package/src/history-view-loader.test.ts +30 -0
- package/src/history-view-loader.ts +32 -0
- package/src/history-view.ts +28 -0
- package/src/index-core.ts +50 -2
- package/src/index-global.ts +55 -0
- package/src/internal/demo-history-provider.test.ts +402 -0
- package/src/internal/demo-history-provider.ts +689 -0
- package/src/internal/history-provider-registry.ts +25 -0
- package/src/internal/history-provider.ts +150 -0
- package/src/internal/runtype-history-provider.test.ts +313 -0
- package/src/internal/runtype-history-provider.ts +240 -0
- package/src/plugin-kit.test.ts +16 -0
- package/src/plugin-kit.ts +13 -5
- package/src/plugins/types.ts +177 -1
- package/src/session-history.test.ts +1397 -0
- package/src/session.ts +1200 -26
- package/src/styles/widget-styles.test.ts +133 -0
- package/src/styles/widget.css +227 -8
- package/src/theme-editor/preview-utils.test.ts +19 -0
- package/src/theme-editor/preview-utils.ts +15 -1
- package/src/theme-editor/preview.ts +7 -1
- package/src/theme-editor/role-mappings.ts +4 -1
- package/src/theme-editor/sections.test.ts +168 -1
- package/src/theme-editor/sections.ts +136 -7
- package/src/theme-editor/webmcp/summary.ts +16 -2
- package/src/theme-reference.ts +20 -6
- package/src/types/theme.ts +213 -7
- package/src/types.ts +927 -7
- package/src/ui.artifact-rail-resize.test.ts +235 -0
- package/src/ui.attachments-drop.test.ts +1 -0
- package/src/ui.header-update-stability.test.ts +2 -2
- package/src/ui.history-chunk.test.ts +72 -0
- package/src/ui.history-config-provider.test.ts +318 -0
- package/src/ui.history-controller.test.ts +711 -0
- package/src/ui.history-lifecycle.test.ts +456 -0
- package/src/ui.history-render-hooks.test.ts +904 -0
- package/src/ui.history-shell.test.ts +1887 -0
- package/src/ui.rail-overlay.test.ts +573 -0
- package/src/ui.rail-resize.test.ts +284 -0
- package/src/ui.rebuild-composer.test.ts +21 -0
- package/src/ui.render-welcome.test.ts +42 -0
- package/src/ui.shortcuts.test.ts +166 -0
- package/src/ui.ts +3071 -240
- package/src/utils/__fixtures__/client-conversation-history-wire.json +108 -0
- package/src/utils/artifact-file.ts +15 -0
- package/src/utils/artifact-gate.test.ts +4 -0
- package/src/utils/artifact-gate.ts +13 -17
- package/src/utils/artifact-resize.test.ts +28 -0
- package/src/utils/artifact-resize.ts +9 -2
- package/src/utils/buttons.ts +188 -1
- package/src/utils/config-merge.test.ts +91 -0
- package/src/utils/dropdown.ts +10 -1
- package/src/utils/history-messages.test.ts +284 -0
- package/src/utils/history-messages.ts +284 -0
- package/src/utils/icons.ts +3 -0
- package/src/utils/shortcuts.test.ts +288 -0
- package/src/utils/shortcuts.ts +221 -0
- package/src/utils/theme.test.ts +501 -4
- package/src/utils/theme.ts +42 -3
- package/src/utils/tokens.ts +153 -15
- package/src/utils/tooltip-timing.ts +4 -0
- package/src/utils/tooltip.test.ts +249 -7
- package/src/utils/tooltip.ts +146 -4
- package/src/utils/visitor-store.test.ts +425 -0
- package/src/utils/visitor-store.ts +337 -0
- package/dist/chunk-QJ4LV2KW.js +0 -1
|
@@ -228,6 +228,71 @@ describe("composer spacing styles", () => {
|
|
|
228
228
|
});
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
describe("header control styles", () => {
|
|
232
|
+
it("sizes and strokes the glyph from the header control tokens", () => {
|
|
233
|
+
const start = widgetCss.indexOf(".persona-header-control--glyph > svg {");
|
|
234
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
235
|
+
|
|
236
|
+
expect(start).toBeGreaterThan(-1);
|
|
237
|
+
expect(rule).toContain(
|
|
238
|
+
"width: var(--persona-header-control-icon-size, 20px)",
|
|
239
|
+
);
|
|
240
|
+
// CSS presentation beats the SVG stroke-width attribute, which stays as
|
|
241
|
+
// the no-CSS fallback.
|
|
242
|
+
expect(rule).toContain(
|
|
243
|
+
"stroke-width: var(--persona-components-header-controlStrokeWidth, 1.5)",
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("thins the sparse glyph stroke by the same 0.7 the JS attributes use", () => {
|
|
248
|
+
const start = widgetCss.indexOf(".persona-header-control--sparse > svg {");
|
|
249
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
250
|
+
|
|
251
|
+
expect(start).toBeGreaterThan(-1);
|
|
252
|
+
expect(rule).toContain(
|
|
253
|
+
"width: calc(var(--persona-header-control-icon-size, 20px) * 1.4)",
|
|
254
|
+
);
|
|
255
|
+
// 0.7 x the 1.5 default is 1.05, the stroke the close X has always
|
|
256
|
+
// rendered at; SPARSE_GLYPHS in header-parts.ts carries the same pair.
|
|
257
|
+
expect(rule).toContain(
|
|
258
|
+
"stroke-width: calc(var(--persona-components-header-controlStrokeWidth, 1.5) * 0.7)",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// The sparse rule must stay after the glyph rule: same specificity, so
|
|
262
|
+
// source order is the only thing that lets it win.
|
|
263
|
+
expect(start).toBeGreaterThan(
|
|
264
|
+
widgetCss.indexOf(".persona-header-control--glyph > svg {"),
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("launcher icon styles", () => {
|
|
270
|
+
it("strokes every launcher glyph from the stroke token, surface-neutrally", () => {
|
|
271
|
+
// Locate the rule by its declaration and read the SHIPPED selector back
|
|
272
|
+
// out of the file, so a rescope cannot slip past a hardcoded expectation.
|
|
273
|
+
const declaration =
|
|
274
|
+
"stroke-width: var(--persona-components-launcher-iconStrokeWidth, 1.5)";
|
|
275
|
+
const declarationAt = widgetCss.indexOf(declaration);
|
|
276
|
+
expect(declarationAt).toBeGreaterThan(-1);
|
|
277
|
+
const selectorStart = widgetCss.lastIndexOf("}", declarationAt) + 1;
|
|
278
|
+
const selector = widgetCss
|
|
279
|
+
.slice(selectorStart, widgetCss.indexOf("{", selectorStart))
|
|
280
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
281
|
+
.trim();
|
|
282
|
+
|
|
283
|
+
// One pill, one line weight: the agent icon and the call-to-action arrow
|
|
284
|
+
// both follow the token.
|
|
285
|
+
expect(selector).toContain('[data-role="launcher-icon"] > svg');
|
|
286
|
+
expect(selector).toContain(
|
|
287
|
+
'[data-role="launcher-call-to-action-icon"] > svg',
|
|
288
|
+
);
|
|
289
|
+
// Both launcher surfaces render under [data-persona-root]; naming either
|
|
290
|
+
// surface here would change stroke weight between the two bundles.
|
|
291
|
+
expect(selector).not.toContain("data-persona-launcher-critical");
|
|
292
|
+
expect(selector).not.toContain("persona-widget-container");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
231
296
|
describe("transcript layout styles", () => {
|
|
232
297
|
it("drops the empty messages wrapper from the flex flow", () => {
|
|
233
298
|
// A zero-height flex child still triggers the body's gap, so an empty
|
|
@@ -332,6 +397,21 @@ describe("plugin welcome styles", () => {
|
|
|
332
397
|
".persona-widget-body.persona-welcome-overlay-active {\n overflow: hidden;\n}",
|
|
333
398
|
);
|
|
334
399
|
});
|
|
400
|
+
|
|
401
|
+
it("takes the transcript out of layout so nothing scrolls under the overlay", () => {
|
|
402
|
+
// The rule above cannot win against the inline `overflow-y: auto` that
|
|
403
|
+
// fill/fullscreen layouts stamp on the body, so the transcript stayed
|
|
404
|
+
// scrollable underneath an `inset: 0` box whose containing block scrolls
|
|
405
|
+
// with it: home and the transcript then read as one column, with no
|
|
406
|
+
// composer, and scrolling past the overlay left the transcript bare.
|
|
407
|
+
const selector =
|
|
408
|
+
".persona-widget-body.persona-welcome-overlay-active\n > :not([data-persona-welcome-overlay]) {";
|
|
409
|
+
const start = widgetCss.indexOf(selector);
|
|
410
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
411
|
+
|
|
412
|
+
expect(start).toBeGreaterThan(-1);
|
|
413
|
+
expect(rule).toContain("display: none !important");
|
|
414
|
+
});
|
|
335
415
|
});
|
|
336
416
|
|
|
337
417
|
describe("scrollbar policy styles", () => {
|
|
@@ -380,3 +460,56 @@ describe("scrollbar policy styles", () => {
|
|
|
380
460
|
);
|
|
381
461
|
});
|
|
382
462
|
});
|
|
463
|
+
|
|
464
|
+
describe("message action button styles", () => {
|
|
465
|
+
it("rounds the hit target on the medium radius, not the near-square small one", () => {
|
|
466
|
+
const start = widgetCss.indexOf(".persona-message-action-btn {");
|
|
467
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
468
|
+
|
|
469
|
+
expect(start).toBeGreaterThan(-1);
|
|
470
|
+
expect(rule).toContain(
|
|
471
|
+
"border-radius: var(--persona-message-action-radius, var(--persona-radius-md, 0.375rem))",
|
|
472
|
+
);
|
|
473
|
+
expect(rule).not.toContain("--persona-radius-sm");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("hovers with the scheme-aware ghost wash and text color, not surface or brand", () => {
|
|
477
|
+
const start = widgetCss.indexOf(".persona-message-action-btn:hover {");
|
|
478
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
479
|
+
|
|
480
|
+
expect(start).toBeGreaterThan(-1);
|
|
481
|
+
expect(rule).toContain(
|
|
482
|
+
"background-color: var(--persona-message-action-hover-bg, var(--persona-button-ghost-hover-bg, rgba(0, 0, 0, 0.05)))",
|
|
483
|
+
);
|
|
484
|
+
expect(rule).toContain(
|
|
485
|
+
"color: var(--persona-message-action-hover-fg, var(--persona-text, #111827))",
|
|
486
|
+
);
|
|
487
|
+
// A surface token is nearly invisible on light and paints a darker box on
|
|
488
|
+
// dark; the brand color is never an icon-hover color.
|
|
489
|
+
expect(rule).not.toContain("--persona-container");
|
|
490
|
+
expect(rule).not.toContain("--persona-primary");
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe("conversation-open skeleton styles", () => {
|
|
495
|
+
it("follows each role's real bubble radius chain", () => {
|
|
496
|
+
const leading = ".persona-conversation-loading-bubble {";
|
|
497
|
+
const start = widgetCss.indexOf(leading);
|
|
498
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
499
|
+
expect(start).toBeGreaterThan(-1);
|
|
500
|
+
expect(rule).toContain(
|
|
501
|
+
"border-radius: var(--persona-message-assistant-radius, var(--persona-radius-lg, 0.5rem))",
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const trailing = ".persona-conversation-loading-bubble--trailing {";
|
|
505
|
+
const trailingStart = widgetCss.indexOf(trailing);
|
|
506
|
+
const trailingRule = widgetCss.slice(
|
|
507
|
+
trailingStart,
|
|
508
|
+
widgetCss.indexOf("\n}", trailingStart),
|
|
509
|
+
);
|
|
510
|
+
expect(trailingStart).toBeGreaterThan(-1);
|
|
511
|
+
expect(trailingRule).toContain(
|
|
512
|
+
"border-radius: var(--persona-message-user-radius, var(--persona-radius-lg, 0.5rem))",
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
});
|
package/src/styles/widget.css
CHANGED
|
@@ -325,6 +325,16 @@
|
|
|
325
325
|
display: none;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/* Launcher glyph weight, agent icon and call-to-action arrow alike: one pill
|
|
329
|
+
must never carry two line weights. CSS wins over the SVG stroke-width
|
|
330
|
+
attribute, which stays the no-CSS fallback. Both the critical launcher
|
|
331
|
+
bundle and the full widget render this markup, so one rule keeps the two
|
|
332
|
+
surfaces identical. */
|
|
333
|
+
[data-persona-root] [data-role="launcher-icon"] > svg,
|
|
334
|
+
[data-persona-root] [data-role="launcher-call-to-action-icon"] > svg {
|
|
335
|
+
stroke-width: var(--persona-components-launcher-iconStrokeWidth, 1.5);
|
|
336
|
+
}
|
|
337
|
+
|
|
328
338
|
/* An empty transcript still counts as a flex child, so the body's gap
|
|
329
339
|
inserts phantom space below the welcome and fakes scrollable height. */
|
|
330
340
|
.persona-widget-messages:empty {
|
|
@@ -957,6 +967,16 @@
|
|
|
957
967
|
overflow: hidden;
|
|
958
968
|
}
|
|
959
969
|
|
|
970
|
+
/* The overlay is absolute inside the scroll body, so anything left scrollable
|
|
971
|
+
underneath strands it at the content origin instead of the viewport (the
|
|
972
|
+
body carries an inline `overflow-y: auto` in fill/fullscreen layouts, which
|
|
973
|
+
outranks the rule above). Hiding the transcript siblings leaves the body
|
|
974
|
+
with nothing to scroll, so the overlay stays over the visible area. */
|
|
975
|
+
.persona-widget-body.persona-welcome-overlay-active
|
|
976
|
+
> :not([data-persona-welcome-overlay]) {
|
|
977
|
+
display: none !important;
|
|
978
|
+
}
|
|
979
|
+
|
|
960
980
|
/* Hero: auto block margins center the surface in the empty conversation
|
|
961
981
|
without `justify-content`, which would make overflowing content
|
|
962
982
|
unreachable in the scroll body. */
|
|
@@ -1016,6 +1036,10 @@
|
|
|
1016
1036
|
background-color: var(--persona-surface, #ffffff);
|
|
1017
1037
|
}
|
|
1018
1038
|
|
|
1039
|
+
.persona-bg-persona-launcher {
|
|
1040
|
+
background-color: var(--persona-launcher-bg, var(--persona-surface, #ffffff));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1019
1043
|
.persona-bg-persona-container {
|
|
1020
1044
|
background-color: var(--persona-container, #f8fafc);
|
|
1021
1045
|
}
|
|
@@ -1052,6 +1076,14 @@
|
|
|
1052
1076
|
color: var(--persona-muted, #6b7280);
|
|
1053
1077
|
}
|
|
1054
1078
|
|
|
1079
|
+
.persona-text-persona-launcher {
|
|
1080
|
+
color: var(--persona-launcher-fg, var(--persona-primary, #171717));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.persona-text-persona-launcher-muted {
|
|
1084
|
+
color: var(--persona-launcher-fg-muted, var(--persona-muted, #6b7280));
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1055
1087
|
.persona-text-white {
|
|
1056
1088
|
color: #ffffff;
|
|
1057
1089
|
}
|
|
@@ -1783,12 +1815,12 @@
|
|
|
1783
1815
|
.persona-control-tooltip {
|
|
1784
1816
|
position: fixed;
|
|
1785
1817
|
box-sizing: border-box;
|
|
1786
|
-
max-width: min(320px, calc(100vw - 16px));
|
|
1818
|
+
max-width: var(--persona-tooltip-max-width, min(320px, calc(100vw - 16px)));
|
|
1787
1819
|
background-color: var(--persona-tooltip-background, #111827);
|
|
1788
1820
|
color: var(--persona-tooltip-foreground, #ffffff);
|
|
1789
|
-
padding: 6px 12px;
|
|
1790
|
-
border-radius: var(--persona-radius-sm, 0.125rem);
|
|
1791
|
-
font-size: 12px;
|
|
1821
|
+
padding: var(--persona-tooltip-padding, 6px 12px);
|
|
1822
|
+
border-radius: var(--persona-tooltip-radius, var(--persona-radius-sm, 0.125rem));
|
|
1823
|
+
font-size: var(--persona-tooltip-font-size, 12px);
|
|
1792
1824
|
line-height: 1.35;
|
|
1793
1825
|
white-space: normal;
|
|
1794
1826
|
overflow-wrap: anywhere;
|
|
@@ -1803,8 +1835,16 @@
|
|
|
1803
1835
|
opacity: 0;
|
|
1804
1836
|
}
|
|
1805
1837
|
|
|
1838
|
+
/* Trailing shortcut hint, e.g. a key combo after the label. */
|
|
1839
|
+
.persona-control-tooltip__hint {
|
|
1840
|
+
margin-left: 6px;
|
|
1841
|
+
color: var(--persona-tooltip-hint-fg, rgba(255, 255, 255, 0.55));
|
|
1842
|
+
white-space: nowrap;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1806
1845
|
.persona-control-tooltip__arrow {
|
|
1807
1846
|
position: absolute;
|
|
1847
|
+
display: var(--persona-tooltip-arrow-display, block);
|
|
1808
1848
|
left: var(--persona-tooltip-arrow-x, 50%);
|
|
1809
1849
|
width: 8px;
|
|
1810
1850
|
height: 8px;
|
|
@@ -1827,6 +1867,44 @@
|
|
|
1827
1867
|
justify-content: center;
|
|
1828
1868
|
}
|
|
1829
1869
|
|
|
1870
|
+
/* Shared header control box: close, clear chat, trailing actions, and the
|
|
1871
|
+
Messages toggle. A per-control config key (`launcher.closeButtonSize`,
|
|
1872
|
+
`launcher.clearChat.size`) writes inline styles, which win. */
|
|
1873
|
+
.persona-header-control {
|
|
1874
|
+
width: var(--persona-header-control-size, 32px);
|
|
1875
|
+
height: var(--persona-header-control-size, 32px);
|
|
1876
|
+
flex-shrink: 0;
|
|
1877
|
+
/* UA button padding would eat into the box and crush the glyph; a configured
|
|
1878
|
+
paddingX/Y is inline and still wins. */
|
|
1879
|
+
padding: 0;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.persona-header-control--glyph > svg {
|
|
1883
|
+
width: var(--persona-header-control-icon-size, 20px);
|
|
1884
|
+
height: var(--persona-header-control-icon-size, 20px);
|
|
1885
|
+
/* Presentation wins over the SVG stroke-width attribute, which stays as the
|
|
1886
|
+
no-CSS fallback. Unitless, straight off the theme token. */
|
|
1887
|
+
stroke-width: var(--persona-components-header-controlStrokeWidth, 1.5);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
/* Sparse-viewBox glyphs (X) need more box to match the visible extent of their
|
|
1891
|
+
siblings, and the same factor thins the stroke so the weight still matches.
|
|
1892
|
+
Multipliers mirror SPARSE_GLYPHS in header-parts.ts. */
|
|
1893
|
+
.persona-header-control--sparse > svg {
|
|
1894
|
+
width: calc(var(--persona-header-control-icon-size, 20px) * 1.4);
|
|
1895
|
+
height: calc(var(--persona-header-control-icon-size, 20px) * 1.4);
|
|
1896
|
+
stroke-width: calc(var(--persona-components-header-controlStrokeWidth, 1.5) * 0.7);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/* A smaller controlSize must never shrink a touch target below 40px. An
|
|
1900
|
+
explicit per-control size is inline, so it opts out of this floor. */
|
|
1901
|
+
@media (pointer: coarse) {
|
|
1902
|
+
.persona-header-control {
|
|
1903
|
+
min-width: 40px;
|
|
1904
|
+
min-height: 40px;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1830
1908
|
@media (prefers-reduced-motion: reduce) {
|
|
1831
1909
|
.persona-control-tooltip {
|
|
1832
1910
|
transition: none;
|
|
@@ -1849,6 +1927,91 @@
|
|
|
1849
1927
|
animation: persona-typing 1s infinite;
|
|
1850
1928
|
}
|
|
1851
1929
|
|
|
1930
|
+
/* Conversation-open stand-in: transcript skeleton shown while a conversation
|
|
1931
|
+
selected from the Messages list is fetched. The container carries the
|
|
1932
|
+
transcript's centered column so custom renderers land where messages will. */
|
|
1933
|
+
.persona-conversation-loading {
|
|
1934
|
+
width: 100%;
|
|
1935
|
+
max-width: var(--persona-content-max-width, 768px);
|
|
1936
|
+
margin: 0 auto;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
.persona-conversation-loading-body {
|
|
1940
|
+
display: flex;
|
|
1941
|
+
flex-direction: column;
|
|
1942
|
+
gap: 12px;
|
|
1943
|
+
padding: 20px;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
/* Leading bubbles stand in for assistant messages, trailing for user ones,
|
|
1947
|
+
so each follows its role's real radius chain. */
|
|
1948
|
+
.persona-conversation-loading-bubble {
|
|
1949
|
+
height: 44px;
|
|
1950
|
+
border-radius: var(--persona-message-assistant-radius, var(--persona-radius-lg, 0.5rem));
|
|
1951
|
+
background: var(--persona-divider, var(--persona-border, #e5e7eb));
|
|
1952
|
+
align-self: flex-start;
|
|
1953
|
+
animation: persona-conversation-loading-pulse 1400ms ease-in-out infinite;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
.persona-conversation-loading-bubble--trailing {
|
|
1957
|
+
align-self: flex-end;
|
|
1958
|
+
border-radius: var(--persona-message-user-radius, var(--persona-radius-lg, 0.5rem));
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
@keyframes persona-conversation-loading-pulse {
|
|
1962
|
+
0%,
|
|
1963
|
+
100% {
|
|
1964
|
+
opacity: 1;
|
|
1965
|
+
}
|
|
1966
|
+
50% {
|
|
1967
|
+
opacity: 0.55;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1972
|
+
.persona-conversation-loading-bubble {
|
|
1973
|
+
animation: none;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/* Failed conversation-open surface. The container carries the transcript
|
|
1978
|
+
column; the body classes style the default block only, so a plugin's
|
|
1979
|
+
renderHistoryOpenError content starts from a clean slate. */
|
|
1980
|
+
.persona-conversation-loading-error {
|
|
1981
|
+
width: 100%;
|
|
1982
|
+
max-width: var(--persona-content-max-width, 768px);
|
|
1983
|
+
margin: 0 auto;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
.persona-conversation-loading-error-body {
|
|
1987
|
+
display: flex;
|
|
1988
|
+
flex-direction: column;
|
|
1989
|
+
align-items: flex-start;
|
|
1990
|
+
gap: 12px;
|
|
1991
|
+
padding: 20px;
|
|
1992
|
+
color: var(--persona-text-muted, #6b7280);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
.persona-conversation-loading-error-title {
|
|
1996
|
+
margin: 0;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
.persona-conversation-loading-error-actions {
|
|
2000
|
+
display: flex;
|
|
2001
|
+
gap: 8px;
|
|
2002
|
+
flex-wrap: wrap;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
.persona-conversation-loading-error-action {
|
|
2006
|
+
padding: 8px 14px;
|
|
2007
|
+
border: 1px solid var(--persona-divider, var(--persona-border, #e5e7eb));
|
|
2008
|
+
border-radius: var(--persona-radius-md, 8px);
|
|
2009
|
+
background: transparent;
|
|
2010
|
+
color: inherit;
|
|
2011
|
+
font: inherit;
|
|
2012
|
+
cursor: pointer;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
1852
2015
|
.persona-space-x-1 > * + * {
|
|
1853
2016
|
margin-left: 0.25rem;
|
|
1854
2017
|
}
|
|
@@ -2675,16 +2838,18 @@
|
|
|
2675
2838
|
height: 28px;
|
|
2676
2839
|
padding: 0;
|
|
2677
2840
|
border: none;
|
|
2678
|
-
border-radius: var(--persona-radius-
|
|
2841
|
+
border-radius: var(--persona-message-action-radius, var(--persona-radius-md, 0.375rem));
|
|
2679
2842
|
background-color: transparent;
|
|
2680
2843
|
color: var(--persona-muted, #6b7280);
|
|
2681
2844
|
cursor: pointer;
|
|
2682
2845
|
transition: background-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
|
|
2683
2846
|
}
|
|
2684
2847
|
|
|
2848
|
+
/* Hover reads the shared ghost wash (scheme-aware) and moves the icon toward
|
|
2849
|
+
text, never a surface fill or the brand color. */
|
|
2685
2850
|
.persona-message-action-btn:hover {
|
|
2686
|
-
background-color: var(--persona-
|
|
2687
|
-
color: var(--persona-
|
|
2851
|
+
background-color: var(--persona-message-action-hover-bg, var(--persona-button-ghost-hover-bg, rgba(0, 0, 0, 0.05)));
|
|
2852
|
+
color: var(--persona-message-action-hover-fg, var(--persona-text, #111827));
|
|
2688
2853
|
}
|
|
2689
2854
|
|
|
2690
2855
|
.persona-message-action-btn:active {
|
|
@@ -3406,6 +3571,42 @@
|
|
|
3406
3571
|
gap: var(--persona-artifact-split-gap, 0.5rem);
|
|
3407
3572
|
}
|
|
3408
3573
|
|
|
3574
|
+
/* Split button: one bordered control, two invocable halves, internal hairline
|
|
3575
|
+
divider (Primer/Fluent anatomy). The wrap paints border, surface, and radius
|
|
3576
|
+
once; the halves stay borderless and transparent, with inner radii carved
|
|
3577
|
+
1px inside the wrap so hover fills never poke past the rounded corners. The
|
|
3578
|
+
half selectors carry a `button.` tag so they outrank context skins' rest
|
|
3579
|
+
rules (0,3,1 vs 0,3,0) while context hover rules (0,4,0) still win. */
|
|
3580
|
+
[data-persona-root] .persona-split-btn {
|
|
3581
|
+
--persona-split-radius: var(--persona-radius-md, 0.375rem);
|
|
3582
|
+
position: relative;
|
|
3583
|
+
display: inline-flex;
|
|
3584
|
+
align-items: stretch;
|
|
3585
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
3586
|
+
border-radius: var(--persona-split-radius);
|
|
3587
|
+
background: var(--persona-surface, #ffffff);
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-primary,
|
|
3591
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-chevron {
|
|
3592
|
+
border: 0;
|
|
3593
|
+
background: transparent;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-primary {
|
|
3597
|
+
border-radius: calc(var(--persona-split-radius) - 1px) 0 0 calc(var(--persona-split-radius) - 1px);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-chevron {
|
|
3601
|
+
border-left: 1px solid var(--persona-split-divider, var(--persona-border, #e5e7eb));
|
|
3602
|
+
border-radius: 0 calc(var(--persona-split-radius) - 1px) calc(var(--persona-split-radius) - 1px) 0;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-primary:where(:hover),
|
|
3606
|
+
[data-persona-root] .persona-split-btn > button.persona-split-btn-chevron:where(:hover) {
|
|
3607
|
+
background: var(--persona-icon-btn-hover-bg, var(--persona-container, #f3f4f6));
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3409
3610
|
/* Document toolbar preset: optional layout vars: --persona-artifact-doc-toolbar-icon-color, etc. */
|
|
3410
3611
|
/* Document-toolbar icon/copy button styling is shared by the artifact pane's
|
|
3411
3612
|
document preset and the inline chrome actions cluster, so both surfaces read
|
|
@@ -3435,6 +3636,7 @@
|
|
|
3435
3636
|
[data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-icon-btn[aria-pressed="true"] {
|
|
3436
3637
|
background: var(--persona-artifact-doc-toggle-active-bg, var(--persona-icon-btn-active-bg, var(--persona-container, #f3f4f6)));
|
|
3437
3638
|
border-color: var(--persona-artifact-doc-toggle-active-border, var(--persona-icon-btn-active-border, var(--persona-border, #e5e7eb)));
|
|
3639
|
+
color: var(--persona-artifact-doc-toggle-active-color, var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827)));
|
|
3438
3640
|
}
|
|
3439
3641
|
|
|
3440
3642
|
[data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn,
|
|
@@ -3457,6 +3659,16 @@
|
|
|
3457
3659
|
background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-icon-btn-hover-bg, var(--persona-container, #f3f4f6)));
|
|
3458
3660
|
}
|
|
3459
3661
|
|
|
3662
|
+
/* Copy split button inside the document toolbar: the wrap takes over the copy
|
|
3663
|
+
pill's themed border/background/radius; the halves fall through to the base
|
|
3664
|
+
split-btn rules above, which outrank these context rest rules by tag. */
|
|
3665
|
+
[data-persona-root] .persona-artifact-toolbar-document .persona-split-btn,
|
|
3666
|
+
[data-persona-root] .persona-artifact-inline-chrome-actions .persona-split-btn {
|
|
3667
|
+
--persona-split-radius: var(--persona-artifact-toolbar-copy-radius, var(--persona-radius-md, 0.375rem));
|
|
3668
|
+
border: var(--persona-artifact-toolbar-copy-border, 1px solid var(--persona-border, #e5e7eb));
|
|
3669
|
+
background: var(--persona-artifact-toolbar-copy-bg, var(--persona-surface, #ffffff));
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3460
3672
|
|
|
3461
3673
|
/* Artifact tab theming */
|
|
3462
3674
|
[data-persona-root] .persona-artifact-tab {
|
|
@@ -3745,6 +3957,9 @@
|
|
|
3745
3957
|
/* Inset ring instead of a border so documentToolbarToggleActiveBorderColor
|
|
3746
3958
|
still applies inside the borderless segment (transparent by default). */
|
|
3747
3959
|
box-shadow: inset 0 0 0 1px var(--persona-artifact-doc-toggle-active-border, transparent);
|
|
3960
|
+
/* Survives the @supports thumb override below, which resets only the
|
|
3961
|
+
background and ring; the active glyph keeps this color either way. */
|
|
3962
|
+
color: var(--persona-artifact-doc-toggle-active-color, var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827)));
|
|
3748
3963
|
}
|
|
3749
3964
|
|
|
3750
3965
|
/* Sliding selection thumb: a container ::before that translates between the
|
|
@@ -3850,7 +4065,9 @@
|
|
|
3850
4065
|
margin: 0.25rem 0;
|
|
3851
4066
|
}
|
|
3852
4067
|
|
|
3853
|
-
/* Draggable split
|
|
4068
|
+
/* Draggable split handles: the artifact seam (desktop split only; hidden in
|
|
4069
|
+
drawer / narrow host / small viewport) and the docked Messages rail's edge. */
|
|
4070
|
+
[data-persona-root] .persona-rail-resizer,
|
|
3854
4071
|
[data-persona-root] .persona-artifact-split-handle {
|
|
3855
4072
|
width: 6px;
|
|
3856
4073
|
flex-shrink: 0;
|
|
@@ -3864,6 +4081,8 @@
|
|
|
3864
4081
|
box-sizing: border-box;
|
|
3865
4082
|
}
|
|
3866
4083
|
|
|
4084
|
+
[data-persona-root] .persona-rail-resizer:hover,
|
|
4085
|
+
[data-persona-root] .persona-rail-resizer:focus-visible,
|
|
3867
4086
|
[data-persona-root] .persona-artifact-split-handle:hover,
|
|
3868
4087
|
[data-persona-root] .persona-artifact-split-handle:focus-visible {
|
|
3869
4088
|
background: color-mix(in srgb, var(--persona-border, #e5e7eb) 55%, transparent);
|
|
@@ -43,6 +43,25 @@ describe("theme editor preview demo data", () => {
|
|
|
43
43
|
);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it("enables the history feature for the messages scene", () => {
|
|
47
|
+
const config = buildPreviewConfig({ scene: "messages" });
|
|
48
|
+
|
|
49
|
+
expect(config.features?.history?.enabled).toBe(true);
|
|
50
|
+
expect(config.features?.history?.presentation).toBe("panel");
|
|
51
|
+
// A transcript still sits behind the Messages surface.
|
|
52
|
+
expect(config.initialMessages?.length).toBeGreaterThan(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("keeps an explicit history presentation in the messages scene", () => {
|
|
56
|
+
const config = buildPreviewConfig({
|
|
57
|
+
scene: "messages",
|
|
58
|
+
config: { features: { history: { enabled: false, presentation: "rail" } } },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(config.features?.history?.enabled).toBe(true);
|
|
62
|
+
expect(config.features?.history?.presentation).toBe("rail");
|
|
63
|
+
});
|
|
64
|
+
|
|
46
65
|
it("seeds tool call preview messages when advanced tool display modes are enabled", () => {
|
|
47
66
|
const config = buildPreviewConfig({
|
|
48
67
|
scene: "conversation",
|
|
@@ -200,7 +200,8 @@ export type PreviewScene =
|
|
|
200
200
|
| 'conversation'
|
|
201
201
|
| 'follow-ups'
|
|
202
202
|
| 'minimized'
|
|
203
|
-
| 'artifact'
|
|
203
|
+
| 'artifact'
|
|
204
|
+
| 'messages';
|
|
204
205
|
|
|
205
206
|
export type PreviewTranscriptEntryPreset =
|
|
206
207
|
| 'user-message'
|
|
@@ -660,6 +661,19 @@ export function applySceneConfig(
|
|
|
660
661
|
if (scene === 'artifact') {
|
|
661
662
|
config.features = { ...config.features, artifacts: { ...config.features?.artifacts, enabled: true } };
|
|
662
663
|
}
|
|
664
|
+
if (scene === 'messages') {
|
|
665
|
+
// The host wires a provider (setHistoryProviderFactory) and opens the view
|
|
666
|
+
// after mount; without one the widget hides its history chrome and the
|
|
667
|
+
// scene degrades to a plain conversation.
|
|
668
|
+
config.features = {
|
|
669
|
+
...config.features,
|
|
670
|
+
history: {
|
|
671
|
+
presentation: 'panel',
|
|
672
|
+
...config.features?.history,
|
|
673
|
+
enabled: true,
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
663
677
|
return config;
|
|
664
678
|
}
|
|
665
679
|
|
|
@@ -308,8 +308,14 @@ export function createThemePreview(
|
|
|
308
308
|
if (isMinimized) controller.close();
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// Inject artifacts if needed
|
|
312
311
|
const scene = options.scene ?? 'conversation';
|
|
312
|
+
// No-op without a history provider (see applySceneConfig): the host
|
|
313
|
+
// registers one via setHistoryProviderFactory before mounting.
|
|
314
|
+
if (scene === 'messages') {
|
|
315
|
+
for (const c of controllers) void c.showHistory({ focus: false });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Inject artifacts if needed
|
|
313
319
|
if (scene === 'artifact' || options.config?.features?.artifacts?.enabled) {
|
|
314
320
|
for (const c of controllers) {
|
|
315
321
|
c.upsertArtifact({
|
|
@@ -52,6 +52,9 @@ export const ROLE_HEADER: RoleAssignmentOptions = {
|
|
|
52
52
|
intensities: ROLE_INTENSITIES,
|
|
53
53
|
targets: [
|
|
54
54
|
{ path: 'components.header.background', kind: 'background' },
|
|
55
|
+
// Anchor of the widget's derivation; the explicit keys below still win,
|
|
56
|
+
// so role themes keep their exact per-key shades.
|
|
57
|
+
{ path: 'components.header.foreground', kind: 'foreground' },
|
|
55
58
|
{ path: 'components.header.border', kind: 'border' },
|
|
56
59
|
{ path: 'components.header.iconBackground', kind: 'accent' },
|
|
57
60
|
{ path: 'components.header.iconForeground', kind: 'foreground' },
|
|
@@ -250,7 +253,7 @@ function resolveHeaderTarget(family: string, solid: boolean, target: RoleTarget)
|
|
|
250
253
|
? `palette.colors.${family}.50`
|
|
251
254
|
: `palette.colors.${family}.50`;
|
|
252
255
|
}
|
|
253
|
-
if (path.endsWith('.titleForeground')) {
|
|
256
|
+
if (path.endsWith('.foreground') || path.endsWith('.titleForeground')) {
|
|
254
257
|
return solid
|
|
255
258
|
? `palette.colors.${family}.50`
|
|
256
259
|
: `palette.colors.${family}.${family === 'gray' ? '900' : '700'}`;
|