Skip to content

Commit 6b98185

Browse files
fix(web): restore debug UI on make start-debug (#1252)
1 parent 6a68421 commit 6b98185

31 files changed

Lines changed: 214 additions & 154 deletions

apps/cli/src/run.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { sortVersionsDesc } from "./version";
1919
import { pickAvailablePort } from "./ports";
2020
import { createProcessSupervisor } from "./process";
2121
import { resolveRuntime, validateBundle } from "./runtime";
22-
import { attachBackendExitHandler, logStartupInfo } from "./shared";
22+
import { attachBackendExitHandler, buildBackendEnv, buildWebEnv, logStartupInfo } from "./shared";
2323
import { launchWebApp, openBrowser } from "./web";
2424

2525
export type RunOptions = {
@@ -195,23 +195,30 @@ async function prepareBundleForLaunch({
195195

196196
const backendBin = path.join(bundleDir, "bin", getBinaryName("kandev"));
197197

198-
const backendEnv: NodeJS.ProcessEnv = {
199-
...process.env,
200-
KANDEV_SERVER_PORT: String(actualBackendPort),
201-
KANDEV_WEB_INTERNAL_URL: `http://localhost:${actualWebPort}`,
202-
KANDEV_AGENT_STANDALONE_PORT: String(agentctlPort),
203-
KANDEV_DATABASE_PATH: dbPath,
204-
KANDEV_LOG_LEVEL: logLevel,
205-
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
206-
};
198+
const backendEnv = buildBackendEnv({
199+
ports: {
200+
backendPort: actualBackendPort,
201+
webPort: actualWebPort,
202+
agentctlPort,
203+
backendUrl,
204+
},
205+
logLevel,
206+
extra: {
207+
KANDEV_DATABASE_PATH: dbPath,
208+
...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
209+
},
210+
});
207211

208-
const webEnv: NodeJS.ProcessEnv = {
209-
...process.env,
210-
KANDEV_API_BASE_URL: backendUrl,
211-
PORT: String(actualWebPort),
212-
HOSTNAME: "127.0.0.1",
213-
};
214-
(webEnv as Record<string, string>).NODE_ENV = "production";
212+
const webEnv = buildWebEnv({
213+
ports: {
214+
backendPort: actualBackendPort,
215+
webPort: actualWebPort,
216+
agentctlPort,
217+
backendUrl,
218+
},
219+
production: true,
220+
debug,
221+
});
215222

216223
return {
217224
bundleDir,

apps/cli/src/shared.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ describe("buildWebEnv", () => {
6464

6565
it("enables debug flag when requested", () => {
6666
expect(buildWebEnv({ ports, debug: true }).NEXT_PUBLIC_KANDEV_DEBUG).toBe("true");
67+
expect(buildWebEnv({ ports, debug: true }).KANDEV_DEBUG).toBe("true");
6768
expect(buildWebEnv({ ports }).NEXT_PUBLIC_KANDEV_DEBUG).toBeUndefined();
69+
expect(buildWebEnv({ ports }).KANDEV_DEBUG).toBeUndefined();
6870
});
6971
});
7072

apps/cli/src/shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export function buildWebEnv(options: WebEnvOptions): NodeJS.ProcessEnv {
121121
}
122122

123123
if (debug) {
124+
env.KANDEV_DEBUG = "true";
124125
env.NEXT_PUBLIC_KANDEV_DEBUG = "true";
125126
}
126127

apps/web/app/layout.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,34 @@ export default async function RootLayout({
4040
// In production single-port mode, this is not needed — the client uses
4141
// window.location.origin (same-origin, works for any domain / reverse proxy).
4242
const apiPort = process.env.NEXT_PUBLIC_KANDEV_API_PORT ?? null;
43-
const debugMode = process.env.NEXT_PUBLIC_KANDEV_DEBUG === "true";
43+
const debugMode =
44+
process.env.KANDEV_DEBUG === "true" || process.env.NEXT_PUBLIC_KANDEV_DEBUG === "true";
4445

4546
// SSR-fetch the deployment's feature flags so the entire client tree
4647
// (including the sidebar nav and gated routes) renders with the correct
4748
// visibility on the first paint. Falls back to all-off when the backend
4849
// is unreachable. See docs/decisions/0007-runtime-feature-flags.md.
4950
const features = await getFeatureFlagsAction();
5051

52+
const runtimeConfigScript =
53+
apiPort || debugMode
54+
? [
55+
apiPort ? `window.__KANDEV_API_PORT = ${JSON.stringify(apiPort)};` : "",
56+
debugMode ? `window.__KANDEV_DEBUG = true;` : "",
57+
]
58+
.filter(Boolean)
59+
.join("\n")
60+
: null;
61+
5162
return (
5263
<html lang="en" suppressHydrationWarning>
5364
<head>
5465
<meta name="apple-mobile-web-app-title" content="Kandev" />
66+
{/* Inject runtime config before Next.js async chunks so debug UI flags
67+
are visible when client modules first evaluate. */}
68+
{runtimeConfigScript ? (
69+
<script dangerouslySetInnerHTML={{ __html: runtimeConfigScript }} />
70+
) : null}
5571
{/* Preload the Seti icon webfont so file-tree glyphs (review dialog,
5672
file browser) don't flash blank on first render. */}
5773
<link
@@ -63,18 +79,6 @@ export default async function RootLayout({
6379
/>
6480
</head>
6581
<body className="antialiased font-sans">
66-
{apiPort || debugMode ? (
67-
<script
68-
dangerouslySetInnerHTML={{
69-
__html: [
70-
apiPort ? `window.__KANDEV_API_PORT = ${JSON.stringify(apiPort)};` : "",
71-
debugMode ? `window.__KANDEV_DEBUG = true;` : "",
72-
]
73-
.filter(Boolean)
74-
.join("\n"),
75-
}}
76-
/>
77-
) : null}
7882
<StateProvider initialState={{ features }}>
7983
<ThemeProvider>
8084
<DiffWorkerPoolProvider>

apps/web/components/session/prepare-progress.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { cn } from "@/lib/utils";
1717
import { stripAnsi } from "@/lib/utils/ansi";
1818
import { isSetupScriptMessage } from "@/hooks/use-processed-messages";
1919
import type { Message } from "@/lib/types/http";
20-
import { createDebugLogger, IS_DEBUG } from "@/lib/debug/log";
20+
import { createDebugLogger, isDebug } from "@/lib/debug/log";
2121
import type {
2222
PrepareStepInfo,
2323
SessionPrepareState,
@@ -466,7 +466,7 @@ export function PrepareProgress({ sessionId }: PrepareProgressProps) {
466466
// env is still "preparing" combined with a Virtuoso initial-scroll race.
467467
const prevSnapshotRef = useRef<PrepareSnapshot | null>(null);
468468
useEffect(() => {
469-
if (!IS_DEBUG) return;
469+
if (!isDebug()) return;
470470
const snapshot: PrepareSnapshot = {
471471
status,
472472
autoExpand,

apps/web/components/state-provider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { createContext, useContext, useEffect, useState } from "react";
44
import type { StoreApi } from "zustand";
55
import { useStore } from "zustand";
6-
import { IS_DEBUG, registerSessionTaskResolver } from "@/lib/debug/log";
6+
import { isDebug, registerSessionTaskResolver } from "@/lib/debug/log";
77
import type { AppState, StoreProviderProps } from "@/lib/state/store";
88
import { createAppStore } from "@/lib/state/store";
99

@@ -28,7 +28,7 @@ export function StateProvider({ children, initialState }: StoreProviderProps) {
2828
// carries a sessionId with `task_id=<...>` so console/log filters can scope to
2929
// a single task (see lib/debug/log.ts). No-op in production.
3030
useEffect(() => {
31-
if (!IS_DEBUG) return;
31+
if (!isDebug()) return;
3232
return registerSessionTaskResolver(
3333
(sessionId) => store.getState().taskSessions.items[sessionId]?.task_id,
3434
);

apps/web/components/task/chat/message-list-virtuoso.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
getSessionRunningState,
1818
getLastTurnGroupId,
1919
} from "./message-list-shared";
20-
import { createDebugLogger, IS_DEBUG } from "@/lib/debug/log";
20+
import { createDebugLogger, isDebug } from "@/lib/debug/log";
2121

2222
const FIRST_INDEX_BASE = 100_000;
2323

@@ -62,7 +62,7 @@ function useStableFirstItemIndex(items: RenderItem[]) {
6262

6363
const [state, setState] = useState<IndexState>(() => {
6464
const firstItemIndex = FIRST_INDEX_BASE - keys.length + 1;
65-
if (IS_DEBUG) {
65+
if (isDebug()) {
6666
debugFirstIndex("init", {
6767
keyCount: keys.length,
6868
firstItemIndex,
@@ -75,7 +75,7 @@ function useStableFirstItemIndex(items: RenderItem[]) {
7575

7676
if (keys !== state.keys) {
7777
const nextIndex = computeFirstItemIndex(state.keys, state.firstItemIndex, keys);
78-
if (IS_DEBUG) {
78+
if (isDebug()) {
7979
debugFirstIndex("transition", {
8080
prevKeyCount: state.keys.length,
8181
nextKeyCount: keys.length,
@@ -204,7 +204,7 @@ function VirtuosoBody(props: VirtuosoBodyProps) {
204204
// anchored on for that lifecycle.
205205
const mountSnapshotRef = useRef<{ itemCount: number; firstItemIndex: number } | null>(null);
206206
useEffect(() => {
207-
if (!IS_DEBUG) return;
207+
if (!isDebug()) return;
208208
if (mountSnapshotRef.current) return;
209209
mountSnapshotRef.current = { itemCount, firstItemIndex };
210210
debugVirtuoso("mount", {
@@ -308,7 +308,7 @@ function useVirtuosoDebugSnapshot({
308308
}: UseVirtuosoDebugSnapshotArgs) {
309309
const prevSnapshotRef = useRef<VirtuosoSnapshot | null>(null);
310310
useEffect(() => {
311-
if (!IS_DEBUG) return;
311+
if (!isDebug()) return;
312312
const snapshot: VirtuosoSnapshot = {
313313
branch: isInitialLoading || items.length === 0 ? "fallback" : "virtuoso",
314314
itemCount: items.length,
@@ -346,14 +346,14 @@ function useVisibleScrollParent() {
346346
const setScrollRef = useCallback((node: HTMLDivElement | null) => {
347347
nodeRef.current = node;
348348
if (node && node.offsetHeight > 0) {
349-
if (IS_DEBUG) {
349+
if (isDebug()) {
350350
debugScrollParent("ref-callback-ready", {
351351
offsetHeight: node.offsetHeight,
352352
path: "synchronous",
353353
});
354354
}
355355
setScrollParent(node);
356-
} else if (IS_DEBUG) {
356+
} else if (isDebug()) {
357357
debugScrollParent("ref-callback-defer", {
358358
hasNode: Boolean(node),
359359
offsetHeight: node?.offsetHeight ?? null,
@@ -364,15 +364,15 @@ function useVisibleScrollParent() {
364364
useEffect(() => {
365365
const node = nodeRef.current;
366366
if (!node || scrollParent) return;
367-
if (IS_DEBUG) {
367+
if (isDebug()) {
368368
debugScrollParent("ro-attach", {
369369
initialHeight: node.offsetHeight,
370370
});
371371
}
372372
const ro = new ResizeObserver((entries) => {
373373
for (const entry of entries) {
374374
if (entry.contentRect.height > 0) {
375-
if (IS_DEBUG) {
375+
if (isDebug()) {
376376
debugScrollParent("ro-ready", {
377377
height: entry.contentRect.height,
378378
});

apps/web/components/task/dockview-layout-restore.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { LayoutState } from "@/lib/state/layout-manager";
1010
import { setPinnedTarget } from "@/lib/state/layout-manager";
1111
import type { AppState } from "@/lib/state/store";
1212
import { getEnvLayout, getEnvMaximizeState, removeEnvMaximizeState } from "@/lib/local-storage";
13-
import { createDebugLogger, IS_DEBUG } from "@/lib/debug/log";
13+
import { createDebugLogger, isDebug } from "@/lib/debug/log";
1414

1515
const debug = createDebugLogger("dockview:restore");
1616

@@ -46,7 +46,7 @@ function logSanitizeOutcome(
4646
validPanels: Record<string, any>,
4747
invalidIds: Set<string>,
4848
): void {
49-
if (!IS_DEBUG) return;
49+
if (!isDebug()) return;
5050
debug("sanitizeLayout", {
5151
mode: describeSanitizeMode(options),
5252
excludeSessionCount: options.excludeSessionIds?.size ?? 0,
@@ -224,7 +224,7 @@ function tryRestoreEnvLayout(
224224
debug("tryRestoreEnvLayout: no saved layout for env", { envId });
225225
return false;
226226
}
227-
if (IS_DEBUG) {
227+
if (isDebug()) {
228228
// eslint-disable-next-line @typescript-eslint/no-explicit-any
229229
const rawPanelIds = Object.keys((envLayout as any).panels ?? {});
230230
debug("tryRestoreEnvLayout: loaded saved layout", {
@@ -241,7 +241,7 @@ function tryRestoreEnvLayout(
241241
debug("tryRestoreEnvLayout: sanitize returned null", { envId });
242242
return false;
243243
}
244-
if (IS_DEBUG) {
244+
if (isDebug()) {
245245
debug("tryRestoreEnvLayout: calling api.fromJSON", {
246246
envId,
247247
sanitizedPanelIds: Object.keys(sanitized.panels),
@@ -306,7 +306,7 @@ export function restoreEnvLayout(
306306
validComponents: Set<string>,
307307
): boolean {
308308
const phantoms = envId ? collectPhantomSessionIdsForEnv(appStore.getState(), envId) : undefined;
309-
if (IS_DEBUG) {
309+
if (isDebug()) {
310310
debug("restoreEnvLayout: entry", {
311311
envId,
312312
phantomCount: phantoms?.size ?? 0,
@@ -315,7 +315,7 @@ export function restoreEnvLayout(
315315
});
316316
}
317317
const result = tryRestoreLayout(api, envId, validComponents, phantoms);
318-
if (IS_DEBUG) {
318+
if (isDebug()) {
319319
debug("restoreEnvLayout: result", {
320320
envId,
321321
restored: result,

apps/web/components/task/dockview-layout-setup.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { setEnvLayout, setGlobalSidebarWidth } from "@/lib/local-storage";
1515
import { panelPortalManager } from "@/lib/layout/panel-portal-manager";
1616
import { stopVscode } from "@/lib/api/domains/vscode-api";
1717
import { stopUserShell } from "@/lib/api/domains/user-shell-api";
18-
import { createDebugLogger, IS_DEBUG } from "@/lib/debug/log";
18+
import { createDebugLogger, isDebug } from "@/lib/debug/log";
1919
import { snapshotColumnWidths, formatWidthsSnapshot } from "@/lib/state/dockview-widths-debug";
2020
import { enforcePinnedTargets, setSashDragging } from "@/lib/state/dockview-pinned-enforce";
2121

@@ -133,7 +133,7 @@ export function setupSashDragCapToggle(api: DockviewReadyEvent["api"]): () => vo
133133
store.setPinnedWidth("sidebar", sidebarW);
134134
}
135135
if (store.rightPanelsVisible) setPinnedTarget("right", sv.getViewSize(sv.length - 1));
136-
if (IS_DEBUG) {
136+
if (isDebug()) {
137137
debugWidths(`sash-drag-end ${formatWidthsSnapshot(snapshotColumnWidths(api))}`);
138138
}
139139
});
@@ -175,7 +175,7 @@ export function setupContainerResizeSync(api: DockviewReadyEvent["api"]): () =>
175175
const h = parent.clientHeight;
176176
if (w <= 0 || h <= 0) return;
177177
if (w === api.width && h === api.height) return;
178-
if (IS_DEBUG) {
178+
if (isDebug()) {
179179
debugWidths(
180180
`container-resize prev=${api.width}x${api.height} next=${w}x${h} ` +
181181
`pre=${formatWidthsSnapshot(snapshotColumnWidths(api))}`,

0 commit comments

Comments
 (0)