Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions packages/app/src/docker-git/menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { runDockerPsNames } from "@effect-template/lib/shell/docker"
import { type InputCancelledError, InputReadError } from "@effect-template/lib/shell/errors"
import { type AppError, renderError } from "@effect-template/lib/usecases/errors"
import { listProjectItems } from "@effect-template/lib/usecases/projects"
import { listProjectItems, listProjectStatus } from "@effect-template/lib/usecases/projects"
import { NodeContext } from "@effect/platform-node"
import { Effect, pipe } from "effect"
import { render, useApp, useInput } from "ink"
Expand Down Expand Up @@ -282,22 +282,39 @@ const TuiApp = () => {
// EFFECT: Effect<void, AppError, FileSystem | Path | CommandExecutor>
// INVARIANT: app exits only on Quit or ctrl+c
// COMPLEXITY: O(1) per input
//
// CHANGE: guard against non-TTY environments (Docker without -t)
// WHY: Ink calls setRawMode(true) on mount — without a TTY stdin does not support
// raw mode, causing an unhandled error and a hang in waitUntilExit().
// Fall back to listProjectStatus in non-interactive environments.
// QUOTE(ТЗ): "вечный цикл зависания на TUI из за ошибки Raw mode is not supported"
// REF: issue-100
// SOURCE: https://github.com/vadimdemedes/ink/#israwmodesupported
// FORMAT THEOREM: ∀ env: isTTY(env) → renderTui ∧ ¬isTTY(env) → listProjectStatus
// INVARIANT: render() is only called when stdin.isTTY ∧ setRawMode ∈ stdin
export const runMenu = pipe(
Effect.sync(() => {
resumeTui()
}),
Effect.zipRight(
Effect.tryPromise({
try: () => render(React.createElement(TuiApp)).waitUntilExit(),
catch: (error) => new InputReadError({ message: error instanceof Error ? error.message : String(error) })
})
),
Effect.ensuring(
Effect.sync(() => {
leaveTui()
})
),
Effect.asVoid
Effect.sync(() => process.stdin.isTTY && typeof process.stdin.setRawMode === "function"),
Effect.flatMap((hasTty) =>
hasTty
? pipe(
Effect.sync(() => {
resumeTui()
}),
Effect.zipRight(
Effect.tryPromise({
try: () => render(React.createElement(TuiApp)).waitUntilExit(),
catch: (error) => new InputReadError({ message: error instanceof Error ? error.message : String(error) })
})
),
Effect.ensuring(
Effect.sync(() => {
leaveTui()
})
),
Effect.asVoid
)
: Effect.ignore(listProjectStatus)
)
)

export type MenuError = AppError | InputCancelledError