@runtypelabs/persona 3.17.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 (43) hide show
  1. package/README.md +142 -0
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-HPZY7oAI.d.cts → types-cwY5HaFD.d.cts} +25 -0
  5. package/dist/animations/{types-HPZY7oAI.d.ts → types-cwY5HaFD.d.ts} +25 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/index.cjs +47 -47
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +300 -1
  11. package/dist/index.d.ts +300 -1
  12. package/dist/index.global.js +75 -75
  13. package/dist/index.global.js.map +1 -1
  14. package/dist/index.js +47 -47
  15. package/dist/index.js.map +1 -1
  16. package/dist/theme-editor.cjs +1432 -159
  17. package/dist/theme-editor.d.cts +218 -0
  18. package/dist/theme-editor.d.ts +218 -0
  19. package/dist/theme-editor.js +1432 -159
  20. package/dist/theme-reference.cjs +1 -1
  21. package/dist/theme-reference.d.cts +14 -0
  22. package/dist/theme-reference.d.ts +14 -0
  23. package/dist/widget.css +432 -0
  24. package/package.json +1 -1
  25. package/src/client.test.ts +134 -0
  26. package/src/client.ts +71 -0
  27. package/src/components/ask-user-question-bubble.test.ts +583 -0
  28. package/src/components/ask-user-question-bubble.ts +924 -0
  29. package/src/components/messages.ts +33 -1
  30. package/src/components/panel.ts +41 -4
  31. package/src/defaults.ts +21 -0
  32. package/src/index.ts +16 -1
  33. package/src/plugins/types.ts +57 -0
  34. package/src/session.test.ts +183 -0
  35. package/src/session.ts +242 -3
  36. package/src/styles/widget.css +432 -0
  37. package/src/types/theme.ts +15 -0
  38. package/src/types.ts +150 -0
  39. package/src/ui.ask-user-question-plugin.test.ts +649 -0
  40. package/src/ui.ts +631 -5
  41. package/src/utils/storage.ts +10 -2
  42. package/src/utils/theme.test.ts +36 -0
  43. package/src/utils/tokens.ts +23 -0
package/src/session.ts CHANGED
@@ -81,9 +81,22 @@ export class AgentWidgetSession {
81
81
  this.messages = this.sortMessages(this.messages);
82
82
  this.client = new AgentWidgetClient(config);
83
83
 
84
+ // Hydrate artifacts from config (mirrors `initialMessages`). Restored
85
+ // records are forced to `status: "complete"` — a mid-stream artifact should
86
+ // never reappear after a refresh with its skeleton still showing.
87
+ for (const rec of config.initialArtifacts ?? []) {
88
+ this.artifacts.set(rec.id, { ...rec, status: "complete" });
89
+ }
90
+ if (config.initialSelectedArtifactId != null) {
91
+ this.selectedArtifactId = config.initialSelectedArtifactId;
92
+ }
93
+
84
94
  if (this.messages.length) {
85
95
  this.callbacks.onMessagesChanged([...this.messages]);
86
96
  }
97
+ if (this.artifacts.size > 0) {
98
+ this.emitArtifactsState();
99
+ }
87
100
  this.callbacks.onStatusChanged(this.status);
88
101
  }
89
102
 
@@ -968,6 +981,194 @@ export class AgentWidgetSession {
968
981
  }
969
982
  }
970
983
 
984
+ /**
985
+ * Resolve a paused `ask_user_question` LOCAL tool call.
986
+ *
987
+ * When the server emits `step_await` for `ask_user_question`, the widget
988
+ * renders the answer-pill sheet and calls this method once the user
989
+ * picks. Steps:
990
+ * 1. POST the answer to `/resume` via `client.resumeFlow`.
991
+ * 2. Pipe the resulting SSE stream through `connectStream()` so the
992
+ * paused agent execution continues.
993
+ * 3. Append a user-visible bubble with the answer text so the
994
+ * transcript reads naturally.
995
+ */
996
+ /**
997
+ * Persist in-progress answers and the current page index for a multi-question
998
+ * `ask_user_question` payload, so a refresh resumes on the same page with
999
+ * prior answers intact. Called by ui.ts on every Back/Next/pick interaction.
1000
+ */
1001
+ public persistAskUserQuestionProgress(
1002
+ toolMessage: AgentWidgetMessage,
1003
+ progress: {
1004
+ answers: Record<string, string | string[]>;
1005
+ currentIndex: number;
1006
+ }
1007
+ ): void {
1008
+ const current = this.messages.find((m) => m.id === toolMessage.id);
1009
+ if (!current) return;
1010
+ this.upsertMessage({
1011
+ ...current,
1012
+ agentMetadata: {
1013
+ ...current.agentMetadata,
1014
+ askUserQuestionAnswers: progress.answers,
1015
+ askUserQuestionIndex: progress.currentIndex,
1016
+ },
1017
+ });
1018
+ }
1019
+
1020
+ /**
1021
+ * Flip an `ask_user_question` tool message from awaiting → answered so
1022
+ * render passes stop re-mounting its answer-pill sheet. Idempotent.
1023
+ * When `answers` is provided, persists the full structured answer Record
1024
+ * atomically with the answered flag — guarding against later events that
1025
+ * could re-emit the tool message and clobber the per-pick persisted
1026
+ * answers via top-level merge.
1027
+ */
1028
+ public markAskUserQuestionResolved(
1029
+ toolMessage: AgentWidgetMessage,
1030
+ answers?: Record<string, string | string[]>
1031
+ ): void {
1032
+ const current = this.messages.find((m) => m.id === toolMessage.id);
1033
+ if (!current) return;
1034
+ this.upsertMessage({
1035
+ ...current,
1036
+ agentMetadata: {
1037
+ ...current.agentMetadata,
1038
+ awaitingLocalTool: false,
1039
+ askUserQuestionAnswered: true,
1040
+ ...(answers ? { askUserQuestionAnswers: answers } : {}),
1041
+ },
1042
+ });
1043
+ }
1044
+
1045
+ public async resolveAskUserQuestion(
1046
+ toolMessage: AgentWidgetMessage,
1047
+ answer: string | Record<string, string | string[]>
1048
+ ): Promise<void> {
1049
+ // Idempotent — guards against rapid double-clicks on answer pills before
1050
+ // the re-render swaps the card to its collapsed/answered state.
1051
+ const live = this.messages.find((m) => m.id === toolMessage.id);
1052
+ if (live?.agentMetadata?.askUserQuestionAnswered === true) return;
1053
+
1054
+ const executionId = toolMessage.agentMetadata?.executionId;
1055
+ const toolName = toolMessage.toolCall?.name;
1056
+ if (!executionId || !toolName) {
1057
+ this.callbacks.onError?.(
1058
+ new Error(
1059
+ "resolveAskUserQuestion: message is missing executionId or toolCall.name"
1060
+ )
1061
+ );
1062
+ return;
1063
+ }
1064
+
1065
+ // Flip answered flag first so the next render skips the sheet re-mount,
1066
+ // avoiding the race between removeAskUserQuestionSheet's 180ms slide-out
1067
+ // timer and the renders that fire as the resume stream lands. Pass the
1068
+ // structured answer Record (when present) so it's atomically persisted
1069
+ // alongside the flag — the answered-state review card depends on
1070
+ // `agentMetadata.askUserQuestionAnswers` being populated at render time.
1071
+ //
1072
+ // For single-question payloads, callers (built-in pick handler, plugins)
1073
+ // resolve with a plain string. Derive a `{ [questionText]: answer }` Record
1074
+ // from the toolCall args so the answered-card render path is consistent
1075
+ // with grouped flows.
1076
+ let structuredAnswers: Record<string, string | string[]> | undefined =
1077
+ typeof answer === "string" ? undefined : answer;
1078
+ if (structuredAnswers === undefined && typeof answer === "string") {
1079
+ const args = toolMessage.toolCall?.args as
1080
+ | { questions?: Array<{ question?: unknown }> }
1081
+ | undefined;
1082
+ const questions = Array.isArray(args?.questions) ? args!.questions : [];
1083
+ if (questions.length === 1) {
1084
+ const qText = typeof questions[0]?.question === "string"
1085
+ ? (questions[0].question as string)
1086
+ : "";
1087
+ if (qText) structuredAnswers = { [qText]: answer };
1088
+ }
1089
+ }
1090
+ this.markAskUserQuestionResolved(toolMessage, structuredAnswers);
1091
+
1092
+ // Inject Q→A pair messages — one assistant bubble per question, one user
1093
+ // bubble per answer — so the transcript reads like a normal conversation.
1094
+ // The original ask_user_question tool message is suppressed by the
1095
+ // renderer once `askUserQuestionAnswered` is true. Skipped questions get
1096
+ // a muted italic `*Skipped*` user bubble (rendered through the standard
1097
+ // markdown pipeline).
1098
+ const toolCallId = toolMessage.toolCall!.id;
1099
+ const args = toolMessage.toolCall?.args as
1100
+ | { questions?: Array<{ question?: unknown; header?: unknown }> }
1101
+ | undefined;
1102
+ const questions = Array.isArray(args?.questions) ? args!.questions : [];
1103
+ if (questions.length === 0) {
1104
+ const fallback =
1105
+ typeof answer === "string"
1106
+ ? answer
1107
+ : Object.entries(answer)
1108
+ .map(
1109
+ ([q, v]) => `${q}: ${Array.isArray(v) ? v.join(", ") : v}`
1110
+ )
1111
+ .join(" | ");
1112
+ this.appendMessage({
1113
+ id: `ask-user-answer-${toolCallId}`,
1114
+ role: "user",
1115
+ content: fallback,
1116
+ createdAt: new Date().toISOString(),
1117
+ streaming: false,
1118
+ sequence: this.nextSequence(),
1119
+ });
1120
+ } else {
1121
+ const stored = structuredAnswers ?? {};
1122
+ questions.forEach((p, i) => {
1123
+ const qText = typeof p?.question === "string" ? p.question : "";
1124
+ if (!qText) return;
1125
+ const ans = stored[qText];
1126
+ const answerStr = Array.isArray(ans)
1127
+ ? ans.join(", ")
1128
+ : typeof ans === "string"
1129
+ ? ans
1130
+ : "";
1131
+ this.appendMessage({
1132
+ id: `ask-user-q-${toolCallId}-${i}`,
1133
+ role: "assistant",
1134
+ content: qText,
1135
+ createdAt: new Date().toISOString(),
1136
+ streaming: false,
1137
+ sequence: this.nextSequence(),
1138
+ });
1139
+ this.appendMessage({
1140
+ id: `ask-user-a-${toolCallId}-${i}`,
1141
+ role: "user",
1142
+ content: answerStr || "*Skipped*",
1143
+ createdAt: new Date().toISOString(),
1144
+ streaming: false,
1145
+ sequence: this.nextSequence(),
1146
+ });
1147
+ });
1148
+ }
1149
+
1150
+ try {
1151
+ const response = await this.client.resumeFlow(executionId, {
1152
+ [toolName]: answer,
1153
+ });
1154
+
1155
+ if (!response.ok) {
1156
+ const errorData = await response.json().catch(() => null);
1157
+ throw new Error(
1158
+ errorData?.error ?? `Resume failed: ${response.status}`
1159
+ );
1160
+ }
1161
+
1162
+ if (response.body) {
1163
+ await this.connectStream(response.body);
1164
+ }
1165
+ } catch (error) {
1166
+ this.callbacks.onError?.(
1167
+ error instanceof Error ? error : new Error(String(error))
1168
+ );
1169
+ }
1170
+ }
1171
+
971
1172
  public cancel() {
972
1173
  this.abortController?.abort();
973
1174
  this.abortController = null;
@@ -1123,6 +1324,18 @@ export class AgentWidgetSession {
1123
1324
  this.callbacks.onMessagesChanged([...this.messages]);
1124
1325
  }
1125
1326
 
1327
+ public hydrateArtifacts(
1328
+ artifacts: PersonaArtifactRecord[],
1329
+ selectedId: string | null = null
1330
+ ) {
1331
+ this.artifacts.clear();
1332
+ for (const rec of artifacts) {
1333
+ this.artifacts.set(rec.id, { ...rec, status: "complete" });
1334
+ }
1335
+ this.selectedArtifactId = selectedId;
1336
+ this.emitArtifactsState();
1337
+ }
1338
+
1126
1339
  private handleEvent = (event: AgentWidgetEvent) => {
1127
1340
  if (event.type === "message") {
1128
1341
  this.upsertMessage(event.message);
@@ -1317,9 +1530,35 @@ export class AgentWidgetSession {
1317
1530
  return;
1318
1531
  }
1319
1532
 
1320
- this.messages = this.messages.map((existing, idx) =>
1321
- idx === index ? { ...existing, ...withSequence } : existing
1322
- );
1533
+ this.messages = this.messages.map((existing, idx) => {
1534
+ if (idx !== index) return existing;
1535
+ const merged = { ...existing, ...withSequence };
1536
+ // Preserve `ask_user_question` answered state across re-emissions.
1537
+ // Top-level merge would otherwise replace `agentMetadata` wholesale —
1538
+ // post-resume events (e.g. `tool_complete` re-emitted from a stale
1539
+ // client-side cache) would wipe `askUserQuestionAnswered` and
1540
+ // `askUserQuestionAnswers`, causing the answered review card to
1541
+ // lose its answers and revert to "(skipped)" placeholders.
1542
+ if (
1543
+ existing.agentMetadata?.askUserQuestionAnswered === true &&
1544
+ withSequence.agentMetadata
1545
+ ) {
1546
+ merged.agentMetadata = {
1547
+ ...withSequence.agentMetadata,
1548
+ askUserQuestionAnswered: true,
1549
+ ...(existing.agentMetadata.askUserQuestionAnswers
1550
+ ? {
1551
+ askUserQuestionAnswers:
1552
+ existing.agentMetadata.askUserQuestionAnswers,
1553
+ }
1554
+ : {}),
1555
+ // Keep awaiting flag false once resolved — never let a stale
1556
+ // re-emit flip us back to awaiting.
1557
+ awaitingLocalTool: false,
1558
+ };
1559
+ }
1560
+ return merged;
1561
+ });
1323
1562
  this.messages = this.sortMessages(this.messages);
1324
1563
  this.callbacks.onMessagesChanged([...this.messages]);
1325
1564
  }
@@ -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;
@@ -2957,3 +2981,411 @@
2957
2981
  background-clip: border-box !important;
2958
2982
  }
2959
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
+ }