Terminal file viewer/editor + per-window app-command lockdown (#60)
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
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>
This commit was merged in pull request #60.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user