@akiojin/gwt 4.11.6 → 4.12.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/bin/gwt.js +36 -10
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +81 -24
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/App.solid.d.ts.map +1 -1
- package/dist/cli/ui/App.solid.js +280 -50
- package/dist/cli/ui/App.solid.js.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
- package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.js +2 -1
- package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.js +67 -13
- package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.d.ts +5 -0
- package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.js +50 -70
- package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
- package/dist/cli/ui/core/theme.d.ts +9 -0
- package/dist/cli/ui/core/theme.d.ts.map +1 -1
- package/dist/cli/ui/core/theme.js +21 -0
- package/dist/cli/ui/core/theme.js.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
- package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
- package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
- package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
- package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -0
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +29 -7
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts +14 -0
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +61 -3
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/cli/ui/utils/installedVersionCache.d.ts +33 -0
- package/dist/cli/ui/utils/installedVersionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/installedVersionCache.js +59 -0
- package/dist/cli/ui/utils/installedVersionCache.js.map +1 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js +16 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/cli/ui/utils/versionCache.d.ts +37 -0
- package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionCache.js +70 -0
- package/dist/cli/ui/utils/versionCache.js.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.js +89 -0
- package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
- package/dist/codex.d.ts +1 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +95 -25
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -1
- package/dist/config/index.js.map +1 -1
- package/dist/gemini.d.ts +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +36 -3
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +43 -8
- package/dist/launcher.js.map +1 -1
- package/dist/logging/agentOutput.d.ts +21 -0
- package/dist/logging/agentOutput.d.ts.map +1 -0
- package/dist/logging/agentOutput.js +164 -0
- package/dist/logging/agentOutput.js.map +1 -0
- package/dist/logging/formatter.d.ts.map +1 -1
- package/dist/logging/formatter.js +18 -4
- package/dist/logging/formatter.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +2 -0
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/reader.d.ts +22 -0
- package/dist/logging/reader.d.ts.map +1 -1
- package/dist/logging/reader.js +116 -1
- package/dist/logging/reader.js.map +1 -1
- package/dist/opentui/index.solid.js +2575 -888
- package/dist/services/codingAgentResolver.d.ts.map +1 -1
- package/dist/services/codingAgentResolver.js +8 -6
- package/dist/services/codingAgentResolver.js.map +1 -1
- package/dist/services/dependency-installer.js +2 -2
- package/dist/services/dependency-installer.js.map +1 -1
- package/dist/shared/codingAgentConstants.d.ts +3 -0
- package/dist/shared/codingAgentConstants.d.ts.map +1 -1
- package/dist/shared/codingAgentConstants.js +66 -0
- package/dist/shared/codingAgentConstants.js.map +1 -1
- package/dist/utils/bun-runtime.d.ts +12 -0
- package/dist/utils/bun-runtime.d.ts.map +1 -0
- package/dist/utils/bun-runtime.js +13 -0
- package/dist/utils/bun-runtime.js.map +1 -0
- package/dist/utils/session/common.d.ts +8 -0
- package/dist/utils/session/common.d.ts.map +1 -1
- package/dist/utils/session/common.js +22 -0
- package/dist/utils/session/common.js.map +1 -1
- package/dist/utils/session/parsers/claude.d.ts +10 -4
- package/dist/utils/session/parsers/claude.d.ts.map +1 -1
- package/dist/utils/session/parsers/claude.js +64 -18
- package/dist/utils/session/parsers/claude.js.map +1 -1
- package/dist/utils/session/parsers/codex.d.ts.map +1 -1
- package/dist/utils/session/parsers/codex.js +47 -28
- package/dist/utils/session/parsers/codex.js.map +1 -1
- package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
- package/dist/utils/session/parsers/gemini.js +43 -6
- package/dist/utils/session/parsers/gemini.js.map +1 -1
- package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
- package/dist/utils/session/parsers/opencode.js +43 -6
- package/dist/utils/session/parsers/opencode.js.map +1 -1
- package/dist/utils/session/types.d.ts +7 -0
- package/dist/utils/session/types.d.ts.map +1 -1
- package/dist/web/client/src/components/ui/alert.d.ts +1 -1
- package/dist/worktree.d.ts +4 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +21 -15
- package/dist/worktree.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +99 -28
- package/src/cli/ui/App.solid.tsx +373 -51
- package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +921 -1
- package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
- package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
- package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
- package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
- package/src/cli/ui/__tests__/solid/components/WizardController.test.tsx +71 -0
- package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +95 -2
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
- package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
- package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
- package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
- package/src/cli/ui/components/solid/WizardController.tsx +85 -12
- package/src/cli/ui/components/solid/WizardSteps.tsx +78 -90
- package/src/cli/ui/core/theme.ts +32 -0
- package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
- package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
- package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
- package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
- package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
- package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
- package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
- package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
- package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
- package/src/cli/ui/types.ts +1 -0
- package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
- package/src/cli/ui/utils/__tests__/installedVersionCache.test.ts +46 -0
- package/src/cli/ui/utils/branchFormatter.ts +35 -7
- package/src/cli/ui/utils/continueSession.ts +90 -3
- package/src/cli/ui/utils/installedVersionCache.ts +84 -0
- package/src/cli/ui/utils/modelOptions.test.ts +6 -0
- package/src/cli/ui/utils/modelOptions.ts +16 -0
- package/src/cli/ui/utils/versionCache.ts +93 -0
- package/src/cli/ui/utils/versionFetcher.ts +120 -0
- package/src/codex.ts +124 -26
- package/src/config/__tests__/saveSession.test.ts +2 -2
- package/src/config/index.ts +11 -1
- package/src/gemini.ts +50 -4
- package/src/index.test.ts +16 -10
- package/src/index.ts +41 -1
- package/src/launcher.ts +49 -8
- package/src/logging/agentOutput.ts +216 -0
- package/src/logging/formatter.ts +23 -4
- package/src/logging/logger.ts +2 -0
- package/src/logging/reader.ts +165 -1
- package/src/services/__tests__/BatchMergeService.test.ts +34 -14
- package/src/services/codingAgentResolver.ts +12 -5
- package/src/services/dependency-installer.ts +2 -2
- package/src/shared/codingAgentConstants.ts +73 -0
- package/src/utils/bun-runtime.ts +29 -0
- package/src/utils/session/common.ts +28 -0
- package/src/utils/session/parsers/claude.ts +79 -29
- package/src/utils/session/parsers/codex.ts +49 -26
- package/src/utils/session/parsers/gemini.ts +46 -5
- package/src/utils/session/parsers/opencode.ts +46 -5
- package/src/utils/session/types.ts +4 -0
- package/src/worktree.ts +28 -15
|
@@ -103,7 +103,7 @@ describe("BranchListScreen icons", () => {
|
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
105
|
const frame = testSetup.captureCharFrame();
|
|
106
|
-
expect(frame).toMatch(/\[\*\] w
|
|
106
|
+
expect(frame).toMatch(/\[\*\] w o feature\/active-clean/);
|
|
107
107
|
expect(frame).toContain("[ ] . ! feature/no-worktree");
|
|
108
108
|
expect(frame).not.toContain(">[*]");
|
|
109
109
|
expect(frame).not.toContain(">[ ]");
|
|
@@ -112,7 +112,7 @@ describe("BranchListScreen icons", () => {
|
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
it("shows spinner
|
|
115
|
+
it("shows spinner for pending safety checks and blank icon for remote branches", async () => {
|
|
116
116
|
const branches = [
|
|
117
117
|
createBranch({
|
|
118
118
|
name: "feature/loading",
|
|
@@ -146,7 +146,7 @@ describe("BranchListScreen icons", () => {
|
|
|
146
146
|
indicators: {},
|
|
147
147
|
footerMessage: null,
|
|
148
148
|
inputLocked: false,
|
|
149
|
-
|
|
149
|
+
safetyPendingBranches: new Set(["feature/loading"]),
|
|
150
150
|
},
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -183,8 +183,6 @@ describe("BranchListScreen worktree footer", () => {
|
|
|
183
183
|
it("falls back to working directory for current branch without worktree", async () => {
|
|
184
184
|
const branch = createBranch({
|
|
185
185
|
isCurrent: true,
|
|
186
|
-
worktree: undefined,
|
|
187
|
-
worktreeStatus: undefined,
|
|
188
186
|
});
|
|
189
187
|
const testSetup = await renderBranchList({
|
|
190
188
|
branches: [branch],
|
|
@@ -241,3 +239,105 @@ describe("BranchListScreen shortcut hints", () => {
|
|
|
241
239
|
}
|
|
242
240
|
});
|
|
243
241
|
});
|
|
242
|
+
|
|
243
|
+
describe("BranchListScreen status legend", () => {
|
|
244
|
+
it("shows legend for uncommitted/unpushed/unmerged indicators", async () => {
|
|
245
|
+
const branch = createBranch({
|
|
246
|
+
name: "feature/legend",
|
|
247
|
+
label: "feature/legend",
|
|
248
|
+
value: "feature/legend",
|
|
249
|
+
});
|
|
250
|
+
const testSetup = await renderBranchList({
|
|
251
|
+
branches: [branch],
|
|
252
|
+
stats: makeStats({ localCount: 1 }),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const frame = testSetup.captureCharFrame();
|
|
257
|
+
expect(frame).toContain(
|
|
258
|
+
"Legend: o Safe ! Uncommitted ! Unpushed * Unmerged",
|
|
259
|
+
);
|
|
260
|
+
} finally {
|
|
261
|
+
testSetup.renderer.destroy();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("BranchListScreen cursor position stability (FR-037a)", () => {
|
|
267
|
+
it("preserves cursor position when safety check completes", async () => {
|
|
268
|
+
const { createSignal } = await import("solid-js");
|
|
269
|
+
const branches = [
|
|
270
|
+
createBranch({
|
|
271
|
+
name: "feature/first",
|
|
272
|
+
label: "feature/first",
|
|
273
|
+
value: "feature/first",
|
|
274
|
+
worktreeStatus: "active",
|
|
275
|
+
}),
|
|
276
|
+
createBranch({
|
|
277
|
+
name: "feature/second",
|
|
278
|
+
label: "feature/second",
|
|
279
|
+
value: "feature/second",
|
|
280
|
+
worktreeStatus: "active",
|
|
281
|
+
}),
|
|
282
|
+
createBranch({
|
|
283
|
+
name: "feature/third",
|
|
284
|
+
label: "feature/third",
|
|
285
|
+
value: "feature/third",
|
|
286
|
+
worktreeStatus: "active",
|
|
287
|
+
}),
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
// safetyPendingBranchesを動的に変更するためのシグナル
|
|
291
|
+
const [safetyPending, setSafetyPending] = createSignal<Set<string>>(
|
|
292
|
+
new Set(["feature/first", "feature/second", "feature/third"]),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const testSetup = await testRender(
|
|
296
|
+
() => (
|
|
297
|
+
<BranchListScreen
|
|
298
|
+
branches={branches}
|
|
299
|
+
stats={statsForBranches(branches)}
|
|
300
|
+
onSelect={() => {}}
|
|
301
|
+
cleanupUI={{
|
|
302
|
+
indicators: {},
|
|
303
|
+
footerMessage: null,
|
|
304
|
+
inputLocked: false,
|
|
305
|
+
safetyPendingBranches: safetyPending(),
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
),
|
|
309
|
+
{ width: 80, height: 24 },
|
|
310
|
+
);
|
|
311
|
+
await testSetup.renderOnce();
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
// 1. 初期状態ではカーソルは最初のブランチにある
|
|
315
|
+
let frame = testSetup.captureCharFrame();
|
|
316
|
+
// 最初のブランチがハイライトされている(選択されている)ことを確認
|
|
317
|
+
expect(frame).toContain("feature/first");
|
|
318
|
+
|
|
319
|
+
// 2. 下矢印キーでカーソルを2番目のブランチに移動
|
|
320
|
+
testSetup.mockInput.pressArrow("down");
|
|
321
|
+
await testSetup.renderOnce();
|
|
322
|
+
|
|
323
|
+
// 3. 安全状態確認が完了したことをシミュレート(pendingから削除)
|
|
324
|
+
setSafetyPending(new Set(["feature/second", "feature/third"]));
|
|
325
|
+
await testSetup.renderOnce();
|
|
326
|
+
|
|
327
|
+
// さらに別のブランチの安全状態確認が完了
|
|
328
|
+
setSafetyPending(new Set(["feature/third"]));
|
|
329
|
+
await testSetup.renderOnce();
|
|
330
|
+
|
|
331
|
+
// 4. カーソル位置が保持されていることを確認
|
|
332
|
+
// カーソルが2番目のブランチにあるので、下矢印でさらに移動できるはず
|
|
333
|
+
testSetup.mockInput.pressArrow("down");
|
|
334
|
+
await testSetup.renderOnce();
|
|
335
|
+
|
|
336
|
+
// 3番目のブランチに移動できていれば、カーソル位置は保持されていた
|
|
337
|
+
frame = testSetup.captureCharFrame();
|
|
338
|
+
expect(frame).toContain("feature/third");
|
|
339
|
+
} finally {
|
|
340
|
+
testSetup.renderer.destroy();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import { describe, expect, it } from "bun:test";
|
|
3
|
+
import { testRender } from "@opentui/solid";
|
|
4
|
+
import { TestRecorder } from "@opentui/core/testing";
|
|
5
|
+
import { ConfirmScreen } from "../../screens/solid/ConfirmScreen.js";
|
|
6
|
+
|
|
7
|
+
const getBgColor = (bg: Float32Array, width: number, x: number, y: number) => {
|
|
8
|
+
const index = (y * width + x) * 4;
|
|
9
|
+
return [
|
|
10
|
+
bg[index] ?? 0,
|
|
11
|
+
bg[index + 1] ?? 0,
|
|
12
|
+
bg[index + 2] ?? 0,
|
|
13
|
+
bg[index + 3] ?? 0,
|
|
14
|
+
];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const isSameColor = (a: number[], b: number[], epsilon = 0.001) =>
|
|
18
|
+
a.length === b.length &&
|
|
19
|
+
a.every((value, i) => Math.abs(value - b[i]) < epsilon);
|
|
20
|
+
|
|
21
|
+
describe("ConfirmScreen selection width", () => {
|
|
22
|
+
it("limits highlight to provided width", async () => {
|
|
23
|
+
const width = 20;
|
|
24
|
+
const height = 5;
|
|
25
|
+
const selectionWidth = 8;
|
|
26
|
+
const testSetup = await testRender(
|
|
27
|
+
() => (
|
|
28
|
+
<ConfirmScreen
|
|
29
|
+
message="Confirm?"
|
|
30
|
+
onConfirm={() => {}}
|
|
31
|
+
yesLabel="OK"
|
|
32
|
+
noLabel="Cancel"
|
|
33
|
+
width={selectionWidth}
|
|
34
|
+
/>
|
|
35
|
+
),
|
|
36
|
+
{ width, height },
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const recorder = new TestRecorder(testSetup.renderer, {
|
|
40
|
+
recordBuffers: { bg: true },
|
|
41
|
+
});
|
|
42
|
+
recorder.rec();
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await testSetup.renderOnce();
|
|
46
|
+
recorder.stop();
|
|
47
|
+
|
|
48
|
+
const frame = recorder.recordedFrames.at(-1);
|
|
49
|
+
if (!frame?.buffers?.bg) {
|
|
50
|
+
throw new Error("No background buffer recorded");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bg = frame.buffers.bg;
|
|
54
|
+
const defaultColor = getBgColor(bg, width, 0, 0);
|
|
55
|
+
const targetRow = 1;
|
|
56
|
+
let highlightLength = 0;
|
|
57
|
+
|
|
58
|
+
for (let x = 0; x < width; x += 1) {
|
|
59
|
+
const color = getBgColor(bg, width, x, targetRow);
|
|
60
|
+
if (!isSameColor(color, defaultColor)) {
|
|
61
|
+
highlightLength += 1;
|
|
62
|
+
} else {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
expect(highlightLength).toBe(selectionWidth);
|
|
68
|
+
|
|
69
|
+
for (let x = selectionWidth; x < width; x += 1) {
|
|
70
|
+
const color = getBgColor(bg, width, x, targetRow);
|
|
71
|
+
expect(isSameColor(color, defaultColor)).toBe(true);
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
testSetup.renderer.destroy();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
import { describe, expect, it, mock } from "bun:test";
|
|
3
|
+
import { testRender } from "@opentui/solid";
|
|
4
|
+
import { parseColor } from "@opentui/core";
|
|
5
|
+
import { createSignal } from "solid-js";
|
|
6
|
+
import type { FormattedLogEntry } from "../../../../logging/formatter.js";
|
|
7
|
+
|
|
8
|
+
const makeEntry = (
|
|
9
|
+
message: string,
|
|
10
|
+
overrides: Partial<FormattedLogEntry> = {},
|
|
11
|
+
): FormattedLogEntry => ({
|
|
12
|
+
id: "1",
|
|
13
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
14
|
+
timestamp: 0,
|
|
15
|
+
timeLabel: "00:00:00",
|
|
16
|
+
levelLabel: "INFO",
|
|
17
|
+
category: "cli",
|
|
18
|
+
message,
|
|
19
|
+
summary: `[00:00:00] [INFO] [cli] ${message}`,
|
|
20
|
+
json: JSON.stringify({ message }, null, 2),
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const findLine = (frame: string, needle: string) => {
|
|
25
|
+
const lines = frame.split("\n");
|
|
26
|
+
const index = lines.findIndex((line) => line.includes(needle));
|
|
27
|
+
if (index < 0) {
|
|
28
|
+
throw new Error(`Line not found: ${needle}`);
|
|
29
|
+
}
|
|
30
|
+
return { index, line: lines[index] ?? "" };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const readColorAt = (
|
|
34
|
+
buffer: Float32Array,
|
|
35
|
+
width: number,
|
|
36
|
+
row: number,
|
|
37
|
+
col: number,
|
|
38
|
+
) => {
|
|
39
|
+
const offset = (row * width + col) * 4;
|
|
40
|
+
return [
|
|
41
|
+
Math.round(buffer[offset] * 255),
|
|
42
|
+
Math.round(buffer[offset + 1] * 255),
|
|
43
|
+
Math.round(buffer[offset + 2] * 255),
|
|
44
|
+
Math.round(buffer[offset + 3] * 255),
|
|
45
|
+
] as const;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
describe("LogScreen", () => {
|
|
49
|
+
it("updates entries when data changes", async () => {
|
|
50
|
+
const [entries, setEntries] = createSignal<FormattedLogEntry[]>([]);
|
|
51
|
+
|
|
52
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
53
|
+
|
|
54
|
+
const testSetup = await testRender(
|
|
55
|
+
() => (
|
|
56
|
+
<LogScreen
|
|
57
|
+
entries={entries()}
|
|
58
|
+
branchLabel="feature/logs"
|
|
59
|
+
sourceLabel="/tmp/feature-logs"
|
|
60
|
+
onBack={() => {}}
|
|
61
|
+
onSelect={() => {}}
|
|
62
|
+
onCopy={() => {}}
|
|
63
|
+
/>
|
|
64
|
+
),
|
|
65
|
+
{ width: 80, height: 24 },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await testSetup.renderOnce();
|
|
70
|
+
let frame = testSetup.captureCharFrame();
|
|
71
|
+
expect(frame).toContain("Branch: feature/logs");
|
|
72
|
+
expect(frame).toContain("Source: /tmp/feature-logs");
|
|
73
|
+
expect(frame).toContain("No logs available.");
|
|
74
|
+
|
|
75
|
+
setEntries([makeEntry("Hello")]);
|
|
76
|
+
await testSetup.renderOnce();
|
|
77
|
+
frame = testSetup.captureCharFrame();
|
|
78
|
+
expect(frame).toContain("[00:00:00] [INFO ] [cli ] Hello");
|
|
79
|
+
} finally {
|
|
80
|
+
testSetup.renderer.destroy();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("filters entries by query and shows counts", async () => {
|
|
85
|
+
const entries = [
|
|
86
|
+
makeEntry("alpha", {
|
|
87
|
+
id: "1",
|
|
88
|
+
category: "cli",
|
|
89
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
90
|
+
}),
|
|
91
|
+
makeEntry("beta", {
|
|
92
|
+
id: "2",
|
|
93
|
+
category: "agent.stdout",
|
|
94
|
+
raw: {
|
|
95
|
+
level: 50,
|
|
96
|
+
time: "2026-01-08T00:00:01.000Z",
|
|
97
|
+
category: "agent.stdout",
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
103
|
+
|
|
104
|
+
const testSetup = await testRender(
|
|
105
|
+
() => (
|
|
106
|
+
<LogScreen
|
|
107
|
+
entries={entries}
|
|
108
|
+
onBack={() => {}}
|
|
109
|
+
onSelect={() => {}}
|
|
110
|
+
onCopy={() => {}}
|
|
111
|
+
/>
|
|
112
|
+
),
|
|
113
|
+
{ width: 80, height: 24 },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await testSetup.renderOnce();
|
|
118
|
+
let frame = testSetup.captureCharFrame();
|
|
119
|
+
expect(frame).toContain("alpha");
|
|
120
|
+
expect(frame).toContain("beta");
|
|
121
|
+
|
|
122
|
+
await testSetup.mockInput.typeText("f");
|
|
123
|
+
await testSetup.renderOnce();
|
|
124
|
+
|
|
125
|
+
await testSetup.mockInput.typeText("agent");
|
|
126
|
+
await testSetup.renderOnce();
|
|
127
|
+
testSetup.mockInput.pressEnter();
|
|
128
|
+
await testSetup.renderOnce();
|
|
129
|
+
|
|
130
|
+
frame = testSetup.captureCharFrame();
|
|
131
|
+
expect(frame).toContain("beta");
|
|
132
|
+
expect(frame).not.toContain("alpha");
|
|
133
|
+
expect(frame).toContain("Showing 1 of 2");
|
|
134
|
+
} finally {
|
|
135
|
+
testSetup.renderer.destroy();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("cycles level filter with v and hides lower levels", async () => {
|
|
140
|
+
const entries = [
|
|
141
|
+
makeEntry("info-log", {
|
|
142
|
+
id: "1",
|
|
143
|
+
levelLabel: "INFO",
|
|
144
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
145
|
+
}),
|
|
146
|
+
makeEntry("error-log", {
|
|
147
|
+
id: "2",
|
|
148
|
+
levelLabel: "ERROR",
|
|
149
|
+
raw: { level: 50, time: "2026-01-08T00:00:01.000Z", category: "cli" },
|
|
150
|
+
}),
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
154
|
+
|
|
155
|
+
const testSetup = await testRender(
|
|
156
|
+
() => (
|
|
157
|
+
<LogScreen
|
|
158
|
+
entries={entries}
|
|
159
|
+
onBack={() => {}}
|
|
160
|
+
onSelect={() => {}}
|
|
161
|
+
onCopy={() => {}}
|
|
162
|
+
/>
|
|
163
|
+
),
|
|
164
|
+
{ width: 80, height: 24 },
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await testSetup.renderOnce();
|
|
169
|
+
let frame = testSetup.captureCharFrame();
|
|
170
|
+
expect(frame).toContain("info-log");
|
|
171
|
+
expect(frame).toContain("error-log");
|
|
172
|
+
|
|
173
|
+
await testSetup.mockInput.typeText("v");
|
|
174
|
+
await testSetup.renderOnce();
|
|
175
|
+
await testSetup.mockInput.typeText("v");
|
|
176
|
+
await testSetup.renderOnce();
|
|
177
|
+
|
|
178
|
+
frame = testSetup.captureCharFrame();
|
|
179
|
+
expect(frame).toContain("error-log");
|
|
180
|
+
expect(frame).not.toContain("info-log");
|
|
181
|
+
expect(frame).toContain("Level: WARN+");
|
|
182
|
+
} finally {
|
|
183
|
+
testSetup.renderer.destroy();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("triggers reload/tail and truncates long lines", async () => {
|
|
188
|
+
const onReload = mock();
|
|
189
|
+
const onToggleTail = mock();
|
|
190
|
+
const longMessage =
|
|
191
|
+
"this-is-a-very-long-log-line-that-should-be-truncated-with-ellipsis";
|
|
192
|
+
const entries = [
|
|
193
|
+
makeEntry(longMessage, {
|
|
194
|
+
id: "1",
|
|
195
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
196
|
+
}),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
200
|
+
|
|
201
|
+
const testSetup = await testRender(
|
|
202
|
+
() => (
|
|
203
|
+
<LogScreen
|
|
204
|
+
entries={entries}
|
|
205
|
+
onBack={() => {}}
|
|
206
|
+
onSelect={() => {}}
|
|
207
|
+
onCopy={() => {}}
|
|
208
|
+
onReload={onReload}
|
|
209
|
+
onToggleTail={onToggleTail}
|
|
210
|
+
/>
|
|
211
|
+
),
|
|
212
|
+
{ width: 40, height: 16 },
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await testSetup.renderOnce();
|
|
217
|
+
|
|
218
|
+
await testSetup.mockInput.typeText("r");
|
|
219
|
+
await testSetup.renderOnce();
|
|
220
|
+
expect(onReload).toHaveBeenCalledTimes(1);
|
|
221
|
+
|
|
222
|
+
await testSetup.mockInput.typeText("t");
|
|
223
|
+
await testSetup.renderOnce();
|
|
224
|
+
expect(onToggleTail).toHaveBeenCalledTimes(1);
|
|
225
|
+
|
|
226
|
+
const frame = testSetup.captureCharFrame();
|
|
227
|
+
expect(frame).toContain("...");
|
|
228
|
+
expect(frame).not.toContain("Wrap:");
|
|
229
|
+
} finally {
|
|
230
|
+
testSetup.renderer.destroy();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("triggers reset and shows Reset in footer", async () => {
|
|
235
|
+
const onReset = mock();
|
|
236
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
237
|
+
|
|
238
|
+
const testSetup = await testRender(
|
|
239
|
+
() => (
|
|
240
|
+
<LogScreen
|
|
241
|
+
entries={[]}
|
|
242
|
+
onBack={() => {}}
|
|
243
|
+
onSelect={() => {}}
|
|
244
|
+
onCopy={() => {}}
|
|
245
|
+
onReset={onReset}
|
|
246
|
+
/>
|
|
247
|
+
),
|
|
248
|
+
{ width: 80, height: 16 },
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
await testSetup.renderOnce();
|
|
253
|
+
const frame = testSetup.captureCharFrame();
|
|
254
|
+
expect(frame).toContain("Reset");
|
|
255
|
+
|
|
256
|
+
await testSetup.mockInput.typeText("x");
|
|
257
|
+
await testSetup.renderOnce();
|
|
258
|
+
expect(onReset).toHaveBeenCalledTimes(1);
|
|
259
|
+
} finally {
|
|
260
|
+
testSetup.renderer.destroy();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("highlights the selected row with full-width cyan background", async () => {
|
|
265
|
+
const entries = [
|
|
266
|
+
makeEntry("alpha", {
|
|
267
|
+
id: "1",
|
|
268
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
269
|
+
}),
|
|
270
|
+
makeEntry("beta", {
|
|
271
|
+
id: "2",
|
|
272
|
+
raw: { level: 30, time: "2026-01-08T00:00:01.000Z", category: "cli" },
|
|
273
|
+
}),
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
277
|
+
|
|
278
|
+
const width = 60;
|
|
279
|
+
const testSetup = await testRender(
|
|
280
|
+
() => (
|
|
281
|
+
<LogScreen
|
|
282
|
+
entries={entries}
|
|
283
|
+
onBack={() => {}}
|
|
284
|
+
onSelect={() => {}}
|
|
285
|
+
onCopy={() => {}}
|
|
286
|
+
/>
|
|
287
|
+
),
|
|
288
|
+
{ width, height: 16 },
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await testSetup.renderOnce();
|
|
293
|
+
const frame = testSetup.captureCharFrame();
|
|
294
|
+
const { index: row } = findLine(frame, "alpha");
|
|
295
|
+
const buffers = testSetup.renderer.currentRenderBuffer.buffers;
|
|
296
|
+
const bg = readColorAt(buffers.bg, width, row, width - 1);
|
|
297
|
+
const fg = readColorAt(buffers.fg, width, row, width - 1);
|
|
298
|
+
|
|
299
|
+
expect(bg).toEqual(parseColor("cyan").toInts());
|
|
300
|
+
expect(fg).toEqual(parseColor("black").toInts());
|
|
301
|
+
} finally {
|
|
302
|
+
testSetup.renderer.destroy();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("colors the level column for non-selected rows", async () => {
|
|
307
|
+
const entries = [
|
|
308
|
+
makeEntry("first", {
|
|
309
|
+
id: "1",
|
|
310
|
+
levelLabel: "INFO",
|
|
311
|
+
raw: { level: 30, time: "2026-01-08T00:00:00.000Z", category: "cli" },
|
|
312
|
+
}),
|
|
313
|
+
makeEntry("second", {
|
|
314
|
+
id: "2",
|
|
315
|
+
levelLabel: "ERROR",
|
|
316
|
+
raw: { level: 50, time: "2026-01-08T00:00:01.000Z", category: "cli" },
|
|
317
|
+
}),
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
const { LogScreen } = await import("../../screens/solid/LogScreen.js");
|
|
321
|
+
|
|
322
|
+
const width = 80;
|
|
323
|
+
const testSetup = await testRender(
|
|
324
|
+
() => (
|
|
325
|
+
<LogScreen
|
|
326
|
+
entries={entries}
|
|
327
|
+
onBack={() => {}}
|
|
328
|
+
onSelect={() => {}}
|
|
329
|
+
onCopy={() => {}}
|
|
330
|
+
/>
|
|
331
|
+
),
|
|
332
|
+
{ width, height: 16 },
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
await testSetup.renderOnce();
|
|
337
|
+
testSetup.mockInput.pressArrow("down");
|
|
338
|
+
await testSetup.renderOnce();
|
|
339
|
+
const frame = testSetup.captureCharFrame();
|
|
340
|
+
const { index: row, line } = findLine(frame, "first");
|
|
341
|
+
const levelIndex = line.indexOf("INFO");
|
|
342
|
+
expect(levelIndex).toBeGreaterThanOrEqual(0);
|
|
343
|
+
|
|
344
|
+
const buffers = testSetup.renderer.currentRenderBuffer.buffers;
|
|
345
|
+
const fg = readColorAt(buffers.fg, width, row, levelIndex);
|
|
346
|
+
expect(fg).toEqual(parseColor("green").toInts());
|
|
347
|
+
} finally {
|
|
348
|
+
testSetup.renderer.destroy();
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
@@ -131,7 +131,11 @@ describe("QuickStartStep", () => {
|
|
|
131
131
|
testSetup.mockInput.pressEnter();
|
|
132
132
|
await testSetup.renderOnce();
|
|
133
133
|
expect(resumedEntry).not.toBeNull();
|
|
134
|
-
|
|
134
|
+
const assertedEntry = resumedEntry as ToolSessionEntry | null;
|
|
135
|
+
if (!assertedEntry) {
|
|
136
|
+
throw new Error("Expected resumed entry");
|
|
137
|
+
}
|
|
138
|
+
expect(assertedEntry.toolId).toBe("claude-code");
|
|
135
139
|
} finally {
|
|
136
140
|
testSetup.renderer.destroy();
|
|
137
141
|
}
|
|
@@ -193,7 +197,7 @@ describe("QuickStartStep", () => {
|
|
|
193
197
|
|
|
194
198
|
try {
|
|
195
199
|
// 矢印キーで最後の項目(Choose different settings)を選択
|
|
196
|
-
for (let i = 0; i <
|
|
200
|
+
for (let i = 0; i < 4; i++) {
|
|
197
201
|
testSetup.mockInput.pressArrow("down");
|
|
198
202
|
await testSetup.renderOnce();
|
|
199
203
|
}
|
|
@@ -234,4 +238,71 @@ describe("QuickStartStep", () => {
|
|
|
234
238
|
}
|
|
235
239
|
});
|
|
236
240
|
});
|
|
241
|
+
|
|
242
|
+
describe("missing sessionId", () => {
|
|
243
|
+
it("does not render Resume option when sessionId is missing", async () => {
|
|
244
|
+
const historyWithoutSession: ToolSessionEntry[] = [
|
|
245
|
+
{
|
|
246
|
+
toolId: "claude-code",
|
|
247
|
+
toolLabel: "Claude Code",
|
|
248
|
+
branch: "feature/test",
|
|
249
|
+
worktreePath: "/path/to/worktree",
|
|
250
|
+
model: "claude-sonnet-4-20250514",
|
|
251
|
+
mode: "normal",
|
|
252
|
+
timestamp: Date.now(),
|
|
253
|
+
sessionId: null,
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
const testSetup = await testRender(
|
|
257
|
+
() => (
|
|
258
|
+
<QuickStartStep
|
|
259
|
+
history={historyWithoutSession}
|
|
260
|
+
onResume={() => {}}
|
|
261
|
+
onStartNew={() => {}}
|
|
262
|
+
onChooseDifferent={() => {}}
|
|
263
|
+
onBack={() => {}}
|
|
264
|
+
/>
|
|
265
|
+
),
|
|
266
|
+
{ width: 80, height: 24 },
|
|
267
|
+
);
|
|
268
|
+
await testSetup.renderOnce();
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const frame = testSetup.captureCharFrame();
|
|
272
|
+
expect(frame).not.toContain("Resume session");
|
|
273
|
+
expect(frame).toContain("Start new (previous settings)");
|
|
274
|
+
expect(frame).toContain("Choose different settings");
|
|
275
|
+
} finally {
|
|
276
|
+
testSetup.renderer.destroy();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("multi-tool display", () => {
|
|
282
|
+
it("renders tool-specific descriptions and session id only for resume", async () => {
|
|
283
|
+
const testSetup = await testRender(
|
|
284
|
+
() => (
|
|
285
|
+
<QuickStartStep
|
|
286
|
+
history={mockHistory}
|
|
287
|
+
onResume={() => {}}
|
|
288
|
+
onStartNew={() => {}}
|
|
289
|
+
onChooseDifferent={() => {}}
|
|
290
|
+
onBack={() => {}}
|
|
291
|
+
/>
|
|
292
|
+
),
|
|
293
|
+
{ width: 90, height: 24 },
|
|
294
|
+
);
|
|
295
|
+
await testSetup.renderOnce();
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const frame = testSetup.captureCharFrame();
|
|
299
|
+
expect(frame).toContain("Claude Code, claude-sonnet-4-20250514");
|
|
300
|
+
expect(frame).toContain("Codex CLI, o3-mini, high");
|
|
301
|
+
expect(frame).toContain("Session: session-123");
|
|
302
|
+
expect(frame).toContain("Session: session-456");
|
|
303
|
+
} finally {
|
|
304
|
+
testSetup.renderer.destroy();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
237
308
|
});
|