Skip to content

Commit e010c74

Browse files
committed
feat(tui): add workflow route and integrate with home view
Implement new workflow route with branding header component. Modify home view to switch to workflow mode instead of spawning external process. This is part of the TUI refactor to keep workflow execution within the OpenTUI environment.
1 parent 5c500da commit e010c74

File tree

4 files changed

+90
-55
lines changed

4 files changed

+90
-55
lines changed

src/cli/tui/app.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/** @jsxImportSource @opentui/solid */
22
import { render, useTerminalDimensions, useKeyboard, useRenderer } from "@opentui/solid"
33
import { VignetteEffect, applyScanlines, TextAttributes } from "@opentui/core"
4-
import { ErrorBoundary, createSignal, Show } from "solid-js"
4+
import { ErrorBoundary, Match, Show, Switch, createSignal } from "solid-js"
55
import { KVProvider } from "@tui/context/kv"
66
import { ToastProvider, useToast } from "@tui/context/toast"
77
import { ThemeProvider, useTheme } from "@tui/context/theme"
88
import { DialogProvider } from "@tui/context/dialog"
99
import { SessionProvider, useSession } from "@tui/context/session"
1010
import { UpdateNotifierProvider, useUpdateNotifier } from "@tui/context/update-notifier"
1111
import { Home } from "@tui/routes/home"
12+
import { Workflow } from "@tui/routes/workflow"
1213
import { homedir } from "os"
1314
import { createRequire } from "node:module"
1415
import { resolvePackageJson } from "../../shared/runtime/pkg.js"
@@ -183,6 +184,7 @@ function App(props: { initialToast?: InitialToast }) {
183184
// Track Ctrl+C presses for confirmation using signals
184185
const [ctrlCPressed, setCtrlCPressed] = createSignal(false)
185186
let ctrlCTimeout: NodeJS.Timeout | null = null
187+
const [view, setView] = createSignal<"home" | "workflow">("home")
186188

187189
// Global Ctrl+C handler with confirmation
188190
useKeyboard((evt) => {
@@ -242,7 +244,17 @@ function App(props: { initialToast?: InitialToast }) {
242244
>
243245
{/* Main content area */}
244246
<box flexGrow={1}>
245-
<Home initialToast={props.initialToast} />
247+
<Switch>
248+
<Match when={view() === "home"}>
249+
<Home
250+
initialToast={props.initialToast}
251+
onStartWorkflow={() => setView("workflow")}
252+
/>
253+
</Match>
254+
<Match when={view() === "workflow"}>
255+
<Workflow />
256+
</Match>
257+
</Switch>
246258
</box>
247259

248260
{/* Footer - fixed height */}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/** @jsxImportSource @opentui/solid */
2+
import { For } from "solid-js"
3+
import { useTheme } from "@tui/context/theme"
4+
5+
export function BrandingHeader(props: { version: string; currentDir: string }) {
6+
const { theme } = useTheme()
7+
8+
const lines = () => [
9+
" _____ _ _____ _ _",
10+
"| |___ _| |___| |___ ___| |_|_|___ ___",
11+
`| --| . | . | -_| | | | .'| _| | | | -_| v${props.version}`,
12+
`|_____|___|___|___|_|_|_|__,|___|_|_|_|_|_|___| ${props.currentDir}`,
13+
]
14+
15+
return (
16+
<box flexDirection="column" paddingLeft={1} paddingRight={1} paddingBottom={1}>
17+
<For each={lines()}>
18+
{(line) => (
19+
<text fg={theme.primary}>{line}</text>
20+
)}
21+
</For>
22+
</box>
23+
)
24+
}

src/cli/tui/routes/home.tsx

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ import { onMount } from "solid-js"
1818
import * as path from "node:path"
1919
import type { InitialToast } from "../app"
2020

21-
export function Home(props: { initialToast?: InitialToast }) {
21+
type HomeProps = {
22+
initialToast?: InitialToast
23+
onStartWorkflow?: () => void
24+
}
25+
26+
export function Home(props: HomeProps) {
2227
const toast = useToast()
2328
const dialog = useDialog()
2429
const renderer = useRenderer()
@@ -76,60 +81,15 @@ export function Home(props: { initialToast?: InitialToast }) {
7681
return // Don't destroy TUI or spawn workflow
7782
}
7883

79-
// Validation passed - now destroy TUI and spawn workflow
80-
81-
// Unmount OpenTUI to release terminal control
82-
renderer.destroy()
83-
84-
// CRITICAL: Restore stdin to normal (cooked) mode before spawning subprocess
85-
// OpenTUI puts stdin in raw mode, but Ink (in subprocess) needs to set it itself
86-
if (process.stdin.isTTY && process.stdin.setRawMode) {
87-
process.stdin.setRawMode(false)
88-
}
89-
90-
// Clear terminal and restore cursor before spawning subprocess
91-
if (process.stdout.isTTY) {
92-
process.stdout.write('\x1b[2J\x1b[H\x1b[?25h') // Clear screen, home cursor, show cursor
93-
}
94-
95-
// Small delay to ensure terminal state changes take effect
96-
await new Promise(resolve => setTimeout(resolve, 50))
97-
98-
// Spawn workflow in separate process (WITHOUT SolidJS transform)
99-
// This prevents JSX conflicts between OpenTUI (SolidJS) and workflow UI (React/Ink)
100-
101-
// Determine command based on environment:
102-
// - Dev mode: bun runner-process.ts
103-
// - Production: codemachine-workflow binary
104-
const isDev = import.meta.url.includes('/src/')
105-
let command: string
106-
let args: string[]
107-
108-
if (isDev) {
109-
// Development mode - use TypeScript source file
110-
const runnerPath = fileURLToPath(new URL("../../../workflows/runner-process.ts", import.meta.url))
111-
command = "bun"
112-
args = [runnerPath, cwd, ""]
113-
} else {
114-
// Production mode - use compiled workflow binary from same directory
115-
const { resolveWorkflowBinary } = await import("../../../shared/utils/resolve-workflow-binary.js")
116-
command = resolveWorkflowBinary()
117-
args = [cwd, ""]
84+
if (props.onStartWorkflow) {
85+
props.onStartWorkflow()
86+
return
11887
}
11988

120-
const { spawnProcess } = await import("../../../infra/process/spawn.js")
121-
const result = await spawnProcess({
122-
command,
123-
args,
124-
// Pass CODEMACHINE_INSTALL_DIR from parent process to child
125-
env: process.env.CODEMACHINE_INSTALL_DIR ? {
126-
CODEMACHINE_INSTALL_DIR: process.env.CODEMACHINE_INSTALL_DIR
127-
} : undefined,
128-
stdioMode: "inherit", // Let workflow process take full terminal control
129-
})
130-
131-
// Workflow completes -> exit with its exit code
132-
process.exit(result.exitCode)
89+
// TODO: legacy Ink workflow spawn is temporarily disabled during TUI refactor.
90+
// Validation passed; workflow execution will be handled by the new OpenTUI flow.
91+
// Previously: destroy renderer → reset stdin → clear terminal → spawn runner-process/binary → exit with code.
92+
return
13393
}
13494

13595
if (cmd === "/templates" || cmd === "/template") {

src/cli/tui/routes/workflow.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/** @jsxImportSource @opentui/solid */
2+
import { createRequire } from "node:module"
3+
import { homedir } from "node:os"
4+
import { resolvePackageJson } from "../../../shared/runtime/pkg.js"
5+
import { BrandingHeader } from "@tui/component/layout/branding-header"
6+
import { useTheme } from "@tui/context/theme"
7+
8+
export function Workflow() {
9+
const { theme } = useTheme()
10+
11+
const getVersion = () => {
12+
const require = createRequire(import.meta.url)
13+
const packageJsonPath = resolvePackageJson(import.meta.url, "workflow route")
14+
const pkg = require(packageJsonPath) as { version: string }
15+
return pkg.version
16+
}
17+
18+
const getCwd = () => {
19+
const cwd = process.env.CODEMACHINE_CWD || process.cwd()
20+
return cwd.replace(homedir(), "~")
21+
}
22+
23+
return (
24+
<box flexDirection="column" gap={1} paddingLeft={1} paddingRight={1} paddingTop={1}>
25+
<BrandingHeader version={getVersion()} currentDir={getCwd()} />
26+
27+
<box
28+
border
29+
borderColor={theme.border}
30+
padding={1}
31+
flexDirection="column"
32+
backgroundColor={theme.backgroundPanel}
33+
>
34+
<text fg={theme.text}>Workflow UI (OpenTUI) is initializing...</text>
35+
<text fg={theme.textMuted}>Next steps: mount timeline and output panels here.</text>
36+
</box>
37+
</box>
38+
)
39+
}

0 commit comments

Comments
 (0)