Files
PaperClipAI/tests/storybook-visual/feature-gap-render-states.ts
T
DottaandPaperclip 9077748894 Remove the Pipelines experiment and preserve its data (#16834)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Pipelines was an unused experiment with its own UI, API, CLI, and
automation.
> - Keeping it leaves unsupported product surfaces and extra task-detail
queries.
> - Removing the experiment must preserve data that users already saved.
> - This pull request removes the feature and keeps its database schema
and migration history.
> - Cases and Routines remain available as separate features.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Remove the unused Pipelines experiment across the application.

**Current behavior**

The experiment includes pipeline boards, stage settings, case
automation, review queues, learnings, permissions, and CLI commands.
Task detail also queries pipeline links.

**Proposed behavior**

Remove all these product surfaces and their implementation. Preserve
existing tables, records, and documents. Ignore the retired boolean in
older managed configurations so upgrades still start.

**Breaking changes**

Pipeline URLs, API endpoints, CLI commands, and `pipelines:write` are
removed. Task detail no longer includes the old `linkedCases` field. The
independent Cases API remains available. No database migration or
cleanup is included.

Related feature work: #7907, #9110, #10905, #11375, #13004, #14671,
#16133. Related issues: #9663, #10310, #10312. This removal supersedes
the feature; it does not implement those pending enhancements or delete
their saved data.

## What Changed

- Remove Pipeline UI pages, navigation, gates, clients, query keys,
helpers, and unused design tokens.
- Remove Pipeline REST routes, services, automation dispatch,
task-detail queries, and OpenAPI discovery.
- Remove the CLI commands, shared contracts, experimental flag, catalog
entry, and permission defaults.
- Remove feature-specific tests, tutorials, smoke scripts, maps, and
Storybook examples.
- Preserve database schema, migration history, and the reserved document
key used by old records.
- Keep Cases routes independent and cover obsolete stored and managed
settings.

## Verification

- Passed: `pnpm build` and `pnpm -r typecheck`.
- Passed: targeted settings, managed configuration, sidebar, settings
UI, routing, and feature-catalog tests. A first sidebar run caught a
shared Workspaces icon import; the import was restored and the sidebar
rerun passed.
- Passed: 40 tests covering Cases routes, OpenAPI discovery, retained
Pipeline table constraints, and schema snapshot drift.
- Passed: token gates, map validation, generated API metadata check,
Storybook catalog validation, and 28 map/catalog tests.
- Passed: isolated local browser verification of navigation and
Experimental settings with Pipelines absent; Cases and Routines still
open normally. Live GET/POST requests to retired Pipeline endpoints
return 404, and the retired setting is absent. CLI help has no Pipeline
commands.
- Passed: 22 access-service and agent-default-grant tests after
restoring unrelated permission assertions flagged in review.
- Local full run: `pnpm test:run` reported one failure in the unchanged
workspace-runtime port-allocation test. That test passed in an isolated
rerun. The long local run was stopped after full CI passed; it is not
reported as a complete local pass.
- Passed: all 56 checks on `def9181f3a4491d95fca1d38e9739f5b821aae3e` (2
conditional checks skipped), including build, typecheck, test shards,
and browser suites. Eleven interrupted CI jobs reported “The operation
was canceled”; their one-time retry passed. [CI
run](https://github.com/paperclipai/paperclip/actions/runs/39255211006).
- Greptile: 5/5 on the final commit, with the only review finding fixed
and resolved.
- Sandbox socket restrictions affected the first build/API test
attempts. The same commands ran with local sockets available.

## Risks

- Existing Pipeline clients must stop using the removed endpoints and
commands.
- Existing data remains in the database and backups, but the removed UI
no longer displays it.
- Historical boolean `enablePipelines` values are ignored. They cannot
restore the feature.
- Cases and Routines must retain their separate behavior. Targeted route
tests cover this boundary.

## Model Used

OpenAI Codex, GPT-6, with repository inspection, editing, shell
execution, and browser verification. The exact deployed model ID and
context-window size are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-19 08:09:46 -05:00

297 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, type FrameLocator } from "@playwright/test";
// These expectations name shipped UI states, not captured visual baselines.
// Explicit fallback cases document where production has no separate error UI.
type State = {
text?: string | RegExp;
absent?: string | RegExp;
skeleton?: true;
selector?: string;
emptyRootReason?: string;
tokenHistory?: "active" | "expired" | "empty";
editable?: true;
defaultEnvironment?: true;
};
const failed: State = { text: "The request failed. Please try again." };
const denied: State = {
text: "You do not have permission to perform this action.",
};
const skeleton: State = { skeleton: true };
const documentFallback: State = {
text: "New document",
absent: "Run the release checks.",
};
const environmentFallback: State = {
defaultEnvironment: true,
absent: "Release sandbox",
};
const states: Record<string, Record<string, State>> = {
"feature-cli-authorization": {
pending: { text: "Approve Paperclip CLI access" },
loading: { text: "Loading CLI auth challenge..." },
expired: { text: "CLI auth challenge expired" },
unavailable: { text: "CLI auth challenge unavailable" },
},
"feature-membership": {
members: { text: "Release reviewer" },
loading: { text: "Loading organization access…" },
empty: { text: "No user memberships found for this organization yet." },
failed,
"permission-denied": {
text: "You do not have permission to manage organization members.",
},
},
"feature-sign-in": {
"signed-out": { text: "Sign in to Paperclip" },
loading: { text: "Loading…" },
"session-failed": {
text: "Unable to check sign-in. Refresh and try again.",
},
},
"feature-run-audit": {
loading: { text: "Loading runs…" },
empty: { text: "No runs yet." },
},
"feature-conference-room": {
empty: { text: "Conference Room", editable: true },
},
"feature-catalog-review": {
quarantined: { text: "Review 1 new action" },
empty: { text: "No actions match “”. Clear the search to see them all." },
refreshing: { text: "Review 1 new action", selector: ".animate-spin" },
saving: { text: "Saving…" },
"read-only": { text: "Actions", absent: "Review 1 new action" },
},
"feature-budget-resolution": {
loading: skeleton,
empty: { text: /No budget policies yet\./ },
"load-failed": {
text: "Budget data could not be loaded. Please try again shortly.",
},
"permission-denied": {
text: "Budget data could not be loaded. Please try again shortly.",
},
},
// Cases currently uses its empty presentation after a failed list query.
"feature-case-browse": {
loading: skeleton,
empty: { text: "No cases yet" },
failed: { text: "No cases yet" },
"permission-denied": { text: "No cases yet" },
},
"feature-case-detail": {
loading: skeleton,
failed: { text: "Case not found." },
"permission-denied": { text: "Case not found." },
"arbitrary-fields": { text: "0.3.1" },
},
"feature-channel-attachments": {
"no-files": { text: "Here is the release report.", absent: /Files ·/ },
"multiple-files": { text: "Files · 2" },
"missing-filename": { text: "attachment" },
},
"feature-company-import": { "choose-source": { text: "Import source" } },
"feature-company-selection": {
organizations: { text: "Archived Research" },
loading: { text: "Loading organizations..." },
empty: { text: "New Organization", absent: "Paperclip Storybook" },
failed,
"permission-denied": {
text: "New Organization",
absent: "Paperclip Storybook",
},
},
"feature-document-revisions": {
empty: documentFallback,
loading: documentFallback,
"read-failed": documentFallback,
"permission-denied": documentFallback,
edit: { text: "Run the release checks.", editable: true },
},
"feature-environment-images": {
"no-image": { text: "Not configured" },
"image-read-failed": { text: "Image metadata is unavailable." },
"unsupported-provider": { text: "Unsupported provider" },
},
"feature-execution-environments": {
loading: environmentFallback,
empty: environmentFallback,
failed: environmentFallback,
"permission-denied": environmentFallback,
"unsupported-provider": { text: "Isolated release checks" },
"edit-configuration": {
text: "Edit environment",
selector: '[data-testid="environment-form-page"]',
},
},
"feature-gateway-tokens": {
"active-token": { tokenHistory: "active" },
"no-tokens": { tokenHistory: "empty" },
"expired-token": { tokenHistory: "expired" },
},
"feature-goal-alignment": {
"empty-relationships": { text: "No sub-goals." },
loading: skeleton,
failed,
"permission-denied": denied,
},
"feature-inbox-views": {
mine: { text: "Create super-detailed storybooks for the project" },
recent: { text: "Create super-detailed storybooks for the project" },
unread: { text: "Create super-detailed storybooks for the project" },
blocked: { text: "No work is stopped." },
empty: { text: "Inbox zero." },
loading: skeleton,
failed: { text: "Inbox zero." },
"permission-denied": { text: "Inbox zero." },
},
// Order reads are allowed to fall back to navigation; section collapse is local.
"feature-sidebar-preferences": {
"saved-order": {
text: "Paperclip Storybook",
selector: 'a[href="/PAP/dashboard"]',
},
"loading-order": {
text: "Paperclip Storybook",
selector: 'a[href="/PAP/dashboard"]',
},
"order-read-failed": {
text: "Paperclip Storybook",
selector: 'a[href="/PAP/dashboard"]',
},
"order-permission-denied": {
text: "Paperclip Storybook",
selector: 'a[href="/PAP/dashboard"]',
},
},
"feature-onboarding-resume": {
"malformed-draft": {
text: "Create your first agent",
selector: '[data-testid="onboarding-wizard"]',
},
},
"feature-plugin-configuration": {
configured: {
text: "Preview plugin",
selector: 'input[value="Release preview"]',
},
loading: { text: "Loading plugin details..." },
unavailable: { text: "Failed to load plugins." },
},
"feature-plugin-contributions": {
loading: { text: "Loading…" },
"missing-contribution": { text: "Preview plugin" },
"discovery-failure": { text: "Loading…" },
},
"feature-plugin-management": {
installed: { text: "Preview plugin" },
empty: { text: "No plugins installed" },
loading: { text: "Loading plugins..." },
failed: { text: "Failed to load plugins." },
"permission-denied": { text: "Failed to load plugins." },
},
"feature-routine-run-now": {
overview: { text: "Review a release" },
loading: skeleton,
failed,
"permission-denied": denied,
},
"feature-secret-proposal-review": {
pending: { text: "dev/github/token" },
loading: { text: "Loading proposals…" },
empty: { text: "No pending proposals" },
failed: { text: "Couldn’t load proposals. Try again." },
},
"feature-skill-test-runs": {
"completed-test": { text: "succeeded" },
queued: { text: "queued" },
running: { text: "running" },
"test-failed": { text: "failed" },
empty: { text: "No test runs yet. Pick an agent and Run." },
loading: { text: "Loading skill…" },
"load-failed": { text: "Skill not found." },
"permission-denied": { text: "Skill not found." },
},
"feature-status-card-states": {
loading: { text: "Loading cards…" },
empty: { text: "No status cards yet" },
"load-failed": { text: "Could not load status cards" },
"permission-denied": {
text: "You do not have permission to perform this action.",
},
"setting-up": { text: "Setting up your card…" },
"generation-failed": { text: "Last update failed" },
"stale-summary": { text: "3 changes since last update" },
},
"feature-composer-draft-recovery": {
restored: { text: "Review the release before publishing.", editable: true },
empty: { editable: true, absent: "Review the release before publishing." },
"uncertain-submission": {
text: /We couldn’t confirm whether this comment was saved\./,
selector: '[role="alert"]',
},
},
"feature-durable-response-queue": {
empty: {
emptyRootReason:
"The shipped queued-message component returns null for zero entries.",
},
},
};
export async function assertRenderState(frame: FrameLocator, id: string) {
const [suite, variant] = id.split("--");
const state = states[suite]?.[variant];
if (!state)
throw new Error(`Declare an explicit render expectation for ${id}`);
expect(
state,
`Declare an explicit render expectation for ${id}`,
).toBeDefined();
if (state.emptyRootReason) {
await expect(frame.locator("#storybook-root")).toBeEmpty();
return;
}
if (state.text)
await expect(
frame
.getByText(state.text, { exact: typeof state.text === "string" })
.first(),
).toBeVisible();
if (state.absent)
await expect(
frame.getByText(state.absent, {
exact: typeof state.absent === "string",
}),
).toHaveCount(0);
if (state.skeleton)
await expect(frame.locator('[data-slot="skeleton"]').first()).toBeVisible();
if (state.selector)
await expect(frame.locator(state.selector).first()).toBeVisible();
if (state.defaultEnvironment) {
const select = frame.getByRole("combobox", { name: "Default environment" });
await expect(select).toBeVisible();
await expect(select).toHaveValue("");
await expect(
select.getByRole("option", { name: "Local", exact: true }),
).toBeAttached();
}
if (state.editable)
await expect(frame.getByRole("textbox").first()).toBeVisible();
if (state.tokenHistory) {
await frame.getByRole("button", { name: /Token history/ }).click();
if (state.tokenHistory === "empty")
await expect(
frame.getByText("No tokens have been issued for this gateway."),
).toBeVisible();
else {
await expect(
frame.getByText("Preview client", { exact: true }).first(),
).toBeVisible();
if (state.tokenHistory === "expired")
await expect(
frame.getByText("Expired", { exact: true }).first(),
).toBeVisible();
}
}
}