@runtypelabs/persona 4.33.0 → 4.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-ui.d.cts +3 -15
- package/dist/activity-ui.d.ts +3 -15
- package/dist/animations/glyph-cycle.d.cts +1 -3
- package/dist/animations/glyph-cycle.d.ts +1 -3
- package/dist/animations/wipe.d.cts +1 -3
- package/dist/animations/wipe.d.ts +1 -3
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +3 -15
- package/dist/approval-ui.d.ts +3 -15
- package/dist/approval-ui.js +1 -1
- package/dist/artifacts-ui.cjs +1 -1
- package/dist/artifacts-ui.d.cts +3 -21
- package/dist/artifacts-ui.d.ts +3 -21
- package/dist/background-runtime.d.cts +2 -3
- package/dist/background-runtime.d.ts +2 -3
- package/dist/{chunk-DW7Vg_l2.js → chunk-B1P8w9_X.js} +1 -1
- package/dist/{chunk-D6U0E82p.js → chunk-B30Jdgg6.js} +1 -1
- package/dist/{chunk-gu34lr-P.js → chunk-Brdx4NCz.js} +1 -1
- package/dist/{chunk-6CUrH_jm.js → chunk-Bzo9GaAi.js} +1 -1
- package/dist/{chunk-Ds_twaZn.js → chunk-C4v-HNhS.js} +1 -1
- package/dist/{chunk-Bkma5hIU.js → chunk-CE4seJxF.js} +1 -1
- package/dist/chunk-CNBMBx-Y.js +2 -0
- package/dist/{chunk-ql5N2I_t.js → chunk-CWzHNuVK.js} +1 -1
- package/dist/{chunk-DbGLjU2P.js → chunk-CYpt7PsO.js} +1 -1
- package/dist/chunk-Cd8mp8Sv.js +1 -0
- package/dist/chunk-CsfE8WRG.js +1 -0
- package/dist/{chunk-DOZmcve8.js → chunk-Csr59G5V.js} +1 -1
- package/dist/{chunk-Co3VVwGe.js → chunk-D2pHRNEq.js} +0 -0
- package/dist/chunk-D3kZO8u-.js +1 -0
- package/dist/chunk-DAthf5H3.js +1 -0
- package/dist/{chunk-CXQJG7pb.js → chunk-DZ_aillY.js} +2 -2
- package/dist/{chunk-6C23QQdP.js → chunk-DgJV9zbK.js} +1 -1
- package/dist/chunk-Dv6KtIT1.js +1 -0
- package/dist/chunk-Dws8IrdR.js +1 -0
- package/dist/chunk-ThxxD8oK.js +1 -0
- package/dist/{chunk-C-EHtpbB.js → chunk-h5aOI_Pa.js} +1 -1
- package/dist/chunk-kizhspe5.js +1 -0
- package/dist/client-dispatch.js +1 -1
- package/dist/client-history.js +1 -1
- package/dist/client-stream.js +1 -1
- package/dist/{code-generators-DqauYyfX.d.cts → code-generators-DPAqTcAz.d.cts} +0 -2
- package/dist/{code-generators-DqauYyfX.d.ts → code-generators-DPAqTcAz.d.ts} +0 -2
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.d.cts +1 -1
- package/dist/codegen.d.ts +1 -1
- package/dist/codegen.js +1 -1
- package/dist/composer-actions-runtime.cjs +1 -1
- package/dist/composer-actions-runtime.d.cts +4 -20
- package/dist/composer-actions-runtime.d.ts +4 -20
- package/dist/composer-actions-runtime.js +1 -1
- package/dist/{composer-input-DnqGCXZz.d.ts → composer-input-BSDNPOqV.d.cts} +1 -7
- package/dist/{composer-input-DnqGCXZz.d.cts → composer-input-BSDNPOqV.d.ts} +1 -7
- package/dist/{composer-store-6Iwy6WLI.d.ts → composer-store-DC-gAjtN.d.cts} +1 -3
- package/dist/{composer-store-6Iwy6WLI.d.cts → composer-store-DC-gAjtN.d.ts} +1 -3
- package/dist/config-diagnostics.d.cts +0 -2
- package/dist/config-diagnostics.d.ts +0 -2
- package/dist/{context-mention-manager-B-rHF98n.d.ts → context-mention-manager-CCpeoGL2.d.cts} +2 -6
- package/dist/{context-mention-manager-B-rHF98n.d.cts → context-mention-manager-CCpeoGL2.d.ts} +2 -6
- package/dist/context-mentions-inline.cjs +1 -1
- package/dist/context-mentions-inline.d.cts +2 -4
- package/dist/context-mentions-inline.d.ts +2 -4
- package/dist/context-mentions-inline.js +1 -1
- package/dist/context-mentions.cjs +2 -2
- package/dist/context-mentions.d.cts +3 -9
- package/dist/context-mentions.d.ts +3 -9
- package/dist/context-mentions.js +1 -1
- package/dist/{defaults-BuOjqUAG.d.ts → defaults-tj5RcN8r.d.cts} +1 -5
- package/dist/{defaults-BuOjqUAG.d.cts → defaults-tj5RcN8r.d.ts} +1 -5
- package/dist/dictation-runtime.cjs +1 -1
- package/dist/dictation-runtime.d.cts +4 -13
- package/dist/dictation-runtime.d.ts +4 -13
- package/dist/dictation-runtime.js +1 -1
- package/dist/{dom-context-DHTabIn7.d.cts → dom-context-Bw-_9Evh.d.cts} +0 -2
- package/dist/{dom-context-DHTabIn7.d.ts → dom-context-Bw-_9Evh.d.ts} +0 -2
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +3 -16
- package/dist/event-stream-view.d.ts +3 -16
- package/dist/{fallback-speech-engine-SqEfkGda.d.ts → fallback-speech-engine-gkhbjLiZ.d.cts} +1 -3
- package/dist/{fallback-speech-engine-SqEfkGda.d.cts → fallback-speech-engine-gkhbjLiZ.d.ts} +1 -3
- package/dist/{feature-preferences-DqAmje-2.d.ts → feature-preferences-CerDWGZ7.d.cts} +1 -5
- package/dist/{feature-preferences-DqAmje-2.d.cts → feature-preferences-CerDWGZ7.d.ts} +1 -5
- package/dist/forms-ui.d.cts +2 -4
- package/dist/forms-ui.d.ts +2 -4
- package/dist/{history-confirm-dialog-B3L7QNcO.d.cts → history-confirm-dialog-CrkSB3Ra.d.cts} +2 -14
- package/dist/{history-confirm-dialog-B3L7QNcO.d.ts → history-confirm-dialog-CrkSB3Ra.d.ts} +2 -14
- package/dist/history-shell.cjs +1 -1
- package/dist/history-shell.d.cts +8 -27
- package/dist/history-shell.d.ts +8 -27
- package/dist/history-shell.js +1 -1
- package/dist/history-view.d.cts +1 -1
- package/dist/history-view.d.ts +1 -1
- package/dist/icons-extra.cjs +1 -1
- package/dist/icons-extra.d.cts +0 -2
- package/dist/icons-extra.d.ts +0 -2
- package/dist/index.cjs +44 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +50 -50
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/{loading-label-runtime-loader-JecjOfJS.d.ts → loading-label-runtime-loader-fWIRrhUF.d.cts} +1 -3
- package/dist/{loading-label-runtime-loader-JecjOfJS.d.cts → loading-label-runtime-loader-fWIRrhUF.d.ts} +1 -3
- package/dist/loading-label-runtime.cjs +1 -1
- package/dist/loading-label-runtime.d.cts +1 -1
- package/dist/loading-label-runtime.d.ts +1 -1
- package/dist/loading-label-runtime.js +1 -1
- package/dist/markdown-parsers.js +1 -1
- package/dist/{message-bubble-CBYtpDsT.d.ts → message-bubble-Czc0lOHr.d.cts} +20 -3
- package/dist/{message-bubble-CBYtpDsT.d.cts → message-bubble-Czc0lOHr.d.ts} +20 -3
- package/dist/{plugin-kit-C1mRjLgW.d.cts → plugin-kit-C30jE-cw.d.cts} +3 -3
- package/dist/{plugin-kit-C1mRjLgW.d.ts → plugin-kit-C30jE-cw.d.ts} +3 -3
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +1 -1
- package/dist/plugin-kit.d.ts +1 -1
- package/dist/plugin-kit.js +1 -1
- package/dist/preferences-runtime.cjs +1 -1
- package/dist/preferences-runtime.d.cts +1 -1
- package/dist/preferences-runtime.d.ts +1 -1
- package/dist/preferences-runtime.js +1 -1
- package/dist/{preview-_UJwwt2L.d.cts → preview-D-osV3FU.d.cts} +2 -6
- package/dist/{preview-_UJwwt2L.d.ts → preview-D-osV3FU.d.ts} +2 -6
- package/dist/{registry-B5ZV3K-h.d.ts → registry-B6w91tx0.d.cts} +1 -8
- package/dist/{registry-B5ZV3K-h.d.cts → registry-B6w91tx0.d.ts} +1 -8
- package/dist/{session-Bys8_XuB.d.cts → session-BUQrs3Of.d.cts} +10 -93
- package/dist/{session-Bys8_XuB.d.ts → session-BUQrs3Of.d.ts} +10 -93
- package/dist/session-reconnect.d.cts +2 -4
- package/dist/session-reconnect.d.ts +2 -4
- package/dist/smart-dom-reader.d.cts +2 -8
- package/dist/smart-dom-reader.d.ts +2 -8
- package/dist/testing.d.cts +0 -2
- package/dist/testing.d.ts +0 -2
- package/dist/theme-editor-preview.cjs +50 -50
- package/dist/theme-editor-preview.d.cts +1 -1
- package/dist/theme-editor-preview.d.ts +1 -1
- package/dist/theme-editor-preview.js +25 -25
- package/dist/theme-editor.cjs +25 -5
- package/dist/theme-editor.d.cts +2 -22
- package/dist/theme-editor.d.ts +2 -22
- package/dist/theme-editor.js +1 -1
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +2 -4
- package/dist/theme-reference.d.ts +2 -4
- package/dist/theme-reference.js +1 -1
- package/dist/{tool-loading-animation-d5qAQcwv.d.ts → tool-loading-animation-B7XUf8TN.d.cts} +1 -7
- package/dist/{tool-loading-animation-d5qAQcwv.d.cts → tool-loading-animation-B7XUf8TN.d.ts} +1 -7
- package/dist/{tooltip-BkW-8MnD.d.cts → tooltip-DbXkAuJL.d.cts} +0 -2
- package/dist/{tooltip-BkW-8MnD.d.ts → tooltip-DbXkAuJL.d.ts} +0 -2
- package/dist/{turn-activity-ui-q6fHVnx-.d.cts → turn-activity-ui-CohuNMHT.d.cts} +2 -12
- package/dist/{turn-activity-ui-q6fHVnx-.d.ts → turn-activity-ui-CohuNMHT.d.ts} +2 -12
- package/dist/turn-activity-ui.d.cts +1 -1
- package/dist/turn-activity-ui.d.ts +1 -1
- package/dist/{types-B_mj21jg.d.cts → types-9i0xcAMT.d.cts} +128 -29
- package/dist/{types-B_mj21jg.d.ts → types-9i0xcAMT.d.ts} +128 -29
- package/dist/ui-extras.js +3 -3
- package/dist/voice-client-tools.d.cts +1 -3
- package/dist/voice-client-tools.d.ts +1 -3
- package/dist/voice-runtime.cjs +1 -1
- package/dist/voice-runtime.d.cts +2 -12
- package/dist/voice-runtime.d.ts +2 -12
- package/dist/voice-runtime.js +2 -2
- package/dist/voice-worklet-player.d.cts +2 -6
- package/dist/voice-worklet-player.d.ts +2 -6
- package/dist/{webmcp-runtime-entry-ClEO3qdI.d.ts → webmcp-runtime-entry-XNaXa-9h.d.cts} +9 -5
- package/dist/{webmcp-runtime-entry-ClEO3qdI.d.cts → webmcp-runtime-entry-XNaXa-9h.d.ts} +9 -5
- package/dist/webmcp-runtime.cjs +1 -1
- package/dist/webmcp-runtime.d.cts +1 -1
- package/dist/webmcp-runtime.d.ts +1 -1
- package/dist/webmcp-runtime.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +5 -5
- package/src/client-dispatch.ts +13 -0
- package/src/client.ts +37 -2
- package/src/client.webmcp-require-confirm.test.ts +212 -0
- package/src/components/composer-model-picker.ts +0 -14
- package/src/components/composer-overflow-menu.ts +0 -15
- package/src/components/composer-tokens.ts +0 -20
- package/src/components/event-stream-view.test.ts +5 -0
- package/src/components/message-bubble.ts +26 -6
- package/src/components/scroll-to-bottom.test.ts +135 -0
- package/src/components/scroll-to-bottom.ts +50 -13
- package/src/components/user-message-collapse.ts +117 -0
- package/src/defaults.ts +6 -0
- package/src/generated/runtype-openapi-contract.ts +13 -0
- package/src/plugin-kit.test.ts +101 -0
- package/src/plugin-kit.ts +29 -1
- package/src/session.ts +6 -2
- package/src/session.webmcp.test.ts +32 -0
- package/src/styles/widget.css +56 -0
- package/src/theme-reference.ts +2 -2
- package/src/types/theme.ts +29 -0
- package/src/types.ts +84 -5
- package/src/ui.composer-models.test.ts +5 -7
- package/src/ui.composer-overflow.test.ts +14 -21
- package/src/ui.scroll-additive.test.ts +219 -117
- package/src/ui.scroll.test.ts +176 -13
- package/src/ui.ts +230 -107
- package/src/ui.turn-activity.test.ts +7 -7
- package/src/ui.user-message-collapse.test.ts +540 -0
- package/src/utils/auto-follow.test.ts +0 -38
- package/src/utils/auto-follow.ts +0 -35
- package/src/utils/context-mention-controller.test.ts +56 -0
- package/src/utils/theme.test.ts +23 -0
- package/src/utils/tokens.ts +16 -0
- package/src/webmcp-bridge.test.ts +102 -0
- package/src/webmcp-runtime-entry.ts +29 -1
- package/dist/chunk-BAkaTZpd.js +0 -1
- package/dist/chunk-BEDTp-dm.js +0 -1
- package/dist/chunk-C7-m0EmI.js +0 -1
- package/dist/chunk-CNhThrLX.js +0 -1
- package/dist/chunk-CVMIncEY.js +0 -1
- package/dist/chunk-CzLEhoB_.js +0 -2
- package/dist/chunk-DVgzoVCB.js +0 -1
- package/dist/chunk-d54BJdKd.js +0 -1
- package/dist/chunk-dLJWKfOb.js +0 -1
package/src/ui.scroll.test.ts
CHANGED
|
@@ -1115,6 +1115,103 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
1115
1115
|
controller.destroy();
|
|
1116
1116
|
});
|
|
1117
1117
|
|
|
1118
|
+
describe("approval waiting below an anchored turn", () => {
|
|
1119
|
+
const approvalMessage = (id: string, status: "pending" | "approved" = "pending") => ({
|
|
1120
|
+
type: "message" as const, message: {
|
|
1121
|
+
id, role: "assistant" as const, content: "", createdAt: STREAM_CREATED_AT,
|
|
1122
|
+
variant: "approval" as const, approval: { id, agentId: "agent", executionId: "execution", status, toolName: "Search", description: "Search documentation", parameters: { query: "docs" } },
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
// `approvals` maps approval ids to card tops; each Allow button sits 40px
|
|
1126
|
+
// into its card (jsdom has no layout, so the geometry is mocked).
|
|
1127
|
+
const setup = (approvals: Record<string, number>, scrollHeight = 1600) => {
|
|
1128
|
+
const raf = installRafMock();
|
|
1129
|
+
const mount = createMount();
|
|
1130
|
+
const controller = createAgentExperience(mount, {
|
|
1131
|
+
apiUrl: "/test", future: { v5Defaults: true }, launcher: { enabled: false },
|
|
1132
|
+
approval: { keyboardShortcuts: "focused" },
|
|
1133
|
+
features: { scrollBehavior: { mode: "anchor-top", anchorTopOffset: 16 } },
|
|
1134
|
+
} as any);
|
|
1135
|
+
const body = mount.querySelector<HTMLElement>("#persona-scroll-container")!;
|
|
1136
|
+
const metrics = installScrollMetrics(body, { scrollHeight, clientHeight: 400 });
|
|
1137
|
+
const tops: Record<string, number> = { "user-approve": 100, ...approvals };
|
|
1138
|
+
vi.spyOn(HTMLElement.prototype, "offsetTop", "get").mockImplementation(function (this: HTMLElement) {
|
|
1139
|
+
const id = this.getAttribute("data-message-id") ?? this.getAttribute("data-wrapper-id");
|
|
1140
|
+
if (id && id in tops) return tops[id];
|
|
1141
|
+
const card = this.matches('[data-action="allow"]') ? this.closest("[data-message-id]")?.getAttribute("data-message-id") : null;
|
|
1142
|
+
return card && card in tops ? tops[card] + 40 : 0;
|
|
1143
|
+
});
|
|
1144
|
+
emitStreamingStatus(controller);
|
|
1145
|
+
emitUserMessage(controller, "user-approve", "Long question ".repeat(80));
|
|
1146
|
+
raf.flush();
|
|
1147
|
+
expect(metrics.getScrollTop()).toBe(84);
|
|
1148
|
+
const composer = mount.querySelector<HTMLTextAreaElement>("textarea")!;
|
|
1149
|
+
composer.focus();
|
|
1150
|
+
for (const id of Object.keys(approvals)) controller.injectTestMessage(approvalMessage(id));
|
|
1151
|
+
raf.flush();
|
|
1152
|
+
const allow = (id: string) => mount.querySelector<HTMLButtonElement>(`[data-message-id="${id}"] [data-action="allow"]`);
|
|
1153
|
+
return { raf, controller, metrics, composer, allow, jump: getScrollToBottomButton(mount)! };
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
it("names it instead of scrolling or moving focus, then focuses it after the jump", () => {
|
|
1157
|
+
const { raf, controller, metrics, composer, allow, jump } = setup({ "approval-below": 1400 });
|
|
1158
|
+
expect(allow("approval-below")).not.toBeNull();
|
|
1159
|
+
// The anchor holds, and Enter-to-allow never lands on an unseen card.
|
|
1160
|
+
expect(metrics.getScrollTop()).toBe(84);
|
|
1161
|
+
expect(document.activeElement).toBe(composer);
|
|
1162
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(true);
|
|
1163
|
+
expect(jump.textContent).toBe("Approval needed");
|
|
1164
|
+
expect(jump.getAttribute("aria-label")).toBe("Approval needed below. Jump to latest");
|
|
1165
|
+
|
|
1166
|
+
jump.click();
|
|
1167
|
+
raf.flush();
|
|
1168
|
+
// Bottom is 1200: the card at 1400 and its Allow button at 1440 are in view.
|
|
1169
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
1170
|
+
expect(document.activeElement).toBe(allow("approval-below"));
|
|
1171
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(false);
|
|
1172
|
+
controller.destroy();
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it("after the jump, focuses only an approval whose action is in view", () => {
|
|
1176
|
+
// The older card ends up above the viewport after the jump; the newer
|
|
1177
|
+
// one is in view. Focus must go to the visible one, never the older.
|
|
1178
|
+
const { raf, controller, composer, allow, jump } = setup({ "approval-old": 700, "approval-new": 1400 });
|
|
1179
|
+
jump.click();
|
|
1180
|
+
raf.flush();
|
|
1181
|
+
expect(document.activeElement).not.toBe(allow("approval-old"));
|
|
1182
|
+
expect(document.activeElement).toBe(allow("approval-new"));
|
|
1183
|
+
expect(composer).not.toBe(document.activeElement);
|
|
1184
|
+
controller.destroy();
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
it("focuses nothing after the jump when no target's action is in view", () => {
|
|
1188
|
+
// The only card lands above the jump's viewport (bottom is 1200).
|
|
1189
|
+
const { raf, controller, allow, jump } = setup({ "approval-above": 700 });
|
|
1190
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(true);
|
|
1191
|
+
jump.click();
|
|
1192
|
+
raf.flush();
|
|
1193
|
+
expect(document.activeElement).not.toBe(allow("approval-above"));
|
|
1194
|
+
controller.destroy();
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it("leaves a visible approval to the card itself", () => {
|
|
1198
|
+
const { controller, metrics, allow, jump } = setup({ "approval-below": 300 });
|
|
1199
|
+
expect(metrics.getScrollTop()).toBe(84);
|
|
1200
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(false);
|
|
1201
|
+
expect(document.activeElement).toBe(allow("approval-below"));
|
|
1202
|
+
controller.destroy();
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
it("drops the label once the approval resolves", () => {
|
|
1206
|
+
const { raf, controller, jump } = setup({ "approval-below": 1400 });
|
|
1207
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(true);
|
|
1208
|
+
controller.injectTestMessage(approvalMessage("approval-below", "approved"));
|
|
1209
|
+
raf.flush();
|
|
1210
|
+
expect(jump.hasAttribute("data-persona-scroll-to-bottom-approval")).toBe(false);
|
|
1211
|
+
controller.destroy();
|
|
1212
|
+
});
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1118
1215
|
it("does not yank a paused reader when content grows without a render event", () => {
|
|
1119
1216
|
const raf = installRafMock();
|
|
1120
1217
|
const resize = installResizeObserverMock();
|
|
@@ -1366,6 +1463,19 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
1366
1463
|
expect(spacer?.style.height).toBe("0px");
|
|
1367
1464
|
expect(spacer?.style.display).toBe("none");
|
|
1368
1465
|
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
1466
|
+
const releasedAt = metrics.getScrollTop();
|
|
1467
|
+
|
|
1468
|
+
// Later streaming, resizes, and stream end must not reinstate the anchor
|
|
1469
|
+
// or its spacer.
|
|
1470
|
+
metrics.setScrollHeight(1500);
|
|
1471
|
+
emitStreamingMessage(controller, "Streaming response, now longer still");
|
|
1472
|
+
raf.flush();
|
|
1473
|
+
resize.trigger();
|
|
1474
|
+
controller.injectTestMessage({ type: "status", status: "idle" });
|
|
1475
|
+
raf.flush();
|
|
1476
|
+
expect(spacer?.style.height).toBe("0px");
|
|
1477
|
+
expect(spacer?.style.display).toBe("none");
|
|
1478
|
+
expect(metrics.getScrollTop()).toBe(releasedAt);
|
|
1369
1479
|
|
|
1370
1480
|
controller.destroy();
|
|
1371
1481
|
});
|
|
@@ -1504,9 +1614,9 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
1504
1614
|
}
|
|
1505
1615
|
});
|
|
1506
1616
|
|
|
1507
|
-
it("anchor-top
|
|
1617
|
+
it("anchor-top keeps a user message taller than the viewport anchored as the reply arrives", () => {
|
|
1508
1618
|
const raf = installRafMock();
|
|
1509
|
-
installResizeObserverMock();
|
|
1619
|
+
const resize = installResizeObserverMock();
|
|
1510
1620
|
const mount = createMount();
|
|
1511
1621
|
const controller = createAgentExperience(mount, {
|
|
1512
1622
|
apiUrl: "https://api.example.com/chat",
|
|
@@ -1517,34 +1627,87 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
1517
1627
|
} as any);
|
|
1518
1628
|
|
|
1519
1629
|
const scrollContainer = mount.querySelector<HTMLElement>("#persona-scroll-container");
|
|
1630
|
+
// Earlier history fills 0..100; the new question spans 100..1082, more
|
|
1631
|
+
// than twice the 400px viewport.
|
|
1520
1632
|
const metrics = installScrollMetrics(scrollContainer!, {
|
|
1521
|
-
scrollHeight:
|
|
1633
|
+
scrollHeight: 1100,
|
|
1522
1634
|
clientHeight: 400
|
|
1523
1635
|
});
|
|
1524
1636
|
|
|
1525
1637
|
emitStreamingStatus(controller);
|
|
1526
|
-
emitUserMessage(controller, "user-
|
|
1638
|
+
emitUserMessage(controller, "user-tall", "Very long question ".repeat(200));
|
|
1527
1639
|
const bubble = scrollContainer!.querySelector<HTMLElement>(
|
|
1528
|
-
'[data-message-id="user-
|
|
1640
|
+
'[data-message-id="user-tall"]'
|
|
1529
1641
|
);
|
|
1530
1642
|
Object.defineProperty(bubble!, "offsetTop", { value: 100 });
|
|
1643
|
+
Object.defineProperty(bubble!, "offsetHeight", { value: 982 });
|
|
1531
1644
|
raf.flush();
|
|
1532
1645
|
expect(metrics.getScrollTop()).toBe(84);
|
|
1533
1646
|
|
|
1534
|
-
// The reply's first block starts
|
|
1535
|
-
//
|
|
1536
|
-
//
|
|
1537
|
-
// the stranded case the handoff exists for (tall send / tool prelude).
|
|
1647
|
+
// The reply's first block starts 1000px below the question's top, entirely
|
|
1648
|
+
// below the fold. The question keeps the anchor: the reader can finish
|
|
1649
|
+
// reading it, and the jump control says there is output below.
|
|
1538
1650
|
emitStreamingMessage(controller, "Streaming response");
|
|
1539
|
-
// Streaming updates render on the next animation frame.
|
|
1540
1651
|
raf.step(1);
|
|
1541
1652
|
const streamBubble = scrollContainer!.querySelector<HTMLElement>(
|
|
1542
1653
|
`[data-message-id="${STREAM_MESSAGE_ID}"]`
|
|
1543
1654
|
);
|
|
1544
|
-
Object.defineProperty(streamBubble!, "offsetTop", { value:
|
|
1545
|
-
|
|
1655
|
+
Object.defineProperty(streamBubble!, "offsetTop", { value: 1100 });
|
|
1656
|
+
for (const [height, text] of [
|
|
1657
|
+
[1300, "Streaming response, longer"],
|
|
1658
|
+
[1600, "Streaming response, longer still"],
|
|
1659
|
+
[2000, "Streaming response, longer still, with more detail"]
|
|
1660
|
+
] as const) {
|
|
1661
|
+
metrics.setScrollHeight(height);
|
|
1662
|
+
emitStreamingMessage(controller, text);
|
|
1663
|
+
raf.flush();
|
|
1664
|
+
resize.trigger();
|
|
1665
|
+
expect(metrics.getScrollTop()).toBe(84);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
const jump = getScrollToBottomButton(mount)!;
|
|
1669
|
+
expect(jump.style.display).toBe("");
|
|
1670
|
+
expect(jump.getAttribute("data-persona-scroll-to-bottom-visible")).toBe("true");
|
|
1671
|
+
|
|
1672
|
+
// Completion does not hand off either.
|
|
1673
|
+
controller.injectTestMessage({
|
|
1674
|
+
type: "message",
|
|
1675
|
+
message: {
|
|
1676
|
+
id: STREAM_MESSAGE_ID,
|
|
1677
|
+
role: "assistant",
|
|
1678
|
+
content: "Streaming response, complete",
|
|
1679
|
+
createdAt: STREAM_CREATED_AT,
|
|
1680
|
+
streaming: false
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
controller.injectTestMessage({ type: "status", status: "idle" });
|
|
1684
|
+
raf.flush();
|
|
1685
|
+
expect(metrics.getScrollTop()).toBe(84);
|
|
1686
|
+
|
|
1687
|
+
// The jump control reveals the answer and releases the anchor.
|
|
1688
|
+
jump.click();
|
|
1689
|
+
raf.flush();
|
|
1690
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
1691
|
+
const releasedAt = metrics.getScrollTop();
|
|
1692
|
+
|
|
1693
|
+
// Later renders and resizes never pull the reader back to the question.
|
|
1694
|
+
emitStreamingStatus(controller);
|
|
1695
|
+
controller.injectTestMessage({
|
|
1696
|
+
type: "message",
|
|
1697
|
+
message: {
|
|
1698
|
+
id: "ast-follow-up",
|
|
1699
|
+
role: "assistant",
|
|
1700
|
+
content: "A follow-up block",
|
|
1701
|
+
createdAt: STREAM_CREATED_AT,
|
|
1702
|
+
streaming: true
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
metrics.setScrollHeight(2300);
|
|
1706
|
+
raf.flush();
|
|
1707
|
+
resize.trigger();
|
|
1708
|
+
controller.injectTestMessage({ type: "status", status: "idle" });
|
|
1546
1709
|
raf.flush();
|
|
1547
|
-
expect(metrics.getScrollTop()).toBe(
|
|
1710
|
+
expect(metrics.getScrollTop()).toBe(releasedAt);
|
|
1548
1711
|
|
|
1549
1712
|
controller.destroy();
|
|
1550
1713
|
});
|