feat(shell): 30/55/15 layout, formatted activity log, instant resume, stop confirmation
Left panels 30vw / central 55% / right rail 15vw, minimal padding. Model-activity log and documents render via .thot-prose; decisions become hairline rows with semantic type chips. Resume switches the view optimistically (instant feedback). The Stop button now asks for confirmation before interrupting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { SessionMenu } from "./SessionMenu";
|
||||
import { SessionDocumentsPanel } from "./SessionDocumentsPanel";
|
||||
import { RenameDialog } from "./RenameDialog";
|
||||
import { DeleteConfirmDialog } from "./DeleteConfirmDialog";
|
||||
import { StopConfirmDialog } from "./StopConfirmDialog";
|
||||
import { SteerInput, ComposerFooter } from "./SteerInput";
|
||||
import { WorkflowBar } from "./WorkflowBar";
|
||||
import { Pencil } from "lucide-react";
|
||||
@@ -42,6 +43,7 @@ export function AppShell() {
|
||||
const [archiveOpen, setArchiveOpen] = useState(false);
|
||||
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
|
||||
const [stopConfirm, setStopConfirm] = useState(false);
|
||||
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
|
||||
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
|
||||
|
||||
@@ -68,12 +70,16 @@ export function AppShell() {
|
||||
});
|
||||
}
|
||||
async function doResume(id: string) {
|
||||
const s = sessions.find((x) => x.id === id) ?? null;
|
||||
// Optimistic switch: change to the session view IMMEDIATELY so the click feels
|
||||
// instant (the resume POST spawns a Pi process and can take seconds). The
|
||||
// working spinner shows straight away; the backend calls run after.
|
||||
setPanelSession(null);
|
||||
resetSession();
|
||||
setActiveSessionId(id);
|
||||
try {
|
||||
await resumeSession(id);
|
||||
setPanelSession(null);
|
||||
setActiveSessionId(id);
|
||||
// Optimistic phase paint on resume: colour the re-entry phase immediately,
|
||||
// before the first gate arrives (mirrors the F1 paint for new questions).
|
||||
// Optimistic phase paint: colour the re-entry phase before the first gate.
|
||||
// The manifest's `phase` is the 1-based current phase (1..8).
|
||||
try {
|
||||
const m = (await getSession(id)) as { phase?: number };
|
||||
@@ -84,6 +90,10 @@ export function AppShell() {
|
||||
/* non-fatal: the first gate will set the phase */
|
||||
}
|
||||
} catch {
|
||||
// Revert the optimistic switch and restore the panel.
|
||||
resetSession();
|
||||
setActiveSessionId(null);
|
||||
if (s) setPanelSession(s);
|
||||
toast.error("Failed to resume session.");
|
||||
}
|
||||
}
|
||||
@@ -216,7 +226,7 @@ export function AppShell() {
|
||||
<SteerInput
|
||||
sessionId={activeSessionId}
|
||||
onSessionCreated={setActiveSessionId}
|
||||
onStop={stopSession}
|
||||
onStop={() => setStopConfirm(true)}
|
||||
inputRef={composerRef}
|
||||
/>
|
||||
</div>
|
||||
@@ -227,7 +237,7 @@ export function AppShell() {
|
||||
</div>
|
||||
|
||||
{/* Right session rail — symmetric to the portal's left sidebar */}
|
||||
<aside className="flex w-[280px] shrink-0 flex-col border-l border-border bg-sidebar">
|
||||
<aside className="flex w-[15vw] shrink-0 flex-col border-l border-border bg-sidebar">
|
||||
<div className="relative px-4 pb-3 pt-5 text-center">
|
||||
<h1 className="font-heading text-xl font-semibold leading-none tracking-tight text-foreground">
|
||||
Thoth<span className="text-primary">II</span>
|
||||
@@ -333,6 +343,12 @@ export function AppShell() {
|
||||
|
||||
<Toaster />
|
||||
|
||||
<StopConfirmDialog
|
||||
open={stopConfirm}
|
||||
onOpenChange={setStopConfirm}
|
||||
onConfirm={stopSession}
|
||||
/>
|
||||
|
||||
{renameTarget && (
|
||||
<RenameDialog
|
||||
open
|
||||
|
||||
Reference in New Issue
Block a user