Skip to content

Commit 6cda273

Browse files
committed
fix(app): address remaining coderabbit review
1 parent 9cf8a46 commit 6cda273

11 files changed

Lines changed: 261 additions & 47 deletions

File tree

.github/actions/free-docker-disk/action.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ runs:
99
run: |
1010
set -euo pipefail
1111
12-
df -h
12+
if command -v df >/dev/null 2>&1; then
13+
df -h || true
14+
else
15+
echo "df is not available; Docker disk cleanup will run without a free-space precheck." >&2
16+
fi
1317
1418
threshold_gib="${DOCKER_GIT_FREE_DOCKER_DISK_MIN_AVAILABLE_GIB:-40}"
1519
force_cleanup="${DOCKER_GIT_FORCE_FREE_DOCKER_DISK:-0}"
@@ -29,7 +33,15 @@ runs:
2933
;;
3034
esac
3135
32-
available_kib="$(df -Pk / | awk 'NR == 2 { print $4 }')"
36+
if command -v df >/dev/null 2>&1; then
37+
available_kib="$(df -Pk / 2>/dev/null | awk 'NR == 2 { print $4 }' || true)"
38+
else
39+
available_kib=""
40+
fi
41+
if [[ ! "$available_kib" =~ ^[0-9]+$ ]]; then
42+
echo "Could not parse available disk space from df output; Docker disk cleanup will run." >&2
43+
available_kib=0
44+
fi
3345
threshold_kib="$((threshold_gib * 1024 * 1024))"
3446
3547
if [[ "$force_cleanup_enabled" != "1" && "$available_kib" -ge "$threshold_kib" ]]; then
@@ -49,5 +61,7 @@ runs:
4961
5062
docker system prune -af --volumes || true
5163
52-
df -h
64+
if command -v df >/dev/null 2>&1; then
65+
df -h || true
66+
fi
5367
timeout 20s docker system df || true

packages/api/src/services/terminal-sessions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,11 +766,11 @@ const writePtyInput = (pty: PtyBridge | null, data: string): void => {
766766
const shellQuote = (value: string): string => `'${value.replace(/'/gu, "'\\''")}'`
767767

768768
// CHANGE: Predicate for when tmux should forward right-click pane events.
769-
// WHY: Mouse-aware apps and copy/view mode still need pane mouse events, while tmux menus must stay disabled.
769+
// WHY: Mouse-aware apps receive pane events; copy/view mode keeps tmux handling unless mouse tracking is active.
770770
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
771771
// REF: PR #342 tmux right-click handling.
772772
// SOURCE: n/a
773-
// FORMAT THEOREM: mouse-aware-or-copy-mode => predicate evaluates truthy in tmux.
773+
// FORMAT THEOREM: mouse_any_flag or non-copy/view pane mode => predicate evaluates truthy in tmux.
774774
// PURITY: CORE
775775
// EFFECT: none
776776
// INVARIANT: The predicate contains only tmux format language and no shell interpolation.

packages/app/src/docker-git/controller.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,5 +317,12 @@ export const restartController = (): Effect.Effect<void, ControllerBootstrapErro
317317
}
318318

319319
const bootstrapContext = yield* _(loadControllerBootstrapContext())
320-
yield* _(startAndRememberController({ ...bootstrapContext, forceRecreateController: true }))
320+
const forceRecreateController = true
321+
const buildController = shouldBuildControllerImage({
322+
currentControllerRevision: bootstrapContext.currentControllerRevision,
323+
currentImageRevision: bootstrapContext.currentImageRevision,
324+
forceRecreateController,
325+
localControllerRevision: bootstrapContext.localControllerRevision
326+
})
327+
yield* _(startAndRememberController({ ...bootstrapContext, buildController, forceRecreateController }))
321328
})

packages/app/src/docker-git/menu-create-advance.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ const resolveNextCreateFlowStep = (
263263
): number =>
264264
currentStep === "repoUrl"
265265
? firstCreateSettingsStepIndex
266-
: currentStepIndex + 1
266+
: currentStepIndex
267267

268268
const shouldCompleteCreateFlow = (
269269
nextSteps: ReadonlyArray<CreateStep>,
@@ -278,14 +278,14 @@ const shouldCompleteCreateFlow = (
278278
* @complexity O(k + s) where s = number of remaining create steps
279279
*/
280280
// CHANGE: advance normal create-flow settings after committing the active prompt
281-
// WHY: applying a non-repo step must move forward instead of reselecting the same index
282-
// QUOTE(ТЗ): "after applying a non-repoUrl step it advances to currentStepIndex + 1"
281+
// WHY: applying a non-repo step removes it from nextSteps, so the same index selects the next remaining row
282+
// QUOTE(ТЗ): "после cpuLimit нельзя пропустить ramLimit"
283283
// REF: issue-339
284284
// SOURCE: n/a
285285
// FORMAT THEOREM: remaining = empty or nextStep past end -> Complete, otherwise Continue(next valid setting)
286286
// PURITY: CORE
287287
// EFFECT: n/a
288-
// INVARIANT: applying the final settings index completes instead of clamping back to it
288+
// INVARIANT: the next view never skips the remaining setting at the applied index
289289
// COMPLEXITY: O(k + s) where s = number of remaining create steps
290290
export const advanceCreateFlow = (
291291
contextOrCwd: string | CreateFlowContext,

packages/app/src/docker-git/menu-create-inputs.ts

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ import { defaultProjectsRoot } from "./frontend-lib/usecases/menu-helpers.js"
33
import type { CreateFlowContext } from "./menu-create-flow-types.js"
44
import type { CreateInputs } from "./menu-types.js"
55

6+
/**
7+
* Removes leading path separators from a path segment.
8+
*
9+
* @param value - Path segment to normalize.
10+
* @returns The segment without leading slash characters.
11+
*
12+
* @pure true
13+
* @effect n/a
14+
* @invariant Result length is less than or equal to input length.
15+
* @precondition `value` is a finite string.
16+
* @postcondition The result does not start with `/` unless it is empty.
17+
* @complexity O(n) time and O(n) space where n = |value|.
18+
* @throws Never
19+
*/
620
const trimLeftSlash = (value: string): string => {
721
let start = 0
822
while (start < value.length && value[start] === "/") {
@@ -11,6 +25,20 @@ const trimLeftSlash = (value: string): string => {
1125
return value.slice(start)
1226
}
1327

28+
/**
29+
* Removes trailing path separators from a path segment.
30+
*
31+
* @param value - Path segment to normalize.
32+
* @returns The segment without trailing slash characters.
33+
*
34+
* @pure true
35+
* @effect n/a
36+
* @invariant Result length is less than or equal to input length.
37+
* @precondition `value` is a finite string.
38+
* @postcondition The result does not end with `/` unless it is empty.
39+
* @complexity O(n) time and O(n) space where n = |value|.
40+
* @throws Never
41+
*/
1442
const trimRightSlash = (value: string): string => {
1543
let end = value.length
1644
while (end > 0 && value[end - 1] === "/") {
@@ -19,24 +47,54 @@ const trimRightSlash = (value: string): string => {
1947
return value.slice(0, end)
2048
}
2149

50+
/**
51+
* Joins normalized POSIX-style path parts while preserving a root `/`.
52+
*
53+
* @param parts - Ordered path parts, starting with the base directory.
54+
* @returns A slash-separated path with empty non-root segments removed.
55+
*
56+
* @pure true
57+
* @effect n/a
58+
* @invariant A leading `/` base remains absolute in the result.
59+
* @precondition Each part is a finite string.
60+
* @postcondition Non-root parts do not introduce duplicate separators.
61+
* @complexity O(p + n) time and O(p + n) space where p = |parts| and n = total input length.
62+
* @throws Never
63+
*/
2264
const joinPath = (...parts: ReadonlyArray<string>): string => {
2365
const cleaned = parts
2466
.filter((part) => part.length > 0)
2567
.map((part, index) => {
2668
if (index === 0) {
27-
return trimRightSlash(part)
69+
const trimmed = trimRightSlash(part)
70+
return trimmed.length === 0 && part.startsWith("/") ? "/" : trimmed
2871
}
2972
return trimRightSlash(trimLeftSlash(part))
3073
})
74+
.filter((part, index) => index === 0 || part.length > 0)
75+
76+
if (cleaned.length === 0) {
77+
return ""
78+
}
79+
if (cleaned[0] === "/") {
80+
return cleaned.length === 1 ? "/" : `/${cleaned.slice(1).join("/")}`
81+
}
3182
return cleaned.join("/")
3283
}
3384

3485
/**
3586
* Normalizes legacy cwd input into the create-flow context record.
3687
*
88+
* @param context - Legacy cwd string or already-normalized context record.
89+
* @returns A context record with at least `cwd` defined.
90+
*
3791
* @pure true
92+
* @effect n/a
3893
* @invariant string input maps to { cwd: input }
39-
* @complexity O(1)
94+
* @precondition `context` is a finite cwd string or CreateFlowContext.
95+
* @postcondition Object context input is preserved by reference.
96+
* @complexity O(1) time and O(1) space.
97+
* @throws Never
4098
*/
4199
// CHANGE: normalize create-flow context boundaries into one record shape
42100
// WHY: pure helpers can share cwd and optional projectsRoot resolution
@@ -55,6 +113,20 @@ export const normalizeCreateFlowContext = (
55113
? { cwd: context }
56114
: context
57115

116+
/**
117+
* Resolves the configured projects root or derives it from cwd.
118+
*
119+
* @param context - Create-flow context with cwd and optional projectsRoot.
120+
* @returns Explicit non-blank projectsRoot, otherwise the cwd-derived default.
121+
*
122+
* @pure true
123+
* @effect n/a
124+
* @invariant Non-blank `projectsRoot` takes precedence over cwd defaults.
125+
* @precondition `context.cwd` is a finite string.
126+
* @postcondition Returned root is a finite string.
127+
* @complexity O(n) time and O(n) space where n = |context.projectsRoot ?? context.cwd|.
128+
* @throws Never
129+
*/
58130
const resolveProjectsRoot = (context: CreateFlowContext): string =>
59131
context.projectsRoot?.trim().length
60132
? context.projectsRoot
@@ -63,9 +135,17 @@ const resolveProjectsRoot = (context: CreateFlowContext): string =>
63135
/**
64136
* Resolves the default output directory for a repo input.
65137
*
138+
* @param context - Create-flow context used to select the projects root.
139+
* @param repoUrl - Repository input accepted by `resolveRepoInput`.
140+
* @returns Default output directory under the resolved projects root.
141+
*
66142
* @pure true
143+
* @effect n/a
67144
* @invariant output is rooted under the resolved projects root
68-
* @complexity O(n) where n = |repoUrl|
145+
* @precondition `repoUrl` is a finite string.
146+
* @postcondition The result contains the repository path parts in order.
147+
* @complexity O(n) time and O(n) space where n = |repoUrl|.
148+
* @throws Never
69149
*/
70150
// CHANGE: derive create-flow output directory from repo identity and context root
71151
// WHY: repo URL, branch suffix, and browser-provided projectsRoot must resolve consistently
@@ -87,9 +167,17 @@ export const resolveDefaultOutDir = (context: CreateFlowContext, repoUrl: string
87167
/**
88168
* Resolves partial create-flow values into total create command inputs.
89169
*
170+
* @param contextOrCwd - Legacy cwd string or create-flow context.
171+
* @param values - Partial create inputs collected by the flow.
172+
* @returns Total create inputs with deterministic defaults.
173+
*
90174
* @pure true
175+
* @effect n/a
91176
* @invariant every CreateInputs field is defined in the result
92-
* @complexity O(n) where n = |repoUrl|
177+
* @precondition `values` is a finite partial record.
178+
* @postcondition Explicit false boolean fields remain false in the result.
179+
* @complexity O(n) time and O(n) space where n = |repoUrl|.
180+
* @throws Never
93181
*/
94182
// CHANGE: totalize create-flow partial values with deterministic defaults
95183
// WHY: completion must hand the shell a complete create command input record

packages/app/src/web/app-ready-terminal-tabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const activeTerminalProject = (
3737
sessions: ReadonlyArray<ActiveTerminalSession>,
3838
activeSessionId: string | null
3939
): { readonly projectId: string; readonly projectKey?: string | undefined } | null => {
40-
const active = sessions.find((session) => terminalSessionId(session) === activeSessionId) ?? sessions.at(-1)
40+
const active = sessions.find((session) => terminalSessionId(session) === activeSessionId) ?? sessions[0]
4141
return active?.browserProjectId === undefined
4242
? null
4343
: { projectId: active.browserProjectId, projectKey: active.browserProjectKey }

packages/app/src/web/panel-project-details.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ const renderDetailsPanel = (
124124
)
125125
}
126126

127+
const selectedProjectKeyForLiveSessions = (
128+
project: SelectPanelProps["project"],
129+
selectedProjectSummary: SelectPanelProps["selectedProjectSummary"]
130+
): string | null => selectedProjectSummary?.projectKey ?? project?.projectKey ?? null
131+
127132
export const SelectPanel = (
128133
{
129134
currentMenu,
@@ -136,7 +141,7 @@ export const SelectPanel = (
136141
selectedProjectSummary
137142
}: SelectPanelProps
138143
): JSX.Element | null => {
139-
const selectedProjectKey = selectedProjectSummary?.projectKey ?? null
144+
const selectedProjectKey = selectedProjectKeyForLiveSessions(project, selectedProjectSummary)
140145

141146
if (currentMenu !== "Select") {
142147
return null

packages/app/src/web/terminal-panel-cleanup-runtime.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ export const cleanupTerminalResources = (
2424
args.lifecycle.disposed = true
2525
clearReconnectTimer(args)
2626
for (const disposable of args.lifecycle.inlineImageDisposables) {
27-
disposable.dispose()
27+
runOptionalTerminalOperation(() => {
28+
disposable.dispose()
29+
})
2830
}
2931
args.lifecycle.inlineImageDisposables = []
3032
revokeTerminalInlineImageObjectUrlCache(args.lifecycle.inlineImageObjectUrls)

0 commit comments

Comments
 (0)