Remove the experimental workspace toggle
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -17,7 +17,6 @@ import { PriorityIcon } from "./PriorityIcon";
|
||||
import { Identity } from "./Identity";
|
||||
import { formatDate, cn, projectUrl } from "../lib/utils";
|
||||
import { timeAgo } from "../lib/timeAgo";
|
||||
import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { User, Hexagon, ArrowUpRight, Tag, Plus, Trash2 } from "lucide-react";
|
||||
@@ -134,7 +133,6 @@ function PropertyPicker({
|
||||
|
||||
export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProps) {
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { enabled: showExperimentalWorkspaceUi } = useExperimentalWorkspacesEnabled();
|
||||
const queryClient = useQueryClient();
|
||||
const companyId = issue.companyId ?? selectedCompanyId;
|
||||
const [assigneeOpen, setAssigneeOpen] = useState(false);
|
||||
@@ -219,11 +217,8 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
const currentProject = issue.projectId
|
||||
? orderedProjects.find((project) => project.id === issue.projectId) ?? null
|
||||
: null;
|
||||
const currentProjectExecutionWorkspacePolicy = showExperimentalWorkspaceUi
|
||||
? currentProject?.executionWorkspacePolicy ?? null
|
||||
: null;
|
||||
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
||||
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
|
||||
const currentProjectWorkspaces = currentProject?.workspaces ?? [];
|
||||
const currentExecutionWorkspaceSelection =
|
||||
issue.executionWorkspacePreference
|
||||
?? issue.executionWorkspaceSettings?.mode
|
||||
@@ -240,7 +235,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
projectWorkspaceId: issue.projectWorkspaceId ?? undefined,
|
||||
reuseEligible: true,
|
||||
}),
|
||||
enabled: Boolean(companyId) && showExperimentalWorkspaceUi && Boolean(issue.projectId),
|
||||
enabled: Boolean(companyId) && Boolean(issue.projectId),
|
||||
});
|
||||
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
|
||||
(workspace) => workspace.id === issue.executionWorkspaceId,
|
||||
@@ -509,10 +504,10 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
const defaultMode = defaultExecutionWorkspaceModeForProject(p);
|
||||
onUpdate({
|
||||
projectId: p.id,
|
||||
projectWorkspaceId: showExperimentalWorkspaceUi ? defaultProjectWorkspaceIdForProject(p) : null,
|
||||
projectWorkspaceId: defaultProjectWorkspaceIdForProject(p),
|
||||
executionWorkspaceId: null,
|
||||
executionWorkspacePreference: showExperimentalWorkspaceUi ? defaultMode : null,
|
||||
executionWorkspaceSettings: showExperimentalWorkspaceUi && p.executionWorkspacePolicy?.enabled
|
||||
executionWorkspacePreference: defaultMode,
|
||||
executionWorkspaceSettings: p.executionWorkspacePolicy?.enabled
|
||||
? { mode: defaultMode }
|
||||
: null,
|
||||
});
|
||||
@@ -602,28 +597,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
{projectContent}
|
||||
</PropertyPicker>
|
||||
|
||||
{showExperimentalWorkspaceUi && currentProjectWorkspaces.length > 0 && (
|
||||
<PropertyRow label="Codebase">
|
||||
<select
|
||||
className="w-full rounded border border-border bg-transparent px-2 py-1.5 text-xs outline-none"
|
||||
value={issue.projectWorkspaceId ?? ""}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
projectWorkspaceId: e.target.value || null,
|
||||
executionWorkspaceId: null,
|
||||
})}
|
||||
>
|
||||
{currentProjectWorkspaces.map((workspace) => (
|
||||
<option key={workspace.id} value={workspace.id}>
|
||||
{workspace.name}
|
||||
{workspace.isPrimary ? " (default)" : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</PropertyRow>
|
||||
)}
|
||||
|
||||
{showExperimentalWorkspaceUi && currentProjectSupportsExecutionWorkspace && (
|
||||
{currentProjectSupportsExecutionWorkspace && (
|
||||
<PropertyRow label="Workspace">
|
||||
<div className="w-full space-y-2">
|
||||
<select
|
||||
|
||||
@@ -51,7 +51,6 @@ import { issueStatusText, issueStatusTextDefault, priorityColor, priorityColorDe
|
||||
import { MarkdownEditor, type MarkdownEditorRef, type MentionOption } from "./MarkdownEditor";
|
||||
import { AgentIcon } from "./AgentIconPicker";
|
||||
import { InlineEntitySelector, type InlineEntityOption } from "./InlineEntitySelector";
|
||||
import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings";
|
||||
|
||||
const DRAFT_KEY = "paperclip:issue-draft";
|
||||
const DEBOUNCE_MS = 800;
|
||||
@@ -280,7 +279,6 @@ function issueExecutionWorkspaceModeForExistingWorkspace(mode: string | null | u
|
||||
export function NewIssueDialog() {
|
||||
const { newIssueOpen, newIssueDefaults, closeNewIssue } = useDialog();
|
||||
const { companies, selectedCompanyId, selectedCompany } = useCompany();
|
||||
const { enabled: showExperimentalWorkspaceUi } = useExperimentalWorkspacesEnabled();
|
||||
const queryClient = useQueryClient();
|
||||
const { pushToast } = useToast();
|
||||
const [title, setTitle] = useState("");
|
||||
@@ -339,7 +337,7 @@ export function NewIssueDialog() {
|
||||
projectWorkspaceId: projectWorkspaceId || undefined,
|
||||
reuseEligible: true,
|
||||
}),
|
||||
enabled: Boolean(effectiveCompanyId) && newIssueOpen && showExperimentalWorkspaceUi && Boolean(projectId),
|
||||
enabled: Boolean(effectiveCompanyId) && newIssueOpen && Boolean(projectId),
|
||||
});
|
||||
const { data: session } = useQuery({
|
||||
queryKey: queryKeys.auth.session,
|
||||
@@ -639,9 +637,7 @@ export function NewIssueDialog() {
|
||||
chrome: assigneeChrome,
|
||||
});
|
||||
const selectedProject = orderedProjects.find((project) => project.id === projectId);
|
||||
const executionWorkspacePolicy = showExperimentalWorkspaceUi
|
||||
? selectedProject?.executionWorkspacePolicy
|
||||
: null;
|
||||
const executionWorkspacePolicy = selectedProject?.executionWorkspacePolicy ?? null;
|
||||
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
|
||||
(workspace) => workspace.id === selectedExecutionWorkspaceId,
|
||||
);
|
||||
@@ -749,10 +745,7 @@ export function NewIssueDialog() {
|
||||
? (agents ?? []).find((a) => a.id === selectedAssigneeAgentId)
|
||||
: null;
|
||||
const currentProject = orderedProjects.find((project) => project.id === projectId);
|
||||
const currentProjectWorkspaces = currentProject?.workspaces ?? [];
|
||||
const currentProjectExecutionWorkspacePolicy = showExperimentalWorkspaceUi
|
||||
? currentProject?.executionWorkspacePolicy ?? null
|
||||
: null;
|
||||
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
||||
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
|
||||
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
|
||||
(workspace) => workspace.id === selectedExecutionWorkspaceId,
|
||||
@@ -822,7 +815,7 @@ export function NewIssueDialog() {
|
||||
setProjectWorkspaceId(defaultProjectWorkspaceIdForProject(project));
|
||||
setExecutionWorkspaceMode(defaultExecutionWorkspaceModeForProject(project));
|
||||
setSelectedExecutionWorkspaceId("");
|
||||
}, [newIssueOpen, orderedProjects, projectId, showExperimentalWorkspaceUi]);
|
||||
}, [newIssueOpen, orderedProjects, projectId]);
|
||||
const modelOverrideOptions = useMemo<InlineEntityOption[]>(
|
||||
() => {
|
||||
return [...(assigneeAdapterModels ?? [])]
|
||||
@@ -1102,29 +1095,8 @@ export function NewIssueDialog() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showExperimentalWorkspaceUi && currentProject && (
|
||||
{currentProject && (
|
||||
<div className="px-4 pb-2 shrink-0 space-y-2">
|
||||
{currentProjectWorkspaces.length > 0 && (
|
||||
<div className="rounded-md border border-border px-3 py-2 space-y-1.5">
|
||||
<div className="text-xs font-medium">Codebase</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
Choose which project workspace this issue should use.
|
||||
</div>
|
||||
<select
|
||||
className="w-full rounded border border-border bg-transparent px-2 py-1.5 text-xs outline-none"
|
||||
value={projectWorkspaceId}
|
||||
onChange={(e) => setProjectWorkspaceId(e.target.value)}
|
||||
>
|
||||
{currentProjectWorkspaces.map((workspace) => (
|
||||
<option key={workspace.id} value={workspace.id}>
|
||||
{workspace.name}
|
||||
{workspace.isPrimary ? " (default)" : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentProjectSupportsExecutionWorkspace && (
|
||||
<div className="rounded-md border border-border px-3 py-2 space-y-1.5">
|
||||
<div className="text-xs font-medium">Execution workspace</div>
|
||||
|
||||
@@ -17,7 +17,6 @@ import { AlertCircle, Archive, ArchiveRestore, Check, ExternalLink, Github, Load
|
||||
import { ChoosePathButton } from "./PathInstructionsModal";
|
||||
import { DraftInput } from "./agent-config-primitives";
|
||||
import { InlineEditor } from "./InlineEditor";
|
||||
import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings";
|
||||
|
||||
const PROJECT_STATUSES = [
|
||||
{ value: "backlog", label: "Backlog" },
|
||||
@@ -152,7 +151,6 @@ function ProjectStatusPicker({ status, onChange }: { status: string; onChange: (
|
||||
|
||||
export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSaveState, onArchive, archivePending }: ProjectPropertiesProps) {
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { enabled: showExperimentalWorkspaceUi } = useExperimentalWorkspacesEnabled();
|
||||
const queryClient = useQueryClient();
|
||||
const [goalOpen, setGoalOpen] = useState(false);
|
||||
const [executionWorkspaceAdvancedOpen, setExecutionWorkspaceAdvancedOpen] = useState(false);
|
||||
@@ -749,9 +747,6 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* PAP-525: workspace UI gated by useExperimentalWorkspacesEnabled() */}
|
||||
{showExperimentalWorkspaceUi && (
|
||||
<>
|
||||
<Separator className="my-4" />
|
||||
|
||||
<div className="py-1.5 space-y-2">
|
||||
@@ -990,8 +985,6 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const WORKSPACES_KEY = "paperclip:experimental:workspaces";
|
||||
|
||||
export function loadExperimentalWorkspacesEnabled(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
return window.localStorage.getItem(WORKSPACES_KEY) === "true";
|
||||
}
|
||||
|
||||
export function saveExperimentalWorkspacesEnabled(enabled: boolean) {
|
||||
if (typeof window === "undefined") return;
|
||||
window.localStorage.setItem(WORKSPACES_KEY, enabled ? "true" : "false");
|
||||
window.dispatchEvent(new CustomEvent("paperclip:experimental:workspaces", { detail: enabled }));
|
||||
}
|
||||
|
||||
export function useExperimentalWorkspacesEnabled() {
|
||||
const [enabled, setEnabled] = useState(loadExperimentalWorkspacesEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorage = (event: StorageEvent) => {
|
||||
if (event.key && event.key !== WORKSPACES_KEY) return;
|
||||
setEnabled(loadExperimentalWorkspacesEnabled());
|
||||
};
|
||||
const handleCustom = () => setEnabled(loadExperimentalWorkspacesEnabled());
|
||||
window.addEventListener("storage", handleStorage);
|
||||
window.addEventListener("paperclip:experimental:workspaces", handleCustom as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener("storage", handleStorage);
|
||||
window.removeEventListener("paperclip:experimental:workspaces", handleCustom as EventListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const update = (next: boolean) => {
|
||||
saveExperimentalWorkspacesEnabled(next);
|
||||
setEnabled(next);
|
||||
};
|
||||
|
||||
return { enabled, setEnabled: update };
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { formatDateTime, relativeTime } from "../lib/utils";
|
||||
import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings";
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
||||
@@ -31,7 +30,6 @@ export function InstanceSettings() {
|
||||
const { setBreadcrumbs } = useBreadcrumbs();
|
||||
const queryClient = useQueryClient();
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
const { enabled: workspacesEnabled, setEnabled: setWorkspacesEnabled } = useExperimentalWorkspacesEnabled();
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs([
|
||||
@@ -112,34 +110,6 @@ export function InstanceSettings() {
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl space-y-6">
|
||||
<div className="space-y-3 rounded-lg border border-border bg-card p-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="text-sm font-semibold">Experimental</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
UI-only feature flags for in-progress product surfaces.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-md border border-border px-3 py-2">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-sm font-medium">Workspaces</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Show workspace, execution workspace, and work product controls in project and issue UI.
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant={workspacesEnabled ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setWorkspacesEnabled(!workspacesEnabled)}
|
||||
>
|
||||
{workspacesEnabled ? "Enabled" : "Disabled"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useCompany } from "../context/CompanyContext";
|
||||
import { usePanel } from "../context/PanelContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings";
|
||||
import { readIssueDetailBreadcrumb } from "../lib/issueDetailBreadcrumb";
|
||||
import { useProjectOrder } from "../hooks/useProjectOrder";
|
||||
import { relativeTime, cn, formatTokens, visibleRunCostUsd } from "../lib/utils";
|
||||
@@ -216,7 +215,6 @@ function ActorIdentity({ evt, agentMap }: { evt: ActivityEvent; agentMap: Map<st
|
||||
export function IssueDetail() {
|
||||
const { issueId } = useParams<{ issueId: string }>();
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { enabled: experimentalWorkspacesEnabled } = useExperimentalWorkspacesEnabled();
|
||||
const { openPanel, closePanel, panelVisible, setPanelVisible } = usePanel();
|
||||
const { setBreadcrumbs } = useBreadcrumbs();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -662,10 +660,7 @@ export function IssueDetail() {
|
||||
// Ancestors are returned oldest-first from the server (root at end, immediate parent at start)
|
||||
const ancestors = issue.ancestors ?? [];
|
||||
const workProducts = issue.workProducts ?? [];
|
||||
const showOutputsTab =
|
||||
experimentalWorkspacesEnabled ||
|
||||
Boolean(issue.currentExecutionWorkspace) ||
|
||||
workProducts.length > 0;
|
||||
const showOutputsTab = Boolean(issue.currentExecutionWorkspace) || workProducts.length > 0;
|
||||
|
||||
const handleFilePicked = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = evt.target.files;
|
||||
|
||||
Reference in New Issue
Block a user