ompchamber 0.5.0 → 0.7.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.md +67 -6
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/{index.5291f58223.css → index.51f68210a5.css} +1 -1
- package/dist/client/static/js/async/{3y.ededeebde8.js → 3y.4c2596f64c.js} +3 -3
- package/dist/client/static/js/async/73.308dff4da8.js +1 -0
- package/dist/client/static/js/async/{ty.3d93cf5f5f.js → 7g.a7814350c3.js} +1 -1
- package/dist/client/static/js/async/a.bce4542392.js +1 -0
- package/dist/client/static/js/async/g.86e2256a70.js +2 -0
- package/dist/client/static/js/async/hn.b645041e00.js +1 -0
- package/dist/client/static/js/async/{jg.69a4beade8.js → jg.d86ebd4013.js} +1 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +1 -0
- package/dist/client/static/js/async/ls.9d9a01026c.js +104 -0
- package/dist/client/static/js/async/mw.ca1512563c.js +10 -0
- package/dist/client/static/js/async/nk.fc7e58325e.js +1 -0
- package/dist/client/static/js/async/{q.7fb4d2f374.js → q.542c184fec.js} +3 -3
- package/dist/client/static/js/async/t.cda4891526.js +1 -0
- package/dist/client/static/js/async/tr.e878658140.js +309 -0
- package/dist/client/static/js/async/uf.24ca3d49c3.js +2 -0
- package/dist/client/static/js/index.eccad0b70a.js +33 -0
- package/dist/client/static/js/{uy.5f791144ce.js → uy.2e8929ed0c.js} +2 -2
- package/package.json +15 -5
- package/src/cli/lib/args.js +6 -6
- package/src/cli/lib/commands/logs.js +76 -34
- package/src/cli/lib/commands/restart.js +40 -8
- package/src/cli/lib/commands/serve.js +12 -4
- package/src/cli/lib/commands/status.js +40 -18
- package/src/cli/lib/commands/stop.js +12 -7
- package/src/cli/lib/commands/update.js +23 -12
- package/src/cli/lib/paths.js +8 -28
- package/src/cli/lib/runtime.js +121 -108
- package/src/client/components/common/code-editor/index.tsx +5 -1
- package/src/client/components/common/code-surface/index.tsx +49 -1
- package/src/client/components/common/code-surface/measure.ts +99 -0
- package/src/client/components/layout/RightActivityBar.tsx +42 -69
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +5 -6
- package/src/client/components/layout/desktop-layout/index.tsx +11 -9
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +22 -29
- package/src/client/components/layout/desktop-layout/resizer/utils.test.ts +33 -0
- package/src/client/components/layout/desktop-layout/resizer/utils.ts +34 -0
- package/src/client/components/mobile/RightSidebar.tsx +42 -119
- package/src/client/components/mobile/mobile-main-view/Header.tsx +17 -1
- package/src/client/components/workspace/editor/index.tsx +4 -1
- package/src/client/hooks/chat/omp/index.ts +4 -109
- package/src/client/hooks/chat/omp/prompt-send.ts +161 -0
- package/src/client/hooks/chat/timeline/send.ts +3 -2
- package/src/client/hooks/editor/use-file-editor.test.ts +158 -0
- package/src/client/hooks/editor/use-file-editor.ts +35 -19
- package/src/server/index.ts +52 -1
- package/src/server/lib/lifecycle/bun.ts +18 -0
- package/src/server/lib/lifecycle/identity.ts +108 -0
- package/src/server/lib/lifecycle/instance.ts +102 -0
- package/src/server/lib/lifecycle/launch-mode.test.ts +32 -0
- package/src/server/lib/lifecycle/launch-mode.ts +40 -0
- package/src/server/lib/lifecycle/occupant.ts +101 -0
- package/src/server/lib/lifecycle/paths.ts +50 -0
- package/src/server/lib/lifecycle/port-guard.ts +170 -0
- package/src/server/lib/lifecycle/probe.ts +121 -0
- package/src/server/lib/lifecycle/restart.test.ts +21 -0
- package/src/server/lib/lifecycle/restart.ts +111 -0
- package/src/server/lib/omp/rpc/session-commands.ts +15 -1
- package/src/server/lib/updates/apply.ts +8 -5
- package/src/server/lib/updates/install.ts +7 -4
- package/src/server/plugins/static.test.ts +63 -0
- package/src/server/plugins/static.ts +28 -4
- package/src/server/routes/health/probe.ts +9 -0
- package/src/shared/lib/markdown/mermaid.ts +8 -0
- package/src/shared/lib/omp/session/stream-state.server.ts +16 -1
- package/src/shared/lib/workspace/panel-widths.test.ts +5 -5
- package/src/shared/lib/workspace/panel-widths.ts +23 -10
- package/src/shared/lib/workspace/right-panels.ts +10 -4
- package/dist/client/static/js/async/73.50280ce58d.js +0 -1
- package/dist/client/static/js/async/a.e05da304c8.js +0 -10
- package/dist/client/static/js/async/ar.1e14da5588.js +0 -1
- package/dist/client/static/js/async/g.7a4bbd4798.js +0 -2
- package/dist/client/static/js/async/hn.7568bdb58b.js +0 -1
- package/dist/client/static/js/async/l.102a97fce6.js +0 -104
- package/dist/client/static/js/async/l6.c56640c205.js +0 -1
- package/dist/client/static/js/async/nk.6de011832e.js +0 -1
- package/dist/client/static/js/async/t.cb3fcf8615.js +0 -1
- package/dist/client/static/js/async/uf.46ad6209fa.js +0 -2
- package/dist/client/static/js/async/vn.e98e2f3b61.js +0 -309
- package/dist/client/static/js/index.0ac93ee878.js +0 -33
- /package/dist/client/static/js/async/{3y.ededeebde8.js.LICENSE.txt → 3y.4c2596f64c.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.7a4bbd4798.js.LICENSE.txt → g.86e2256a70.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{a.e05da304c8.js.LICENSE.txt → mw.ca1512563c.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.7fb4d2f374.js.LICENSE.txt → q.542c184fec.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.46ad6209fa.js.LICENSE.txt → uf.24ca3d49c3.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.0ac93ee878.js.LICENSE.txt → index.eccad0b70a.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{uy.5f791144ce.js.LICENSE.txt → uy.2e8929ed0c.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Replacing the instance that is serving an update request.
|
|
8
|
+
*
|
|
9
|
+
* A server cannot restart itself: the process holding the port has to exit
|
|
10
|
+
* before a replacement can bind it, and the update response has to leave first.
|
|
11
|
+
* So once the files on disk are replaced, the work goes to a detached
|
|
12
|
+
* `ompchamber restart --port <port>` — the same stop-then-serve the CLI runs
|
|
13
|
+
* after `ompchamber update`.
|
|
14
|
+
*
|
|
15
|
+
* Instances the CLI did not start are left alone. `bun run dev` and
|
|
16
|
+
* `bun run start` execute this package's source in the user's own shell, and an
|
|
17
|
+
* external supervisor (systemd, pm2, a container) owns the process it started;
|
|
18
|
+
* all of them record `direct`, and replacing any of them would break a loop
|
|
19
|
+
* OMPChamber does not own — or fight a second daemon for the port. Their own
|
|
20
|
+
* launcher is what brings the new files up.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync } from 'node:fs';
|
|
24
|
+
import { join as joinPath, resolve as resolvePath } from 'node:path';
|
|
25
|
+
|
|
26
|
+
import { resolveBunBin } from '@/server/lib/lifecycle/bun';
|
|
27
|
+
import { listInstanceRecords } from '@/server/lib/lifecycle/instance';
|
|
28
|
+
import { resolveLaunchMode } from '@/server/lib/lifecycle/launch-mode';
|
|
29
|
+
|
|
30
|
+
/** Package root of the running copy: src/server/lib/lifecycle -> four levels up. */
|
|
31
|
+
const PKG_ROOT = resolvePath(import.meta.dir, '..', '..', '..', '..');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Time between answering the update request and stopping the server. The
|
|
35
|
+
* response is a small JSON body on a local listener; this is the margin that
|
|
36
|
+
* keeps the console's toast from dying with the process that sent it.
|
|
37
|
+
*/
|
|
38
|
+
export const RESTART_GRACE_MS = 1_200;
|
|
39
|
+
|
|
40
|
+
export interface SelfRestartPlan {
|
|
41
|
+
/** A detached restart is armed; `message` says so. */
|
|
42
|
+
restarting: boolean;
|
|
43
|
+
/** What to tell the user, either way. */
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* True when the CLI owns this instance's lifecycle and may replace it. `direct`
|
|
49
|
+
* is every server OMPChamber did not start: `bun run dev`, `bun run start`, a
|
|
50
|
+
* manual `bun src/server/index.ts`, or an external supervisor.
|
|
51
|
+
*/
|
|
52
|
+
export function isAutoRestartable(launchMode: string | null | undefined): boolean {
|
|
53
|
+
return launchMode === 'daemon' || launchMode === 'foreground';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One-line reason an instance is left running, shared by the CLI and console. */
|
|
57
|
+
export function skipRestartNote(launchMode: string | null | undefined): string {
|
|
58
|
+
if (launchMode === 'direct') {
|
|
59
|
+
return 'this instance runs from source (`bun run dev` / `bun run start`) — start it again to apply the update';
|
|
60
|
+
}
|
|
61
|
+
return 'this instance is not managed by the ompchamber CLI — restart it yourself to apply the update';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The record this process wrote for itself; falls back to the env it was launched with. */
|
|
65
|
+
function ownInstance(): { port: number; launchMode: string } | null {
|
|
66
|
+
const record = listInstanceRecords().find((entry) => entry.pid === process.pid);
|
|
67
|
+
if (record) return { port: record.port, launchMode: record.launchMode };
|
|
68
|
+
|
|
69
|
+
const port = Number(Bun.env.PORT);
|
|
70
|
+
if (!Number.isFinite(port) || port <= 0) return null;
|
|
71
|
+
return { port, launchMode: resolveLaunchMode() };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `ompchamber restart --port <port>` for the install this server runs from. */
|
|
75
|
+
function restartCommand(port: number): string[] | null {
|
|
76
|
+
const cli = joinPath(PKG_ROOT, 'src', 'cli', 'ompchamber.js');
|
|
77
|
+
if (!existsSync(cli)) return null;
|
|
78
|
+
return [resolveBunBin(), cli, 'restart', '--port', String(port)];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stop and start this port again, once the response has been delivered. The
|
|
83
|
+
* helper is detached so this process exiting does not take it down, and it
|
|
84
|
+
* removes this port's record itself (`stopInstance`) before serving.
|
|
85
|
+
*/
|
|
86
|
+
export function scheduleSelfRestart(): SelfRestartPlan {
|
|
87
|
+
const self = ownInstance();
|
|
88
|
+
if (!self) {
|
|
89
|
+
return { restarting: false, message: 'This instance could not be identified — restart it yourself to apply the update.' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!isAutoRestartable(self.launchMode)) {
|
|
93
|
+
return { restarting: false, message: skipRestartNote(self.launchMode) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cmd = restartCommand(self.port);
|
|
97
|
+
if (!cmd) {
|
|
98
|
+
return { restarting: false, message: `The ompchamber CLI is missing from ${PKG_ROOT} — restart it yourself to apply the update.` };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
console.log(`[ompchamber] restarting on port ${self.port}: ${cmd.join(' ')}`);
|
|
103
|
+
try {
|
|
104
|
+
Bun.spawn({ cmd, cwd: PKG_ROOT, stdin: 'ignore', stdout: 'inherit', stderr: 'inherit', detached: true }).unref();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(`[ompchamber] restart helper could not be started: ${err instanceof Error ? err.message : String(err)}`);
|
|
107
|
+
}
|
|
108
|
+
}, RESTART_GRACE_MS);
|
|
109
|
+
|
|
110
|
+
return { restarting: true, message: `Restarting OMPChamber on port ${self.port} to apply it — the console reconnects on its own.` };
|
|
111
|
+
}
|
|
@@ -13,7 +13,7 @@ import { RpcCommandTimeoutError, type RpcProcess } from '@/server/lib/omp/rpc/pr
|
|
|
13
13
|
import { AWAITING_AGENT_START_TIMEOUT_MS, GET_STATE_TIMEOUT_MS, IMAGE_BEARING_COMMANDS, PASSTHROUGH_COMMANDS, PROMPT_ACK_TIMEOUT_MS, RESTARTING_MESSAGE, SESSION_BUSY_MESSAGE, WebRpcError, toImageContents, type AgentEvent, type RpcSessionState, validateAgentImages } from '@/server/lib/omp/rpc/constants';
|
|
14
14
|
import { clearSessionFileCaches } from '@/server/lib/omp/session/files';
|
|
15
15
|
import { notifyRunningChange } from '@/server/lib/omp/rpc/session-registry';
|
|
16
|
-
import { markStreamStatus } from '@/shared/lib/omp/session/stream-state.server';
|
|
16
|
+
import { clearStreamStatus, markStreamStatus } from '@/shared/lib/omp/session/stream-state.server';
|
|
17
17
|
import { buildWebState, type WebStateHost } from '@/server/lib/omp/rpc/web-state';
|
|
18
18
|
|
|
19
19
|
/** Runtime surface AgentSessionWrapper exposes to the command dispatcher. */
|
|
@@ -66,12 +66,24 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
66
66
|
throw new Error('Cannot send a prompt while a shell command is running');
|
|
67
67
|
}
|
|
68
68
|
const streamingBehavior = command.streamingBehavior as 'steer' | 'followUp' | undefined;
|
|
69
|
+
// The live `stream` row starts with the DISPATCH, not with agent_start:
|
|
70
|
+
// the spawn/ack round trip that precedes agent_start must not read as
|
|
71
|
+
// "nothing happened" in the sidebar after the user hit send. Skipped
|
|
72
|
+
// while a turn already streams — that row belongs to the running turn,
|
|
73
|
+
// and this dispatch's failure must never roll it back.
|
|
74
|
+
const ownsStreamRow = !streamingBehavior && !host.streaming && Boolean(host.sessionId);
|
|
75
|
+
// A prompt that never started a turn leaves no run behind, so the row we
|
|
76
|
+
// wrote must go — unless a turn began meanwhile, which owns it now.
|
|
77
|
+
const releaseStreamRow = (): void => {
|
|
78
|
+
if (ownsStreamRow && !host.streaming) void clearStreamStatus(host.sessionId);
|
|
79
|
+
};
|
|
69
80
|
if (!streamingBehavior) {
|
|
70
81
|
host.promptRunning = true;
|
|
71
82
|
host.promptDispatchPendingCount += 1;
|
|
72
83
|
host.awaitingAgentStart = false;
|
|
73
84
|
host.awaitingAgentStartDeadline = 0;
|
|
74
85
|
host.continuationGraceUntil = 0;
|
|
86
|
+
if (ownsStreamRow) void markStreamStatus(host.sessionId, 'stream');
|
|
75
87
|
notifyRunningChange();
|
|
76
88
|
}
|
|
77
89
|
try {
|
|
@@ -85,6 +97,7 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
85
97
|
host.promptRunning = false;
|
|
86
98
|
host.awaitingAgentStart = false;
|
|
87
99
|
host.awaitingAgentStartDeadline = 0;
|
|
100
|
+
releaseStreamRow();
|
|
88
101
|
host.emit({ type: 'prompt_result', agentInvoked: false });
|
|
89
102
|
notifyRunningChange();
|
|
90
103
|
} else if (!streamingBehavior && ack?.agentInvoked !== false) {
|
|
@@ -95,6 +108,7 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
95
108
|
host.promptRunning = false;
|
|
96
109
|
host.awaitingAgentStart = false;
|
|
97
110
|
host.awaitingAgentStartDeadline = 0;
|
|
111
|
+
releaseStreamRow();
|
|
98
112
|
notifyRunningChange();
|
|
99
113
|
// The ack may simply be queued behind a running turn: omp accepts the
|
|
100
114
|
// prompt before the turn it starts. No reset, and the client must not
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Applies a requested update. oh-my-pi is updated through the omp binary;
|
|
3
3
|
* OMPChamber through the shared self-update engine, which replaces the running
|
|
4
|
-
* install in place (bun global install, or git fast-forward + rebuild)
|
|
4
|
+
* install in place (bun global install, or git fast-forward + rebuild) and then
|
|
5
|
+
* restarts this instance through a detached `ompchamber restart` — unless the
|
|
6
|
+
* instance was started from source, which its own launcher has to restart.
|
|
5
7
|
*/
|
|
6
8
|
|
|
9
|
+
import { scheduleSelfRestart } from '@/server/lib/lifecycle/restart';
|
|
7
10
|
import { updateOmpChamber } from '@/server/lib/updates/install';
|
|
8
11
|
import { applyOmpUpdate } from '@/server/lib/updates/omp';
|
|
9
12
|
import type { UpdateApplyResult, UpdateTarget } from '@/shared/types/updates';
|
|
10
13
|
|
|
11
|
-
/** A server cannot replace its own process, so the user restarts it. */
|
|
12
|
-
const RESTART_NOTE = ' Restart the server (`ompchamber restart`) to apply it.';
|
|
13
|
-
|
|
14
14
|
export async function applyUpdate(target: UpdateTarget): Promise<UpdateApplyResult> {
|
|
15
15
|
if (target === 'omp') {
|
|
16
16
|
try {
|
|
@@ -28,11 +28,14 @@ export async function applyUpdate(target: UpdateTarget): Promise<UpdateApplyResu
|
|
|
28
28
|
|
|
29
29
|
if (target === 'ompchamber') {
|
|
30
30
|
const result = await updateOmpChamber();
|
|
31
|
+
// Only a call that actually replaced files has something to restart for:
|
|
32
|
+
// an "already up to date" answer must not bounce a healthy server.
|
|
33
|
+
const restart = result.success && result.updated ? scheduleSelfRestart() : null;
|
|
31
34
|
return {
|
|
32
35
|
success: result.success,
|
|
33
36
|
target: 'ompchamber',
|
|
34
37
|
manual: result.manual,
|
|
35
|
-
message: `${result.message}${
|
|
38
|
+
message: restart ? `${result.message} ${restart.message}` : result.message,
|
|
36
39
|
output: result.output || undefined,
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { join as joinPath, resolve as resolvePath } from 'node:path';
|
|
16
16
|
|
|
17
|
+
import { resolveBunBin } from '@/server/lib/lifecycle/bun';
|
|
17
18
|
import { fetchLatestRelease, type GitHubRelease } from '@/server/lib/updates/github';
|
|
18
19
|
import {
|
|
19
20
|
detectInstallMethod,
|
|
@@ -137,10 +138,12 @@ export function resolveInstallContext(pkgRoot = DEFAULT_PKG_ROOT): InstallContex
|
|
|
137
138
|
const remote = gitWorkTree ? runSync(['git', '-C', root, 'remote', 'get-url', 'origin']) : null;
|
|
138
139
|
const gitRemote = remote?.ok && remote.out.length > 0 ? remote.out : null;
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
return {
|
|
142
|
+
...detectInstallMethod({ pkgRoot: root, gitWorkTree, gitRemote }),
|
|
143
|
+
pkgRoot: root,
|
|
144
|
+
gitRemote,
|
|
145
|
+
bunBin: resolveBunBin(),
|
|
146
|
+
};
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
/** The commands that install `version` for a given install method. */
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
import { servePublicAsset } from '@/server/plugins/static';
|
|
7
|
+
|
|
8
|
+
// Two roots, as they exist in real life: `public/` is the source folder a
|
|
9
|
+
// checkout has, `dist/client` is the build output an installed package ships.
|
|
10
|
+
const CHECKOUT_PUBLIC = join(mkdtempSync(join(tmpdir(), 'omp-static-')), 'public');
|
|
11
|
+
const BUILD_OUTPUT = join(mkdtempSync(join(tmpdir(), 'omp-static-')), 'dist/client');
|
|
12
|
+
const ROOTS = [CHECKOUT_PUBLIC, BUILD_OUTPUT] as const;
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
mkdirSync(CHECKOUT_PUBLIC, { recursive: true });
|
|
16
|
+
mkdirSync(BUILD_OUTPUT, { recursive: true });
|
|
17
|
+
writeFileSync(join(CHECKOUT_PUBLIC, 'icon.svg'), '<svg id="checkout"/>');
|
|
18
|
+
writeFileSync(join(BUILD_OUTPUT, 'icon.svg'), '<svg id="build"/>');
|
|
19
|
+
writeFileSync(join(BUILD_OUTPUT, 'manifest.webmanifest'), '{"name":"OMPChamber"}');
|
|
20
|
+
writeFileSync(join(BUILD_OUTPUT, 'index.html'), '<!doctype html><title>shell</title>');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
rmSync(CHECKOUT_PUBLIC, { recursive: true, force: true });
|
|
25
|
+
rmSync(BUILD_OUTPUT, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('servePublicAsset', () => {
|
|
29
|
+
// The bug this guards: an installed `ompchamber` has no `public/` folder, so a
|
|
30
|
+
// lookup confined to it answered the HTML shell for `/icon.svg` and the About
|
|
31
|
+
// modal rendered a broken image.
|
|
32
|
+
test('falls back to the build output when the source folder has no copy', async () => {
|
|
33
|
+
const response = await servePublicAsset('/manifest.webmanifest', ROOTS);
|
|
34
|
+
expect(response?.headers.get('content-type')).toBe('application/manifest+json');
|
|
35
|
+
expect(await response?.text()).toBe('{"name":"OMPChamber"}');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('prefers the source folder so a dev edit lands without a rebuild', async () => {
|
|
39
|
+
const response = await servePublicAsset('/icon.svg', ROOTS);
|
|
40
|
+
expect(response?.headers.get('content-type')).toBe('image/svg+xml');
|
|
41
|
+
expect(await response?.text()).toBe('<svg id="checkout"/>');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('leaves the HTML shell to the SSR route', async () => {
|
|
45
|
+
expect(await servePublicAsset('/index.html', ROOTS)).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('keeps a sibling of the root unreachable', async () => {
|
|
49
|
+
const outside = join(BUILD_OUTPUT, '..', 'secret.txt');
|
|
50
|
+
writeFileSync(outside, 'not an asset');
|
|
51
|
+
expect(await servePublicAsset('/../secret.txt', [BUILD_OUTPUT])).toBeNull();
|
|
52
|
+
expect(await servePublicAsset('/%2e%2e/secret.txt', [BUILD_OUTPUT])).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('short-caches the fallback, whose filename carries no content hash', async () => {
|
|
56
|
+
const response = await servePublicAsset('/manifest.webmanifest', ROOTS);
|
|
57
|
+
expect(response?.headers.get('cache-control')).toBe('public, max-age=3600');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('returns null for a missing asset so the request reaches the app', async () => {
|
|
61
|
+
expect(await servePublicAsset('/nope.svg', ROOTS)).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -68,14 +68,38 @@ async function serveFile(filePath: string, cacheControl: string): Promise<Respon
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/** Roots searched for a non-`/static/` asset, in preference order. */
|
|
72
|
+
const PUBLIC_ROOTS = [PUBLIC_ROOT, CLIENT_ROOT] as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The first readable copy of `pathname` under `roots`, or null.
|
|
76
|
+
*
|
|
77
|
+
* Non-`/static/` paths are the public assets (`icon.svg`, the web manifest, the
|
|
78
|
+
* service worker, the touch icons). They exist twice: `public/` in a checkout
|
|
79
|
+
* and `dist/client/` in the build — and only the latter ships, because
|
|
80
|
+
* `package.json#files` lists `src`, `dist/client` and `tsconfig.json`. Serving
|
|
81
|
+
* `public/` alone therefore 404s every one of them into the SSR shell when the
|
|
82
|
+
* chamber runs from an installed package. The source folder is tried first so a
|
|
83
|
+
* dev edit lands without a rebuild; the build output is the fallback. HTML is
|
|
84
|
+
* excluded so `/index.html` keeps going through the SSR route that injects the
|
|
85
|
+
* theme and bootstrap, and the cache stays short because these filenames carry
|
|
86
|
+
* no content hash.
|
|
87
|
+
*/
|
|
88
|
+
export async function servePublicAsset(pathname: string, roots: readonly string[]): Promise<Response | null> {
|
|
89
|
+
for (const root of roots) {
|
|
90
|
+
const filePath = resolveWithin(root, pathname);
|
|
91
|
+
if (!filePath || filePath.endsWith('.html')) continue;
|
|
92
|
+
const response = await serveFile(filePath, SHORT);
|
|
93
|
+
if (response) return response;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
71
98
|
/** A file for `pathname`, or null when the request should reach the app. */
|
|
72
99
|
export async function tryServeStatic(pathname: string): Promise<Response | null> {
|
|
73
100
|
if (pathname.startsWith('/static/')) {
|
|
74
101
|
const filePath = resolveWithin(CLIENT_ROOT, pathname);
|
|
75
102
|
return filePath ? serveFile(filePath, ASSET_CACHE) : null;
|
|
76
103
|
}
|
|
77
|
-
|
|
78
|
-
const publicPath = resolveWithin(PUBLIC_ROOT, pathname);
|
|
79
|
-
if (!publicPath) return null;
|
|
80
|
-
return serveFile(publicPath, SHORT);
|
|
104
|
+
return servePublicAsset(pathname, PUBLIC_ROOTS);
|
|
81
105
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { json, NO_STORE_HEADERS } from '@/server/lib/remix-compat';
|
|
2
2
|
import pkg from '@/../package.json';
|
|
3
|
+
import { resolveLaunchMode } from '@/server/lib/lifecycle/launch-mode';
|
|
3
4
|
import { isMockMode } from '@/server/mock.server';
|
|
4
5
|
|
|
5
6
|
const STARTED_AT = Date.now();
|
|
@@ -21,6 +22,14 @@ export async function loader() {
|
|
|
21
22
|
mock: isMockMode(),
|
|
22
23
|
runtime: 'bun',
|
|
23
24
|
bun: Bun.version,
|
|
25
|
+
// Identity of the listener itself, read by the CLI and by a starting
|
|
26
|
+
// server deciding whether this port belongs to OMPChamber: `mode` and
|
|
27
|
+
// `launchMode` are what `status` prints, and the pair with `pid` is what
|
|
28
|
+
// separates a dev server from a stale published build.
|
|
29
|
+
mode: Bun.env.NODE_ENV === 'production' ? 'prod' : 'dev',
|
|
30
|
+
launchMode: resolveLaunchMode(),
|
|
31
|
+
port: Number(Bun.env.PORT) || 3000,
|
|
32
|
+
host: Bun.env.HOST || 'localhost',
|
|
24
33
|
},
|
|
25
34
|
{ headers: NO_STORE_HEADERS },
|
|
26
35
|
);
|
|
@@ -69,6 +69,14 @@ async function loadMermaid(themeKey: ThemeKey): Promise<MermaidModule['default']
|
|
|
69
69
|
// diagram types this app sees, so pin it here and alias `elkjs` to a
|
|
70
70
|
// stub in the build (`src/client/stubs/elkjs.ts`).
|
|
71
71
|
layout: 'dagre',
|
|
72
|
+
// On a parse/draw failure mermaid draws its own "Syntax error in text /
|
|
73
|
+
// mermaid version 12.x" diagram into a temp `<div id="d<render-id>">`
|
|
74
|
+
// appended to `document.body`, and throws BEFORE removing it — so every
|
|
75
|
+
// failed render leaks a full-size error SVG into the page (the stray
|
|
76
|
+
// bottom banner). `suppressErrorRendering` makes that path drop the temp
|
|
77
|
+
// elements first; `renderBlock`'s catch already degrades the block to
|
|
78
|
+
// its raw source, which is the only fallback we want.
|
|
79
|
+
suppressErrorRendering: true,
|
|
72
80
|
flowchart: { useMaxWidth: false, wrappingWidth: 100000 },
|
|
73
81
|
sequence: { useMaxWidth: false },
|
|
74
82
|
});
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* sidebar can render spinner/check from the SAME loader that already refresh
|
|
9
9
|
* on stream events (`omp:session-updated` → revalidator). Statuses:
|
|
10
10
|
*
|
|
11
|
-
* stream — a run is in flight (agent_start)
|
|
11
|
+
* stream — a run is in flight (prompt dispatch → agent_start)
|
|
12
12
|
* finish — the run ended normally (agent_end), not yet seen
|
|
13
13
|
* abort — the user stopped it, not yet seen
|
|
14
14
|
* error — the run failed (prompt_error / process exit), not yet seen
|
|
@@ -41,6 +41,21 @@ export async function markStreamStatus(sessionId: string, status: SessionStreamS
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Drop an optimistic `stream` row whose prompt never started a turn: the ack
|
|
46
|
+
* reported `agentInvoked: false`, or the dispatch failed before omp accepted
|
|
47
|
+
* it. Live-only — a `stream` row a concurrent `agent_start` wrote is still the
|
|
48
|
+
* truth, and terminal badges are cleared by `markStreamSeen`.
|
|
49
|
+
*/
|
|
50
|
+
export async function clearStreamStatus(sessionId: string): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
const db = await getDb();
|
|
53
|
+
await db.run("DELETE FROM session_stream_state WHERE session_id = ? AND status = 'stream'", [sessionId]);
|
|
54
|
+
} catch {
|
|
55
|
+
// Best-effort.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
/**
|
|
45
60
|
* Opening the session clears its terminal badge (one-shot contract).
|
|
46
61
|
*
|
|
@@ -11,7 +11,6 @@ describe('normalizePanelWidths', () => {
|
|
|
11
11
|
test('keeps every panel slot from a current blob', () => {
|
|
12
12
|
const raw = {
|
|
13
13
|
left: 342,
|
|
14
|
-
chat: 700,
|
|
15
14
|
editor: 620,
|
|
16
15
|
diff: 900,
|
|
17
16
|
right: { files: 300, browser: 804 },
|
|
@@ -29,8 +28,10 @@ describe('normalizePanelWidths', () => {
|
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
test('drops unusable entries instead of trusting the blob', () => {
|
|
31
|
+
// `chat` is an obsolete slot — the chat column is the filler now — and a
|
|
32
|
+
// blob written before that must not resurrect it.
|
|
32
33
|
const widths = normalizePanelWidths(
|
|
33
|
-
{ left: -10, chat:
|
|
34
|
+
{ left: -10, chat: 700, editor: 620.4, diff: null, right: { files: 0, bogus: 300, git: 260 } },
|
|
34
35
|
'files',
|
|
35
36
|
);
|
|
36
37
|
|
|
@@ -47,7 +48,6 @@ describe('mergePanelWidths', () => {
|
|
|
47
48
|
test('merging one view keeps every other remembered width', () => {
|
|
48
49
|
const stored = {
|
|
49
50
|
left: 342,
|
|
50
|
-
chat: 700,
|
|
51
51
|
editor: 620,
|
|
52
52
|
diff: 900,
|
|
53
53
|
right: { files: 300, git: 260 },
|
|
@@ -63,10 +63,10 @@ describe('mergePanelWidths', () => {
|
|
|
63
63
|
|
|
64
64
|
test('a resized panel replaces only its own slot', () => {
|
|
65
65
|
const merged = mergePanelWidths(
|
|
66
|
-
{
|
|
66
|
+
{ left: 300, editor: 620, diff: 900, right: { files: 300 } },
|
|
67
67
|
{ editor: 480 },
|
|
68
68
|
);
|
|
69
69
|
|
|
70
|
-
expect(merged).toEqual({
|
|
70
|
+
expect(merged).toEqual({ left: 300, editor: 480, diff: 900, right: { files: 300 } });
|
|
71
71
|
});
|
|
72
72
|
});
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Per-panel width state for the desktop layout.
|
|
3
3
|
*
|
|
4
4
|
* Every resizable surface remembers its OWN width: the session sidebar, the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* into one slot per group is what made switching a panel look
|
|
5
|
+
* editor panel (source and diff tabs separately, since a diff wants room a
|
|
6
|
+
* file view does not) and each right-panel view (see `right-panels.ts`).
|
|
7
|
+
* Collapsing those into one slot per group is what made switching a panel look
|
|
8
|
+
* like a reset. The chat column is the group's filler — flexbox sizes it — so
|
|
9
|
+
* it has no remembered width, only a floor it may not be pushed below.
|
|
8
10
|
*
|
|
9
11
|
* Sizes are pixels — the unit the panel constraints are written in — and are
|
|
10
12
|
* persisted as `app_settings.desktopLayoutSizes`.
|
|
@@ -17,8 +19,6 @@ export type EditorWidthMode = 'editor' | 'diff';
|
|
|
17
19
|
export interface PanelWidths {
|
|
18
20
|
/** Session sidebar. */
|
|
19
21
|
left?: number;
|
|
20
|
-
/** Chat timeline column, the inner stack's first panel. */
|
|
21
|
-
chat?: number;
|
|
22
22
|
/** Editor panel while a source file tab is active. */
|
|
23
23
|
editor?: number;
|
|
24
24
|
/** Editor panel while a diff tab is active. */
|
|
@@ -28,13 +28,26 @@ export interface PanelWidths {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** Fallbacks for panels that have never been resized. */
|
|
31
|
-
export const DEFAULT_PANEL_WIDTHS: Record<EditorWidthMode | 'left'
|
|
31
|
+
export const DEFAULT_PANEL_WIDTHS: Record<EditorWidthMode | 'left', number> = {
|
|
32
32
|
left: 268,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
editor: 600,
|
|
34
|
+
// Side-by-side diffs need both halves readable at once: `SplitView` states a
|
|
35
|
+
// 700px floor of its own, so anything below it scrolls sideways on open.
|
|
36
|
+
diff: 720,
|
|
36
37
|
};
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Panel floors and ceilings. The sidebar ceiling and the chat floor are a pair:
|
|
41
|
+
* `MIN_CHAT_PANEL_WIDTH` is what stops the workspace stack from eating the
|
|
42
|
+
* conversation, and it is also why the sidebar cannot open past 520 and still
|
|
43
|
+
* leave the editor (300) and a browser view (320) room on a 1440px display.
|
|
44
|
+
*/
|
|
45
|
+
export const MIN_LEFT_PANEL_WIDTH = 200;
|
|
46
|
+
export const MAX_LEFT_PANEL_WIDTH = 520;
|
|
47
|
+
export const MIN_CHAT_PANEL_WIDTH = 420;
|
|
48
|
+
export const MIN_EDITOR_PANEL_WIDTH = 300;
|
|
49
|
+
export const MAX_EDITOR_PANEL_WIDTH = 1200;
|
|
50
|
+
|
|
38
51
|
function pixelWidth(value: unknown): number | undefined {
|
|
39
52
|
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
|
|
40
53
|
}
|
|
@@ -50,7 +63,7 @@ export function normalizePanelWidths(raw: unknown, legacyView: RightPanelType):
|
|
|
50
63
|
const source = raw as Record<string, unknown>;
|
|
51
64
|
|
|
52
65
|
const widths: PanelWidths = {};
|
|
53
|
-
for (const key of ['left', '
|
|
66
|
+
for (const key of ['left', 'editor', 'diff'] as const) {
|
|
54
67
|
const value = pixelWidth(source[key]);
|
|
55
68
|
if (value !== undefined) widths[key] = value;
|
|
56
69
|
}
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The list lives here rather than in the activity bar component so the layout
|
|
6
6
|
* can key one remembered width per view (`panel-widths.ts`) and validate a
|
|
7
|
-
* stored blob without importing UI.
|
|
7
|
+
* stored blob without importing UI. It is also the single source of the views'
|
|
8
|
+
* *order*: the desktop activity bar, the desktop panel stack and the phone's
|
|
9
|
+
* tab bar all render from it, so the two layouts cannot drift apart.
|
|
8
10
|
*/
|
|
9
11
|
export const RIGHT_PANEL_TYPES = [
|
|
12
|
+
'context',
|
|
10
13
|
'files',
|
|
11
14
|
'search',
|
|
12
15
|
'git',
|
|
13
16
|
'terminal',
|
|
14
|
-
'context',
|
|
15
17
|
'user-browser',
|
|
16
18
|
'browser',
|
|
17
19
|
'usage',
|
|
@@ -24,7 +26,9 @@ export const DEFAULT_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
|
24
26
|
files: 268,
|
|
25
27
|
search: 268,
|
|
26
28
|
git: 268,
|
|
27
|
-
terminal
|
|
29
|
+
// 80 columns at the terminal's 12px Fira Code (≈7.2px/char) need ≈600px
|
|
30
|
+
// before the scrollbar; the old 536 landed at ≈74 columns and wrapped.
|
|
31
|
+
terminal: 640,
|
|
28
32
|
context: 536,
|
|
29
33
|
'user-browser': 804,
|
|
30
34
|
browser: 804,
|
|
@@ -36,7 +40,9 @@ export const MIN_RIGHT_PANEL_WIDTHS: Record<RightPanelType, number> = {
|
|
|
36
40
|
files: 200,
|
|
37
41
|
search: 200,
|
|
38
42
|
git: 260,
|
|
39
|
-
terminal
|
|
43
|
+
// A 200px terminal is ≈27 columns — below every shell's assumption; 320
|
|
44
|
+
// holds a two-pane TUI or a git log with the graph.
|
|
45
|
+
terminal: 320,
|
|
40
46
|
context: 420,
|
|
41
47
|
'user-browser': 320,
|
|
42
48
|
browser: 320,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.rspackChunkompchamber=self.rspackChunkompchamber||[]).push([[73],{tc(e,r,t){var n=t("I6l"),a=t("P0");t("MM"),t("Yr");var s=t("M4"),p=t("Gt"),l=t("Xe"),i=(0,l.WG)().RailroadEbnf.parser.LangiumParser,o=(0,p.K)(e=>{let r=e.alternatives.map(m);return 1===r.length?r[0]:{type:"choice",alternatives:r}},"transformChoice"),m=(0,p.K)(e=>{let r=e.elements.map(f);return 1===r.length?r[0]:{type:"sequence",elements:r}},"transformSequence"),u=(0,p.K)(e=>{switch(e.$type){case"EbnfTerminal":return{type:"terminal",value:e.value};case"EbnfNonTerminal":return{type:"nonterminal",name:e.name};case"EbnfSpecial":return{type:"special",text:e.text};case"EbnfGroup":return o(e.element);case"EbnfOptional":return{type:"optional",element:o(e.element)};case"EbnfRepetition":return{type:"repetition",element:o(e.element),min:0,max:1/0};default:throw Error(`Unsupported EBNF primary node: ${e.$type}`)}},"transformPrimary"),c=(0,p.K)((e,r)=>{switch(r.$type){case"EbnfOptionalPostfix":return{type:"optional",element:e};case"EbnfZeroOrMorePostfix":return{type:"repetition",element:e,min:0,max:1/0};case"EbnfOneOrMorePostfix":return{type:"repetition",element:e,min:1,max:1/0};case"EbnfExceptionPostfix":return{type:"sequence",elements:[e,{type:"terminal",value:"-"},u(r.except)]};default:throw Error(`Unsupported EBNF postfix node: ${r.$type}`)}},"transformPostfix"),f=(0,p.K)(e=>e.postfixes.reduce((e,r)=>c(e,r),u(e.base)),"transformTerm"),d=(0,p.K)(e=>({name:e.name,definition:o(e.definition)}),"transformRule"),b=(0,p.K)(e=>{(0,a.S)(e,n.db),e.title&&n.db.setTitle(e.title),e.rules.map(e=>n.db.addRule(d(e)))},"populateDb"),y={parser:{parse:(0,p.K)(e=>{n.db.clear(),s.R.debug("[EBNF Parser] Starting Langium parse");let r=i.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new l.zg(r);let t=r.value;s.R.debug("[EBNF Parser] Parsed rules:",t.rules.length),b(t),s.R.debug("[EBNF Parser] Parse complete")},"parse"),parser:{yy:n.db}},db:n.db,renderer:n.U,styles:n.$};t.d(r,{diagram:()=>y})}}]);
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/*! LICENSE: a.e05da304c8.js.LICENSE.txt */
|
|
2
|
-
"use strict";(self.rspackChunkompchamber=self.rspackChunkompchamber||[]).push([["a"],{HO(e,t,n){n.d(t,{P:()=>u});var r=n("j"),i=n("H");let s=`
|
|
3
|
-
.code-editor-native-textarea {
|
|
4
|
-
-webkit-text-fill-color: transparent;
|
|
5
|
-
}
|
|
6
|
-
.code-editor-native-textarea::placeholder {
|
|
7
|
-
-webkit-text-fill-color: var(--theme-ink);
|
|
8
|
-
opacity: 0.45;
|
|
9
|
-
}
|
|
10
|
-
`;function a(){return(0,r.Y)("style",{dangerouslySetInnerHTML:{__html:s}})}function l(e,t){return e.substring(0,t).split("\n")}function o(e){let{value:t,onValueChange:n,highlight:s,padding:o=0,preClassName:c,textareaClassName:d,style:u,className:p,placeholder:h,readOnly:f,autoFocus:m}=e,x=(0,i.li)(null),b=(0,i.li)({stack:[],offset:-1}),[g,y]=(0,i.J0)(!0);(0,i.vJ)(()=>{let e=x.current;if(!e)return;let t={value:e.value,selectionStart:e.selectionStart,selectionEnd:e.selectionEnd,timestamp:Date.now()},{stack:n,offset:r}=b.current;n.length&&r>-1&&(b.current.stack=n.slice(0,r+1)),b.current.stack.push(t),b.current.offset=b.current.stack.length-1},[t]);let k=e=>{let t=x.current,r=b.current.stack[b.current.offset];if(r&&t&&(b.current.stack[b.current.offset]={...r,selectionStart:t.selectionStart,selectionEnd:t.selectionEnd}),b.current.stack.push({...e,timestamp:Date.now()}),b.current.offset=b.current.stack.length-1,b.current.stack.length>100){let e=b.current.stack.length-100;b.current.stack=b.current.stack.slice(e),b.current.offset=Math.max(b.current.offset-e,0)}n(e.value),t&&(t.value=e.value,t.selectionStart=e.selectionStart,t.selectionEnd=e.selectionEnd)},v={paddingTop:o,paddingRight:o,paddingBottom:o,paddingLeft:o},w=s(t),N={margin:0,border:0,background:"none",boxSizing:"inherit",display:"inherit",fontFamily:"inherit",fontSize:"inherit",fontStyle:"inherit",fontVariantLigatures:"inherit",fontWeight:"inherit",letterSpacing:"inherit",lineHeight:"inherit",tabSize:"inherit",textIndent:"inherit",textRendering:"inherit",textTransform:"inherit",whiteSpace:"pre-wrap",wordBreak:"keep-all",overflowWrap:"break-word"};return(0,r.FD)("div",{className:p,style:{position:"relative",textAlign:"left",boxSizing:"border-box",padding:0,overflow:"hidden",...u},children:[(0,r.Y)("pre",{className:c,"aria-hidden":"true",style:{...N,...v,position:"relative",pointerEvents:"none"},dangerouslySetInnerHTML:{__html:w+"<br />"}}),(0,r.Y)("textarea",{ref:x,className:`code-editor-native-textarea${d?` ${d}`:""}`,style:{...N,...v,position:"absolute",top:0,left:0,height:"100%",width:"100%",resize:"none",color:"inherit",overflow:"hidden",WebkitTextFillColor:"transparent"},value:t,onChange:e=>{let{value:t,selectionStart:r,selectionEnd:i}=e.target,{stack:s,offset:a}=b.current,o=Date.now();s.length&&a>-1&&(b.current.stack=s.slice(0,a+1));let c=b.current.stack[b.current.offset];if(c&&o-c.timestamp<3e3){let e=/[^a-z0-9]([a-z0-9]+)$/i,s=l(c.value,c.selectionStart).pop()?.match(e),a=l(t,r).pop()?.match(e);if(s?.[1]&&a?.[1]?.startsWith(s[1])){b.current.stack[b.current.offset]={value:t,selectionStart:r,selectionEnd:i,timestamp:o},n(t);return}}b.current.stack.push({value:t,selectionStart:r,selectionEnd:i,timestamp:o}),b.current.offset=b.current.stack.length-1,n(t)},onKeyDown:e=>{if("Escape"===e.key)return void e.currentTarget.blur();let{value:t,selectionStart:r,selectionEnd:i}=e.currentTarget,s=/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform),a=/Win/i.test(navigator.platform);if("Tab"===e.key&&g){if(e.preventDefault(),e.shiftKey){let e=l(t,r),n=e.length-1,s=l(t,i).length-1,a=t.split("\n").map((e,t)=>t>=n&&t<=s&&e.startsWith(" ")?e.substring(2):e).join("\n");if(a!==t){let s=e[n];k({value:a,selectionStart:s?.startsWith(" ")?r-2:r,selectionEnd:i-(t.length-a.length)})}}else if(r!==i){let e=l(t,r),n=e.length-1,s=l(t,i).length-1,a=e[n];k({value:t.split("\n").map((e,t)=>t>=n&&t<=s?" "+e:e).join("\n"),selectionStart:a&&/\S/.test(a)?r+2:r,selectionEnd:i+2*(s-n+1)})}else{let e=r+2;k({value:t.substring(0,r)+" "+t.substring(i),selectionStart:e,selectionEnd:e})}return}if("Backspace"===e.key&&r===i){if(t.substring(0,r).endsWith(" ")){e.preventDefault();let n=r-2;k({value:t.substring(0,r-2)+t.substring(i),selectionStart:n,selectionEnd:n})}return}if("Enter"===e.key&&r===i){let n=l(t,r).pop(),s=n?.match(/^\s+/);if(s?.[0]){e.preventDefault();let n="\n"+s[0],a=r+n.length;k({value:t.substring(0,r)+n+t.substring(i),selectionStart:a,selectionEnd:a})}return}if((s?e.metaKey&&"z"===e.key.toLowerCase():e.ctrlKey&&"z"===e.key.toLowerCase())&&!e.shiftKey&&!e.altKey){e.preventDefault();let{stack:t,offset:r}=b.current,i=t[r-1];i&&x.current&&(x.current.value=i.value,x.current.selectionStart=i.selectionStart,x.current.selectionEnd=i.selectionEnd,n(i.value),b.current.offset=Math.max(r-1,0));return}if((s?e.metaKey&&e.shiftKey&&"z"===e.key.toLowerCase():a?e.ctrlKey&&"y"===e.key.toLowerCase():e.ctrlKey&&e.shiftKey&&"z"===e.key.toLowerCase())&&!e.altKey){e.preventDefault();let{stack:t,offset:r}=b.current,i=t[r+1];i&&x.current&&(x.current.value=i.value,x.current.selectionStart=i.selectionStart,x.current.selectionEnd=i.selectionEnd,n(i.value),b.current.offset=Math.min(r+1,t.length-1));return}e.ctrlKey&&"m"===e.key.toLowerCase()&&(!s||e.shiftKey)&&(e.preventDefault(),y(e=>!e))},placeholder:h,readOnly:f,autoFocus:m,autoCapitalize:"off",autoComplete:"off",autoCorrect:"off",spellcheck:!1,"data-gramm":!1}),(0,r.Y)(a,{})]})}var c=n("DU"),d=n("Po");function u(e){let{value:t,onValueChange:n,language:i,wordWrap:s,rootClassName:a,gutterClassName:l,gutterStyle:u,gutterLineClassName:p,editorWrapperClassName:h,editorClassName:f,editorPadding:m=0,editorStyle:x}=e,b=s?"!whitespace-pre-wrap !break-words":"!whitespace-pre !break-normal";return(0,d.w)(),(0,r.FD)("div",{className:a,children:[(0,r.Y)("div",{className:l,style:u,children:t.split("\n").map((e,t)=>(0,r.Y)("div",{className:p,children:t+1},t+1))}),(0,r.Y)("div",{className:h,children:(0,r.Y)(o,{value:t,onValueChange:n,highlight:e=>(0,c.wY)(e,i),padding:m,textareaClassName:`focus:outline-none ${b}`,preClassName:b,style:x,className:f})})]})}},Xg(e,t,n){n.d(t,{M:()=>S});var r=n("j"),i=n("H"),s=n("X"),a=n("FL"),l=n("DU"),o=n("l"),c=n("y");let d=(0,c.A)({name:"text-align-justify",size:24,node:[["path",{d:"M3 5h18",key:"1u36vt"}],["path",{d:"M3 12h18",key:"1i2n21"}],["path",{d:"M3 19h18",key:"awlh7x"}]],aliases:["align-justify"]}),u=(0,c.A)({name:"space",size:24,node:[["path",{d:"M22 17v1c0 .5-.5 1-1 1H3c-.5 0-1-.5-1-1v-1",key:"lt2kga"}]]});var p=n("n"),h=n("z"),f=n("b0"),m=n("I"),x=n("DV"),b=n("r"),g=n("AB"),y=n("ob"),k=n("LQ");function v(e){let{filePath:t,status:n,isStaged:i,viewMode:s,ignoreWhitespace:a,additions:l,deletions:c,isLoading:v,isCopied:w,onToggleViewMode:N,onToggleWhitespace:Y,onStageUnstage:D,onDiscard:S,onOpenInEditor:F,onRefresh:z,onCopyDiff:T}=e,C=t.split("/").pop()||t,L=(0,k.R)(n,i);return(0,r.FD)("div",{className:"flex items-center justify-between px-3 py-1.5 bg-paper border-b border-ink/10 text-xs font-sans select-none flex-shrink-0 overflow-x-auto no-scrollbar",children:[(0,r.FD)("div",{className:"hidden md:flex items-center space-x-2 min-w-0 pr-2",children:[(0,r.Y)(y.o,{name:C,size:14,className:"flex-shrink-0"}),(0,r.Y)("span",{className:"font-mono text-ink font-medium truncate",title:t,children:C}),(0,r.FD)("span",{className:`px-1.5 py-0.5 text-[10px] font-mono font-semibold rounded border ${L.badgeBgClass} flex-shrink-0`,children:[L.charStatus," ",L.label]}),(l>0||c>0)&&(0,r.FD)("div",{className:"flex items-center space-x-1.5 text-[11px] font-mono flex-shrink-0 ml-1",children:[l>0&&(0,r.FD)("span",{className:"text-success font-semibold",children:["+",l]}),c>0&&(0,r.FD)("span",{className:"text-error font-semibold",children:["-",c]})]})]}),(0,r.FD)("div",{className:"flex items-center space-x-1 text-ink/70 flex-shrink-0 ml-auto",children:[(0,r.FD)("div",{className:"flex items-center bg-canvas rounded border border-ink/15 p-0.5 mr-1",children:[(0,r.FD)("button",{type:"button",onClick:N,className:`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors ${"split"===s?"bg-paper text-ink shadow-xs":"text-ink/60 hover:text-ink"}`,title:"Split (side-by-side) Diff",children:[(0,r.Y)(o.A,{size:12}),(0,r.Y)("span",{className:"hidden sm:inline",children:"Split"})]}),(0,r.FD)("button",{type:"button",onClick:N,className:`px-2 py-0.5 rounded text-[11px] font-medium flex items-center space-x-1 transition-colors ${"unified"===s?"bg-paper text-ink shadow-xs":"text-ink/60 hover:text-ink"}`,title:"Unified Diff",children:[(0,r.Y)(d,{size:12}),(0,r.Y)("span",{className:"hidden sm:inline",children:"Unified"})]})]}),(0,r.Y)("button",{type:"button",onClick:Y,className:`p-1.5 rounded transition-colors ${a?"bg-ink/10 text-ink":"text-ink/50 hover:text-ink hover:bg-ink/5"}`,title:a?"Whitespace ignored":"Ignore whitespace",children:(0,r.Y)(u,{size:13})}),(0,r.Y)("button",{type:"button",onClick:z,className:"p-1.5 text-ink/50 hover:text-ink hover:bg-ink/5 rounded transition-colors",title:"Refresh Diff",disabled:v,children:(0,r.Y)(p.A,{size:13,className:v?"animate-spin":""})}),(0,r.Y)("button",{type:"button",onClick:T,className:"p-1.5 text-ink/50 hover:text-ink hover:bg-ink/5 rounded transition-colors",title:"Copy Diff Text",children:w?(0,r.Y)(h.A,{size:13,className:"text-success"}):(0,r.Y)(f.A,{size:13})}),!i&&(0,r.Y)("button",{type:"button",onClick:S,className:"p-1.5 text-ink/50 hover:text-error hover:bg-error/10 rounded transition-colors",title:"Discard changes in this file",children:(0,r.Y)(m.A,{size:13})}),(0,r.Y)("button",{type:"button",onClick:D,className:"px-2 py-1 text-[11px] font-medium rounded border border-ink/15 hover:bg-ink/5 flex items-center space-x-1 transition-colors text-ink",title:i?"Unstage file":"Stage file",children:i?(0,r.FD)(r.FK,{children:[(0,r.Y)(x.A,{size:12}),(0,r.Y)("span",{children:"Unstage"})]}):(0,r.FD)(r.FK,{children:[(0,r.Y)(b.A,{size:12}),(0,r.Y)("span",{children:"Stage"})]})}),F&&(0,r.FD)("button",{type:"button",onClick:F,className:"px-2 py-1 text-[11px] font-medium rounded bg-ink text-canvas hover:bg-ink/90 flex items-center space-x-1 transition-colors shadow-xs ml-1",title:"Open in Code Editor",children:[(0,r.Y)(g.A,{size:12}),(0,r.Y)("span",{className:"hidden sm:inline",children:"Edit File"})]})]})]})}var w=n("Po");function N(e){let{lines:t,language:n}=e,s=(0,w.w)(),a=(0,i.Kr)(()=>{let e=Array(t.length).fill(""),r=-1,i=i=>{if(r<0)return;let s=(0,l.O9)(t.slice(r,i).map(e=>e.text).join("\n"),n);for(let t=r;t<i;t++)e[t]=s[t-r]??"";r=-1};return t.forEach((e,t)=>{"meta"===e.type?i(t):r<0&&(r=t)}),i(t.length),e},[t,n,s]);return 0===t.length?(0,r.Y)("div",{className:"flex-1 flex items-center justify-center text-ink/40 font-mono text-xs p-8",children:"No differences between working file and target."}):(0,r.Y)("div",{className:"w-full h-full overflow-auto font-mono text-xs select-text bg-paper text-ink code-surface",children:(0,r.Y)("table",{className:"w-max min-w-full border-collapse",children:(0,r.Y)("tbody",{children:t.map((e,t)=>{if("meta"===e.type)return(0,r.FD)("tr",{className:"bg-canvas border-y border-ink/10 text-meta select-none",children:[(0,r.Y)("td",{className:"w-12 text-center py-1 px-2 text-[10px] opacity-60 border-r border-ink/10 font-mono",children:"..."}),(0,r.Y)("td",{className:"w-12 text-center py-1 px-2 text-[10px] opacity-60 border-r border-ink/10 font-mono",children:"..."}),(0,r.Y)("td",{className:"py-1 px-3 text-[11px] font-semibold text-meta font-mono tracking-tight",children:e.text})]},t);let n="add"===e.type,i="del"===e.type;return(0,r.FD)("tr",{className:`transition-colors group ${n?"bg-success/10 hover:bg-success/15":i?"bg-error/10 hover:bg-error/15":"hover:bg-ink/[0.03]"}`,children:[(0,r.Y)("td",{className:`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${i?"bg-error/15 text-error font-semibold":n?"bg-success/15 text-success/70":"bg-canvas/50 text-ink/40"}`,children:e.oldLineNumber||""}),(0,r.Y)("td",{className:`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${n?"bg-success/15 text-success font-semibold":i?"bg-error/15 text-error/70":"bg-canvas/50 text-ink/40"}`,children:e.newLineNumber||""}),(0,r.FD)("td",{className:"py-0.5 px-3 whitespace-pre text-ink pr-6",children:[(0,r.Y)("span",{className:`inline-block w-4 text-center select-none font-bold ${n?"text-success":i?"text-error":"opacity-0"}`,children:n?"+":i?"-":" "}),(0,r.Y)("span",{className:"shiki",dangerouslySetInnerHTML:{__html:e.text&&a[t]||" "}})]})]},t)})})})})}function Y(e){let{rows:t,language:n}=e,s=(0,w.w)(),a=(0,i.Kr)(()=>{let e=Array(t.length).fill(""),r=Array(t.length).fill(""),i=-1,s=s=>{if(i<0)return;let a=t.slice(i,s),o=(0,l.O9)(a.map(e=>e.left?.text??"").join("\n"),n),c=(0,l.O9)(a.map(e=>e.right?.text??"").join("\n"),n);for(let t=i;t<s;t++)e[t]=o[t-i]??"",r[t]=c[t-i]??"";i=-1};return t.forEach((e,t)=>{e.isMeta?s(t):i<0&&(i=t)}),s(t.length),{left:e,right:r}},[t,n,s]);return 0===t.length?(0,r.Y)("div",{className:"flex-1 flex items-center justify-center text-ink/40 font-mono text-xs p-8",children:"No differences between working file and target."}):(0,r.Y)("div",{className:"w-full h-full overflow-auto font-mono text-xs select-text bg-paper text-ink code-surface",children:(0,r.FD)("div",{className:"w-max min-w-full min-w-[700px]",children:[(0,r.FD)("div",{className:"grid grid-cols-2 sticky top-0 z-10 bg-canvas border-b border-ink/10 text-[11px] text-ink/70 font-sans select-none shadow-xs",children:[(0,r.Y)("div",{className:"px-3 py-1 font-medium border-r border-ink/10 flex items-center justify-between",children:(0,r.Y)("span",{children:"Original (HEAD / Base)"})}),(0,r.Y)("div",{className:"px-3 py-1 font-medium flex items-center justify-between",children:(0,r.Y)("span",{children:"Modified (Working Tree)"})})]}),(0,r.Y)("table",{className:"w-full border-collapse",children:(0,r.Y)("tbody",{children:t.map((e,t)=>{if(e.isMeta)return(0,r.Y)("tr",{className:"bg-canvas border-y border-ink/10 text-meta select-none",children:(0,r.Y)("td",{colSpan:4,className:"py-1 px-3 text-[11px] font-semibold text-meta text-center font-mono",children:e.metaText})},t);let n=e.left,i=e.right,s=n?.type==="del",l=i?.type==="add";return(0,r.FD)("tr",{className:"border-b border-ink/[0.04]",children:[(0,r.Y)("td",{className:`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${s?"bg-error/15 text-error font-semibold":n?"bg-canvas/50 text-ink/40":"bg-canvas/80 text-transparent"}`,children:n?.lineNumber||""}),(0,r.Y)("td",{className:`min-w-[320px] py-0.5 px-2 whitespace-pre border-r border-ink/10 text-ink ${s?"bg-error/10":n?"hover:bg-ink/[0.02]":"bg-canvas/40 select-none"}`,children:n?(0,r.FD)(r.FK,{children:[(0,r.Y)("span",{className:`inline-block w-4 text-center select-none font-bold ${s?"text-error":"opacity-0"}`,children:s?"-":" "}),(0,r.Y)("span",{className:"shiki",dangerouslySetInnerHTML:{__html:n.text&&a.left[t]||" "}})]}):(0,r.Y)("span",{className:"opacity-0",children:" "})}),(0,r.Y)("td",{className:`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${l?"bg-success/15 text-success font-semibold":i?"bg-canvas/50 text-ink/40":"bg-canvas/80 text-transparent"}`,children:i?.lineNumber||""}),(0,r.Y)("td",{className:`min-w-[320px] py-0.5 px-2 whitespace-pre text-ink ${l?"bg-success/10":i?"hover:bg-ink/[0.02]":"bg-canvas/40 select-none"}`,children:i?(0,r.FD)(r.FK,{children:[(0,r.Y)("span",{className:`inline-block w-4 text-center select-none font-bold ${l?"text-success":"opacity-0"}`,children:l?"+":" "}),(0,r.Y)("span",{className:"shiki",dangerouslySetInnerHTML:{__html:i.text&&a.right[t]||" "}})]}):(0,r.Y)("span",{className:"opacity-0",children:" "})})]},t)})})})]})})}var D=n("p");function S(e){let{filePath:t,status:n="M",isStaged:o=!1,root:c,repo:d=".",onOpenInEditor:u,onFileSaved:h,className:f=""}=e,[m,x]=(0,a.LO)("diff.viewMode","unified"),[b,g]=(0,a.LO)("diff.ignoreWhitespace",!1),[y,k]=(0,i.J0)(""),[w,S]=(0,i.J0)(n),[F,z]=(0,i.J0)(o),[T,C]=(0,i.J0)(!0),[L,j]=(0,i.J0)(null),[A,M]=(0,i.J0)(!1),[E,W]=(0,i.J0)(!1),$=(0,s.L)(),J=(0,i.hb)(async()=>{if(t){C(!0),j(null);try{let e=new URLSearchParams({fileDiff:"1",file:t,staged:F?"1":"0",repo:d||"."});(n||w)&&e.set("status",n||w),c&&e.set("root",c);let r=await fetch(`/api/fs/git?${e.toString()}`),i=await r.json();i.success?(k(i.diff||""),i.status&&S(i.status)):j(i.error||"Failed to load file diff")}catch(e){j(e.message||"Error fetching diff")}finally{C(!1)}}},[t,F,d,c]);(0,i.vJ)(()=>{S(n),z(o)},[n,o,t]),(0,i.vJ)(()=>{J()},[J]);let K=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=[],r=[],i=0,s=0;if(!e||!e.trim())return{lines:n,splitRows:r,additions:i,deletions:s};let a=e.split(/\r?\n/),l=a.some(e=>e.startsWith("@@"))?a:[`@@ -1,${a.length} +1,${a.length} @@`,...a],o=1,c=1,d=[],u=[],p=()=>{let e=Math.max(d.length,u.length);for(let t=0;t<e;t++)r.push({left:d[t]?{type:"del",text:d[t].text,lineNumber:d[t].lineNumber}:void 0,right:u[t]?{type:"add",text:u[t].text,lineNumber:u[t].lineNumber}:void 0});d=[],u=[]};for(let e of l)if(!(e.startsWith("diff --git")||e.startsWith("index ")||e.startsWith("---")||e.startsWith("+++")||e.startsWith("new file mode")||e.startsWith("deleted file mode")||e.startsWith("similarity index")||e.startsWith("old mode")||e.startsWith("new mode")||e.startsWith("\\ No newline"))){if(e.startsWith("@@")){p();let t=e.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);t&&(o=parseInt(t[1],10),c=parseInt(t[2],10)),n.push({type:"meta",text:e}),r.push({isMeta:!0,metaText:e});continue}if(e.startsWith("+")){let r=e.slice(1),s=t?r.trim():r;i++,n.push({type:"add",text:s,newLineNumber:c}),u.push({text:s,lineNumber:c}),c++}else if(e.startsWith("-")){let r=e.slice(1),i=t?r.trim():r;s++,n.push({type:"del",text:i,oldLineNumber:o}),d.push({text:i,lineNumber:o}),o++}else{p();let i=e.startsWith(" ")?e.slice(1):e,s=t?i.trim():i;n.push({type:"context",text:s,oldLineNumber:o,newLineNumber:c}),r.push({left:{type:"context",text:s,lineNumber:o},right:{type:"context",text:s,lineNumber:c}}),o++,c++}}return p(),{lines:n,splitRows:r,additions:i,deletions:s}}(y,b),O=(0,l.sQ)(t);return(0,r.FD)("div",{className:`flex flex-col h-full bg-paper select-text relative font-sans ${f}`,children:[(0,r.Y)(v,{filePath:t,status:w,isStaged:F,viewMode:m,ignoreWhitespace:b,additions:K.additions,deletions:K.deletions,isLoading:T,isCopied:A,onToggleViewMode:()=>{x("unified"===m?"split":"unified")},onToggleWhitespace:()=>{g(!b)},onStageUnstage:()=>{let e=F?"unstage":"stage",n=new FormData;n.set("actionType",e),n.set("file",t),n.set("repo",d||"."),c&&n.set("root",c),$.submit(n,{method:"POST",action:"/api/fs/git"}),z(!F),setTimeout(()=>{J(),h?.()},300)},onDiscard:()=>{W(!0)},onOpenInEditor:u,onRefresh:J,onCopyDiff:()=>{y&&(navigator.clipboard.writeText(y),M(!0),setTimeout(()=>M(!1),2e3))}}),(0,r.Y)("div",{className:"flex-1 overflow-hidden relative flex flex-col bg-paper",children:T?(0,r.FD)("div",{className:"flex-1 flex items-center justify-center space-x-2 text-ink/50 text-xs font-mono",children:[(0,r.Y)(p.A,{size:14,className:"animate-spin"}),(0,r.Y)("span",{children:"Loading file diff..."})]}):L?(0,r.FD)("div",{className:"flex-1 flex flex-col items-center justify-center p-6 text-center space-y-2",children:[(0,r.Y)(D.A,{size:20,className:"text-error"}),(0,r.Y)("p",{className:"text-xs text-error font-medium",children:L}),(0,r.Y)("button",{onClick:J,className:"px-3 py-1 text-xs bg-ink/10 hover:bg-ink/15 text-ink rounded font-sans transition-colors",children:"Retry"})]}):"split"===m?(0,r.Y)(Y,{rows:K.splitRows,language:O}):(0,r.Y)(N,{lines:K.lines,language:O})}),E&&(0,r.Y)("div",{className:"fixed inset-0 bg-ink/20 z-50 flex items-center justify-center p-4 backdrop-blur-xs",children:(0,r.FD)("div",{className:"bg-paper rounded-lg shadow-xl p-4 w-full max-w-sm border border-ink/15",children:[(0,r.Y)("h3",{className:"font-semibold text-ink mb-1.5 text-sm",children:"Discard Changes?"}),(0,r.FD)("p",{className:"text-xs text-ink/70 mb-4",children:["Are you sure you want to discard all unstaged changes in ",(0,r.Y)("span",{className:"font-mono font-medium text-ink",children:t}),"? This cannot be undone."]}),(0,r.FD)("div",{className:"flex justify-end space-x-2",children:[(0,r.Y)("button",{type:"button",onClick:()=>W(!1),className:"px-3 py-1.5 text-xs text-ink hover:bg-ink/5 rounded transition-colors",children:"Cancel"}),(0,r.Y)("button",{type:"button",onClick:()=>{W(!1);let e=new FormData;e.set("actionType","discard"),e.set("file",t),e.set("repo",d||"."),c&&e.set("root",c),$.submit(e,{method:"POST",action:"/api/fs/git"}),setTimeout(()=>{J(),h?.()},300)},className:"px-3 py-1.5 text-xs bg-error text-white rounded hover:bg-error/90 font-medium transition-colors",children:"Discard Changes"})]})]})})]})}},XY(e,t,n){var r=n("H"),i=n("DU");function s(e){return String(e.id??e.path??e.name)}function a(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},{fallback:n,reportLoadError:a=!1,flushOnUnmount:l=!1,reportSaveError:o=!1,downloadMimeType:c="text/plain",onFileSaved:d}=t,[u,p]=(0,r.J0)({}),[h,f]=(0,r.J0)({}),[m,x]=(0,r.J0)(!1),[b,g]=(0,r.J0)(null),[y,k]=(0,r.J0)("idle"),[v,w]=(0,r.J0)(!1),N=e?s(e):"",Y=e?u[N]??e.content??"":"",D=Y!==(h[N]??Y),S=(0,r.li)(new Set),F=(0,r.li)(null),z=(0,r.hb)((e,t)=>{p(n=>({...n,[e]:t})),f(n=>({...n,[e]:t}))},[]),T=(0,r.hb)(async(e,t)=>{if(!e.path)return;k("saving");let n=new FormData;n.append("actionType","save"),n.append("path",e.path),n.append("content",t),e.root&&n.append("root",e.root),e.repo&&"."!==e.repo&&n.append("repo",e.repo);try{let r=await fetch("/api/fs/action",{method:"POST",body:n}),i=await r.json().catch(()=>null);r.ok&&i?.success?(k("saved"),f(n=>({...n,[s(e)]:t})),d?.()):k(o?"error":"idle")}catch{k(o?"error":"idle")}},[d,o]),C=(0,r.li)(T);C.current=T,(0,r.vJ)(()=>{if(!e)return;let t=s(e);if(S.current.has(t))return;if(S.current.add(t),void 0!==e.content){z(t,e.content),g(null);return}if(!e.path)return a?void g("No file path supplied for this attachment."):void z(t,n?n(e.name):"");let r=!1;x(!0),g(null);let i=new URLSearchParams({path:e.path});return e.root&&i.set("root",e.root),e.repo&&"."!==e.repo&&i.set("repo",e.repo),fetch(`/api/fs/read?${i.toString()}`).then(async e=>{let t=await e.json().catch(()=>null);if(a){if(!e.ok||!t||"string"!=typeof t.content)throw Error(t?.error||`HTTP ${e.status}`);return t.content}return"string"==typeof t?.content?t.content:null}).then(i=>{r||z(t,i??(n?n(e.name):""))}).catch(i=>{r||(a?(z(t,""),g(i instanceof Error?i.message:"Failed to read file")):z(t,n?n(e.name):""))}).finally(()=>{r||x(!1)}),()=>{r=!0}},[N,e,n,a,z]),(0,r.vJ)(()=>{if("saved"!==y)return;let e=window.setTimeout(()=>k("idle"),1500);return()=>window.clearTimeout(e)},[y]);let L=(0,r.hb)(t=>{if(!e)return;let n=s(e);if(p(e=>({...e,[n]:t})),!e.path)return;F.current&&window.clearTimeout(F.current.timer);let r=window.setTimeout(()=>{F.current=null,C.current(e,t)},800);F.current={timer:r,file:e,content:t}},[e]),j=(0,r.hb)(()=>{if(!e)return;let t=u[s(e)];void 0!==t&&(F.current&&(window.clearTimeout(F.current.timer),F.current=null),C.current(e,t))},[e,u]),A=(0,r.hb)(()=>{e&&navigator.clipboard&&navigator.clipboard.writeText(u[s(e)]??"").then(()=>{w(!0),window.setTimeout(()=>w(!1),1500)}).catch(()=>{})},[e,u]),M=(0,r.hb)(()=>{if(!e)return;let t=new Blob([u[s(e)]??""],{type:c}),n=URL.createObjectURL(t),r=document.createElement("a");r.href=n,r.download=e.name,r.click(),URL.revokeObjectURL(n)},[e,u,c]),E=(0,r.hb)(()=>{N&&(S.current.delete(N),p(e=>{if(void 0===e[N])return e;let t={...e};return delete t[N],t}))},[N]),W=(0,r.hb)(()=>{S.current.clear(),p({}),f({})},[]),$=(0,r.hb)(e=>{let t=new Set(e.map(String));for(let e of Array.from(S.current))t.has(e)||S.current.delete(e);p(e=>{let n=Object.keys(e);if(n.every(e=>t.has(e)))return e;let r={};for(let i of n)t.has(i)&&(r[i]=e[i]);return r})},[]);return(0,r.vJ)(()=>{if(l)return()=>{let e=F.current;e&&(window.clearTimeout(e.timer),F.current=null,C.current(e.file,e.content))}},[l]),{content:Y,onChange:L,isLoading:m,loadError:b,saveStatus:y,copied:v,isDirty:D,language:e?(0,i.sQ)(e.name):"javascript",saveNow:j,copy:A,download:M,reload:E,reset:W,retain:$}}n.d(t,{n:()=>a})},Soe(e,t,n){let r=(0,n("y").A)({name:"eye-off",size:24,node:[["path",{d:"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",key:"ct8e1f"}],["path",{d:"M14.084 14.158a3 3 0 0 1-4.242-4.242",key:"151rxh"}],["path",{d:"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",key:"13bj9a"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]});n.d(t,{},{A:r})},DV(e,t,n){let r=(0,n("y").A)({name:"minus",size:24,node:[["path",{d:"M5 12h14",key:"1ays0h"}]]});n.d(t,{},{A:r})},yh(e,t,n){let r=(0,n("y").A)({name:"text-wrap",size:24,node:[["path",{d:"m16 16-3 3 3 3",key:"117b85"}],["path",{d:"M3 12h14.5a1 1 0 0 1 0 7H13",key:"18xa6z"}],["path",{d:"M3 19h6",key:"1ygdsz"}],["path",{d:"M3 5h18",key:"1u36vt"}]],aliases:["wrap-text"]});n.d(t,{},{A:r})},na(e,t,n){let r=(0,n("y").A)({name:"zoom-in",size:24,node:[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"11",x2:"11",y1:"8",y2:"14",key:"1vmskp"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]});n.d(t,{},{A:r})},hj(e,t,n){let r=(0,n("y").A)({name:"zoom-out",size:24,node:[["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}],["line",{x1:"21",x2:"16.65",y1:"21",y2:"16.65",key:"13gj7c"}],["line",{x1:"8",x2:"14",y1:"11",y2:"11",key:"durymu"}]]});n.d(t,{},{A:r})}}]);
|