Build App / compute-version (push) Successful in 7s
Secret Scan / scan (push) Successful in 8s
Build App / build-macos (push) Successful in 2m53s
Build App / build-linux (push) Successful in 5m12s
Build App / build-windows (push) Successful in 5m15s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 1m5s
Clicking a file path in Claude's terminal output now opens the file in its own window with a CodeMirror 6 editor. The editor highlights the target line, live-reloads while the file changes, and saves explicitly with hash-based conflict detection. The viewer commands are gated by window label. Every app command is now ACL-gated per window through a Tauri AppManifest. build.rs checks the handler list against the capability files and fails the build on any mismatch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import Button from "../components/ui/Button";
|
|
import { viewerChooseFile, viewerGetState } from "../lib/tauri-commands";
|
|
import type { ViewerState } from "../lib/types";
|
|
import EditorPane from "./EditorPane";
|
|
|
|
const errorText = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
|
|
|
export default function ViewerApp() {
|
|
const [state, setState] = useState<ViewerState | { error: string } | null>(null);
|
|
const [chooseError, setChooseError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
viewerGetState().then(setState, (e) => setState({ error: errorText(e) }));
|
|
}, []);
|
|
|
|
if (state === null) return <p className="p-4 text-sm text-[var(--text-secondary)]">Loading…</p>;
|
|
if ("error" in state) return <p className="p-4 text-sm">{state.error}</p>;
|
|
|
|
const choose = (index: number) => {
|
|
setChooseError(null);
|
|
viewerChooseFile(index).then(setState, (e) => setChooseError(errorText(e)));
|
|
};
|
|
|
|
switch (state.state.kind) {
|
|
case "resolved":
|
|
return <EditorPane state={state} />;
|
|
case "not_found":
|
|
return (
|
|
<div className="p-4 text-sm">
|
|
<p>Could not find <span className="font-mono">{state.raw_path}</span> in the container. Looked in:</p>
|
|
<ul className="mt-2 list-disc pl-6 font-mono text-xs text-[var(--text-secondary)]">
|
|
{state.state.tried.map((p) => <li key={p}>{p}</li>)}
|
|
</ul>
|
|
</div>
|
|
);
|
|
case "choose":
|
|
return (
|
|
<div className="p-4 text-sm">
|
|
<p>Several files match <span className="font-mono">{state.raw_path}</span>. Open which?</p>
|
|
<ul className="mt-2 flex flex-col items-start gap-1">
|
|
{state.state.candidates.map((p, i) => (
|
|
<li key={p}>
|
|
<Button size="sm" onClick={() => choose(i)}>
|
|
<span className="font-mono">{p}</span>
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{chooseError && <p role="alert" className="mt-2">{chooseError}</p>}
|
|
</div>
|
|
);
|
|
}
|
|
}
|