pi-mono/apps/p2/plugins/sessions/SessionSheet.tsx
Mario Zechner 20d86c3657 feat(p2): Chord v2 cleanup pass
p2 rewritten by the Chord v2 authoring rules (decision "App authoring rules confirmed
by the p2 audit"), in four parts merged together:
- core: session lifecycle (relocate, clone, fork, archive) and search rebuild are
  persisted jobs on a new server store; Sessions v6 with client-chosen ids and visible
  operations; Retry answers with the worker's real outcome; workers push branch,
  subagent and status state to the server; roots take the method's ctx.
- services: models, envs, ports, login, mcp, logs, sharing and voice follow external
  state (self.follow, followNative) instead of copy loops; methods answer after their
  state commits; presence from host.references.
- chat: chat mode, artifacts and prompt resources follow Durable and native state;
  shell commands are Chord jobs (shellId is the requestId); status pushed before
  send and stop answer; race-free draft claim.
- ui: optimistic updates on web (useOptimistic) and terminal (TuiScope.optimistic,
  extended in Chord with a reducer and a pending flag), pending states for long work,
  visible rollback, load waterfalls removed; merged with the one composer and
  immediate first send (ccff9876e) and the folder + button (5a31a6d5f).
docs/optimistic-ui.md describes the pattern.
2026-11-05 18:22:13 +00:00

79 lines
2.4 KiB
TypeScript

import { ChevronIcon, locationLabel, Sheet } from "@p2/app/v1/web";
import { useState } from "react";
import type { Session } from "./contract.ts";
/** The session's search index rebuild (`Search.rebuilds`): running, failed with why, or none. */
export type Reindex = { readonly phase: "running" } | { readonly phase: "failed"; readonly error: string } | undefined;
export function SessionSheet({
session,
onClose,
onRename,
onPin,
onArchive,
onShare,
onCwd,
reindex,
onReindex,
}: {
session: Session;
onClose: () => void;
onRename: (title: string) => void;
onPin: () => void;
onArchive: () => void;
onShare: () => void;
onCwd: () => void;
reindex: Reindex;
/** Re-indexes the session's history for search (long work: the row shows it running); absent without search. */
onReindex: (() => void) | undefined;
}) {
const [title, setTitle] = useState(session.title);
const changed = title.trim() !== "" && title.trim() !== session.title;
return (
<Sheet title="Session" onClose={onClose}>
<form
className="inline-form"
onSubmit={(event) => {
event.preventDefault();
if (changed) onRename(title.trim());
onClose();
}}
>
<input
className="input"
aria-label="Title"
value={title}
onChange={(event) => setTitle(event.target.value)}
/>
{changed && (
<button type="submit" className="text-action">
Save
</button>
)}
</form>
<div className="list">
<button type="button" className="row-button" onClick={onShare}>
<span>Share</span>
<ChevronIcon />
</button>
<button type="button" className="row-button" onClick={onCwd}>
<span>Working directory</span>
<small className="row-value mono">{locationLabel(session.env, session.cwd)}</small>
<ChevronIcon />
</button>
<button type="button" className="row-button" onClick={onPin}>
<span>{session.pinned ? "Unpin" : "Pin to top"}</span>
</button>
{onReindex && (
<button type="button" className="row-button" disabled={reindex?.phase === "running"} onClick={onReindex}>
<span>{reindex?.phase === "running" ? "Rebuilding search index…" : "Rebuild search index"}</span>
{reindex?.phase === "failed" && <small className="row-value error">{reindex.error}</small>}
</button>
)}
<button type="button" className="row-button danger" onClick={onArchive}>
<span>Archive</span>
</button>
</div>
</Sheet>
);
}