feat: v2.7.8 — Artifacts hub, clone related repos as extra folders, quieter resume prompt
- Artifacts (/artifacts): claude.ai pages sessions publish (captured from the Artifact tool's hook), pinned workspace files, skills and plugins, each kept as a snapshot; shared ones appear in a Hub and a skill or plugin installs into the viewer's own account (server/lib/artifacts.js, /api/artifacts, docs/plans/artifacts/). - Workspace extra folders: clone one or more related repos beside the workspace and add them in one step (POST /api/lanes/:id/sources/clone). - A pane that ended by itself while tmux kept running is no longer offered for resume (tmux.serverStartedAt in reconcileOrphans). - Changes panel: a folder that is not a git repo opens on Files, and its files open from there. - Plus the parallel sessions' work in this tree: File Explorer button in the panel header, user delete, RSA SSH keys, chat/terminal and pet fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -149,15 +149,21 @@ export function usePrefersReducedMotion(): boolean {
|
||||
* changes via ResizeObserver) so a tall panel near a screen edge slides fully
|
||||
* into view instead of being cropped.
|
||||
*/
|
||||
/** `front`: above the mascot itself — the pet menu, which may overlap it. */
|
||||
/**
|
||||
* `front`: above the mascot itself — the pet menu, which may overlap it.
|
||||
* `beside`: at the mascot's side instead of above it — the chat window, on the
|
||||
* screen side away from the edge it docks to, its bottom level with the pet's.
|
||||
*/
|
||||
function SagiFlyout({
|
||||
anchor,
|
||||
children,
|
||||
front = false,
|
||||
beside = false,
|
||||
}: {
|
||||
anchor: Anchor;
|
||||
children: ReactNode;
|
||||
front?: boolean;
|
||||
beside?: boolean;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [style, setStyle] = useState<CSSProperties>({ visibility: "hidden" });
|
||||
@@ -170,6 +176,18 @@ function SagiFlyout({
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
|
||||
if (beside) {
|
||||
let left =
|
||||
anchor.side === "left"
|
||||
? anchor.left + anchor.size + FLYOUT_GAP
|
||||
: anchor.left - w - FLYOUT_GAP;
|
||||
left = Math.min(vw - w - VIEWPORT_MARGIN, Math.max(VIEWPORT_MARGIN, left));
|
||||
let top = anchor.top + anchor.size - h;
|
||||
top = Math.min(vh - h - VIEWPORT_MARGIN, Math.max(VIEWPORT_MARGIN, top));
|
||||
setStyle({ left, top, visibility: "visible" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Horizontal: hug the avatar's docked edge, then clamp on-screen.
|
||||
let left = anchor.side === "left" ? anchor.left : anchor.left + anchor.size - w;
|
||||
left = Math.min(vw - w - VIEWPORT_MARGIN, Math.max(VIEWPORT_MARGIN, left));
|
||||
@@ -182,7 +200,7 @@ function SagiFlyout({
|
||||
top = Math.min(vh - h - VIEWPORT_MARGIN, Math.max(VIEWPORT_MARGIN, top));
|
||||
|
||||
setStyle({ left, top, visibility: "visible" });
|
||||
}, [anchor.left, anchor.top, anchor.size, anchor.side, anchor.openUp]);
|
||||
}, [anchor.left, anchor.top, anchor.size, anchor.side, anchor.openUp, beside]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
place();
|
||||
@@ -431,37 +449,32 @@ function SagiBody() {
|
||||
onClearAlerts={brain.clearAlerts}
|
||||
onNavigate={onNavigate}
|
||||
onAsk={onAsk}
|
||||
onOpenChat={() => {
|
||||
setOpen(false);
|
||||
setChatOpen(true);
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
/>
|
||||
</SagiFlyout>
|
||||
)}
|
||||
|
||||
{/* Kept mounted while dragging (only hidden): unmounting would detach
|
||||
its terminal and lose a half-typed message. */}
|
||||
{/* Beside the pet, riding along with it mid-drag too (SagiFlyout follows
|
||||
it 1:1), and kept mounted: unmounting would detach its terminal and
|
||||
lose a half-typed message. */}
|
||||
{chatOpen && (
|
||||
<div style={place.dragging ? { display: "none" } : undefined}>
|
||||
<SagiFlyout anchor={anchor}>
|
||||
<PetChat
|
||||
name={name}
|
||||
initialPrompt={chatPrompt}
|
||||
onPromptTaken={() => setChatPrompt(null)}
|
||||
onBack={() => {
|
||||
setChatOpen(false);
|
||||
setOpen(true);
|
||||
}}
|
||||
onClose={() => setChatOpen(false)}
|
||||
onOpenWorkspace={(laneId, runId) => {
|
||||
navigate(`/workspace?lane=${laneId}&run=${encodeURIComponent(runId)}`);
|
||||
setChatOpen(false);
|
||||
}}
|
||||
onBusy={onBusy}
|
||||
/>
|
||||
</SagiFlyout>
|
||||
</div>
|
||||
<SagiFlyout anchor={anchor} beside>
|
||||
<PetChat
|
||||
name={name}
|
||||
initialPrompt={chatPrompt}
|
||||
onPromptTaken={() => setChatPrompt(null)}
|
||||
onBack={() => {
|
||||
setChatOpen(false);
|
||||
setOpen(true);
|
||||
}}
|
||||
onClose={() => setChatOpen(false)}
|
||||
onOpenWorkspace={(laneId, runId) => {
|
||||
navigate(`/workspace?lane=${laneId}&run=${encodeURIComponent(runId)}`);
|
||||
setChatOpen(false);
|
||||
}}
|
||||
onBusy={onBusy}
|
||||
/>
|
||||
</SagiFlyout>
|
||||
)}
|
||||
|
||||
{!place.dragging && menu && (
|
||||
|
||||
Reference in New Issue
Block a user