@runtypelabs/persona 4.7.0 → 4.8.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.
Files changed (117) hide show
  1. package/dist/animations/glyph-cycle.cjs +2 -2
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/glyph-cycle.js +2 -2
  5. package/dist/animations/{types-CSmiKRVa.d.cts → types-BsZtXPKK.d.cts} +43 -3
  6. package/dist/animations/{types-CSmiKRVa.d.ts → types-BsZtXPKK.d.ts} +43 -3
  7. package/dist/animations/wipe.cjs +2 -2
  8. package/dist/animations/wipe.d.cts +1 -1
  9. package/dist/animations/wipe.d.ts +1 -1
  10. package/dist/chunk-5EIIHQLQ.js +1 -0
  11. package/dist/codegen.cjs +12 -12
  12. package/dist/codegen.js +14 -14
  13. package/dist/index.cjs +91 -68
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +877 -172
  16. package/dist/index.d.ts +877 -172
  17. package/dist/index.global.js +81 -57
  18. package/dist/index.global.js.map +1 -1
  19. package/dist/index.js +91 -68
  20. package/dist/index.js.map +1 -1
  21. package/dist/install.global.js +1 -1
  22. package/dist/install.global.js.map +1 -1
  23. package/dist/launcher.global.js +3 -2
  24. package/dist/launcher.global.js.map +1 -1
  25. package/dist/markdown-parsers.js +24 -24
  26. package/dist/plugin-kit.cjs +1 -1
  27. package/dist/plugin-kit.js +1 -1
  28. package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
  29. package/dist/runtype-tts.js +1 -1
  30. package/dist/session-reconnect-JKIJBHS5.js +1 -0
  31. package/dist/smart-dom-reader.cjs +17 -17
  32. package/dist/smart-dom-reader.d.cts +753 -15
  33. package/dist/smart-dom-reader.d.ts +753 -15
  34. package/dist/smart-dom-reader.js +17 -17
  35. package/dist/testing.cjs +3 -3
  36. package/dist/testing.js +3 -3
  37. package/dist/theme-editor-preview.cjs +81 -58
  38. package/dist/theme-editor-preview.d.cts +761 -15
  39. package/dist/theme-editor-preview.d.ts +761 -15
  40. package/dist/theme-editor-preview.js +81 -58
  41. package/dist/theme-editor.cjs +6 -6
  42. package/dist/theme-editor.d.cts +753 -15
  43. package/dist/theme-editor.d.ts +753 -15
  44. package/dist/theme-editor.js +10 -10
  45. package/dist/theme-reference.cjs +1 -1
  46. package/dist/theme-reference.d.cts +74 -0
  47. package/dist/theme-reference.d.ts +74 -0
  48. package/dist/theme-reference.js +1 -1
  49. package/dist/voice-worklet-player.cjs +2 -2
  50. package/dist/voice-worklet-player.js +2 -2
  51. package/dist/webmcp-polyfill.js +2 -2
  52. package/dist/widget.css +1 -1
  53. package/package.json +2 -3
  54. package/src/artifacts-session.test.ts +178 -0
  55. package/src/client.test.ts +186 -1
  56. package/src/client.ts +92 -9
  57. package/src/components/artifact-card.test.ts +333 -0
  58. package/src/components/artifact-card.ts +75 -28
  59. package/src/components/artifact-inline.test.ts +1328 -0
  60. package/src/components/artifact-inline.ts +920 -0
  61. package/src/components/artifact-pane.test.ts +1042 -0
  62. package/src/components/artifact-pane.ts +440 -131
  63. package/src/components/artifact-preview.test.ts +1155 -0
  64. package/src/components/artifact-preview.ts +994 -0
  65. package/src/components/pill-composer-builder.test.ts +6 -2
  66. package/src/components/pill-composer-builder.ts +6 -6
  67. package/src/components/reasoning-bubble.ts +1 -13
  68. package/src/components/registry.ts +38 -3
  69. package/src/components/tool-bubble.ts +1 -13
  70. package/src/defaults.ts +1 -0
  71. package/src/generated/runtype-openapi-contract.ts +55 -3
  72. package/src/index-core.ts +20 -1
  73. package/src/index.ts +8 -0
  74. package/src/markdown-parsers-loader.test.ts +158 -0
  75. package/src/markdown-parsers-loader.ts +74 -9
  76. package/src/runtime/host-layout.test.ts +163 -0
  77. package/src/runtime/host-layout.ts +110 -7
  78. package/src/runtime/init.ts +18 -61
  79. package/src/session.ts +76 -22
  80. package/src/styles/widget.css +773 -26
  81. package/src/theme-editor/preview.ts +2 -0
  82. package/src/theme-editor/sections.test.ts +26 -1
  83. package/src/theme-editor/sections.ts +10 -2
  84. package/src/theme-reference.ts +2 -2
  85. package/src/tool-call-display-defaults.test.ts +1 -0
  86. package/src/types/theme.ts +77 -0
  87. package/src/types.ts +516 -17
  88. package/src/ui.artifact-pane-gating.test.ts +636 -0
  89. package/src/ui.component-directive.test.ts +104 -0
  90. package/src/ui.composer-bar.test.ts +60 -2
  91. package/src/ui.detached-panel.test.ts +1049 -0
  92. package/src/ui.tool-display.test.ts +51 -0
  93. package/src/ui.ts +698 -109
  94. package/src/utils/artifact-custom-actions.ts +128 -0
  95. package/src/utils/artifact-display.test.ts +42 -0
  96. package/src/utils/artifact-display.ts +84 -0
  97. package/src/utils/artifact-file.test.ts +116 -0
  98. package/src/utils/artifact-file.ts +117 -0
  99. package/src/utils/artifact-gate.test.ts +112 -5
  100. package/src/utils/artifact-gate.ts +39 -14
  101. package/src/utils/artifact-loading-status.ts +55 -0
  102. package/src/utils/artifact-status-label.ts +190 -0
  103. package/src/utils/buttons.ts +7 -1
  104. package/src/utils/code-highlight.test.ts +186 -0
  105. package/src/utils/code-highlight.ts +400 -0
  106. package/src/utils/icons.ts +2 -0
  107. package/src/utils/roving-tablist.test.ts +152 -0
  108. package/src/utils/roving-tablist.ts +111 -0
  109. package/src/utils/spinner.ts +45 -0
  110. package/src/utils/theme.test.ts +48 -0
  111. package/src/utils/theme.ts +7 -0
  112. package/src/utils/tokens.ts +91 -0
  113. package/src/utils/tool-loading-animation.test.ts +32 -0
  114. package/src/utils/tool-loading-animation.ts +24 -0
  115. package/dist/chunk-DFBSCFYN.js +0 -1
  116. package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
  117. package/dist/session-reconnect-U77QFUR7.js +0 -1
@@ -725,6 +725,10 @@
725
725
  flex-shrink: 0;
726
726
  }
727
727
 
728
+ .persona-shrink-0 {
729
+ flex-shrink: 0;
730
+ }
731
+
728
732
  .persona-min-w-0 {
729
733
  min-width: 0;
730
734
  }
@@ -863,6 +867,10 @@
863
867
  overflow-y: auto;
864
868
  }
865
869
 
870
+ .persona-overflow-x-auto {
871
+ overflow-x: auto;
872
+ }
873
+
866
874
  .persona-widget-body {
867
875
  overscroll-behavior: contain;
868
876
  }
@@ -2668,7 +2676,12 @@
2668
2676
  }
2669
2677
 
2670
2678
  /* Document toolbar preset: optional layout vars: --persona-artifact-doc-toolbar-icon-color, etc. */
2671
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn {
2679
+ /* Document-toolbar icon/copy button styling is shared by the artifact pane's
2680
+ document preset and the inline chrome actions cluster, so both surfaces read
2681
+ the same `components.artifact.toolbar` tokens (the vars are emitted globally
2682
+ at the widget root). */
2683
+ [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn,
2684
+ [data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-icon-btn {
2672
2685
  display: inline-flex;
2673
2686
  align-items: center;
2674
2687
  justify-content: center;
@@ -2681,17 +2694,20 @@
2681
2694
  line-height: 1;
2682
2695
  }
2683
2696
 
2684
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn:hover {
2697
+ [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn:hover,
2698
+ [data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-icon-btn:hover {
2685
2699
  color: var(--persona-artifact-toolbar-icon-hover-color, inherit);
2686
- background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
2700
+ background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-icon-btn-hover-bg, var(--persona-container, #f3f4f6)));
2687
2701
  }
2688
2702
 
2689
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn[aria-pressed="true"] {
2690
- background: var(--persona-artifact-doc-toggle-active-bg, var(--persona-container, #f3f4f6));
2691
- border-color: var(--persona-artifact-doc-toggle-active-border, var(--persona-border, #e5e7eb));
2703
+ [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn[aria-pressed="true"],
2704
+ [data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-icon-btn[aria-pressed="true"] {
2705
+ background: var(--persona-artifact-doc-toggle-active-bg, var(--persona-icon-btn-active-bg, var(--persona-container, #f3f4f6)));
2706
+ border-color: var(--persona-artifact-doc-toggle-active-border, var(--persona-icon-btn-active-border, var(--persona-border, #e5e7eb)));
2692
2707
  }
2693
2708
 
2694
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn {
2709
+ [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn,
2710
+ [data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-copy-btn {
2695
2711
  display: inline-flex;
2696
2712
  align-items: center;
2697
2713
  gap: 0.35rem;
@@ -2705,8 +2721,9 @@
2705
2721
  line-height: 1.25;
2706
2722
  }
2707
2723
 
2708
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn:hover {
2709
- background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
2724
+ [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn:hover,
2725
+ [data-persona-root] .persona-artifact-inline-chrome-actions .persona-artifact-doc-copy-btn:hover {
2726
+ background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-icon-btn-hover-bg, var(--persona-container, #f3f4f6)));
2710
2727
  }
2711
2728
 
2712
2729
  [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-copy-label {
@@ -2718,6 +2735,12 @@
2718
2735
  background: var(--persona-artifact-tab-bg, transparent);
2719
2736
  border-radius: var(--persona-artifact-tab-radius, 0.5rem);
2720
2737
  color: var(--persona-artifact-tab-color, inherit);
2738
+ /* Keep each tab on one line and cap its width so the strip scrolls
2739
+ horizontally instead of wrapping/overflowing the pane. */
2740
+ white-space: nowrap;
2741
+ max-width: var(--persona-artifact-tab-max-width, 12rem);
2742
+ overflow: hidden;
2743
+ text-overflow: ellipsis;
2721
2744
  }
2722
2745
 
2723
2746
  [data-persona-root] .persona-artifact-tab.persona-bg-persona-container {
@@ -2730,27 +2753,40 @@
2730
2753
  background: var(--persona-artifact-toolbar-bg, var(--persona-surface, #fff));
2731
2754
  }
2732
2755
 
2733
- /* Toggle group gap */
2734
- [data-persona-root] .persona-artifact-toggle-group {
2735
- gap: var(--persona-artifact-toolbar-toggle-group-gap, 0.25rem);
2736
- }
2737
-
2738
- /* Toggle button border-radius (view/code buttons) */
2739
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-view-btn,
2740
- [data-persona-root] .persona-artifact-toolbar-document .persona-artifact-code-btn {
2741
- border-radius: var(--persona-artifact-toolbar-toggle-radius, var(--persona-artifact-toolbar-icon-radius, var(--persona-radius-md, 0.375rem)));
2742
- }
2756
+ /* Toggle group + view/code button styling lives in the segmented
2757
+ .persona-toggle-group.persona-artifact-toggle-group block further down. */
2743
2758
 
2744
2759
  /* Tab hover */
2745
2760
  [data-persona-root] .persona-artifact-tab:hover {
2746
2761
  background: var(--persona-artifact-tab-hover-bg, var(--persona-container, #f3f4f6));
2747
2762
  }
2748
2763
 
2764
+ /* Keyboard focus ring, themed to match cards/buttons instead of the UA default
2765
+ (which varies by browser). Custom renderTabBar bars reuse .persona-artifact-tab,
2766
+ so they inherit the same ring; give a scroll container enough padding that the
2767
+ 2px offset isn't clipped. */
2768
+ [data-persona-root] .persona-artifact-tab:focus-visible {
2769
+ outline: 2px solid var(--persona-accent, #171717);
2770
+ outline-offset: 2px;
2771
+ }
2772
+
2749
2773
  /* Tab list container */
2750
2774
  [data-persona-root] .persona-artifact-list {
2751
2775
  background: var(--persona-artifact-tab-list-bg, transparent);
2752
2776
  border-bottom-color: var(--persona-artifact-tab-list-border-color, var(--persona-border, #e5e7eb));
2753
2777
  padding: var(--persona-artifact-tab-list-padding, 0.5rem);
2778
+ /* Thin, unobtrusive horizontal scrollbar for the tab strip. */
2779
+ scrollbar-width: thin;
2780
+ scroll-behavior: smooth;
2781
+ }
2782
+
2783
+ [data-persona-root] .persona-artifact-list::-webkit-scrollbar {
2784
+ height: 6px;
2785
+ }
2786
+
2787
+ [data-persona-root] .persona-artifact-list::-webkit-scrollbar-thumb {
2788
+ background: var(--persona-artifact-tab-list-scrollbar, var(--persona-border, #e5e7eb));
2789
+ border-radius: 9999px;
2754
2790
  }
2755
2791
 
2756
2792
  /* Toolbar border override */
@@ -2758,6 +2794,23 @@
2758
2794
  border-bottom: var(--persona-artifact-toolbar-border, 1px solid var(--persona-border, #e5e7eb));
2759
2795
  }
2760
2796
 
2797
+ /* Artifact reference card (chat thread) */
2798
+ [data-persona-root] .persona-artifact-card {
2799
+ background: var(--persona-artifact-card-bg, var(--persona-surface, #ffffff));
2800
+ border: var(--persona-artifact-card-border, 1px solid var(--persona-border, #e5e7eb));
2801
+ border-radius: var(--persona-artifact-card-radius, var(--persona-message-assistant-radius, var(--persona-radius-lg, 0.5rem)));
2802
+ cursor: pointer;
2803
+ transition: background-color 0.15s ease, border-color 0.15s ease;
2804
+ }
2805
+ [data-persona-root] .persona-artifact-card:hover {
2806
+ background: var(--persona-artifact-card-hover-bg, #f3f4f6);
2807
+ border-color: var(--persona-artifact-card-hover-border, var(--persona-border, #e5e7eb));
2808
+ }
2809
+ [data-persona-root] .persona-artifact-card:focus-visible {
2810
+ outline: 2px solid var(--persona-accent, #171717);
2811
+ outline-offset: 2px;
2812
+ }
2813
+
2761
2814
  /* ── Composable button utilities ── */
2762
2815
 
2763
2816
  /* Icon button: base for all icon-only buttons created by createIconButton() */
@@ -2929,6 +2982,89 @@
2929
2982
  border-bottom-right-radius: var(--persona-toggle-group-radius, var(--persona-icon-btn-radius, var(--persona-radius-md, 0.375rem)));
2930
2983
  }
2931
2984
 
2985
+ /* Artifact view/source toggle: one segmented pill. The container carries the
2986
+ border; segments are borderless and the active one is highlighted, so the
2987
+ pair reads as a single mutually exclusive switch instead of two buttons. */
2988
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group {
2989
+ gap: var(--persona-artifact-toolbar-toggle-group-gap, 2px);
2990
+ padding: var(--persona-artifact-toolbar-toggle-group-padding, 2px);
2991
+ border: var(--persona-artifact-toolbar-toggle-group-border, 1px solid var(--persona-border, #e5e7eb));
2992
+ /* Default to the toolbar button radius (document preset var, then the plain
2993
+ icon-button var) so the pill matches the neighboring copy/close buttons
2994
+ instead of standing out as a capsule. */
2995
+ border-radius: var(--persona-artifact-toolbar-toggle-group-radius, var(--persona-artifact-toolbar-icon-radius, var(--persona-icon-btn-radius, var(--persona-radius-md, 0.375rem))));
2996
+ background: var(--persona-artifact-toolbar-toggle-group-bg, var(--persona-surface, #ffffff));
2997
+ }
2998
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group > .persona-icon-btn {
2999
+ border: none;
3000
+ background: transparent;
3001
+ /* Segment padding = standalone icon-button padding minus the container's
3002
+ padding, so the pill's outer height matches the neighboring icon buttons
3003
+ (the container's 1px border stands in for the border the segments lost).
3004
+ calc() clamps to 0 if a theme sets group padding larger than icon padding. */
3005
+ padding: calc(var(--persona-artifact-toolbar-icon-padding, var(--persona-icon-btn-padding, 0.25rem)) - var(--persona-artifact-toolbar-toggle-group-padding, 2px));
3006
+ /* Container radius minus the 2px padding so the segment nests concentrically. */
3007
+ border-radius: var(--persona-artifact-toolbar-toggle-radius, calc(var(--persona-artifact-toolbar-icon-radius, var(--persona-icon-btn-radius, var(--persona-radius-md, 0.375rem))) - 2px));
3008
+ }
3009
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group > .persona-icon-btn:hover {
3010
+ background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-icon-btn-hover-bg, var(--persona-container, #f3f4f6)));
3011
+ }
3012
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group > .persona-icon-btn[aria-pressed="true"] {
3013
+ background: var(--persona-artifact-doc-toggle-active-bg, var(--persona-icon-btn-active-bg, var(--persona-container, #f3f4f6)));
3014
+ /* Inset ring instead of a border so documentToolbarToggleActiveBorderColor
3015
+ still applies inside the borderless segment (transparent by default). */
3016
+ box-shadow: inset 0 0 0 1px var(--persona-artifact-doc-toggle-active-border, transparent);
3017
+ }
3018
+
3019
+ /* Sliding selection thumb: a container ::before that translates between the
3020
+ two equal-width segments, keyed off aria-pressed via :has(). The static
3021
+ aria-pressed background above stays as the fallback for browsers without
3022
+ :has() and is disabled inside the @supports block so the highlight is only
3023
+ ever painted once. Geometry assumes the group's two segments are equal
3024
+ width (both are square icon buttons). */
3025
+ @supports selector(:has(*)) {
3026
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group {
3027
+ position: relative;
3028
+ }
3029
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group::before {
3030
+ content: "";
3031
+ position: absolute;
3032
+ top: var(--persona-artifact-toolbar-toggle-group-padding, 2px);
3033
+ bottom: var(--persona-artifact-toolbar-toggle-group-padding, 2px);
3034
+ left: var(--persona-artifact-toolbar-toggle-group-padding, 2px);
3035
+ width: calc(
3036
+ 50% - var(--persona-artifact-toolbar-toggle-group-padding, 2px) -
3037
+ (var(--persona-artifact-toolbar-toggle-group-gap, 2px) / 2)
3038
+ );
3039
+ border-radius: var(--persona-artifact-toolbar-toggle-radius, calc(var(--persona-artifact-toolbar-icon-radius, var(--persona-icon-btn-radius, var(--persona-radius-md, 0.375rem))) - 2px));
3040
+ background: var(--persona-artifact-doc-toggle-active-bg, var(--persona-icon-btn-active-bg, var(--persona-container, #f3f4f6)));
3041
+ box-shadow: inset 0 0 0 1px var(--persona-artifact-doc-toggle-active-border, transparent);
3042
+ transition: transform 0.18s ease;
3043
+ pointer-events: none;
3044
+ }
3045
+ [data-persona-root]
3046
+ .persona-toggle-group.persona-artifact-toggle-group:has(
3047
+ > .persona-icon-btn:last-child[aria-pressed="true"]
3048
+ )::before {
3049
+ transform: translateX(calc(100% + var(--persona-artifact-toolbar-toggle-group-gap, 2px)));
3050
+ }
3051
+ /* Positioned so the segments paint above the absolutely positioned thumb
3052
+ (document order applies once both are in the positioned layer). */
3053
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group > .persona-icon-btn {
3054
+ position: relative;
3055
+ }
3056
+ /* The thumb carries the highlight; keep the segment itself transparent. */
3057
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group > .persona-icon-btn[aria-pressed="true"] {
3058
+ background: transparent;
3059
+ box-shadow: none;
3060
+ }
3061
+ @media (prefers-reduced-motion: reduce) {
3062
+ [data-persona-root] .persona-toggle-group.persona-artifact-toggle-group::before {
3063
+ transition: none;
3064
+ }
3065
+ }
3066
+ }
3067
+
2932
3068
  /* Combo button: label + chevron with dropdown */
2933
3069
  [data-persona-root] .persona-combo-btn {
2934
3070
  font-size: var(--persona-combo-btn-font-size, 0.8125rem);
@@ -3019,11 +3155,560 @@
3019
3155
  max-width: var(--persona-artifact-pane-max-width, 28rem);
3020
3156
  min-width: var(--persona-artifact-pane-min-width, 0);
3021
3157
  flex-shrink: 0;
3022
- box-shadow: var(--persona-artifact-pane-shadow, -2px 0 12px rgba(15, 23, 42, 0.06));
3158
+ /* No default elevation on the pane; welded reads as one card, detached and
3159
+ the drawer set their own shadow. Explicit paneShadow still wins. */
3160
+ box-shadow: var(--persona-artifact-pane-shadow, none);
3023
3161
  }
3024
3162
  }
3025
3163
 
3164
+ /* Expanded artifact pane: pane takes the whole split root, chat column hides.
3165
+ Split-view only: below 641px / narrow hosts the drawer already covers everything. */
3166
+ @media (min-width: 641px) {
3167
+ [data-persona-root].persona-artifact-expanded .persona-artifact-split-root > :first-child {
3168
+ display: none;
3169
+ }
3170
+ [data-persona-root].persona-artifact-expanded .persona-artifact-pane {
3171
+ width: 100%;
3172
+ max-width: none;
3173
+ }
3174
+ [data-persona-root].persona-artifact-expanded .persona-artifact-split-handle {
3175
+ display: none !important;
3176
+ }
3177
+ }
3178
+ @media (max-width: 640px) {
3179
+ [data-persona-root] .persona-artifact-expand-btn {
3180
+ display: none !important;
3181
+ }
3182
+ }
3183
+ [data-persona-root].persona-artifact-narrow-host .persona-artifact-expand-btn {
3184
+ display: none !important;
3185
+ }
3186
+
3026
3187
  /* paneBorderRadius: works on any paneAppearance; overflow clips content to rounded shape */
3188
+ /* Positioned frame wrapping the sandboxed preview iframe (artifact-preview.ts).
3189
+ `position: relative` gives the absolute loading overlay a containing block
3190
+ (the shared preview body is display: contents, so the iframe alone has no
3191
+ positioning context). The frame owns the rendered height and the iframe is
3192
+ 100% of it — see the pane default here and the inline overrides below.
3193
+
3194
+ Opaque-canvas / color-scheme trap: an opaque-origin srcdoc (sandbox without
3195
+ allow-same-origin) that declares no color-scheme can get a UA-forced opaque
3196
+ canvas that flashes before content paints. Declaring color-scheme on the
3197
+ iframe plus an explicit background on both the frame and iframe makes the
3198
+ pre-paint surface a deliberate, themeable canvas rather than an uncontrolled
3199
+ flash. Artifact content generally assumes a light canvas, so white stays the
3200
+ default; override via --persona-artifact-frame-bg. */
3201
+ [data-persona-root] .persona-artifact-frame {
3202
+ position: relative;
3203
+ width: 100%;
3204
+ height: 100%;
3205
+ background: var(--persona-artifact-frame-bg, #ffffff);
3206
+ }
3207
+ /* Previewable file artifact: sandboxed iframe fills its frame. */
3208
+ [data-persona-root] .persona-artifact-iframe {
3209
+ display: block;
3210
+ width: 100%;
3211
+ height: 100%;
3212
+ border: 0;
3213
+ color-scheme: light;
3214
+ background: var(--persona-artifact-frame-bg, #ffffff);
3215
+ }
3216
+
3217
+ /* Loading overlay above the preview iframe (artifact-preview.ts): absolutely
3218
+ fills the frame with the same themed canvas the iframe paints onto, so the
3219
+ pre-paint area reads as one deliberate surface. Appended only after
3220
+ `loading.delayMs`; the --out modifier fades it before removal. */
3221
+ [data-persona-root] .persona-artifact-frame-loading {
3222
+ position: absolute;
3223
+ inset: 0;
3224
+ display: flex;
3225
+ align-items: center;
3226
+ justify-content: center;
3227
+ background: var(--persona-artifact-frame-bg, #ffffff);
3228
+ opacity: 1;
3229
+ transition: opacity var(--persona-artifact-frame-loading-fade, 200ms) ease;
3230
+ }
3231
+ [data-persona-root] .persona-artifact-frame-loading--out {
3232
+ opacity: 0;
3233
+ }
3234
+ /* Default indicator column: the icon spinner, with the escalation label (when
3235
+ present) stacked below it. Icon-first, text only as a later escalation. */
3236
+ [data-persona-root] .persona-artifact-frame-loading-indicator {
3237
+ display: flex;
3238
+ flex-direction: column;
3239
+ align-items: center;
3240
+ gap: 0.625rem;
3241
+ }
3242
+ /* Escalation label: calm, muted, small — NO shimmer (deliberate, per research).
3243
+ Hidden until setupPreviewLoading adds --visible after labelDelayMs; a plain
3244
+ opacity transition fades it in. */
3245
+ [data-persona-root] .persona-artifact-frame-loading-text {
3246
+ color: var(
3247
+ --persona-artifact-frame-loading-color,
3248
+ var(--persona-text-muted, #6b7280)
3249
+ );
3250
+ font-size: 0.8125rem;
3251
+ font-weight: 500;
3252
+ opacity: 0;
3253
+ transition: opacity 240ms ease;
3254
+ }
3255
+ [data-persona-root] .persona-artifact-frame-loading-text--visible {
3256
+ opacity: 1;
3257
+ }
3258
+
3259
+ /* Reusable icon spinner (utils/spinner.ts): a faint full track ring with a
3260
+ rotating arc on top. Icon-first loading is the norm across preview surfaces
3261
+ (Sandpack, embeds, v0/ChatGPT) and design systems (HIG/Material/Carbon/Geist);
3262
+ text-only "loading" is used by none. The arc spins via a GPU-friendly
3263
+ `transform: rotate` keyframe. Fallback chains:
3264
+ - size: --persona-artifact-spinner-size (default 28px)
3265
+ - arc: --persona-artifact-spinner-color → --persona-accent → --persona-primary
3266
+ → neutral dark (the interactive/brand hue)
3267
+ - track: --persona-artifact-spinner-track-color → --persona-border → light gray
3268
+ - speed: --persona-artifact-spinner-speed (default 0.8s) */
3269
+ [data-persona-root] .persona-spinner {
3270
+ width: var(--persona-artifact-spinner-size, 28px);
3271
+ height: var(--persona-artifact-spinner-size, 28px);
3272
+ transform-origin: center;
3273
+ animation: persona-spinner-rotate var(--persona-artifact-spinner-speed, 0.8s)
3274
+ linear infinite;
3275
+ }
3276
+ [data-persona-root] .persona-spinner-track {
3277
+ fill: none;
3278
+ stroke: var(
3279
+ --persona-artifact-spinner-track-color,
3280
+ var(--persona-border, #e5e7eb)
3281
+ );
3282
+ stroke-width: 2.5;
3283
+ }
3284
+ [data-persona-root] .persona-spinner-arc {
3285
+ fill: none;
3286
+ stroke: var(
3287
+ --persona-artifact-spinner-color,
3288
+ var(--persona-accent, var(--persona-primary, #171717))
3289
+ );
3290
+ stroke-width: 2.5;
3291
+ stroke-linecap: round;
3292
+ /* r=9 → circumference ≈ 56.5; ~15u arc reads as a quarter-ish sweep. */
3293
+ stroke-dasharray: 15 42;
3294
+ }
3295
+ @keyframes persona-spinner-rotate {
3296
+ from {
3297
+ transform: rotate(0deg);
3298
+ }
3299
+ to {
3300
+ transform: rotate(360deg);
3301
+ }
3302
+ }
3303
+ /* Reduced motion: stop the rotation (the static arc still reads as a ring) and
3304
+ drop the label fade. */
3305
+ @media (prefers-reduced-motion: reduce) {
3306
+ [data-persona-root] .persona-spinner {
3307
+ animation: none;
3308
+ }
3309
+ [data-persona-root] .persona-artifact-frame-loading-text {
3310
+ transition: none;
3311
+ }
3312
+ }
3313
+
3314
+ /* Shared artifact preview body wrapper (artifact-preview.ts): generates no box
3315
+ so children (e.g. the iframe's height: 100%) lay out against the host
3316
+ container exactly as if they were its direct children. */
3317
+ [data-persona-root] .persona-artifact-preview-body {
3318
+ display: contents;
3319
+ }
3320
+
3321
+ /* Syntax-highlighted source view (code-highlight.ts). The host <pre> keeps its
3322
+ wrapping (persona-whitespace-pre-wrap); <code> resets white-space to normal so
3323
+ the literal "\n" separator text nodes between block line spans collapse
3324
+ visually while still reconstructing the verbatim source via textContent. Each
3325
+ line reserves a left gutter whose number is drawn by a CSS counter — never
3326
+ real text, so it is not selectable or copyable. */
3327
+ /* The source view sits directly on the pane surface (transparent, matching
3328
+ Claude's code view); set --persona-code-bg via theme.components.code.background
3329
+ to render it as an inset sheet instead. */
3330
+ [data-persona-root] .persona-code-pre {
3331
+ margin: 0;
3332
+ background: var(--persona-code-bg, transparent);
3333
+ border-radius: var(--persona-radius-md, 0.375rem);
3334
+ padding: 0.5rem 0.75rem 0.5rem 0.25rem;
3335
+ }
3336
+
3337
+ /* Pane source view is full-bleed: the pane drops its content padding (class
3338
+ toggled in artifact-pane.ts when the body rendered a code pre) so the
3339
+ line-number gutter sits flush against the pane's left edge. */
3340
+ [data-persona-root] .persona-artifact-content-flush {
3341
+ padding: 0;
3342
+ }
3343
+ [data-persona-root] .persona-artifact-content-flush .persona-code-pre {
3344
+ padding: 0.5rem 0.75rem 0.5rem 0;
3345
+ border-radius: 0;
3346
+ min-height: 100%;
3347
+ }
3348
+ [data-persona-root] .persona-code {
3349
+ display: block;
3350
+ white-space: normal;
3351
+ counter-reset: persona-code-line;
3352
+ /* Defend against host-page element rules (`code { background/padding/… }`):
3353
+ without Shadow DOM those selectors reach into the widget, and inline-code
3354
+ chip styling on a full source view reads as a mystery tinted block. */
3355
+ background: transparent;
3356
+ padding: 0;
3357
+ border-radius: 0;
3358
+ font-family: inherit;
3359
+ font-size: inherit;
3360
+ color: inherit;
3361
+ }
3362
+ [data-persona-root] .persona-code-line {
3363
+ display: block;
3364
+ position: relative;
3365
+ white-space: pre-wrap;
3366
+ padding-left: 3em;
3367
+ counter-increment: persona-code-line;
3368
+ /* An empty source line emits a childless span — a zero-height block whose
3369
+ absolutely-positioned gutter number would paint on top of the next line's.
3370
+ Reserve one line box (1lh; 1em as the older-engine fallback). */
3371
+ min-height: 1em;
3372
+ min-height: 1lh;
3373
+ }
3374
+ [data-persona-root] .persona-code-line::before {
3375
+ content: counter(persona-code-line);
3376
+ position: absolute;
3377
+ top: 0;
3378
+ left: 0;
3379
+ width: 2em;
3380
+ padding-right: 0.5em;
3381
+ text-align: right;
3382
+ color: var(--persona-code-line-number-color, #a0a1a7);
3383
+ border-right: 1px solid
3384
+ var(--persona-code-gutter-border-color, var(--persona-border, #e5e7eb));
3385
+ -webkit-user-select: none;
3386
+ user-select: none;
3387
+ }
3388
+ /* Default syntax palette: One Light (Atom), the same theme Claude.ai's code
3389
+ view uses. Numbers and attribute names share One's constant orange; CSS
3390
+ properties / JSON keys take the function blue so keys stay distinct from
3391
+ string values. */
3392
+ [data-persona-root] .persona-code-token-keyword {
3393
+ color: var(--persona-code-keyword-color, #a626a4);
3394
+ }
3395
+ [data-persona-root] .persona-code-token-string {
3396
+ color: var(--persona-code-string-color, #50a14f);
3397
+ }
3398
+ [data-persona-root] .persona-code-token-comment {
3399
+ color: var(--persona-code-comment-color, #a0a1a7);
3400
+ font-style: italic;
3401
+ }
3402
+ [data-persona-root] .persona-code-token-number {
3403
+ color: var(--persona-code-number-color, #986801);
3404
+ }
3405
+ [data-persona-root] .persona-code-token-tag {
3406
+ color: var(--persona-code-tag-color, #e45649);
3407
+ }
3408
+ [data-persona-root] .persona-code-token-attr {
3409
+ color: var(--persona-code-attr-color, #986801);
3410
+ }
3411
+ [data-persona-root] .persona-code-token-property {
3412
+ color: var(--persona-code-property-color, #4078f2);
3413
+ }
3414
+
3415
+ /* Dark-scheme defaults for the syntax palette (One Dark counterparts of the
3416
+ One Light defaults above). Keyed off the widget's resolved scheme, stamped
3417
+ as data-persona-color-scheme by applyThemeVariables — NOT the OS
3418
+ prefers-color-scheme, which diverges whenever a host pins colorScheme.
3419
+ The widget's semantic vars can't drive these (syntax colors aren't
3420
+ derivable from surface/text). Explicit theme.components.code values still
3421
+ win in both schemes. */
3422
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-line::before {
3423
+ color: var(--persona-code-line-number-color, #4b5263);
3424
+ border-right-color: var(--persona-code-gutter-border-color, #3b4048);
3425
+ }
3426
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-keyword {
3427
+ color: var(--persona-code-keyword-color, #c678dd);
3428
+ }
3429
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-string {
3430
+ color: var(--persona-code-string-color, #98c379);
3431
+ }
3432
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-comment {
3433
+ color: var(--persona-code-comment-color, #5c6370);
3434
+ }
3435
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-number {
3436
+ color: var(--persona-code-number-color, #d19a66);
3437
+ }
3438
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-tag {
3439
+ color: var(--persona-code-tag-color, #e06c75);
3440
+ }
3441
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-attr {
3442
+ color: var(--persona-code-attr-color, #d19a66);
3443
+ }
3444
+ [data-persona-root][data-persona-color-scheme="dark"] .persona-code-token-property {
3445
+ color: var(--persona-code-property-color, #61afef);
3446
+ }
3447
+
3448
+ /* Inline artifact block (artifact-inline.ts, display: "inline"): carries its
3449
+ own border/surface (registered with bubbleChrome: false, like the card).
3450
+ Reads like a file preview: a flush title/toolbar chrome bar above the body.
3451
+ Default vars draw from the same semantic surface/border/text/muted family
3452
+ the card uses, so light/dark themes work with zero config. */
3453
+ [data-persona-root] .persona-artifact-inline {
3454
+ border: var(--persona-artifact-inline-border, 1px solid var(--persona-border, #e5e7eb));
3455
+ border-radius: var(
3456
+ --persona-artifact-inline-radius,
3457
+ var(--persona-message-assistant-radius, var(--persona-radius-lg, 0.5rem))
3458
+ );
3459
+ background: var(--persona-artifact-inline-bg, var(--persona-surface, #ffffff));
3460
+ overflow: hidden;
3461
+ }
3462
+
3463
+ /* completeDisplay: "card" — once the block collapses to the compact reference
3464
+ card it keeps the same root (updater keying + theme zone survive) but must
3465
+ drop the inline frame styling, since the nested card carries its own border,
3466
+ background, and radius. overflow: visible so the card's focus outline (which
3467
+ uses outline-offset) isn't clipped. */
3468
+ [data-persona-root] .persona-artifact-inline.persona-artifact-inline--card {
3469
+ border: none;
3470
+ background: transparent;
3471
+ border-radius: 0;
3472
+ overflow: visible;
3473
+ }
3474
+
3475
+ /* Title/toolbar chrome bar: flush to the frame edge (no frame padding). */
3476
+ [data-persona-root] .persona-artifact-inline-chrome {
3477
+ display: flex;
3478
+ align-items: center;
3479
+ gap: 0.5rem;
3480
+ padding: 0.5rem 0.75rem;
3481
+ background: var(--persona-artifact-inline-chrome-bg, var(--persona-surface, #ffffff));
3482
+ border-bottom: 1px solid var(--persona-artifact-inline-chrome-border, var(--persona-border, #e5e7eb));
3483
+ }
3484
+
3485
+ /* Leading cluster: file icon + title + type label / streaming status. */
3486
+ [data-persona-root] .persona-artifact-inline-chrome-lead {
3487
+ display: flex;
3488
+ align-items: center;
3489
+ gap: 0.5rem;
3490
+ min-width: 0;
3491
+ flex: 1 1 auto;
3492
+ color: var(--persona-artifact-inline-title-color, var(--persona-text, #111827));
3493
+ font-size: 0.8125rem;
3494
+ font-weight: 500;
3495
+ }
3496
+
3497
+ [data-persona-root] .persona-artifact-inline-chrome-lead .persona-artifact-inline-type,
3498
+ [data-persona-root] .persona-artifact-inline-chrome-lead .persona-artifact-inline-status {
3499
+ color: var(--persona-artifact-inline-muted-color, var(--persona-text-muted, #6b7280));
3500
+ font-weight: 400;
3501
+ }
3502
+
3503
+ /* Streaming status: animated label span + optional plain detail span (live
3504
+ counters). The label carries the loading animation; the detail updates freely
3505
+ per delta and stays un-animated. Shared by the card, the inline chrome, and
3506
+ the inline status body (features.artifacts.statusLabel). Keep the status span
3507
+ from blowing out the chrome bar when a long detail streams in. */
3508
+ [data-persona-root] .persona-artifact-inline-chrome-lead .persona-artifact-inline-status {
3509
+ min-width: 0;
3510
+ overflow: hidden;
3511
+ white-space: nowrap;
3512
+ text-overflow: ellipsis;
3513
+ }
3514
+
3515
+ [data-persona-root] .persona-artifact-status-detail {
3516
+ color: var(--persona-artifact-inline-muted-color, var(--persona-text-muted, #6b7280));
3517
+ margin-left: 0.375rem;
3518
+ font-variant-numeric: tabular-nums;
3519
+ }
3520
+
3521
+ /* Trailing cluster: custom inlineActions, copy, expand. */
3522
+ [data-persona-root] .persona-artifact-inline-chrome-actions {
3523
+ display: flex;
3524
+ align-items: center;
3525
+ gap: 0.25rem;
3526
+ flex: 0 0 auto;
3527
+ }
3528
+
3529
+ /* Body wraps the chrome-free preview; padding lives here so the chrome sits
3530
+ flush to the frame edge. */
3531
+ [data-persona-root] .persona-artifact-inline-body {
3532
+ padding: 0.75rem;
3533
+ }
3534
+
3535
+ /* The preview frame (and its 100%-height iframe) is height: 100%, which
3536
+ collapses inside the auto-height inline block; pin a themeable fixed height on
3537
+ the frame instead. Height var chain: inlineBody.height
3538
+ (--persona-artifact-inline-body-height, set per state by artifact-inline.ts) →
3539
+ the legacy --persona-artifact-inline-frame-height override → 320px. The legacy
3540
+ var still wins when the new one is unset (inlineBody.height "auto"), so themes
3541
+ pinned to it keep working. The --sized rule below (equal specificity, declared
3542
+ later) overrides this to 100% when the body wrapper owns the height. */
3543
+ [data-persona-root] .persona-artifact-inline .persona-artifact-frame {
3544
+ height: var(
3545
+ --persona-artifact-inline-body-height,
3546
+ var(--persona-artifact-inline-frame-height, 320px)
3547
+ );
3548
+ }
3549
+
3550
+ /* Inline body height model (features.artifacts.inlineBody). The block root sets
3551
+ --persona-artifact-inline-body-height (px) for the current state; leaving it
3552
+ unset (inlineBody.height "auto") lets the body size to content and the iframe
3553
+ fall back through the chain above. */
3554
+
3555
+ /* Code bodies are full-bleed, matching the pane's source-view treatment (same
3556
+ class the pane toggles): the line-number gutter sits flush against the frame
3557
+ edge. Declared after .persona-artifact-inline-body so padding: 0 wins the
3558
+ specificity tie. */
3559
+ [data-persona-root] .persona-artifact-inline-body.persona-artifact-content-flush {
3560
+ padding: 0;
3561
+ }
3562
+
3563
+ /* Numeric inlineBody.height: the body wrapper owns the reserved height
3564
+ (border-box), so flush code, padded iframe, and padded status bodies all
3565
+ occupy the same outer box and every state swap is layout neutral. Fill-capable
3566
+ content stretches to the wrapper. */
3567
+ [data-persona-root] .persona-artifact-inline-body--sized {
3568
+ height: var(--persona-artifact-inline-body-height, auto);
3569
+ }
3570
+ [data-persona-root] .persona-artifact-inline-body--sized .persona-artifact-source-window--fixed,
3571
+ [data-persona-root] .persona-artifact-inline-body--sized .persona-artifact-frame {
3572
+ height: 100%;
3573
+ }
3574
+ [data-persona-root] .persona-artifact-inline-body--sized .persona-artifact-status-view {
3575
+ min-height: 100%;
3576
+ }
3577
+
3578
+ /* Fixed-height streaming source window: a reserved scroll viewport so streamed
3579
+ source no longer grows an unbounded <pre> and then snaps to the preview. The
3580
+ height comes from the sized body wrapper above (the --fixed class is only
3581
+ ever applied together with it). The bare (non-fixed) window is an inert
3582
+ wrapper (pane path never adds it; the source <pre> sits directly on el
3583
+ there). */
3584
+ [data-persona-root] .persona-artifact-source-window {
3585
+ min-width: 0;
3586
+ }
3587
+ [data-persona-root] .persona-artifact-source-window--fixed {
3588
+ overflow-y: auto;
3589
+ }
3590
+
3591
+ /* Clipped preview window (inlineBody.overflow: "clip"): a fixed-height window
3592
+ showing the top of the document with no internal scroll. Applied together with
3593
+ --fixed (which supplies the reserved height via the --sized body wrapper and
3594
+ the fill-capable detection in artifact-inline.ts); declared after --fixed so
3595
+ this overflow: hidden wins its overflow-y: auto on the specificity tie. */
3596
+ [data-persona-root] .persona-artifact-source-window--clip {
3597
+ overflow: hidden;
3598
+ }
3599
+
3600
+ /* Edge fade masks on the fixed window (top fade signals content clipped above).
3601
+ mask-image works over any themed background; size via --persona-artifact-fade-size
3602
+ (defaults to 40px). */
3603
+ [data-persona-root]
3604
+ .persona-artifact-fade-top:not(.persona-artifact-fade-bottom) {
3605
+ -webkit-mask-image: linear-gradient(
3606
+ to bottom,
3607
+ transparent 0,
3608
+ #000 var(--persona-artifact-fade-size, 40px)
3609
+ );
3610
+ mask-image: linear-gradient(
3611
+ to bottom,
3612
+ transparent 0,
3613
+ #000 var(--persona-artifact-fade-size, 40px)
3614
+ );
3615
+ }
3616
+ [data-persona-root]
3617
+ .persona-artifact-fade-bottom:not(.persona-artifact-fade-top) {
3618
+ -webkit-mask-image: linear-gradient(
3619
+ to top,
3620
+ transparent 0,
3621
+ #000 var(--persona-artifact-fade-size, 40px)
3622
+ );
3623
+ mask-image: linear-gradient(
3624
+ to top,
3625
+ transparent 0,
3626
+ #000 var(--persona-artifact-fade-size, 40px)
3627
+ );
3628
+ }
3629
+ [data-persona-root]
3630
+ .persona-artifact-fade-top.persona-artifact-fade-bottom {
3631
+ -webkit-mask-image: linear-gradient(
3632
+ to bottom,
3633
+ transparent 0,
3634
+ #000 var(--persona-artifact-fade-size, 40px),
3635
+ #000 calc(100% - var(--persona-artifact-fade-size, 40px)),
3636
+ transparent 100%
3637
+ );
3638
+ mask-image: linear-gradient(
3639
+ to bottom,
3640
+ transparent 0,
3641
+ #000 var(--persona-artifact-fade-size, 40px),
3642
+ #000 calc(100% - var(--persona-artifact-fade-size, 40px)),
3643
+ transparent 100%
3644
+ );
3645
+ }
3646
+
3647
+ /* Edge fade on the horizontally scrollable tab strip. Visual hint only; the
3648
+ tablist keyboard behavior is the real affordance. Sized via
3649
+ --persona-artifact-tab-fade-size (default 24px). */
3650
+ [data-persona-root]
3651
+ .persona-artifact-tab-fade-start:not(.persona-artifact-tab-fade-end) {
3652
+ -webkit-mask-image: linear-gradient(
3653
+ to right,
3654
+ transparent 0,
3655
+ #000 var(--persona-artifact-tab-fade-size, 24px)
3656
+ );
3657
+ mask-image: linear-gradient(
3658
+ to right,
3659
+ transparent 0,
3660
+ #000 var(--persona-artifact-tab-fade-size, 24px)
3661
+ );
3662
+ }
3663
+ [data-persona-root]
3664
+ .persona-artifact-tab-fade-end:not(.persona-artifact-tab-fade-start) {
3665
+ -webkit-mask-image: linear-gradient(
3666
+ to left,
3667
+ transparent 0,
3668
+ #000 var(--persona-artifact-tab-fade-size, 24px)
3669
+ );
3670
+ mask-image: linear-gradient(
3671
+ to left,
3672
+ transparent 0,
3673
+ #000 var(--persona-artifact-tab-fade-size, 24px)
3674
+ );
3675
+ }
3676
+ [data-persona-root]
3677
+ .persona-artifact-tab-fade-start.persona-artifact-tab-fade-end {
3678
+ -webkit-mask-image: linear-gradient(
3679
+ to right,
3680
+ transparent 0,
3681
+ #000 var(--persona-artifact-tab-fade-size, 24px),
3682
+ #000 calc(100% - var(--persona-artifact-tab-fade-size, 24px)),
3683
+ transparent 100%
3684
+ );
3685
+ mask-image: linear-gradient(
3686
+ to right,
3687
+ transparent 0,
3688
+ #000 var(--persona-artifact-tab-fade-size, 24px),
3689
+ #000 calc(100% - var(--persona-artifact-tab-fade-size, 24px)),
3690
+ transparent 100%
3691
+ );
3692
+ }
3693
+
3694
+ /* Reserved-height status placeholder (inlineBody.streamingView: "status").
3695
+ Numeric height reserves the var; "auto" falls back to a modest 160px min-height
3696
+ so the card doesn't collapse. */
3697
+ [data-persona-root] .persona-artifact-status-view {
3698
+ display: flex;
3699
+ align-items: center;
3700
+ justify-content: center;
3701
+ min-height: var(--persona-artifact-inline-body-height, 160px);
3702
+ }
3703
+
3704
+ /* Complete-state height cap for non-iframe bodies (rendered markdown, source
3705
+ view, component). The iframe path is sized by the iframe rule above instead,
3706
+ so this class is only applied when the completed body has no iframe. */
3707
+ [data-persona-root] .persona-artifact-inline-body--cap {
3708
+ max-height: var(--persona-artifact-inline-body-height, none);
3709
+ overflow-y: auto;
3710
+ }
3711
+
3027
3712
  [data-persona-root] .persona-artifact-pane {
3028
3713
  border-radius: var(--persona-artifact-pane-radius, 0);
3029
3714
  overflow: hidden;
@@ -3072,23 +3757,71 @@
3072
3757
  border: var(--persona-artifact-pane-border) !important;
3073
3758
  }
3074
3759
 
3760
+ /* paneAppearance: 'detached': floating card with gap, all-side border, radius, elevation, canvas behind */
3761
+ /* Split gap defaults to --persona-panel-inset from ui.ts; canvas paints behind both columns. */
3762
+ [data-persona-root].persona-artifact-appearance-detached .persona-artifact-split-root {
3763
+ background: var(--persona-panel-canvas-bg);
3764
+ }
3765
+
3766
+ /* Desktop split only (drawer keeps its own chrome). Non-!important so explicit
3767
+ paneBorder / paneShadow / paneBorderRadius overrides still win. */
3768
+ @media (min-width: 641px) {
3769
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-appearance-detached .persona-artifact-pane {
3770
+ border-radius: var(
3771
+ --persona-artifact-pane-radius,
3772
+ var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))
3773
+ );
3774
+ border: var(--persona-panel-border, 1px solid var(--persona-border, #e5e7eb));
3775
+ box-shadow: var(
3776
+ --persona-artifact-pane-shadow,
3777
+ var(--persona-panel-shadow, var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25)))
3778
+ );
3779
+ }
3780
+ }
3781
+
3782
+ /* chatSurface: 'flush': chat is flat flush background, only the pane floats. The
3783
+ chat has no perimeter inset, so inset the pane on 3 sides (left gap comes from
3784
+ the existing detached split flex gap). Keeps the pane's border/radius/shadow. */
3785
+ @media (min-width: 641px) {
3786
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-chat-flush .persona-artifact-pane {
3787
+ margin-top: var(--persona-panel-inset);
3788
+ margin-right: var(--persona-panel-inset);
3789
+ margin-bottom: var(--persona-panel-inset);
3790
+ }
3791
+ }
3792
+
3075
3793
  /*
3076
- * Unified split chrome: one visual shell (desktop split only; narrowed via :not(.narrow-host) + breakpoint).
3077
- * Main chat card (.persona-widget-container) loses inner-right rounding; artifact gains matching outer-right.
3794
+ * Welded split chrome (panel + seamless): one card. The outer panel carries the
3795
+ * border + radius + shadow (set in ui.ts); each column rounds only its outer
3796
+ * corners and clips, so the columns fill the panel's rounded rect and butt flush
3797
+ * at the seam. Desktop split only (narrowed via :not(.narrow-host) + breakpoint).
3798
+ * Toggled from ui.ts by splitChromeMode(); the pane's flex layout owns gap 0.
3078
3799
  */
3079
3800
  @media (min-width: 641px) {
3080
- [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-unified-split .persona-widget-container {
3801
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-welded-split .persona-artifact-split-root {
3802
+ position: relative;
3803
+ }
3804
+
3805
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-welded-split .persona-widget-container {
3081
3806
  border-top-right-radius: 0 !important;
3082
3807
  border-bottom-right-radius: 0 !important;
3808
+ overflow: hidden;
3083
3809
  }
3084
3810
 
3085
- [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-unified-split .persona-artifact-pane {
3811
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-welded-split .persona-artifact-pane {
3086
3812
  border-top-left-radius: 0 !important;
3087
3813
  border-bottom-left-radius: 0 !important;
3088
- border-top-right-radius: var(--persona-artifact-unified-outer-radius, var(--persona-radius-lg, 1rem)) !important;
3089
- border-bottom-right-radius: var(--persona-artifact-unified-outer-radius, var(--persona-radius-lg, 1rem)) !important;
3814
+ border-top-right-radius: var(--persona-artifact-welded-outer-radius, var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))) !important;
3815
+ border-bottom-right-radius: var(--persona-artifact-welded-outer-radius, var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))) !important;
3090
3816
  overflow: hidden;
3091
3817
  }
3818
+
3819
+ /* panel appearance: a hairline divider on the pane's chat-facing edge. seamless
3820
+ omits it (it already zeroes border-left). Non-!important so paneBorder /
3821
+ paneBorderLeft overrides win. */
3822
+ [data-persona-root]:not(.persona-artifact-narrow-host).persona-artifact-welded-split.persona-artifact-appearance-panel .persona-artifact-pane {
3823
+ border-left: 1px solid var(--persona-border, #e5e7eb);
3824
+ }
3092
3825
  }
3093
3826
 
3094
3827
  /* Narrow floating panel: drawer inside panel (class toggled by JS from panel width) */
@@ -3789,6 +4522,20 @@
3789
4522
  gap: 8px;
3790
4523
  }
3791
4524
 
4525
+ /* Configured / agent-pushed suggestion chips belong to the expanded
4526
+ * conversation surface, not the always-on collapsed composer. Keeping the
4527
+ * row in pillRoot lets it sit directly above the textarea without re-entering
4528
+ * the transcript's scrolling region. */
4529
+ .persona-widget-pill-root[data-state="collapsed"] [data-persona-composer-suggestions] {
4530
+ display: none;
4531
+ }
4532
+
4533
+ .persona-widget-pill-root[data-state="expanded"] [data-persona-composer-suggestions] {
4534
+ display: flex;
4535
+ justify-content: center;
4536
+ margin: 0 8px 4px;
4537
+ }
4538
+
3792
4539
  /* Attachment previews row (above the pill). AttachmentManager toggles
3793
4540
  * display:flex when items are added; default is display:none. */
3794
4541
  .persona-pill-composer__previews {