@elabs-ai/components-ai 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -11
- package/dist/{_audio-player-media-chrome-KA5DY54G.js → _audio-player-media-chrome-T3XVXWRZ.js} +8 -4
- package/dist/_audio-player-media-chrome-T3XVXWRZ.js.map +1 -0
- package/dist/{_flow-boundary-D63PJ65S.js → _flow-boundary-SHNWLQG5.js} +32 -43
- package/dist/_flow-boundary-SHNWLQG5.js.map +1 -0
- package/dist/{_persona-rive-RFR2EUWP.js → _persona-rive-JEG44YHX.js} +9 -5
- package/dist/_persona-rive-JEG44YHX.js.map +1 -0
- package/dist/index.d.ts +1109 -202
- package/dist/index.js +4867 -2878
- package/dist/index.js.map +1 -1
- package/package.json +31 -16
- package/src/_audio-player-media-chrome.tsx +102 -15
- package/src/_flow-boundary.tsx +68 -49
- package/src/_lazy-boundary-conformance.ts +38 -0
- package/src/_lazy-engine-boundary.tsx +61 -0
- package/src/_lazy-mermaid-absent.test.ts +53 -0
- package/src/_lazy-mermaid.test.ts +15 -0
- package/src/_lazy-mermaid.ts +24 -1
- package/src/_mermaid-error-panel.test.tsx +50 -0
- package/src/_mermaid-error-panel.tsx +66 -0
- package/src/_persona-rive.tsx +62 -10
- package/src/_streamdown-i18n.ts +21 -1
- package/src/_streamdown-safety.ts +170 -0
- package/src/agent-event.stories.tsx +97 -0
- package/src/agent-event.test.tsx +145 -0
- package/src/agent-event.tsx +187 -0
- package/src/agent-timeline.stories.tsx +9 -1
- package/src/agent.test.tsx +47 -0
- package/src/agent.tsx +28 -14
- package/src/agentic-workspace.stories.tsx +27 -20
- package/src/artifact.tsx +15 -12
- package/src/audio-player.test.tsx +78 -0
- package/src/audio-player.tsx +243 -56
- package/src/audio-visualizer.stories.tsx +126 -0
- package/src/audio-visualizer.test.tsx +438 -0
- package/src/audio-visualizer.tsx +367 -0
- package/src/canvas.stories.tsx +150 -1
- package/src/chat-shell.stories.tsx +18 -3
- package/src/chat.stories.tsx +16 -2
- package/src/code-block.stories.tsx +9 -1
- package/src/composer.stories.tsx +372 -9
- package/src/composer.test.tsx +357 -6
- package/src/composer.tsx +217 -35
- package/src/confirmation.stories.tsx +72 -1
- package/src/confirmation.test.tsx +216 -2
- package/src/confirmation.tsx +263 -3
- package/src/context-panel.stories.tsx +9 -1
- package/src/context-panel.tsx +2 -1
- package/src/conversation.stories.tsx +63 -2
- package/src/conversation.test.tsx +13 -0
- package/src/conversation.tsx +20 -2
- package/src/diff-view.stories.tsx +196 -0
- package/src/diff-view.test.tsx +188 -0
- package/src/diff-view.tsx +642 -0
- package/src/gallery.tsx +4 -2
- package/src/index.ts +14 -4
- package/src/jsx-preview.stories.tsx +2 -2
- package/src/markdown-view.stories.tsx +92 -1
- package/src/markdown-view.test.tsx +232 -1
- package/src/markdown-view.tsx +150 -6
- package/src/message-compare.stories.tsx +175 -0
- package/src/message-compare.test.tsx +207 -0
- package/src/message-compare.tsx +453 -0
- package/src/message-form.stories.tsx +29 -1
- package/src/message.stories.tsx +9 -1
- package/src/message.test.tsx +176 -0
- package/src/message.tsx +90 -4
- package/src/microcopy.test.tsx +40 -0
- package/src/{model-selector.stories.tsx → model-provider-logo.stories.tsx} +17 -8
- package/src/{model-selector.test.tsx → model-provider-logo.test.tsx} +10 -10
- package/src/model-provider-logo.tsx +149 -0
- package/src/permission-mode-select.stories.tsx +82 -0
- package/src/permission-mode-select.test.tsx +100 -0
- package/src/permission-mode-select.tsx +137 -0
- package/src/persona-missing-peer.test.tsx +54 -0
- package/src/persona.tsx +68 -22
- package/src/plan.stories.tsx +166 -0
- package/src/plan.test.tsx +267 -0
- package/src/plan.tsx +182 -20
- package/src/prompt-input-effort.stories.tsx +123 -0
- package/src/prompt-input-effort.test.tsx +83 -0
- package/src/prompt-input-effort.tsx +136 -0
- package/src/prompt-input-mode.stories.tsx +108 -0
- package/src/prompt-input-mode.test.tsx +99 -0
- package/src/prompt-input-mode.tsx +169 -0
- package/src/prompt-input-slash.stories.tsx +211 -0
- package/src/prompt-input-slash.test.tsx +262 -0
- package/src/prompt-input-slash.tsx +541 -0
- package/src/prompt-input.stories.tsx +2 -2
- package/src/reasoning.tsx +27 -13
- package/src/sandbox.stories.tsx +9 -1
- package/src/schema-display.tsx +5 -2
- package/src/selection-toolbar.stories.tsx +9 -1
- package/src/session-header.stories.tsx +128 -0
- package/src/session-header.test.tsx +138 -0
- package/src/session-header.tsx +243 -0
- package/src/session-status-bar.stories.tsx +73 -0
- package/src/session-status-bar.test.tsx +94 -0
- package/src/session-status-bar.tsx +165 -0
- package/src/snippet.stories.tsx +9 -1
- package/src/stack-trace.tsx +6 -2
- package/src/streamdown-i18n.test.tsx +1 -1
- package/src/task.stories.tsx +13 -3
- package/src/templates-ai-assistant.stories.tsx +21 -1
- package/src/token-usage.stories.tsx +24 -0
- package/src/token-usage.test.tsx +92 -0
- package/src/{context.tsx → token-usage.tsx} +67 -53
- package/src/tool-result-card.stories.tsx +9 -1
- package/src/tool.stories.tsx +13 -3
- package/src/tool.tsx +15 -8
- package/src/turn-status.stories.tsx +124 -0
- package/src/turn-status.test.tsx +74 -0
- package/src/turn-status.tsx +174 -0
- package/src/use-audio-level.ts +104 -0
- package/src/web-preview.tsx +8 -3
- package/dist/_audio-player-media-chrome-KA5DY54G.js.map +0 -1
- package/dist/_flow-boundary-D63PJ65S.js.map +0 -1
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js +0 -11
- package/dist/_interactive-terminal-xterm-ATJ5EW3G.js.map +0 -1
- package/dist/_persona-rive-RFR2EUWP.js.map +0 -1
- package/src/_interactive-terminal-xterm.ts +0 -32
- package/src/blocks-ai-composer.stories.tsx +0 -83
- package/src/context.stories.tsx +0 -16
- package/src/interactive-terminal.stories.tsx +0 -165
- package/src/interactive-terminal.test.tsx +0 -448
- package/src/interactive-terminal.tsx +0 -444
- package/src/model-selector.tsx +0 -225
- package/src/terminal.tsx +0 -244
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { render, screen, within } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { type DiffLine } from "@elabs-ai/components-ui";
|
|
5
|
+
import { DiffView } from "./diff-view";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* No `LocaleProvider` wrapper: `useLocale()` falls back to the shipped English
|
|
9
|
+
* defaults, so these assertions prove the real `ai.diffView.*` catalogue in
|
|
10
|
+
* `packages/ui/src/components/locale-provider/messages.ts` — not a local stub
|
|
11
|
+
* that would keep passing if the central entry were missing or misspelled.
|
|
12
|
+
*/
|
|
13
|
+
function renderDiffView(ui: React.ReactElement) {
|
|
14
|
+
return render(ui);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MIXED_LINES: DiffLine[] = [
|
|
18
|
+
{ type: "hunk", text: "@@ -1,3 +1,4 @@" },
|
|
19
|
+
{ type: "context", oldNumber: 1, newNumber: 1, text: "function add(a, b) {" },
|
|
20
|
+
{ type: "del", oldNumber: 2, text: " return a - b; // bug" },
|
|
21
|
+
{ type: "add", newNumber: 2, text: " return a + b;" },
|
|
22
|
+
{ type: "add", newNumber: 3, text: " // fixed" },
|
|
23
|
+
{ type: "context", oldNumber: 3, newNumber: 4, text: "}" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
describe("DiffView — gutter correctness (the off-by-one this model exists to prevent)", () => {
|
|
27
|
+
it("shows an add row's new number only, and a del row's old number only", () => {
|
|
28
|
+
const { container } = renderDiffView(<DiffView lines={MIXED_LINES} />);
|
|
29
|
+
const rows = container.querySelectorAll('[data-slot="diff-view-row"][data-diff-type]');
|
|
30
|
+
|
|
31
|
+
const del = Array.from(rows).find((r) => r.getAttribute("data-diff-type") === "del")!;
|
|
32
|
+
expect(within(del as HTMLElement).getByText("2")).toBeInTheDocument();
|
|
33
|
+
expect(del.querySelector('[data-slot="diff-view-gutter-old"]')?.textContent).toBe("2");
|
|
34
|
+
expect(del.querySelector('[data-slot="diff-view-gutter-new"]')?.textContent).toBe("");
|
|
35
|
+
|
|
36
|
+
const add = Array.from(rows).find(
|
|
37
|
+
(r) => r.getAttribute("data-diff-type") === "add" && r.textContent?.includes("fixed"),
|
|
38
|
+
)!;
|
|
39
|
+
expect(add.querySelector('[data-slot="diff-view-gutter-new"]')?.textContent).toBe("3");
|
|
40
|
+
expect(add.querySelector('[data-slot="diff-view-gutter-old"]')?.textContent).toBe("");
|
|
41
|
+
|
|
42
|
+
const context = Array.from(rows).find((r) => r.getAttribute("data-diff-type") === "context")!;
|
|
43
|
+
expect(context.querySelector('[data-slot="diff-view-gutter-old"]')?.textContent).toBe("1");
|
|
44
|
+
expect(context.querySelector('[data-slot="diff-view-gutter-new"]')?.textContent).toBe("1");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("DiffView — colour is never the only channel (accessibility.md)", () => {
|
|
49
|
+
it("gives add and del rows DIFFERENT accessible text and a different marker glyph — not merely different classes", () => {
|
|
50
|
+
const { container } = renderDiffView(
|
|
51
|
+
<DiffView
|
|
52
|
+
lines={[
|
|
53
|
+
{ type: "del", oldNumber: 1, text: "old line" },
|
|
54
|
+
{ type: "add", newNumber: 1, text: "new line" },
|
|
55
|
+
]}
|
|
56
|
+
/>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Accessible text: the sr-only polarity word actually differs.
|
|
60
|
+
expect(screen.getByText("Added:", { exact: false })).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByText("Removed:", { exact: false })).toBeInTheDocument();
|
|
62
|
+
|
|
63
|
+
// Marker glyph: a real, different rendered character per row — the shape
|
|
64
|
+
// channel a colour-blind or greyscale reader relies on.
|
|
65
|
+
const delMarker = container.querySelector(
|
|
66
|
+
'[data-diff-type="del"] [data-slot="diff-view-marker"]',
|
|
67
|
+
);
|
|
68
|
+
const addMarker = container.querySelector(
|
|
69
|
+
'[data-diff-type="add"] [data-slot="diff-view-marker"]',
|
|
70
|
+
);
|
|
71
|
+
expect(delMarker?.textContent).toBe("−");
|
|
72
|
+
expect(addMarker?.textContent).toBe("+");
|
|
73
|
+
expect(delMarker?.textContent).not.toBe(addMarker?.textContent);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("gives the del row's marker a distinct tone class from the add row's — but that alone is insufficient (locked above by text+glyph)", () => {
|
|
77
|
+
const { container } = renderDiffView(
|
|
78
|
+
<DiffView
|
|
79
|
+
lines={[
|
|
80
|
+
{ type: "del", oldNumber: 1, text: "old line" },
|
|
81
|
+
{ type: "add", newNumber: 1, text: "new line" },
|
|
82
|
+
]}
|
|
83
|
+
/>,
|
|
84
|
+
);
|
|
85
|
+
const delMarker = container.querySelector(
|
|
86
|
+
'[data-diff-type="del"] [data-slot="diff-view-marker"]',
|
|
87
|
+
);
|
|
88
|
+
const addMarker = container.querySelector(
|
|
89
|
+
'[data-diff-type="add"] [data-slot="diff-view-marker"]',
|
|
90
|
+
);
|
|
91
|
+
expect(delMarker?.className).not.toBe(addMarker?.className);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("DiffView — loading vocabulary (loading-states.md)", () => {
|
|
96
|
+
it("loading renders layout-shaped skeleton rows sharing the real row's height class (no shift on settle)", () => {
|
|
97
|
+
const { container: loadingContainer } = renderDiffView(<DiffView lines={[]} loading />);
|
|
98
|
+
const { container: settledContainer } = renderDiffView(<DiffView lines={MIXED_LINES} />);
|
|
99
|
+
|
|
100
|
+
const skeletonRow = loadingContainer.querySelector('[data-slot="diff-view-row"]');
|
|
101
|
+
const realRow = settledContainer.querySelector(
|
|
102
|
+
'[data-slot="diff-view-row"][data-diff-type="add"]',
|
|
103
|
+
);
|
|
104
|
+
expect(skeletonRow).toBeTruthy();
|
|
105
|
+
expect(realRow).toBeTruthy();
|
|
106
|
+
// Same grid template + padding class => same row height; only the tint axis differs.
|
|
107
|
+
expect(skeletonRow?.className).toContain("grid-cols-[2.75rem_2.75rem_1.25rem_1fr]");
|
|
108
|
+
expect(realRow?.className).toContain("grid-cols-[2.75rem_2.75rem_1.25rem_1fr]");
|
|
109
|
+
|
|
110
|
+
expect(screen.getByText("Loading diff…")).toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("isStreaming with a truncated final line renders no error surface", () => {
|
|
114
|
+
const truncated: DiffLine[] = [
|
|
115
|
+
{ type: "context", oldNumber: 1, newNumber: 1, text: "const partial = {" },
|
|
116
|
+
{ type: "add", newNumber: 2, text: ' key: "unterminated' },
|
|
117
|
+
];
|
|
118
|
+
renderDiffView(<DiffView lines={truncated} isStreaming />);
|
|
119
|
+
|
|
120
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
121
|
+
expect(screen.getByText("Generating…")).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("DiffView — variants", () => {
|
|
126
|
+
it('renders one row per line for variant="inline" (default)', () => {
|
|
127
|
+
const { container } = renderDiffView(<DiffView lines={MIXED_LINES} />);
|
|
128
|
+
expect(container.querySelectorAll('[data-slot="diff-view-row"]')).toHaveLength(
|
|
129
|
+
MIXED_LINES.length,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('renders two aligned columns for variant="split"', () => {
|
|
134
|
+
const { container } = renderDiffView(
|
|
135
|
+
<DiffView
|
|
136
|
+
lines={[
|
|
137
|
+
{ type: "del", oldNumber: 1, text: "old" },
|
|
138
|
+
{ type: "add", newNumber: 1, text: "new" },
|
|
139
|
+
]}
|
|
140
|
+
variant="split"
|
|
141
|
+
/>,
|
|
142
|
+
);
|
|
143
|
+
const rows = container.querySelectorAll('[data-slot="diff-view-row"][data-diff-type]');
|
|
144
|
+
expect(rows).toHaveLength(2);
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
// Each row is split into exactly two side-by-side panes.
|
|
147
|
+
expect(row.children).toHaveLength(2);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("DiffView — pager (absorbs CodexDiff)", () => {
|
|
153
|
+
it("renders a scroll-position indicator, a key legend, and a named region", () => {
|
|
154
|
+
renderDiffView(<DiffView lines={MIXED_LINES} pager file="src/add.ts" />);
|
|
155
|
+
|
|
156
|
+
const region = screen.getByRole("region", { name: "Code diff: src/add.ts" });
|
|
157
|
+
expect(region).toBeInTheDocument();
|
|
158
|
+
expect(
|
|
159
|
+
within(region).getByText("Arrow keys scroll, Page Up/Down page, Home/End jump"),
|
|
160
|
+
).toBeInTheDocument();
|
|
161
|
+
expect(within(region).getByText("0%")).toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("DiffView — contextLines collapsing", () => {
|
|
166
|
+
const longContext: DiffLine[] = Array.from({ length: 10 }, (_, i) => ({
|
|
167
|
+
type: "context" as const,
|
|
168
|
+
oldNumber: i + 1,
|
|
169
|
+
newNumber: i + 1,
|
|
170
|
+
text: `line ${i + 1}`,
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
it("collapses a long context run behind a show-more control that restores it", async () => {
|
|
174
|
+
const user = userEvent.setup();
|
|
175
|
+
const { container } = renderDiffView(<DiffView lines={longContext} contextLines={4} />);
|
|
176
|
+
|
|
177
|
+
expect(screen.queryByText("line 5")).not.toBeInTheDocument();
|
|
178
|
+
const control = screen.getByRole("button", { name: /more line/ });
|
|
179
|
+
expect(control).toBeInTheDocument();
|
|
180
|
+
|
|
181
|
+
await user.click(control);
|
|
182
|
+
|
|
183
|
+
expect(screen.getByText("line 5")).toBeInTheDocument();
|
|
184
|
+
expect(container.querySelectorAll('[data-slot="diff-view-row"]')).toHaveLength(
|
|
185
|
+
longContext.length,
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|