@akiojin/gwt 2.13.0 → 2.14.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.ja.md +33 -0
- package/README.md +31 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +17 -11
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +0 -6
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +27 -88
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.js +2 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js +14 -2
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +1 -1
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/client/assets/index-DPWWHorC.js +72 -0
- package/dist/client/assets/index-DsDNCy5f.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +21 -11
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -2
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/shared-env.d.ts +41 -0
- package/dist/config/shared-env.d.ts.map +1 -0
- package/dist/config/shared-env.js +114 -0
- package/dist/config/shared-env.js.map +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +20 -17
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -7
- package/dist/index.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +4 -1
- package/dist/logging/logger.js.map +1 -1
- package/dist/qwen.d.ts.map +1 -1
- package/dist/qwen.js +15 -11
- package/dist/qwen.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts +41 -0
- package/dist/services/aiToolResolver.d.ts.map +1 -0
- package/dist/services/aiToolResolver.js +194 -0
- package/dist/services/aiToolResolver.js.map +1 -0
- package/dist/services/customToolResolver.d.ts +10 -0
- package/dist/services/customToolResolver.d.ts.map +1 -0
- package/dist/services/customToolResolver.js +71 -0
- package/dist/services/customToolResolver.js.map +1 -0
- package/dist/shared/aiToolConstants.d.ts +9 -0
- package/dist/shared/aiToolConstants.d.ts.map +1 -0
- package/dist/shared/aiToolConstants.js +29 -0
- package/dist/shared/aiToolConstants.js.map +1 -0
- package/dist/types/tools.d.ts +12 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +15 -6
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/terminal.d.ts +12 -3
- package/dist/utils/terminal.d.ts.map +1 -1
- package/dist/utils/terminal.js +5 -34
- package/dist/utils/terminal.js.map +1 -1
- package/dist/utils/webui.d.ts +8 -0
- package/dist/utils/webui.d.ts.map +1 -0
- package/dist/utils/webui.js +35 -0
- package/dist/utils/webui.js.map +1 -0
- package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
- package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
- package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
- package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +46 -49
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
- package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
- package/dist/web/client/src/components/CustomToolForm.js +209 -0
- package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
- package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
- package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
- package/dist/web/client/src/components/CustomToolList.js +57 -0
- package/dist/web/client/src/components/CustomToolList.js.map +1 -0
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +33 -26
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
- package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
- package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
- package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
- package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
- package/dist/web/client/src/components/Terminal.js +10 -3
- package/dist/web/client/src/components/Terminal.js.map +1 -1
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
- package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/index.js +5 -0
- package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
- package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
- package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
- package/dist/web/client/src/components/common/BranchCard.js +36 -0
- package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
- package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
- package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
- package/dist/web/client/src/components/common/MetricCard.js +10 -0
- package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
- package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
- package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
- package/dist/web/client/src/components/common/PageHeader.js +14 -0
- package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
- package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
- package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
- package/dist/web/client/src/components/common/SearchInput.js +15 -0
- package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
- package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
- package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
- package/dist/web/client/src/components/common/StatusBadge.js +15 -0
- package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
- package/dist/web/client/src/components/common/index.d.ts +6 -0
- package/dist/web/client/src/components/common/index.d.ts.map +1 -0
- package/dist/web/client/src/components/common/index.js +6 -0
- package/dist/web/client/src/components/common/index.js.map +1 -0
- package/dist/web/client/src/components/ui/alert.d.ts +9 -0
- package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/alert.js +25 -0
- package/dist/web/client/src/components/ui/alert.js.map +1 -0
- package/dist/web/client/src/components/ui/badge.d.ts +10 -0
- package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/badge.js +25 -0
- package/dist/web/client/src/components/ui/badge.js.map +1 -0
- package/dist/web/client/src/components/ui/button.d.ts +12 -0
- package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/button.js +33 -0
- package/dist/web/client/src/components/ui/button.js.map +1 -0
- package/dist/web/client/src/components/ui/card.d.ts +9 -0
- package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/card.js +16 -0
- package/dist/web/client/src/components/ui/card.js.map +1 -0
- package/dist/web/client/src/components/ui/index.d.ts +8 -0
- package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/index.js +8 -0
- package/dist/web/client/src/components/ui/index.js.map +1 -0
- package/dist/web/client/src/components/ui/input.d.ts +4 -0
- package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/input.js +8 -0
- package/dist/web/client/src/components/ui/input.js.map +1 -0
- package/dist/web/client/src/components/ui/select.d.ts +14 -0
- package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/select.js +39 -0
- package/dist/web/client/src/components/ui/select.js.map +1 -0
- package/dist/web/client/src/components/ui/table.d.ts +11 -0
- package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/table.js +21 -0
- package/dist/web/client/src/components/ui/table.js.map +1 -0
- package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
- package/dist/web/client/src/hooks/useSessions.js +6 -1
- package/dist/web/client/src/hooks/useSessions.js.map +1 -1
- package/dist/web/client/src/lib/utils.d.ts +7 -0
- package/dist/web/client/src/lib/utils.d.ts.map +1 -0
- package/dist/web/client/src/lib/utils.js +10 -0
- package/dist/web/client/src/lib/utils.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts +7 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +44 -0
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +113 -361
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +89 -127
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
- package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
- package/dist/web/client/src/pages/ConfigPage.js +216 -0
- package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
- package/dist/web/client/vite.config.d.ts.map +1 -1
- package/dist/web/client/vite.config.js +8 -1
- package/dist/web/client/vite.config.js.map +1 -1
- package/dist/web/server/index.d.ts +24 -2
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +46 -15
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts +12 -10
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +76 -43
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +35 -2
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/routes/worktrees.js +2 -2
- package/dist/web/server/routes/worktrees.js.map +1 -1
- package/dist/web/server/services/worktrees.d.ts.map +1 -1
- package/dist/web/server/services/worktrees.js +7 -1
- package/dist/web/server/services/worktrees.js.map +1 -1
- package/dist/web/server/tray.d.ts +24 -0
- package/dist/web/server/tray.d.ts.map +1 -0
- package/dist/web/server/tray.js +79 -0
- package/dist/web/server/tray.js.map +1 -0
- package/dist/web/server/websocket/handler.d.ts +18 -2
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +82 -9
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +15 -2
- package/src/claude.ts +26 -15
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +17 -1
- package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +4 -4
- package/src/cli/ui/components/App.tsx +33 -133
- package/src/cli/ui/components/common/Select.tsx +2 -2
- package/src/cli/ui/components/screens/BranchListScreen.tsx +43 -23
- package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +41 -46
- package/src/cli/ui/utils/continueSession.ts +1 -7
- package/src/codex.ts +31 -22
- package/src/config/builtin-tools.ts +6 -2
- package/src/config/shared-env.ts +139 -0
- package/src/gemini.ts +35 -22
- package/src/index.ts +54 -7
- package/src/logging/logger.ts +5 -2
- package/src/qwen.ts +28 -19
- package/src/services/aiToolResolver.ts +276 -0
- package/src/services/customToolResolver.ts +98 -0
- package/src/shared/aiToolConstants.ts +30 -0
- package/src/trayicon.d.ts +30 -0
- package/src/types/tools.ts +15 -0
- package/src/utils/prompt.ts +15 -9
- package/src/utils/session.ts +80 -26
- package/src/utils/terminal.ts +11 -41
- package/src/utils/webui.ts +43 -0
- package/src/web/client/components.json +21 -0
- package/src/web/client/src/components/AIToolLaunchModal.tsx +575 -0
- package/src/web/client/src/components/BranchGraph.tsx +95 -75
- package/src/web/client/src/components/CustomToolForm.tsx +386 -0
- package/src/web/client/src/components/CustomToolList.tsx +119 -0
- package/src/web/client/src/components/EnvEditor.tsx +91 -81
- package/src/web/client/src/components/EnvironmentEditor.tsx +97 -0
- package/src/web/client/src/components/Terminal.tsx +11 -3
- package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +179 -0
- package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +181 -0
- package/src/web/client/src/components/branch-detail/TerminalPanel.tsx +92 -0
- package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +327 -0
- package/src/web/client/src/components/branch-detail/index.ts +4 -0
- package/src/web/client/src/components/common/BranchCard.tsx +117 -0
- package/src/web/client/src/components/common/MetricCard.tsx +22 -0
- package/src/web/client/src/components/common/PageHeader.tsx +44 -0
- package/src/web/client/src/components/common/SearchInput.tsx +40 -0
- package/src/web/client/src/components/common/StatusBadge.tsx +37 -0
- package/src/web/client/src/components/common/index.ts +5 -0
- package/src/web/client/src/components/ui/alert.tsx +63 -0
- package/src/web/client/src/components/ui/badge.tsx +44 -0
- package/src/web/client/src/components/ui/button.tsx +57 -0
- package/src/web/client/src/components/ui/card.tsx +82 -0
- package/src/web/client/src/components/ui/index.ts +32 -0
- package/src/web/client/src/components/ui/input.tsx +21 -0
- package/src/web/client/src/components/ui/select.tsx +156 -0
- package/src/web/client/src/components/ui/table.tsx +119 -0
- package/src/web/client/src/hooks/useSessions.ts +10 -1
- package/src/web/client/src/index.css +46 -816
- package/src/web/client/src/lib/utils.ts +10 -0
- package/src/web/client/src/lib/websocket.ts +48 -1
- package/src/web/client/src/pages/BranchDetailPage.tsx +222 -694
- package/src/web/client/src/pages/BranchListPage.tsx +190 -236
- package/src/web/client/src/pages/ConfigManagementPage.tsx +94 -76
- package/src/web/client/src/pages/ConfigPage.tsx +362 -0
- package/src/web/client/vite.config.ts +8 -1
- package/src/web/server/index.ts +72 -15
- package/src/web/server/pty/manager.ts +128 -55
- package/src/web/server/routes/sessions.ts +59 -7
- package/src/web/server/routes/worktrees.ts +3 -3
- package/src/web/server/services/worktrees.ts +12 -4
- package/src/web/server/tray.ts +93 -0
- package/src/web/server/websocket/handler.ts +119 -13
- package/dist/client/assets/index-DeNwPosA.css +0 -1
- package/dist/client/assets/index-Dl798X5w.js +0 -32
package/src/claude.ts
CHANGED
|
@@ -51,6 +51,9 @@ export async function launchClaudeCode(
|
|
|
51
51
|
options.sessionId && options.sessionId.trim().length > 0
|
|
52
52
|
? options.sessionId.trim()
|
|
53
53
|
: null;
|
|
54
|
+
const usedExplicitSessionId =
|
|
55
|
+
Boolean(resumeSessionId) &&
|
|
56
|
+
(options.mode === "continue" || options.mode === "resume");
|
|
54
57
|
|
|
55
58
|
// Handle execution mode
|
|
56
59
|
switch (options.mode) {
|
|
@@ -173,14 +176,16 @@ export async function launchClaudeCode(
|
|
|
173
176
|
|
|
174
177
|
terminal.exitRawMode();
|
|
175
178
|
|
|
176
|
-
const baseEnv = {
|
|
177
|
-
|
|
178
|
-
...(options.envOverrides ?? {}),
|
|
179
|
-
};
|
|
180
|
-
const launchEnv =
|
|
179
|
+
const baseEnv = { ...process.env, ...(options.envOverrides ?? {}) };
|
|
180
|
+
const launchEnvSource =
|
|
181
181
|
options.skipPermissions && !baseEnv.IS_SANDBOX
|
|
182
182
|
? { ...baseEnv, IS_SANDBOX: "1" }
|
|
183
183
|
: baseEnv;
|
|
184
|
+
const launchEnv = Object.fromEntries(
|
|
185
|
+
Object.entries(launchEnvSource).filter(
|
|
186
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
187
|
+
),
|
|
188
|
+
);
|
|
184
189
|
|
|
185
190
|
const childStdio = createChildStdio();
|
|
186
191
|
|
|
@@ -199,7 +204,7 @@ export async function launchClaudeCode(
|
|
|
199
204
|
stdout: childStdio.stdout,
|
|
200
205
|
stderr: childStdio.stderr,
|
|
201
206
|
env: launchEnv,
|
|
202
|
-
}
|
|
207
|
+
});
|
|
203
208
|
} else {
|
|
204
209
|
console.log(
|
|
205
210
|
chalk.cyan(
|
|
@@ -233,7 +238,7 @@ export async function launchClaudeCode(
|
|
|
233
238
|
stdout: childStdio.stdout,
|
|
234
239
|
stderr: childStdio.stderr,
|
|
235
240
|
env: launchEnv,
|
|
236
|
-
}
|
|
241
|
+
});
|
|
237
242
|
}
|
|
238
243
|
} finally {
|
|
239
244
|
childStdio.cleanup();
|
|
@@ -250,10 +255,13 @@ export async function launchClaudeCode(
|
|
|
250
255
|
preferClosestTo: finishedAt,
|
|
251
256
|
windowMs: 10 * 60 * 1000,
|
|
252
257
|
});
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
const detectedSessionId = latest?.id ?? null;
|
|
259
|
+
// When we explicitly resumed a specific session, keep that ID as the source of truth.
|
|
260
|
+
capturedSessionId = usedExplicitSessionId
|
|
261
|
+
? resumeSessionId
|
|
262
|
+
: detectedSessionId;
|
|
255
263
|
} catch {
|
|
256
|
-
capturedSessionId = resumeSessionId
|
|
264
|
+
capturedSessionId = usedExplicitSessionId ? resumeSessionId : null;
|
|
257
265
|
}
|
|
258
266
|
|
|
259
267
|
if (capturedSessionId) {
|
|
@@ -270,11 +278,12 @@ export async function launchClaudeCode(
|
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
return capturedSessionId ? { sessionId: capturedSessionId } : {};
|
|
273
|
-
} catch (error:
|
|
281
|
+
} catch (error: unknown) {
|
|
274
282
|
const hasLocalClaude = await isClaudeCommandAvailable();
|
|
275
283
|
let errorMessage: string;
|
|
284
|
+
const err = error as NodeJS.ErrnoException;
|
|
276
285
|
|
|
277
|
-
if (
|
|
286
|
+
if (err.code === "ENOENT") {
|
|
278
287
|
if (hasLocalClaude) {
|
|
279
288
|
errorMessage =
|
|
280
289
|
"claude command not found. Please ensure Claude Code is properly installed.";
|
|
@@ -283,7 +292,8 @@ export async function launchClaudeCode(
|
|
|
283
292
|
"bunx command not found. Please ensure Bun is installed so Claude Code can run via bunx.";
|
|
284
293
|
}
|
|
285
294
|
} else {
|
|
286
|
-
|
|
295
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
296
|
+
errorMessage = `Failed to launch Claude Code: ${details || "Unknown error"}`;
|
|
287
297
|
}
|
|
288
298
|
|
|
289
299
|
if (process.platform === "win32") {
|
|
@@ -344,8 +354,9 @@ export async function isClaudeCodeAvailable(): Promise<boolean> {
|
|
|
344
354
|
try {
|
|
345
355
|
await execa("bunx", [CLAUDE_CLI_PACKAGE, "--version"], { shell: true });
|
|
346
356
|
return true;
|
|
347
|
-
} catch (error:
|
|
348
|
-
|
|
357
|
+
} catch (error: unknown) {
|
|
358
|
+
const err = error as NodeJS.ErrnoException;
|
|
359
|
+
if (err.code === "ENOENT") {
|
|
349
360
|
console.error(chalk.yellow("\n⚠️ bunx command not found"));
|
|
350
361
|
console.error(
|
|
351
362
|
chalk.gray(
|
|
@@ -183,6 +183,17 @@ describe("Select", () => {
|
|
|
183
183
|
expect(onSelect).toHaveBeenCalled();
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
it("should call onSelect when linefeed is pressed (cooked mode Enter)", () => {
|
|
187
|
+
const onSelect = vi.fn();
|
|
188
|
+
const { stdin } = render(
|
|
189
|
+
<Select items={mockItems} onSelect={onSelect} />,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
stdin.write("\n"); // Linefeed key
|
|
193
|
+
|
|
194
|
+
expect(onSelect).toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
186
197
|
it("should pass selected item to onSelect callback", () => {
|
|
187
198
|
const onSelect = vi.fn();
|
|
188
199
|
render(<Select items={mockItems} onSelect={onSelect} initialIndex={2} />);
|
|
@@ -123,6 +123,20 @@ describe("BranchListScreen", () => {
|
|
|
123
123
|
expect(getAllByText(/enter/i).length).toBeGreaterThan(0);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
it("should display Web UI URL line", () => {
|
|
127
|
+
const onSelect = vi.fn();
|
|
128
|
+
const { container } = render(
|
|
129
|
+
<BranchListScreen
|
|
130
|
+
branches={mockBranches}
|
|
131
|
+
stats={mockStats}
|
|
132
|
+
onSelect={onSelect}
|
|
133
|
+
/>,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(container.textContent).toContain("Web UI:");
|
|
137
|
+
expect(container.textContent).toContain("http://localhost:3000");
|
|
138
|
+
});
|
|
139
|
+
|
|
126
140
|
it("should handle empty branch list", () => {
|
|
127
141
|
const onSelect = vi.fn();
|
|
128
142
|
const emptyStats: Statistics = {
|
|
@@ -431,7 +445,9 @@ describe("BranchListScreen", () => {
|
|
|
431
445
|
stdin.write(" ");
|
|
432
446
|
});
|
|
433
447
|
|
|
434
|
-
const frame = stripControlSequences(
|
|
448
|
+
const frame = stripControlSequences(
|
|
449
|
+
stripAnsi(renderResult.lastFrame() ?? ""),
|
|
450
|
+
);
|
|
435
451
|
expect(frame).toContain("[*] 🟢 🛡");
|
|
436
452
|
expect(frame).toContain("feature/login");
|
|
437
453
|
});
|
|
@@ -66,9 +66,7 @@ describe("BranchQuickStartScreen", () => {
|
|
|
66
66
|
/>,
|
|
67
67
|
);
|
|
68
68
|
|
|
69
|
-
expect(
|
|
70
|
-
getByText(/Model: opus \/ Skip: No \/ ID: abc-123/),
|
|
71
|
-
).toBeDefined();
|
|
69
|
+
expect(getByText(/Model: opus \/ Skip: No \/ ID: abc-123/)).toBeDefined();
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
it("disables previous options when no history", () => {
|
|
@@ -132,7 +130,9 @@ describe("BranchQuickStartScreen", () => {
|
|
|
132
130
|
|
|
133
131
|
expect(getAllByText(/\[Codex\]/i)).toHaveLength(2);
|
|
134
132
|
expect(
|
|
135
|
-
getByText(
|
|
133
|
+
getByText(
|
|
134
|
+
/Model: gpt-5.1-codex \/ Reasoning: High \/ Skip: Yes \/ ID: codex-123/,
|
|
135
|
+
),
|
|
136
136
|
).toBeDefined();
|
|
137
137
|
expect(getAllByText(/\[Claude\]/i)).toHaveLength(2);
|
|
138
138
|
expect(
|
|
@@ -11,7 +11,6 @@ import { BranchListScreen } from "./screens/BranchListScreen.js";
|
|
|
11
11
|
import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
|
|
12
12
|
import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
|
|
13
13
|
import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
|
|
14
|
-
import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
|
|
15
14
|
import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
|
|
16
15
|
import type { ExecutionMode } from "./screens/ExecutionModeSelectorScreen.js";
|
|
17
16
|
import { BranchQuickStartScreen } from "./screens/BranchQuickStartScreen.js";
|
|
@@ -93,13 +92,6 @@ export interface AppProps {
|
|
|
93
92
|
loadingIndicatorDelay?: number;
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
export interface SessionOption {
|
|
97
|
-
sessionId: string;
|
|
98
|
-
toolLabel: string;
|
|
99
|
-
branch: string;
|
|
100
|
-
timestamp: number;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
95
|
/**
|
|
104
96
|
* App - Top-level component for Ink.js UI
|
|
105
97
|
* Integrates ErrorBoundary, data fetching, screen navigation, and all screens
|
|
@@ -119,13 +111,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
119
111
|
// Version state
|
|
120
112
|
const [version, setVersion] = useState<string | null>(null);
|
|
121
113
|
const [repoRoot, setRepoRoot] = useState<string | null>(null);
|
|
122
|
-
const [sessionOptions, setSessionOptions] = useState<SessionOption[]>([]);
|
|
123
|
-
const [sessionLoading, setSessionLoading] = useState(false);
|
|
124
|
-
const [sessionError, setSessionError] = useState<string | null>(null);
|
|
125
|
-
const [pendingExecution, setPendingExecution] = useState<{
|
|
126
|
-
mode: ExecutionMode;
|
|
127
|
-
skipPermissions: boolean;
|
|
128
|
-
} | null>(null);
|
|
129
114
|
const [continueSessionId, setContinueSessionId] = useState<string | null>(
|
|
130
115
|
null,
|
|
131
116
|
);
|
|
@@ -140,8 +125,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
140
125
|
timestamp?: number | null;
|
|
141
126
|
}[]
|
|
142
127
|
>([]);
|
|
143
|
-
const [branchQuickStartLoading, setBranchQuickStartLoading] =
|
|
144
|
-
useState(false);
|
|
128
|
+
const [branchQuickStartLoading, setBranchQuickStartLoading] = useState(false);
|
|
145
129
|
|
|
146
130
|
// Selection state (for branch → tool → mode flow)
|
|
147
131
|
const [selectedBranch, setSelectedBranch] =
|
|
@@ -274,9 +258,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
274
258
|
if (cancelled) return;
|
|
275
259
|
const safe = new Set(
|
|
276
260
|
targets
|
|
277
|
-
.filter(
|
|
278
|
-
(t) => !t.hasUncommittedChanges && !t.hasUnpushedCommits,
|
|
279
|
-
)
|
|
261
|
+
.filter((t) => !t.hasUncommittedChanges && !t.hasUnpushedCommits)
|
|
280
262
|
.map((t) => t.branch),
|
|
281
263
|
);
|
|
282
264
|
setSafeBranches(safe);
|
|
@@ -290,53 +272,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
290
272
|
};
|
|
291
273
|
}, [branches, worktrees]);
|
|
292
274
|
|
|
293
|
-
// Load available sessions when entering session selector
|
|
294
|
-
useEffect(() => {
|
|
295
|
-
if (currentScreen !== "session-selector") {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (!selectedTool || !selectedBranch) {
|
|
299
|
-
setSessionOptions([]);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
setSessionLoading(true);
|
|
304
|
-
setSessionError(null);
|
|
305
|
-
|
|
306
|
-
(async () => {
|
|
307
|
-
try {
|
|
308
|
-
const root = repoRoot ?? (await getRepositoryRoot());
|
|
309
|
-
if (!repoRoot && root) {
|
|
310
|
-
setRepoRoot(root);
|
|
311
|
-
}
|
|
312
|
-
const sessionData = root ? await loadSession(root) : null;
|
|
313
|
-
const history = sessionData?.history ?? [];
|
|
314
|
-
|
|
315
|
-
const filtered = history
|
|
316
|
-
.filter(
|
|
317
|
-
(entry) =>
|
|
318
|
-
entry.sessionId &&
|
|
319
|
-
entry.toolId === selectedTool &&
|
|
320
|
-
entry.branch === selectedBranch.name,
|
|
321
|
-
)
|
|
322
|
-
.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0))
|
|
323
|
-
.map((entry) => ({
|
|
324
|
-
sessionId: entry.sessionId as string,
|
|
325
|
-
toolLabel: entry.toolLabel,
|
|
326
|
-
branch: entry.branch,
|
|
327
|
-
timestamp: entry.timestamp,
|
|
328
|
-
}));
|
|
329
|
-
|
|
330
|
-
setSessionOptions(filtered);
|
|
331
|
-
} catch (_err) {
|
|
332
|
-
setSessionOptions([]);
|
|
333
|
-
setSessionError("セッション一覧の取得に失敗しました");
|
|
334
|
-
} finally {
|
|
335
|
-
setSessionLoading(false);
|
|
336
|
-
}
|
|
337
|
-
})();
|
|
338
|
-
}, [currentScreen, selectedTool, selectedBranch, repoRoot]);
|
|
339
|
-
|
|
340
275
|
// Load quick start options for selected branch (latest per tool)
|
|
341
276
|
useEffect(() => {
|
|
342
277
|
if (!selectedBranch) {
|
|
@@ -387,60 +322,55 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
387
322
|
const mapped = await Promise.all(
|
|
388
323
|
latestPerTool.map(async (entry) => {
|
|
389
324
|
let sessionId = entry.sessionId ?? null;
|
|
325
|
+
const worktree = selectedWorktreePath ?? workingDirectory;
|
|
390
326
|
|
|
391
327
|
// For Codex, prefer a newer filesystem session over stale history
|
|
392
|
-
if (entry.toolId === "codex-cli") {
|
|
328
|
+
if (!sessionId && entry.toolId === "codex-cli") {
|
|
393
329
|
try {
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
330
|
+
const historyTs = entry.timestamp ?? null;
|
|
331
|
+
const latestCodex = await findLatestCodexSession({
|
|
332
|
+
...(historyTs
|
|
333
|
+
? {
|
|
334
|
+
since: historyTs - 60_000,
|
|
335
|
+
preferClosestTo: historyTs,
|
|
336
|
+
windowMs: 60 * 60 * 1000,
|
|
337
|
+
}
|
|
338
|
+
: {}),
|
|
339
|
+
cwd: worktree,
|
|
340
|
+
});
|
|
341
|
+
sessionId =
|
|
342
|
+
latestCodex?.id ??
|
|
343
|
+
(await findLatestCodexSessionId({ cwd: worktree })) ??
|
|
344
|
+
null;
|
|
407
345
|
} catch {
|
|
408
346
|
// ignore lookup failure
|
|
409
347
|
}
|
|
410
348
|
}
|
|
411
349
|
|
|
412
350
|
// For Claude Code, prefer the newest session file in the worktree even if history is stale.
|
|
413
|
-
if (entry.toolId === "claude-code") {
|
|
351
|
+
if (!sessionId && entry.toolId === "claude-code") {
|
|
414
352
|
try {
|
|
415
|
-
const worktree =
|
|
416
|
-
selectedWorktreePath ??
|
|
417
|
-
selectedBranch?.displayName ??
|
|
418
|
-
workingDirectory;
|
|
419
|
-
|
|
420
353
|
// Always resolve freshest on-disk session for this worktree (no window restriction)
|
|
421
354
|
const latestAny = await findLatestClaudeSession(worktree);
|
|
422
|
-
sessionId = latestAny?.id ??
|
|
423
|
-
|
|
355
|
+
sessionId = latestAny?.id ?? null;
|
|
424
356
|
} catch {
|
|
425
357
|
// ignore lookup failure
|
|
426
358
|
}
|
|
427
359
|
}
|
|
428
360
|
|
|
429
361
|
// For Gemini, prefer newest session file (Gemini keeps per-project chats)
|
|
430
|
-
if (entry.toolId === "gemini-cli") {
|
|
362
|
+
if (!sessionId && entry.toolId === "gemini-cli") {
|
|
431
363
|
try {
|
|
432
|
-
const gemSession = await findLatestGeminiSession(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
);
|
|
443
|
-
if (gemSession?.id) sessionId = gemSession.id;
|
|
364
|
+
const gemSession = await findLatestGeminiSession(worktree, {
|
|
365
|
+
...(entry.timestamp !== null && entry.timestamp !== undefined
|
|
366
|
+
? {
|
|
367
|
+
since: entry.timestamp - 60_000,
|
|
368
|
+
preferClosestTo: entry.timestamp,
|
|
369
|
+
}
|
|
370
|
+
: {}),
|
|
371
|
+
windowMs: 60 * 60 * 1000,
|
|
372
|
+
});
|
|
373
|
+
sessionId = gemSession?.id ?? null;
|
|
444
374
|
} catch {
|
|
445
375
|
// ignore
|
|
446
376
|
}
|
|
@@ -1165,18 +1095,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
1165
1095
|
],
|
|
1166
1096
|
);
|
|
1167
1097
|
|
|
1168
|
-
// Handle session selection
|
|
1169
|
-
const handleSessionSelect = useCallback(
|
|
1170
|
-
(session: string) => {
|
|
1171
|
-
const execution = pendingExecution ?? {
|
|
1172
|
-
mode: "resume" as ExecutionMode,
|
|
1173
|
-
skipPermissions: false,
|
|
1174
|
-
};
|
|
1175
|
-
completeSelection(execution.mode, execution.skipPermissions, session);
|
|
1176
|
-
},
|
|
1177
|
-
[pendingExecution, completeSelection],
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
1098
|
const handleQuickStartSelect = useCallback(
|
|
1181
1099
|
(action: QuickStartAction, toolId?: AITool | null) => {
|
|
1182
1100
|
if (action === "manual" || !branchQuickStart.length) {
|
|
@@ -1228,14 +1146,9 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
1228
1146
|
// Handle execution mode and skipPermissions selection
|
|
1229
1147
|
const handleModeSelect = useCallback(
|
|
1230
1148
|
(result: { mode: ExecutionMode; skipPermissions: boolean }) => {
|
|
1231
|
-
if (result.mode === "resume") {
|
|
1232
|
-
setPendingExecution(result);
|
|
1233
|
-
navigateTo("session-selector");
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
1149
|
completeSelection(result.mode, result.skipPermissions, null);
|
|
1237
1150
|
},
|
|
1238
|
-
[completeSelection
|
|
1151
|
+
[completeSelection],
|
|
1239
1152
|
);
|
|
1240
1153
|
|
|
1241
1154
|
// Render screen based on currentScreen
|
|
@@ -1345,19 +1258,6 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
1345
1258
|
/>
|
|
1346
1259
|
);
|
|
1347
1260
|
|
|
1348
|
-
case "session-selector":
|
|
1349
|
-
// TODO: Implement session data fetching
|
|
1350
|
-
return (
|
|
1351
|
-
<SessionSelectorScreen
|
|
1352
|
-
sessions={sessionOptions}
|
|
1353
|
-
loading={sessionLoading}
|
|
1354
|
-
errorMessage={sessionError}
|
|
1355
|
-
onBack={goBack}
|
|
1356
|
-
onSelect={handleSessionSelect}
|
|
1357
|
-
version={version}
|
|
1358
|
-
/>
|
|
1359
|
-
);
|
|
1360
|
-
|
|
1361
1261
|
case "execution-mode-selector":
|
|
1362
1262
|
return (
|
|
1363
1263
|
<ExecutionModeSelectorScreen
|
|
@@ -164,8 +164,8 @@ const SelectComponent = <T extends SelectItem = SelectItem>({
|
|
|
164
164
|
|
|
165
165
|
return newIndex;
|
|
166
166
|
});
|
|
167
|
-
} else if (key.return) {
|
|
168
|
-
// Select current item
|
|
167
|
+
} else if (key.return || input === "\n") {
|
|
168
|
+
// Select current item (handle both CR in raw mode and LF in cooked mode)
|
|
169
169
|
const selectedItem = items[selectedIndex];
|
|
170
170
|
if (selectedItem && !disabled) {
|
|
171
171
|
onSelect(selectedItem);
|
|
@@ -11,6 +11,7 @@ import type { BranchItem, Statistics } from "../../types.js";
|
|
|
11
11
|
import stringWidth from "string-width";
|
|
12
12
|
import stripAnsi from "strip-ansi";
|
|
13
13
|
import chalk from "chalk";
|
|
14
|
+
import { resolveWebUiPort } from "../../../../utils/webui.js";
|
|
14
15
|
|
|
15
16
|
// Emoji 幅は端末によって 1 または 2 になることがあるため、最小幅を上書きして
|
|
16
17
|
// 実測より小さくならないようにする(過小評価=折り返しの原因を防ぐ)
|
|
@@ -125,9 +126,11 @@ export function BranchListScreen({
|
|
|
125
126
|
onToggleSelect,
|
|
126
127
|
}: BranchListScreenProps) {
|
|
127
128
|
const { rows } = useTerminalSize();
|
|
128
|
-
const headerText =
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const headerText = " Legend: [ ]/[ * ] select 🟢/⚪ worktree 🛡/⚠ safe";
|
|
130
|
+
const selectedSet = useMemo(
|
|
131
|
+
() => new Set(selectedBranches),
|
|
132
|
+
[selectedBranches],
|
|
133
|
+
);
|
|
131
134
|
|
|
132
135
|
// Filter state - allow test control via props
|
|
133
136
|
const [internalFilterQuery, setInternalFilterQuery] = useState("");
|
|
@@ -243,15 +246,22 @@ export function BranchListScreen({
|
|
|
243
246
|
// Filter input: 1 line
|
|
244
247
|
// Stats: 1 line
|
|
245
248
|
// Empty line: 1 line
|
|
249
|
+
// Web UI URL: 1 line
|
|
246
250
|
// Footer: 1 line
|
|
247
|
-
// Total fixed:
|
|
251
|
+
// Total fixed: 7 lines
|
|
248
252
|
const headerLines = 2;
|
|
249
253
|
const filterLines = 1;
|
|
250
254
|
const statsLines = 1;
|
|
251
255
|
const emptyLine = 1;
|
|
256
|
+
const webUiLines = 1;
|
|
252
257
|
const footerLines = 1;
|
|
253
258
|
const fixedLines =
|
|
254
|
-
headerLines +
|
|
259
|
+
headerLines +
|
|
260
|
+
filterLines +
|
|
261
|
+
statsLines +
|
|
262
|
+
emptyLine +
|
|
263
|
+
webUiLines +
|
|
264
|
+
footerLines;
|
|
255
265
|
const contentHeight = rows - fixedLines;
|
|
256
266
|
const limit = Math.max(5, contentHeight); // Minimum 5 items visible
|
|
257
267
|
|
|
@@ -308,25 +318,28 @@ export function BranchListScreen({
|
|
|
308
318
|
return result + ellipsis;
|
|
309
319
|
}, []);
|
|
310
320
|
|
|
311
|
-
const colorToolLabel = useCallback(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
321
|
+
const colorToolLabel = useCallback(
|
|
322
|
+
(label: string, toolId?: string | null) => {
|
|
323
|
+
switch (toolId) {
|
|
324
|
+
case "claude-code":
|
|
325
|
+
return chalk.hex("#ffaf00")(label); // orange-ish
|
|
326
|
+
case "codex-cli":
|
|
327
|
+
return chalk.cyan(label);
|
|
328
|
+
case "gemini-cli":
|
|
329
|
+
return chalk.magenta(label);
|
|
330
|
+
case "qwen-cli":
|
|
331
|
+
return chalk.green(label);
|
|
332
|
+
default: {
|
|
333
|
+
const trimmed = label.trim().toLowerCase();
|
|
334
|
+
if (!toolId || trimmed === "unknown") {
|
|
335
|
+
return chalk.gray(label);
|
|
336
|
+
}
|
|
337
|
+
return chalk.white(label);
|
|
325
338
|
}
|
|
326
|
-
return chalk.white(label);
|
|
327
339
|
}
|
|
328
|
-
}
|
|
329
|
-
|
|
340
|
+
},
|
|
341
|
+
[],
|
|
342
|
+
);
|
|
330
343
|
|
|
331
344
|
const renderBranchRow = useCallback(
|
|
332
345
|
(item: BranchItem, isSelected: boolean, context: { columns: number }) => {
|
|
@@ -450,7 +463,9 @@ export function BranchListScreen({
|
|
|
450
463
|
|
|
451
464
|
// 端末幅を超えた場合は隙間→ラベルの順で詰めて収める
|
|
452
465
|
const clampToWidth = () => {
|
|
453
|
-
const finalWidth = measureDisplayWidth(
|
|
466
|
+
const finalWidth = measureDisplayWidth(
|
|
467
|
+
stripAnsi(lineWithColoredTimestamp),
|
|
468
|
+
);
|
|
454
469
|
if (finalWidth <= columns) {
|
|
455
470
|
return;
|
|
456
471
|
}
|
|
@@ -608,6 +623,11 @@ export function BranchListScreen({
|
|
|
608
623
|
</Box>
|
|
609
624
|
)}
|
|
610
625
|
|
|
626
|
+
{/* Web UI URL */}
|
|
627
|
+
<Box>
|
|
628
|
+
<Text dimColor>Web UI: http://localhost:{resolveWebUiPort()}</Text>
|
|
629
|
+
</Box>
|
|
630
|
+
|
|
611
631
|
{/* Footer */}
|
|
612
632
|
<Footer actions={footerActions} />
|
|
613
633
|
</Box>
|