diff --git a/.agents/skills/directions-lab/references/today-fixtures-and-build.md b/.agents/skills/directions-lab/references/today-fixtures-and-build.md index fe2c2c0502a..6dfe211d817 100644 --- a/.agents/skills/directions-lab/references/today-fixtures-and-build.md +++ b/.agents/skills/directions-lab/references/today-fixtures-and-build.md @@ -45,7 +45,7 @@ export function loadKit(id: DirectionId): Promise { - `ThreadWorkProduct`: a task document becomes a `TaskChatProtocolCard` resource item with ``subtitle: `Document ยท rev ${artifact.revision}` ``; anything else becomes `RichWorkProductCard` with `workProductHref(workProduct)` (`ThreadWorkProduct.tsx:13-31`; the product split is `ui/src/components/TaskChatThread.tsx:1290-1312`). - `ThreadAttachments`: mirrored; uses `@/components/ui/attachment`, `ArtifactPreview`, `fileKindForAttachment`, `formatFileSize`, `isImageAttachment`, `isVideoLikeOutput` (`ThreadAttachments.tsx:1-15`). -**GENERIC**, flag-gated components. Before mounting, grep the feature's flag hook under `ui/src/components` and `ui/src/pages`. For artifacts, `useArtifactsV1Enabled()` is called at `IssuePropertiesArtifactsTab.tsx:414`, `TaskChatBubble.tsx:149`, `TaskChatProtocolCard.tsx:1045` and in `pages/Artifacts.tsx`; it resolves `settings?.enableArtifactsV1 === true` (`ui/src/hooks/useArtifactsV1Enabled.ts:7-9`) from `GET /api/instance/settings/experimental` (`ui/src/api/instanceSettings.ts:20-21`). The artifact stub has no route for it, answers `{}`, and the flag is false: Today's panel and its task-document thread row render the flag-OFF presentation (the generic card from `TaskChatProtocolCard.tsx:1072`), while `ArtifactCard`, `ArtifactGroupCard` and `RichWorkProductCard` are flag-independent. To show a flagged presentation under Today, register a route returning `{ enableArtifactsV1: true }` for that pathname. A generated lab already has that route: `api-routes.ts` answers any pathname ending `/instance/settings/experimental` with `registry.today.flags` (`{}` as scaffolded), so set the flag in `registry.ts` (`today: { flags: { enableArtifactsV1: true } }`) or at run time with `setLabFlags`. +**GENERIC**, flag-gated components. Before mounting, grep the feature's flag hook under `ui/src/components` and `ui/src/pages`. For artifacts, `useArtifactsV1Enabled()` is called at `IssuePropertiesArtifactsTab.tsx:414`, `TaskChatBubble.tsx:149`, `TaskChatProtocolCard.tsx:1045` and in `pages/Artifacts.tsx`; it resolves to on unless a managed `enableArtifactsV1: false` holds it off (`resolveArtifactsV1Enabled` in `ui/src/hooks/useArtifactsV1Enabled.ts`), read from `GET /api/instance/settings/experimental` (`ui/src/api/instanceSettings.ts:20-21`). The artifact stub has no route for it and answers `{}`, so Today's panel and its task-document thread row render the V1 rows, while `ArtifactCard`, `ArtifactGroupCard` and `RichWorkProductCard` are flag-independent. To show the previous cards under Today, return `{ enableArtifactsV1: false, managedKeys: { enableArtifactsV1: { managed: true, managedBy: "paperclip-cloud" } } }` for that pathname. A generated lab already has that route: `api-routes.ts` answers any pathname ending `/instance/settings/experimental` with `registry.today.flags` (`{}` as scaffolded), so set those values in `registry.ts` or at run time with `setLabFlags`. ## 2. Adapters: lab fixtures to product types diff --git a/doc/plans/2026-10-22-artifacts-mvp-v1.md b/doc/plans/2026-10-22-artifacts-mvp-v1.md index 270e77e5762..9f51fafeb65 100644 --- a/doc/plans/2026-10-22-artifacts-mvp-v1.md +++ b/doc/plans/2026-10-22-artifacts-mvp-v1.md @@ -143,3 +143,12 @@ the wiring is covered by tests. fetched to find out, and a front that moves on to another picture gets a fresh chance. - Token count grows by about thirty `--artifact-*` entries; they are namespaced and documented in the token block. + +## Update 2026-11-04: on for every instance + +Artifacts V1 now renders for every instance and the Experimental toggle is gone. `enableArtifactsV1` +stays in the schema and catalog with a default of on. `resolveArtifactsV1Enabled` ignores a stored +false, because every experimental write persists the whole normalized object, so a stored false only +means an instance saved some other experiment. A value from `PAPERCLIP_MANAGED_CONFIG` is still +honoured, which leaves operators an off switch outside Cloud staging. The previous cards remain in +the code until that switch is retired. diff --git a/packages/shared/src/feature-catalog.ts b/packages/shared/src/feature-catalog.ts index 363f529b2a8..0d774f636c3 100644 --- a/packages/shared/src/feature-catalog.ts +++ b/packages/shared/src/feature-catalog.ts @@ -211,8 +211,8 @@ export const INSTANCE_FEATURE_CATALOG: Record { + it("is on for self-hosted and Cloud instances that never stored it", () => { + expect(INSTANCE_FEATURE_CATALOG.enableArtifactsV1).toMatchObject({ cloudDefault: true, selfHostedDefault: true }); + expect(applyCloudCatalogDefaults(normalizeExperimentalSettings({}), {}, null).enableArtifactsV1).toBe(true); + expect(applyCloudCatalogDefaults(normalizeExperimentalSettings({}), {}, managedConfig()).enableArtifactsV1).toBe(true); + }); +}); + describe("applyCloudCatalogDefaults", () => { it("pins the catalog so this rule has something to guard", () => { // The rule exists for flags that default on for self-hosted and off for diff --git a/server/src/__tests__/instance-settings-service.test.ts b/server/src/__tests__/instance-settings-service.test.ts index 217c2107bf3..2574df39fa0 100644 --- a/server/src/__tests__/instance-settings-service.test.ts +++ b/server/src/__tests__/instance-settings-service.test.ts @@ -110,7 +110,7 @@ describe("instance settings service", () => { enableMuse: false, enableGrokBot: false, enableCombinedInboxTasks: false, - enableArtifactsV1: false, + enableArtifactsV1: true, enableChatConnectors: false, enableGitHubReviewBots: false, enableMemoryConnectors: false, diff --git a/server/src/services/instance-settings.ts b/server/src/services/instance-settings.ts index c3614530765..3080be641ac 100644 --- a/server/src/services/instance-settings.ts +++ b/server/src/services/instance-settings.ts @@ -268,7 +268,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta enableCases: parsed.data.enableCases ?? false, enableAgentChat: parsed.data.enableAgentChat ?? false, enableCombinedInboxTasks: parsed.data.enableCombinedInboxTasks ?? false, - enableArtifactsV1: parsed.data.enableArtifactsV1 ?? false, + enableArtifactsV1: parsed.data.enableArtifactsV1 ?? true, enableConferenceRoomChat: parsed.data.enableConferenceRoomChat ?? false, enableClassicTaskInterface: parsed.data.enableClassicTaskInterface ?? false, enableIssuePlanDecompositions: parsed.data.enableIssuePlanDecompositions ?? false, @@ -325,7 +325,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta enableCases: false, enableAgentChat: false, enableCombinedInboxTasks: false, - enableArtifactsV1: false, + enableArtifactsV1: true, enableConferenceRoomChat: false, enableClassicTaskInterface: false, enableIssuePlanDecompositions: false, diff --git a/tests/e2e/artifact-uploads.spec.ts b/tests/e2e/artifact-uploads.spec.ts index 9ccce84f188..9a7dcb478ae 100644 --- a/tests/e2e/artifact-uploads.spec.ts +++ b/tests/e2e/artifact-uploads.spec.ts @@ -62,10 +62,10 @@ for (const mobile of [false, true]) { const document = panel.locator("#document-core-handoff"); await expect(document.getByRole("heading", { name: "Engineering handoff", exact: true })).toBeVisible(); await expect(document.getByText("Start with the loop inventory and preserve current task ownership.", { exact: true })).toBeVisible(); - await expect(panel.getByRole("link", { name: "Download file", exact: true })).toBeVisible(); + await expect(panel.getByRole("link", { name: "Download loop-inventory.csv", exact: true })).toBeVisible(); await expectTaskPanelTab(panel, mobile, "Artifacts"); const download = page.waitForEvent("download"); - await panel.getByRole("link", { name: "Download file", exact: true }).click(); + await panel.getByRole("link", { name: "Download loop-inventory.csv", exact: true }).click(); const downloaded = await download; expect(downloaded.suggestedFilename()).toBe("loop-inventory.csv"); expect(await readFile((await downloaded.path())!, "utf8")).toBe(csv); diff --git a/tests/e2e/text-attachment-tabs.spec.ts b/tests/e2e/text-attachment-tabs.spec.ts index 93ce9c89e1f..e2377001f35 100644 --- a/tests/e2e/text-attachment-tabs.spec.ts +++ b/tests/e2e/text-attachment-tabs.spec.ts @@ -38,7 +38,8 @@ for (const mobile of [false, true]) { })); } await page.goto(`/${company.issuePrefix}/issues/${issue.identifier}`); - await page.getByRole("link", { name: "Open AGENTS.md", exact: true }).click(); + const threadFiles = page.getByTestId("task-chat-bubble-artifacts-v1"); + await threadFiles.getByRole("link", { name: "Open AGENTS.md", exact: true }).click(); const panel = mobile ? page.getByTestId("mobile-task-side-panel") : page.locator("aside").filter({ has: page.getByRole("tab", { name: "AGENTS.md", exact: true }) }); await expectTaskPanelTab(panel, mobile, "AGENTS.md"); await expect(panel.getByRole("heading", { name: "File charter", exact: true, level: 1 })).toBeVisible(); @@ -53,7 +54,7 @@ for (const mobile of [false, true]) { await panel.getByRole("link", { name: "Download AGENTS.md", exact: true }).click(); expect(await fs.readFile((await (await markdownDownload).path())!)).toEqual(files[0]!.buffer); if (mobile) await panel.getByRole("button", { name: "Close side panel", exact: true }).click(); - await page.getByRole("link", { name: "Open notes.txt", exact: true }).click(); + await threadFiles.getByRole("link", { name: "Open notes.txt", exact: true }).click(); await expectTaskPanelTab(panel, mobile, "notes.txt"); await expect(panel.getByLabel("notes.txt raw text")).toContainText(""); await expect(panel.getByRole("group", { name: "Markdown view", exact: true })).toHaveCount(0); @@ -73,19 +74,19 @@ for (const mobile of [false, true]) { } await panel.getByRole("button", { name: "Open a new tab", exact: true }).click(); await page.getByRole("option", { name: /^Artifacts(?: Already open)?$/ }).click(); - const markdownCard = panel.getByRole("article").filter({ has: page.getByRole("heading", { name: "Delivered AGENTS.md", exact: true }) }); - const review = markdownCard.getByRole("button", { name: /^(Read|Close) document$/ }); + const artifactRows = panel.getByTestId("artifacts-v1-list").getByRole("listitem"); + const markdownRow = artifactRows.filter({ has: page.getByRole("button", { name: "Open in tab: Delivered AGENTS.md", exact: true }) }); + const review = markdownRow.getByRole("button", { name: "Open AGENTS.md", exact: true }); await review.click(); await expect(review).toHaveAttribute("aria-expanded", "true"); await expect(panel.getByRole("heading", { name: "File charter", exact: true, level: 1 })).toBeVisible(); - await expect(markdownCard).toContainText("revision 1"); await expect(panel.getByRole("link", { name: "Download Delivered AGENTS.md", exact: true })).toBeVisible(); const cardDownload = page.waitForEvent("download"); - const textCard = panel.getByRole("article").filter({ has: page.getByRole("heading", { name: "Delivered notes.txt", exact: true }) }); - await textCard.getByRole("link", { name: "Download file", exact: true }).click(); + const textRow = artifactRows.filter({ hasText: "For review" }).filter({ has: page.getByRole("link", { name: "Open notes.txt", exact: true }) }); + await textRow.getByRole("link", { name: "Download notes.txt", exact: true }).click(); expect(await fs.readFile((await (await cardDownload).path())!)).toEqual(files[1]!.buffer); await expectTaskPanelTab(panel, mobile, "Artifacts"); - await panel.getByRole("button", { name: "Open in tab: Delivered notes.txt", exact: true }).click(); + await textRow.getByRole("link", { name: "Open notes.txt", exact: true }).click(); await expectTaskPanelTab(panel, mobile, "notes.txt"); await selectTaskPanelTab(page, panel, mobile, "Artifacts"); await panel.getByRole("button", { name: "Open in tab: Delivered AGENTS.md", exact: true }).click(); diff --git a/tests/runner-e2e/task-text-artifact-download.ts b/tests/runner-e2e/task-text-artifact-download.ts index 2fa81dd4e29..a5e00f99f70 100644 --- a/tests/runner-e2e/task-text-artifact-download.ts +++ b/tests/runner-e2e/task-text-artifact-download.ts @@ -2,7 +2,7 @@ import { expect, type Page } from "@playwright/test"; export async function downloadTaskTextArtifact(page: Page, attachmentId: string, name: string) { const contentPath = `/api/attachments/${encodeURIComponent(attachmentId)}/content`; - const chip = page.getByTestId("task-chat-bubble-attachments").locator(`a[href*="${contentPath}"]`).last(); + const chip = page.getByTestId("task-chat-bubble-artifacts-v1").locator(`a[href*="${contentPath}"]`).last(); await expect(chip).toBeVisible({ timeout: 30_000 }); await expect(chip).toHaveAttribute("aria-label", `Open ${name}`); await chip.click(); diff --git a/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.v1.test.tsx b/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.v1.test.tsx index d572aa543e8..72cab5dbb24 100644 --- a/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.v1.test.tsx +++ b/ui/src/components/issue-properties/IssuePropertiesArtifactsTab.v1.test.tsx @@ -6,6 +6,7 @@ import { createRoot, type Root } from "react-dom/client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { Issue, IssueAttachment, IssueDocument, IssueWorkProduct } from "@paperclipai/shared"; +import { TextAttachmentContext } from "@/context/TextAttachmentContext"; import { IssuePropertiesArtifactsTab, TaskGeneralDocuments, type TaskDocumentEditorOptions } from "./IssuePropertiesArtifactsTab"; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; @@ -177,12 +178,14 @@ describe("IssuePropertiesArtifactsTab with Artifacts V1 on", () => { vi.clearAllMocks(); }); - function render(props: { onOpenDocument?: (document: IssueDocument) => void; documentEditor?: TaskDocumentEditorOptions } = {}) { + function render(props: { onOpenDocument?: (document: IssueDocument) => void; documentEditor?: TaskDocumentEditorOptions; documentDeepLink?: { requestId: number; documentKey: string } } = {}, openText?: (attachmentId: string, name: string) => void) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } }); flushSync(() => { root.render( - + + + , ); }); @@ -299,6 +302,55 @@ describe("IssuePropertiesArtifactsTab with Artifacts V1 on", () => { expect(service?.textContent).not.toContain("Running"); }); + describe("a Markdown work product", () => { + const contentPath = `/api/attachments/${ATTACHMENT_ID}/content`; + const reviewKey = "artifact-review-wp-report"; + const markdownWorkProduct = { + ...pdfWorkProduct, + id: "wp-report", + title: "Verification report", + metadata: { attachmentId: ATTACHMENT_ID, contentType: "text/markdown", byteSize: 64, contentPath, openPath: contentPath, downloadPath: `${contentPath}?download=1`, originalFilename: "report.md" }, + }; + const reviewDocument = { ...planDocument, id: "doc-review", key: reviewKey, title: "Verification report", body: "Rendered review body" }; + const reviewRow = () => container.querySelector('button[aria-label="Open report.md"]'); + + beforeEach(() => { + mockIssuesApi.listAttachments.mockResolvedValue([]); + mockIssuesApi.listWorkProducts.mockResolvedValue([markdownWorkProduct]); + mockUseIssueDocuments.mockReturnValue({ data: [reviewDocument] }); + }); + + it("expands its review in place from the row", async () => { + render(); + await waitFor(() => expect(reviewRow()).not.toBeNull()); + expect(reviewRow()!.getAttribute("aria-expanded")).toBe("false"); + expect(container.textContent).not.toContain("Rendered review body"); + act(() => reviewRow()!.click()); + expect(reviewRow()!.getAttribute("aria-expanded")).toBe("true"); + await waitFor(() => expect(container.textContent).toContain("Rendered review body")); + expect(mockIssuesApi.ensureWorkProductReviewDocument).not.toHaveBeenCalled(); + }); + + it("opens its review from a deep link", async () => { + render({ documentDeepLink: { requestId: 1, documentKey: reviewKey } }); + await waitFor(() => expect(container.textContent).toContain("Rendered review body")); + expect(reviewRow()!.getAttribute("aria-expanded")).toBe("true"); + }); + + it("keeps its open, raw and download actions beside the row", async () => { + const openText = vi.fn(); + render({}, openText); + await waitFor(() => expect(reviewRow()).not.toBeNull()); + const raw = container.querySelector('a[aria-label="Open raw Verification report"]'); + expect(raw?.getAttribute("href")).toBe(contentPath); + expect(raw?.getAttribute("target")).toBe("_blank"); + expect(container.querySelector('a[aria-label="Download Verification report"]')?.getAttribute("href")).toBe(`${contentPath}?download=1`); + act(() => container.querySelector('button[aria-label="Open in tab: Verification report"]')!.click()); + expect(openText).toHaveBeenCalledWith(ATTACHMENT_ID, "report.md"); + expect(reviewRow()!.getAttribute("aria-expanded")).toBe("false"); + }); + }); + it("opens a task document the way the panel asks, through onOpenDocument", async () => { const onOpenDocument = vi.fn(); render({ onOpenDocument }); diff --git a/ui/src/components/issue-properties/IssuePropertiesMarkdownWorkProduct.test.tsx b/ui/src/components/issue-properties/IssuePropertiesMarkdownWorkProduct.test.tsx index d2fc25f5ffc..79d6774caa0 100644 --- a/ui/src/components/issue-properties/IssuePropertiesMarkdownWorkProduct.test.tsx +++ b/ui/src/components/issue-properties/IssuePropertiesMarkdownWorkProduct.test.tsx @@ -26,6 +26,7 @@ const mockUseIssueDocuments = vi.hoisted(() => vi.fn((): { data: IssueDocument[] } => ({ data: [] })), ); vi.mock("@/hooks/useIssueDocuments", () => ({ useIssueDocuments: mockUseIssueDocuments })); +vi.mock("@/hooks/useArtifactsV1Enabled", () => ({ useArtifactsV1Enabled: () => ({ enabled: false, loaded: true }) })); vi.mock("@/lib/router", () => ({ Link: ({ to, children, ...props }: { to: string; children: React.ReactNode }) => {children}, useLocation: () => ({ hash: "" }), diff --git a/ui/src/hooks/useArtifactsV1Enabled.test.ts b/ui/src/hooks/useArtifactsV1Enabled.test.ts index 8c18307f129..830182cad92 100644 --- a/ui/src/hooks/useArtifactsV1Enabled.test.ts +++ b/ui/src/hooks/useArtifactsV1Enabled.test.ts @@ -2,14 +2,21 @@ import { describe, expect, it } from "vitest"; import { resolveArtifactsV1Enabled } from "./useArtifactsV1Enabled"; describe("resolveArtifactsV1Enabled", () => { - it("is off until an instance opts in", () => { - expect(resolveArtifactsV1Enabled(undefined)).toBe(false); - expect(resolveArtifactsV1Enabled(null)).toBe(false); - expect(resolveArtifactsV1Enabled({})).toBe(false); - expect(resolveArtifactsV1Enabled({ enableArtifactsV1: false })).toBe(false); - }); - - it("is on when the setting is", () => { + it("is on before settings load and on every unmanaged instance", () => { + expect(resolveArtifactsV1Enabled(undefined)).toBe(true); + expect(resolveArtifactsV1Enabled(null)).toBe(true); + expect(resolveArtifactsV1Enabled({})).toBe(true); expect(resolveArtifactsV1Enabled({ enableArtifactsV1: true })).toBe(true); }); + + it("ignores a stored false, which other experiment writes persist", () => { + expect(resolveArtifactsV1Enabled({ enableArtifactsV1: false })).toBe(true); + expect(resolveArtifactsV1Enabled({ enableArtifactsV1: false, managedKeys: {} })).toBe(true); + }); + + it("honours a hosting operator's managed value", () => { + const managedKeys = { enableArtifactsV1: { managed: true as const, managedBy: "paperclip-cloud" as const } }; + expect(resolveArtifactsV1Enabled({ enableArtifactsV1: false, managedKeys })).toBe(false); + expect(resolveArtifactsV1Enabled({ enableArtifactsV1: true, managedKeys })).toBe(true); + }); }); diff --git a/ui/src/hooks/useArtifactsV1Enabled.ts b/ui/src/hooks/useArtifactsV1Enabled.ts index 96490ebd5e7..8cfd2beb339 100644 --- a/ui/src/hooks/useArtifactsV1Enabled.ts +++ b/ui/src/hooks/useArtifactsV1Enabled.ts @@ -1,11 +1,11 @@ import { useContext } from "react"; -import type { InstanceExperimentalSettings } from "@paperclipai/shared"; +import type { InstanceExperimentalSettingsWithManaged } from "@paperclipai/shared"; import { QueryClient, QueryClientContext, useQuery } from "@tanstack/react-query"; import { instanceSettingsApi } from "@/api/instanceSettings"; import { queryKeys } from "@/lib/queryKeys"; -export function resolveArtifactsV1Enabled(settings: Partial> | null | undefined): boolean { - return settings?.enableArtifactsV1 === true; +export function resolveArtifactsV1Enabled(settings: Partial> | null | undefined): boolean { + return !settings?.managedKeys?.enableArtifactsV1 || settings.enableArtifactsV1 !== false; } let detachedClient: QueryClient | null = null; @@ -15,11 +15,13 @@ function getDetachedClient(): QueryClient { } /** - * Artifacts V1 (Settings > Experimental): the artifact directions lab's - * presentation on the Artifacts page, the panel's Artifacts tab and the task - * thread. Off by default โ€” and off when rendered outside a query client, as - * the app root is in some harnesses โ€” so every surface renders as before - * until an instance opts in. See doc/plans/2026-10-22-artifacts-mvp-v1.md. + * Artifacts V1: the artifact directions lab's presentation on the Artifacts + * page, the panel's Artifacts tab and the task thread. On for every instance, + * with no setting to change it: writes persist every flag, so a stored false + * only means an instance saved some other experiment. A hosting operator's + * managed value can still hold it off. Off when rendered outside a query + * client, as the app root is in some harnesses. See + * doc/plans/2026-10-22-artifacts-mvp-v1.md. */ export function useArtifactsV1Enabled(): { enabled: boolean; loaded: boolean } { const contextClient = useContext(QueryClientContext); diff --git a/ui/src/pages/InstanceExperimentalSettings.test.tsx b/ui/src/pages/InstanceExperimentalSettings.test.tsx index d040cd59667..2297abed2fd 100644 --- a/ui/src/pages/InstanceExperimentalSettings.test.tsx +++ b/ui/src/pages/InstanceExperimentalSettings.test.tsx @@ -1176,6 +1176,12 @@ describe("InstanceExperimentalSettings โ€” operator-hidden cards", () => { expect(container.textContent).not.toContain("Show the Apps navigation"); }); + it("offers no Artifacts V1 toggle", async () => { + await renderPage([]); + expect(container.textContent).toContain("Beta skills"); + expect(container.textContent).not.toContain("Artifacts V1"); + }); + it("keeps only permitted controls in alphabetical order, including when hidden features are enabled", async () => { setWorktreeRuntimeMeta(true); const visible = new Set(["enableExternalObjects", "enableMemoryConnectors", "enableSimplifiedEnglishInteractions"]); diff --git a/ui/src/pages/InstanceExperimentalSettings.tsx b/ui/src/pages/InstanceExperimentalSettings.tsx index 706a2b28226..0a724e84337 100644 --- a/ui/src/pages/InstanceExperimentalSettings.tsx +++ b/ui/src/pages/InstanceExperimentalSettings.tsx @@ -320,18 +320,6 @@ export function InstanceExperimentalSettings() { ariaLabel="Toggle agent chat experimental setting" /> - toggleMutation.mutate({ enableArtifactsV1: checked })} - disabled={toggleMutation.isPending} - settingKey="enableArtifactsV1" - managed={managedKeys.enableArtifactsV1} - ariaLabel="Toggle artifacts V1 experimental setting" - /> -