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:
@@ -64,12 +64,14 @@ import {
|
||||
PanelLeftClose,
|
||||
PanelRightClose,
|
||||
Maximize2,
|
||||
FolderOpen,
|
||||
Minimize2,
|
||||
ChartColumn,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import { api } from "../lib/api";
|
||||
import { usePermissions } from "../components/AuthGate";
|
||||
import type {
|
||||
CwdSuggestion,
|
||||
LaneSource,
|
||||
@@ -130,6 +132,7 @@ import PipelineMap from "../components/lanes/PipelineMap";
|
||||
import LaneCard, { since } from "../components/lanes/LaneCard";
|
||||
import { AddLaneModal } from "../components/lanes/AddLaneModal";
|
||||
import { LaneChangesPanel } from "../components/lanes/LaneChangesPanel";
|
||||
import { FileExplorer } from "../components/lanes/FileExplorer";
|
||||
import { WorkspaceContext } from "../components/lanes/WorkspaceContext";
|
||||
import { OPEN_IN_CHANGES_EVENT, type OpenInChanges } from "../lib/openInChanges";
|
||||
import { LaneGitRailCounts } from "../components/lanes/LaneGitHeader";
|
||||
@@ -659,6 +662,7 @@ function ConsoleArea({
|
||||
|
||||
export function Workspace() {
|
||||
const { t: tLanes } = useTranslation("lanes");
|
||||
const { local } = usePermissions();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Lane state: the list is the shared store the sidebar reads too; the
|
||||
@@ -773,6 +777,10 @@ export function Workspace() {
|
||||
// Whether the Changes panel is open. A viewer preference, so it lives in
|
||||
// localStorage and never reaches the server — same rule as the lane strip's
|
||||
// order (client/src/lib/laneOrderStorage.ts).
|
||||
// The File Explorer dialog, opened from the Changes panel header.
|
||||
const [explorerOpen, setExplorerOpen] = useState(false);
|
||||
// Bumped when the File Explorer changed files: the Files tree reads again.
|
||||
const [filesChanged, setFilesChanged] = useState(0);
|
||||
const [changesOpen, setChangesOpen] = useState(() => {
|
||||
try {
|
||||
return window.localStorage.getItem(CHANGES_OPEN_KEY) === "1";
|
||||
@@ -1511,6 +1519,35 @@ export function Workspace() {
|
||||
>
|
||||
<ChartColumn className="h-4 w-4" />
|
||||
</button>
|
||||
{/* The File Explorer, beside the window controls — on this
|
||||
workspace (the session's own worktree when it has one).
|
||||
Not in local mode: the desktop's file manager is there. */}
|
||||
{local ? (
|
||||
<span className="ml-auto" />
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="panel-file-explorer"
|
||||
disabled={!panelAt}
|
||||
onClick={() => setExplorerOpen(true)}
|
||||
aria-label={tLanes("changes.openExplorer")}
|
||||
title={tLanes("changes.openExplorer")}
|
||||
className="ml-auto flex h-6 w-6 items-center justify-center rounded-md text-fg-muted transition-colors hover:bg-surface-4 hover:text-fg-primary disabled:opacity-40"
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</button>
|
||||
{panelAt && (
|
||||
<FileExplorer
|
||||
open={explorerOpen}
|
||||
laneId={panelAt.laneId}
|
||||
session={panelAt.session}
|
||||
onChanged={() => setFilesChanged((n) => n + 1)}
|
||||
onClose={() => setExplorerOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* A window's two controls: Full screen (Restore at full
|
||||
screen, to the last dragged width) and Collapse. */}
|
||||
<button
|
||||
@@ -1519,7 +1556,7 @@ export function Workspace() {
|
||||
onClick={() => setChangesSize(changesFull ? lastDraggedChanges() : "full")}
|
||||
aria-label={changesSizeLabel}
|
||||
title={changesSizeLabel}
|
||||
className="ml-auto flex h-6 w-6 items-center justify-center rounded-md text-fg-muted transition-colors hover:bg-surface-4 hover:text-fg-primary"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-fg-muted transition-colors hover:bg-surface-4 hover:text-fg-primary"
|
||||
>
|
||||
{changesFull ? (
|
||||
<Minimize2 className="h-4 w-4" />
|
||||
@@ -1568,6 +1605,7 @@ export function Workspace() {
|
||||
wide={changesRatio(changesSize) >= 0.45}
|
||||
onDirtyChange={dirtyFor(key)}
|
||||
openRequest={source ? null : changesRequest}
|
||||
filesChanged={source ? 0 : filesChanged}
|
||||
// A sibling worktree forks from the lane's SOURCE repo
|
||||
// when it has one (this lane is itself a worktree of
|
||||
// it); an adopted lane is its own repo.
|
||||
|
||||
Reference in New Issue
Block a user