pi-mono/apps/p2/plugins/sessions/BranchSheet.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

66 lines
1.9 KiB
TypeScript

// The owner's sheet for a `/tree` branch's label (docs/tree.md): a new label, or for conversation 1 the session title.
import { Sheet } from "@p2/app/v1/web";
import { useState } from "react";
import { PREVIEW_LENGTH } from "../tree/contract.ts";
/**
* Renames a branch; `label` null shows `title` (conversation 1's default), and only conversation 1 may go back to it.
* Saving closes the sheet: the row shows the new label while the rename is in flight.
*/
export function BranchSheet({
title,
conversation,
label,
onSave,
onClose,
}: {
/** The session title, what a null label shows. */
title: string;
conversation: number;
label: string | null;
onSave: (label: string | null) => void;
onClose: () => void;
}) {
const [draft, setDraft] = useState(label ?? title);
const next = draft.replace(/\s+/g, " ").trim();
const resettable = conversation === 1;
const unchanged = next === (label ?? title);
const save = (value: string | null) => {
onSave(value);
onClose();
};
return (
<Sheet title="Branch name" onClose={onClose}>
<form
className="stack"
onSubmit={(event) => {
event.preventDefault();
if (!unchanged && (next || resettable)) save(next || null);
}}
>
<label className="field">
<span className="label">Label in the sidebar and /tree</span>
<input
className="input"
value={draft}
maxLength={PREVIEW_LENGTH}
onChange={(event) => setDraft(event.target.value)}
/>
</label>
{resettable && (
<p className="muted small">The first branch shows the session title until it gets a label of its own.</p>
)}
<div className="inline-form">
<button type="submit" className="button" disabled={unchanged || (!next && !resettable)}>
Save
</button>
{resettable && label !== null && (
<button type="button" className="text-action" onClick={() => save(null)}>
Use the session title
</button>
)}
</div>
</form>
</Sheet>
);
}