@runtypelabs/persona 3.16.0 → 3.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.
Files changed (71) hide show
  1. package/README.md +142 -0
  2. package/dist/animations/glyph-cycle.cjs +279 -0
  3. package/dist/animations/glyph-cycle.d.cts +5 -0
  4. package/dist/animations/glyph-cycle.d.ts +5 -0
  5. package/dist/animations/glyph-cycle.js +252 -0
  6. package/dist/animations/types-cwY5HaFD.d.cts +307 -0
  7. package/dist/animations/types-cwY5HaFD.d.ts +307 -0
  8. package/dist/animations/wipe.cjs +107 -0
  9. package/dist/animations/wipe.d.cts +5 -0
  10. package/dist/animations/wipe.d.ts +5 -0
  11. package/dist/animations/wipe.js +80 -0
  12. package/dist/index.cjs +49 -48
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +504 -1
  15. package/dist/index.d.ts +504 -1
  16. package/dist/index.global.js +143 -88
  17. package/dist/index.global.js.map +1 -1
  18. package/dist/index.js +49 -48
  19. package/dist/index.js.map +1 -1
  20. package/dist/testing.cjs +85 -0
  21. package/dist/testing.d.cts +39 -0
  22. package/dist/testing.d.ts +39 -0
  23. package/dist/testing.js +56 -0
  24. package/dist/theme-editor.cjs +2095 -207
  25. package/dist/theme-editor.d.cts +432 -2
  26. package/dist/theme-editor.d.ts +432 -2
  27. package/dist/theme-editor.js +2093 -207
  28. package/dist/theme-reference.cjs +1 -1
  29. package/dist/theme-reference.d.cts +14 -0
  30. package/dist/theme-reference.d.ts +14 -0
  31. package/dist/widget.css +565 -0
  32. package/package.json +20 -3
  33. package/src/animations/glyph-cycle.ts +332 -0
  34. package/src/animations/wipe.ts +66 -0
  35. package/src/client.test.ts +275 -0
  36. package/src/client.ts +99 -0
  37. package/src/components/ask-user-question-bubble.test.ts +583 -0
  38. package/src/components/ask-user-question-bubble.ts +924 -0
  39. package/src/components/composer-builder.ts +61 -10
  40. package/src/components/message-bubble.test.ts +181 -2
  41. package/src/components/message-bubble.ts +209 -14
  42. package/src/components/messages.ts +33 -1
  43. package/src/components/panel.ts +45 -5
  44. package/src/defaults.ts +37 -0
  45. package/src/index-global.ts +31 -0
  46. package/src/index.ts +34 -1
  47. package/src/plugins/types.ts +57 -0
  48. package/src/session.test.ts +276 -1
  49. package/src/session.ts +247 -3
  50. package/src/styles/widget.css +565 -0
  51. package/src/testing/index.ts +11 -0
  52. package/src/testing/mock-stream.test.ts +80 -0
  53. package/src/testing/mock-stream.ts +94 -0
  54. package/src/testing.ts +2 -0
  55. package/src/theme-editor/index.ts +4 -0
  56. package/src/theme-editor/preview-utils.test.ts +60 -0
  57. package/src/theme-editor/preview-utils.ts +129 -0
  58. package/src/theme-editor/sections.test.ts +19 -0
  59. package/src/theme-editor/sections.ts +84 -1
  60. package/src/types/theme.ts +15 -0
  61. package/src/types.ts +360 -0
  62. package/src/ui.ask-user-question-plugin.test.ts +649 -0
  63. package/src/ui.stop-button.test.ts +165 -0
  64. package/src/ui.ts +706 -11
  65. package/src/utils/message-fingerprint.ts +2 -0
  66. package/src/utils/morph.ts +7 -0
  67. package/src/utils/storage.ts +10 -2
  68. package/src/utils/stream-animation.test.ts +417 -0
  69. package/src/utils/stream-animation.ts +449 -0
  70. package/src/utils/theme.test.ts +36 -0
  71. package/src/utils/tokens.ts +23 -0
package/dist/widget.css CHANGED
@@ -1759,6 +1759,30 @@
1759
1759
  animation: persona-message-actions-fade-in 0.3s ease-out forwards;
1760
1760
  }
1761
1761
 
1762
+ /* ask_user_question — collapsed answered state.
1763
+ * When the user picks an option, the interactive card is replaced with a
1764
+ * plain assistant bubble showing the question text. A short fade + slight
1765
+ * upward slide sells the "card resolved into history" feel.
1766
+ */
1767
+ @keyframes persona-ask-resolved {
1768
+ from {
1769
+ opacity: 0;
1770
+ transform: translateY(-4px);
1771
+ }
1772
+ to {
1773
+ opacity: 1;
1774
+ transform: translateY(0);
1775
+ }
1776
+ }
1777
+ [data-ask-answered="true"] {
1778
+ animation: persona-ask-resolved 0.22s ease-out;
1779
+ }
1780
+ @media (prefers-reduced-motion: reduce) {
1781
+ [data-ask-answered="true"] {
1782
+ animation: none;
1783
+ }
1784
+ }
1785
+
1762
1786
  /* Action bar alignment */
1763
1787
  .persona-message-actions-left {
1764
1788
  justify-content: flex-start;
@@ -2824,3 +2848,544 @@
2824
2848
  transform: translateX(0);
2825
2849
  }
2826
2850
  }
2851
+
2852
+ /* ============================================================
2853
+ Stream animations — reveal effects for assistant message text
2854
+ while streaming. Opt-in via `features.streamAnimation.type`.
2855
+ Units are staggered via `--char-index` / `--word-index`
2856
+ (set inline on each wrapper span). Timing is configured via
2857
+ `--persona-stream-step` and `--persona-stream-duration` on
2858
+ the `.persona-message-content` container.
2859
+ ============================================================ */
2860
+
2861
+ /* Per-char/per-word spans need to be inline-block so `transform` works for
2862
+ the rise/fade animations. Each span animates from the moment it is first
2863
+ added to the DOM — streaming itself provides the visible stagger, so the
2864
+ CSS animation has no per-index delay. An index-based delay would compound
2865
+ with the stream's arrival cadence and leave later chars permanently hidden. */
2866
+ [data-persona-root] .persona-stream-char,
2867
+ [data-persona-root] .persona-stream-word {
2868
+ display: inline-block;
2869
+ will-change: opacity, transform, filter;
2870
+ }
2871
+
2872
+ /* Group chars belonging to the same word so the browser treats the word as a
2873
+ single break unit. Without this, every inline-block char introduces a break
2874
+ opportunity and words get split mid-letter during streaming, then snap back
2875
+ when the final content replaces the wrapped spans. */
2876
+ [data-persona-root] .persona-stream-word-group {
2877
+ white-space: nowrap;
2878
+ }
2879
+
2880
+ /* ---------- typewriter: fade per arriving char + blinking caret ---------- */
2881
+ @keyframes persona-stream-typewriter-in {
2882
+ from { opacity: 0; }
2883
+ to { opacity: 1; }
2884
+ }
2885
+ [data-persona-root] .persona-stream-typewriter .persona-stream-char {
2886
+ animation: persona-stream-typewriter-in var(--persona-stream-step, 120ms) ease-out both;
2887
+ }
2888
+
2889
+ /* ---------- letter-rise: per-char translateY + fade ---------- */
2890
+ @keyframes persona-stream-letter-rise {
2891
+ from { opacity: 0; transform: translateY(8px); }
2892
+ to { opacity: 1; transform: translateY(0); }
2893
+ }
2894
+ [data-persona-root] .persona-stream-letter-rise .persona-stream-char {
2895
+ animation: persona-stream-letter-rise calc(var(--persona-stream-step, 120ms) * 2)
2896
+ ease-out both;
2897
+ }
2898
+
2899
+ /* ---------- word-fade: per-word blur + translateY fade-in ---------- */
2900
+ @keyframes persona-stream-word-fade {
2901
+ from { opacity: 0; filter: blur(4px); transform: translateY(3px); }
2902
+ to { opacity: 1; filter: blur(0); transform: translateY(0); }
2903
+ }
2904
+ [data-persona-root] .persona-stream-word-fade .persona-stream-word {
2905
+ animation: persona-stream-word-fade calc(var(--persona-stream-step, 120ms) * 3)
2906
+ ease-out both;
2907
+ }
2908
+
2909
+ /* The following animations live in subpath plugin modules — their CSS is
2910
+ injected by the plugin when activated, not by the core stylesheet:
2911
+ - `wipe` → @runtypelabs/persona/animations/wipe
2912
+ - `glyph-cycle` → @runtypelabs/persona/animations/glyph-cycle */
2913
+
2914
+ /* ---------- pop-bubble: scale + opacity entrance on the bubble ---------- */
2915
+ @keyframes persona-stream-pop-in {
2916
+ 0% { transform: scale(0.6); opacity: 0; }
2917
+ 100% { transform: scale(1); opacity: 1; }
2918
+ }
2919
+ [data-persona-root] .persona-stream-pop {
2920
+ transform-origin: bottom left;
2921
+ animation: persona-stream-pop-in 400ms cubic-bezier(0.2, 0.9, 0.3, 1.4) both;
2922
+ }
2923
+
2924
+ /* ---------- caret used by typewriter ---------- */
2925
+ @keyframes persona-stream-blink {
2926
+ 0%, 50% { opacity: 1; }
2927
+ 50.01%, 100% { opacity: 0; }
2928
+ }
2929
+ [data-persona-root] .persona-stream-caret {
2930
+ display: inline-block;
2931
+ width: 2px;
2932
+ height: 1em;
2933
+ margin-left: 1px;
2934
+ vertical-align: -2px;
2935
+ background: currentColor;
2936
+ animation: persona-stream-blink 1s steps(1) infinite;
2937
+ }
2938
+
2939
+ /* ---------- skeleton placeholder (pre-first-token) ---------- */
2940
+ @keyframes persona-stream-skeleton-shimmer {
2941
+ 0% { background-position: 200% 0; }
2942
+ 100% { background-position: -200% 0; }
2943
+ }
2944
+ [data-persona-root] .persona-stream-skeleton {
2945
+ padding: 2px 0;
2946
+ /* The assistant bubble sizes to content. Give the skeleton an intrinsic
2947
+ width so the bubble expands; the bubble's own `max-width: 85%` clamps
2948
+ the upper bound. */
2949
+ width: 260px;
2950
+ max-width: 100%;
2951
+ }
2952
+ [data-persona-root] .persona-stream-skeleton-line {
2953
+ width: 100%;
2954
+ height: 10px;
2955
+ border-radius: 3px;
2956
+ background: linear-gradient(
2957
+ 90deg,
2958
+ color-mix(in srgb, currentColor 12%, transparent) 0%,
2959
+ color-mix(in srgb, currentColor 22%, transparent) 50%,
2960
+ color-mix(in srgb, currentColor 12%, transparent) 100%
2961
+ );
2962
+ background-size: 200% 100%;
2963
+ animation: persona-stream-skeleton-shimmer 1.4s linear infinite;
2964
+ }
2965
+
2966
+ /* ---------- reduced-motion: disable per-unit and container animations ---------- */
2967
+ @media (prefers-reduced-motion: reduce) {
2968
+ [data-persona-root] .persona-stream-typewriter .persona-stream-char,
2969
+ [data-persona-root] .persona-stream-letter-rise .persona-stream-char,
2970
+ [data-persona-root] .persona-stream-word-fade .persona-stream-word,
2971
+ [data-persona-root] .persona-stream-pop,
2972
+ [data-persona-root] .persona-stream-caret,
2973
+ [data-persona-root] .persona-stream-skeleton-line {
2974
+ animation: none !important;
2975
+ opacity: 1 !important;
2976
+ filter: none !important;
2977
+ transform: none !important;
2978
+ color: inherit !important;
2979
+ background: none !important;
2980
+ -webkit-background-clip: border-box !important;
2981
+ background-clip: border-box !important;
2982
+ }
2983
+ }
2984
+
2985
+ /* ========================================================================
2986
+ * ask_user_question — built-in answer-pill sheet
2987
+ * Slides in over the composer when the assistant invokes `ask_user_question`.
2988
+ * Overridable via `config.features.askUserQuestion.styles`.
2989
+ * ======================================================================== */
2990
+
2991
+ [data-persona-root] .persona-hidden {
2992
+ display: none !important;
2993
+ }
2994
+
2995
+ /* In-transcript stub — small "Awaiting…" chip. */
2996
+ [data-persona-root] .persona-ask-stub {
2997
+ padding: 0.35rem 0.7rem;
2998
+ border-radius: 999px;
2999
+ font-size: 0.8rem;
3000
+ color: var(--persona-muted, #6b7280);
3001
+ background: var(--persona-container, #f3f4f6);
3002
+ border: 1px dashed var(--persona-border, #e5e7eb);
3003
+ }
3004
+
3005
+ [data-persona-root] .persona-ask-stub-label {
3006
+ font-weight: 500;
3007
+ }
3008
+
3009
+ /* Sheet container — absolute inside composerOverlay. */
3010
+ [data-persona-root] .persona-ask-sheet {
3011
+ position: absolute;
3012
+ left: 0.75rem;
3013
+ right: 0.75rem;
3014
+ bottom: calc(100% + 0.5rem);
3015
+ box-sizing: border-box;
3016
+ padding: 0.85rem 1rem;
3017
+ background: var(--persona-ask-sheet-bg, var(--persona-surface, #ffffff));
3018
+ border: 1px solid var(--persona-ask-sheet-border, var(--persona-border, #e5e7eb));
3019
+ border-radius: 1rem;
3020
+ box-shadow: var(--persona-ask-sheet-shadow, 0 12px 28px -10px rgba(0, 0, 0, 0.15), 0 4px 10px -6px rgba(0, 0, 0, 0.08));
3021
+ transition: transform var(--persona-ask-sheet-duration, 180ms) ease, opacity var(--persona-ask-sheet-duration, 180ms) ease;
3022
+ opacity: 1;
3023
+ transform: translateY(0);
3024
+ }
3025
+
3026
+ [data-persona-root] .persona-ask-sheet.persona-ask-sheet-enter {
3027
+ opacity: 0;
3028
+ transform: translateY(8px);
3029
+ }
3030
+
3031
+ [data-persona-root] .persona-ask-sheet.persona-ask-sheet-leave {
3032
+ opacity: 0;
3033
+ transform: translateY(8px);
3034
+ pointer-events: none;
3035
+ }
3036
+
3037
+ /* Header row — question + close button */
3038
+ [data-persona-root] .persona-ask-sheet-header {
3039
+ margin-bottom: 0.55rem;
3040
+ }
3041
+
3042
+ [data-persona-root] .persona-ask-sheet-question {
3043
+ font-size: 0.92rem;
3044
+ font-weight: 500;
3045
+ line-height: 1.35;
3046
+ color: var(--persona-text, #1f2937);
3047
+ min-height: 1.35em;
3048
+ }
3049
+
3050
+ [data-persona-root] .persona-ask-question-skeleton {
3051
+ display: block;
3052
+ width: 60%;
3053
+ height: 0.85rem;
3054
+ border-radius: 0.3rem;
3055
+ background: linear-gradient(90deg,
3056
+ var(--persona-container, #f3f4f6) 0%,
3057
+ var(--persona-border, #e5e7eb) 50%,
3058
+ var(--persona-container, #f3f4f6) 100%);
3059
+ background-size: 200% 100%;
3060
+ animation: persona-ask-skeleton-shimmer 1.2s ease-in-out infinite;
3061
+ color: transparent;
3062
+ }
3063
+
3064
+ [data-persona-root] .persona-ask-sheet-step-inline {
3065
+ flex: 0 0 auto;
3066
+ font-size: 0.78rem;
3067
+ line-height: 1;
3068
+ color: var(--persona-text-muted, #6b7280);
3069
+ font-variant-numeric: tabular-nums;
3070
+ letter-spacing: 0.01em;
3071
+ white-space: nowrap;
3072
+ }
3073
+
3074
+ [data-persona-root] .persona-ask-sheet-step-inline:empty {
3075
+ display: none;
3076
+ }
3077
+
3078
+ /* Option list — stacked one per row by default ("rows" layout). The "pills"
3079
+ layout opt-in switches to horizontal wrap with compact pills; see also
3080
+ horizontalPillsAskPlugin example for a custom variant. */
3081
+ [data-persona-root] .persona-ask-pills {
3082
+ display: flex;
3083
+ flex-direction: column;
3084
+ flex-wrap: nowrap;
3085
+ row-gap: 0.4rem;
3086
+ column-gap: 0;
3087
+ }
3088
+
3089
+ [data-persona-root] .persona-ask-pills--rows {
3090
+ flex-direction: column;
3091
+ flex-wrap: nowrap;
3092
+ gap: 0.4rem;
3093
+ }
3094
+
3095
+ /* Pills layout opt-in — horizontal wrap, auto-width compact pills. */
3096
+ [data-persona-root] .persona-ask-sheet--pills .persona-ask-pills:not(.persona-ask-pills--rows) {
3097
+ flex-direction: row;
3098
+ flex-wrap: wrap;
3099
+ gap: 0.4rem;
3100
+ }
3101
+
3102
+ [data-persona-root] .persona-ask-sheet--pills .persona-ask-pills:not(.persona-ask-pills--rows) .persona-ask-pill {
3103
+ width: auto;
3104
+ padding: 0.4rem 0.9rem;
3105
+ border-radius: var(--persona-ask-pill-radius, 9999px);
3106
+ }
3107
+
3108
+ [data-persona-root] .persona-ask-pill {
3109
+ display: flex;
3110
+ align-items: center;
3111
+ justify-content: flex-start;
3112
+ width: 100%;
3113
+ text-align: left;
3114
+ padding: 0.65rem 0.9rem;
3115
+ border-radius: var(--persona-ask-pill-radius, 0.6rem);
3116
+ background: var(--persona-ask-pill-bg, transparent);
3117
+ color: var(--persona-ask-pill-fg, var(--persona-text, #1f2937));
3118
+ border: 1px solid var(--persona-border, #e5e7eb);
3119
+ font-size: 0.9rem;
3120
+ font-weight: 500;
3121
+ cursor: pointer;
3122
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
3123
+ }
3124
+
3125
+ [data-persona-root] .persona-ask-pill:hover:not(:disabled):not(.persona-ask-pill-skeleton):not(.persona-ask-pill-selected):not([aria-pressed="true"]) {
3126
+ border-color: var(--persona-text, #1f2937);
3127
+ background: var(--persona-container, #f3f4f6);
3128
+ }
3129
+
3130
+ [data-persona-root] .persona-ask-pill:active {
3131
+ transform: translateY(1px);
3132
+ }
3133
+
3134
+ [data-persona-root] .persona-ask-pill-selected,
3135
+ [data-persona-root] .persona-ask-pill[aria-pressed="true"] {
3136
+ background: var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
3137
+ color: var(--persona-ask-pill-fg-selected, #fafafa);
3138
+ border-color: var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
3139
+ }
3140
+
3141
+ [data-persona-root] .persona-ask-pill:focus:not(:focus-visible) {
3142
+ outline: none;
3143
+ }
3144
+
3145
+ [data-persona-root] .persona-ask-pill:focus-visible {
3146
+ outline: 2px solid var(--persona-accent, #0f0f0f);
3147
+ outline-offset: 2px;
3148
+ }
3149
+
3150
+ [data-persona-root] .persona-ask-pill-custom {
3151
+ border-style: dashed;
3152
+ }
3153
+
3154
+ [data-persona-root] .persona-ask-pill-skeleton {
3155
+ min-width: 5rem;
3156
+ height: 2rem;
3157
+ padding: 0;
3158
+ border: 1px solid var(--persona-border, #e5e7eb);
3159
+ background: linear-gradient(90deg,
3160
+ var(--persona-container, #f3f4f6) 0%,
3161
+ var(--persona-border, #e5e7eb) 50%,
3162
+ var(--persona-container, #f3f4f6) 100%);
3163
+ background-size: 200% 100%;
3164
+ animation: persona-ask-skeleton-shimmer 1.2s ease-in-out infinite;
3165
+ }
3166
+
3167
+ @keyframes persona-ask-skeleton-shimmer {
3168
+ 0% { background-position: 100% 50%; }
3169
+ 100% { background-position: -100% 50%; }
3170
+ }
3171
+
3172
+ /* Row layout — full-width stacked rows with always-visible description
3173
+ and a right-edge affordance (number badge for single-select, check for
3174
+ multi-select). Layered on top of .persona-ask-pill base styles. */
3175
+ [data-persona-root] .persona-ask-row {
3176
+ align-items: stretch;
3177
+ justify-content: space-between;
3178
+ gap: 0.75rem;
3179
+ padding: 0.7rem 0.9rem;
3180
+ }
3181
+
3182
+ [data-persona-root] .persona-ask-row-content {
3183
+ display: flex;
3184
+ flex-direction: column;
3185
+ gap: 0.15rem;
3186
+ flex: 1 1 auto;
3187
+ min-width: 0;
3188
+ }
3189
+
3190
+ [data-persona-root] .persona-ask-row-label {
3191
+ font-size: 0.92rem;
3192
+ font-weight: 600;
3193
+ line-height: 1.25;
3194
+ color: inherit;
3195
+ white-space: normal;
3196
+ overflow-wrap: anywhere;
3197
+ }
3198
+
3199
+ [data-persona-root] .persona-ask-row-description {
3200
+ font-size: 0.82rem;
3201
+ font-weight: 400;
3202
+ line-height: 1.35;
3203
+ color: var(--persona-text-muted, #6b7280);
3204
+ white-space: normal;
3205
+ overflow-wrap: anywhere;
3206
+ }
3207
+
3208
+ [data-persona-root] .persona-ask-pill-selected .persona-ask-row-description,
3209
+ [data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-description {
3210
+ color: var(--persona-ask-pill-fg-selected, #fafafa);
3211
+ opacity: 0.85;
3212
+ }
3213
+
3214
+ [data-persona-root] .persona-ask-row-affordance {
3215
+ display: inline-flex;
3216
+ align-items: center;
3217
+ justify-content: center;
3218
+ flex: 0 0 auto;
3219
+ align-self: center;
3220
+ }
3221
+
3222
+ [data-persona-root] .persona-ask-row-badge {
3223
+ display: inline-flex;
3224
+ align-items: center;
3225
+ justify-content: center;
3226
+ width: 1.5rem;
3227
+ height: 1.5rem;
3228
+ border-radius: 0.4rem;
3229
+ border: 1px solid var(--persona-border, #e5e7eb);
3230
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
3231
+ font-size: 0.78rem;
3232
+ font-weight: 600;
3233
+ color: var(--persona-text-muted, #6b7280);
3234
+ background: transparent;
3235
+ }
3236
+
3237
+ [data-persona-root] .persona-ask-pill-selected .persona-ask-row-badge,
3238
+ [data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-badge {
3239
+ border-color: var(--persona-ask-pill-fg-selected, #fafafa);
3240
+ color: var(--persona-ask-pill-fg-selected, #fafafa);
3241
+ }
3242
+
3243
+ [data-persona-root] .persona-ask-row-check {
3244
+ display: inline-flex;
3245
+ align-items: center;
3246
+ justify-content: center;
3247
+ width: 1.4rem;
3248
+ height: 1.4rem;
3249
+ border-radius: 0.35rem;
3250
+ border: 1.5px solid var(--persona-border, #d1d5db);
3251
+ background: transparent;
3252
+ position: relative;
3253
+ }
3254
+
3255
+ [data-persona-root] .persona-ask-pill-selected .persona-ask-row-check,
3256
+ [data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-check {
3257
+ background: var(--persona-ask-pill-fg-selected, #fafafa);
3258
+ border-color: var(--persona-ask-pill-fg-selected, #fafafa);
3259
+ }
3260
+
3261
+ [data-persona-root] .persona-ask-pill-selected .persona-ask-row-check::after,
3262
+ [data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-check::after {
3263
+ content: "";
3264
+ width: 0.45rem;
3265
+ height: 0.75rem;
3266
+ border-right: 2px solid var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
3267
+ border-bottom: 2px solid var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
3268
+ transform: rotate(45deg) translate(-1px, -1px);
3269
+ }
3270
+
3271
+ [data-persona-root] .persona-ask-row.persona-ask-pill-skeleton {
3272
+ height: 3rem;
3273
+ }
3274
+
3275
+ /* Other row (rows mode) — composite row that contains the free-text input
3276
+ * directly. The input fills the row body; the badge sits on the right. */
3277
+ [data-persona-root] .persona-ask-row--other {
3278
+ cursor: text;
3279
+ }
3280
+
3281
+ [data-persona-root] .persona-ask-row-input {
3282
+ flex: 1;
3283
+ min-width: 0;
3284
+ border: none;
3285
+ background: transparent;
3286
+ padding: 0;
3287
+ margin: 0;
3288
+ font: inherit;
3289
+ font-size: 0.92rem;
3290
+ color: inherit;
3291
+ outline: none;
3292
+ width: 100%;
3293
+ }
3294
+
3295
+ [data-persona-root] .persona-ask-row-input::placeholder {
3296
+ color: var(--persona-text-muted, #6b7280);
3297
+ opacity: 1;
3298
+ }
3299
+
3300
+ /* Free-text expansion row (pills layout only) */
3301
+ [data-persona-root] .persona-ask-free-text {
3302
+ width: 100%;
3303
+ }
3304
+
3305
+ [data-persona-root] .persona-ask-free-text--rows {
3306
+ margin-top: 0.4rem;
3307
+ }
3308
+
3309
+ [data-persona-root] .persona-ask-free-text-input {
3310
+ padding: 0.5rem 0.8rem;
3311
+ border: 1px solid var(--persona-border, #e5e7eb);
3312
+ border-radius: 0.55rem;
3313
+ font-size: 0.88rem;
3314
+ background: var(--persona-ask-input-bg, var(--persona-surface, #ffffff));
3315
+ color: var(--persona-text, #1f2937);
3316
+ }
3317
+
3318
+ [data-persona-root] .persona-ask-free-text-input:focus {
3319
+ outline: 2px solid var(--persona-accent, #0f0f0f);
3320
+ outline-offset: 1px;
3321
+ }
3322
+
3323
+ [data-persona-root] .persona-ask-free-text-submit,
3324
+ [data-persona-root] .persona-ask-multi-submit {
3325
+ padding: 0.5rem 1rem;
3326
+ border: none;
3327
+ border-radius: 0.55rem;
3328
+ background: var(--persona-accent, #0f0f0f);
3329
+ color: #fafafa;
3330
+ font-size: 0.85rem;
3331
+ font-weight: 600;
3332
+ cursor: pointer;
3333
+ transition: opacity 0.15s ease;
3334
+ }
3335
+
3336
+ [data-persona-root] .persona-ask-free-text-submit:disabled,
3337
+ [data-persona-root] .persona-ask-multi-submit:disabled {
3338
+ opacity: 0.4;
3339
+ cursor: not-allowed;
3340
+ }
3341
+
3342
+ /* Nav row — Back / Skip / Next-or-Submit buttons for grouped payloads. */
3343
+ [data-persona-root] .persona-ask-nav-back,
3344
+ [data-persona-root] .persona-ask-nav-skip {
3345
+ padding: 0.45rem 0.85rem;
3346
+ border: 1px solid transparent;
3347
+ border-radius: 0.55rem;
3348
+ background: transparent;
3349
+ color: var(--persona-text-muted, #6b7280);
3350
+ font-size: 0.85rem;
3351
+ font-weight: 500;
3352
+ cursor: pointer;
3353
+ transition: background 0.15s ease, color 0.15s ease;
3354
+ }
3355
+
3356
+ [data-persona-root] .persona-ask-nav-back:hover:not(:disabled),
3357
+ [data-persona-root] .persona-ask-nav-skip:hover:not(:disabled) {
3358
+ background: var(--persona-container, #f3f4f6);
3359
+ color: var(--persona-text, #1f2937);
3360
+ }
3361
+
3362
+ [data-persona-root] .persona-ask-nav-back:disabled {
3363
+ opacity: 0.35;
3364
+ cursor: not-allowed;
3365
+ }
3366
+
3367
+ [data-persona-root] .persona-ask-nav-next {
3368
+ padding: 0.5rem 1rem;
3369
+ border: none;
3370
+ border-radius: 0.55rem;
3371
+ background: var(--persona-accent, #0f0f0f);
3372
+ color: #fafafa;
3373
+ font-size: 0.85rem;
3374
+ font-weight: 600;
3375
+ cursor: pointer;
3376
+ transition: opacity 0.15s ease;
3377
+ }
3378
+
3379
+ [data-persona-root] .persona-ask-nav-next:disabled {
3380
+ opacity: 0.4;
3381
+ cursor: not-allowed;
3382
+ }
3383
+
3384
+ @media (prefers-reduced-motion: reduce) {
3385
+ [data-persona-root] .persona-ask-sheet,
3386
+ [data-persona-root] .persona-ask-pill-skeleton,
3387
+ [data-persona-root] .persona-ask-question-skeleton {
3388
+ transition: none !important;
3389
+ animation: none !important;
3390
+ }
3391
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -22,6 +22,21 @@
22
22
  "import": "./dist/theme-editor.js",
23
23
  "require": "./dist/theme-editor.cjs"
24
24
  },
25
+ "./testing": {
26
+ "types": "./dist/testing.d.ts",
27
+ "import": "./dist/testing.js",
28
+ "require": "./dist/testing.cjs"
29
+ },
30
+ "./animations/glyph-cycle": {
31
+ "types": "./dist/animations/glyph-cycle.d.ts",
32
+ "import": "./dist/animations/glyph-cycle.js",
33
+ "require": "./dist/animations/glyph-cycle.cjs"
34
+ },
35
+ "./animations/wipe": {
36
+ "types": "./dist/animations/wipe.d.ts",
37
+ "import": "./dist/animations/wipe.js",
38
+ "require": "./dist/animations/wipe.cjs"
39
+ },
25
40
  "./widget.css": "./dist/widget.css"
26
41
  },
27
42
  "files": [
@@ -76,11 +91,13 @@
76
91
  "access": "public"
77
92
  },
78
93
  "scripts": {
79
- "build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:theme-ref && pnpm run build:theme-editor",
94
+ "build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:theme-ref && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:animations",
80
95
  "build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --dts --out-dir dist --no-splitting",
96
+ "build:testing": "tsup src/testing.ts --format esm,cjs --dts --out-dir dist --no-splitting",
97
+ "build:animations": "tsup src/animations/glyph-cycle.ts src/animations/wipe.ts --format esm,cjs --dts --out-dir dist/animations --no-splitting",
81
98
  "build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
82
99
  "build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
83
- "build:client": "tsup src/index.ts --format esm,cjs,iife --global-name AgentWidget --minify --sourcemap --splitting false --dts --loader \".css=text\"",
100
+ "build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader \".css=text\" && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
84
101
  "build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
85
102
  "lint": "eslint . --ext .ts",
86
103
  "typecheck": "tsc --noEmit",