mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The experimental settings page renders one interactive toggle per feature from the settings API > - On managed instances some values are enforced by the hosting control plane, and the API now reports those keys as managed > - Rendering enforced values as live toggles misleads users: the click appears to work, and the value silently snaps back > - This pull request renders managed keys as locked toggles with a "Managed by Paperclip Cloud" badge and guards the handlers so no PATCH can be emitted > - The benefit is UI honesty on managed instances, with self-hosted responses rendering exactly as before ## Linked Issues or Issue Description Builds on #10058 — renders the per-key `managedKeys` metadata #10058 adds to settings responses (typing shared from #10058 at rebase). No public issue exists; `feature_request` template fields: - **Problem or motivation:** on managed instances users see fully interactive toggles for settings the control plane enforces; changes appear to apply and never do, with no explanation. - **Proposed solution:** disabled toggle + badge + guarded handler driven by the settings response's managed-key metadata; the ~17 uniform setting cards are extracted into one shared component with copy, aria-labels, and patch payloads preserved verbatim. - **Alternatives considered:** hiding managed settings entirely (users lose sight of the effective value and why it is fixed); tooltip-only hints on still-active toggles (doomed PATCHes are still emitted and stripped server-side). - **Roadmap alignment:** supports the in-progress "Cloud deployments" milestone in `ROADMAP.md`. ## What Changed - When the settings API reports a feature key as managed (`managedKeys` from the managed-config overlay), the experimental settings page renders that toggle disabled with a badge and a guarded handler, so a click can never emit a PATCH. Previously, managed-instance users saw fully interactive toggles they could never actually change. Self-hosted responses (no `managedKeys`) render exactly as before. - The ~17 copy-pasted uniform setting cards are extracted into one `ExperimentalToggleCard` component with titles, descriptions, footnotes, aria-labels, and patch payloads preserved verbatim; the two bespoke cards get inline managed handling (the managed auto-recovery toggle also cannot open its preview dialog). - `ui/src/api/instanceSettings.ts` response typing now uses the shared `InstanceExperimentalSettingsWithManaged` / `ManagedSettingMetadata` types from #10058; `ui/src/pages/InstanceExperimentalSettings.tsx` locked rendering + card extraction; tests. ## Verification - 24 page tests (20 existing unmodified + 4 new: locked badge with no PATCH while unmanaged keys stay editable; managed auto-recovery opens no dialog; an open recovery preview closes with no PATCH when a refresh marks auto-recovery managed; self-hosted unaffected): `pnpm --filter @paperclipai/ui exec vitest run src/pages/InstanceExperimentalSettings.test.tsx` - `pnpm --filter @paperclipai/ui typecheck` clean ## Risks - Low risk. UI-only change; no server or API behavior changes. Self-hosted responses carry no `managedKeys`, so the page renders exactly as before there. The card extraction preserves copy, aria-labels, and patch payloads verbatim, covered by the 20 pre-existing page tests passing unmodified. ## Model Used Claude Fable 5 (`claude-fable-5`), extended thinking, agentic tool use; independently peer-reviewed by a second AI agent before push ## 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 - [ ] 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>
58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import type {
|
|
InstanceExperimentalSettingsWithManaged,
|
|
InstanceGeneralSettings,
|
|
InstanceSettings,
|
|
IssueGraphLivenessAutoRecoveryPreview,
|
|
PatchInstanceSettings,
|
|
PatchInstanceGeneralSettings,
|
|
PatchInstanceExperimentalSettings,
|
|
} from "@paperclipai/shared";
|
|
import { api } from "./client";
|
|
|
|
export const instanceSettingsApi = {
|
|
get: () =>
|
|
api.get<InstanceSettings>("/instance/settings"),
|
|
update: (patch: PatchInstanceSettings) =>
|
|
api.patch<InstanceSettings>("/instance/settings", patch),
|
|
getGeneral: () =>
|
|
api.get<InstanceGeneralSettings>("/instance/settings/general"),
|
|
updateGeneral: (patch: PatchInstanceGeneralSettings) =>
|
|
api.patch<InstanceGeneralSettings>("/instance/settings/general", patch),
|
|
getExperimental: () =>
|
|
api.get<InstanceExperimentalSettingsWithManaged>("/instance/settings/experimental"),
|
|
updateExperimental: (patch: PatchInstanceExperimentalSettings) =>
|
|
api.patch<InstanceExperimentalSettingsWithManaged>("/instance/settings/experimental", patch),
|
|
previewIssueGraphLivenessAutoRecovery: (input: { lookbackHours?: number }) =>
|
|
api.post<IssueGraphLivenessAutoRecoveryPreview>(
|
|
"/instance/settings/experimental/issue-graph-liveness-auto-recovery/preview",
|
|
input,
|
|
),
|
|
runIssueGraphLivenessAutoRecovery: (input: { lookbackHours?: number }) =>
|
|
api.post<{
|
|
findings: number;
|
|
autoRecoveryEnabled: boolean;
|
|
lookbackHours: number;
|
|
cutoff: string;
|
|
escalationsCreated: number;
|
|
existingEscalations: number;
|
|
skipped: number;
|
|
skippedAutoRecoveryDisabled: number;
|
|
skippedOutsideLookback: number;
|
|
dependencyWakeBackstopChecked: number;
|
|
dependencyWakesHealed: number;
|
|
dependencyWakeExistingSkipped: number;
|
|
dependencyWakeLivePathSkipped: number;
|
|
dependencyWakeInteractionSkipped: number;
|
|
dependencyWakePauseHoldSkipped: number;
|
|
dependencyWakeNotReadySkipped: number;
|
|
dependencyWakeCandidateLimitSkipped: number;
|
|
dependencyWakeDeferredOrFailed: number;
|
|
dependencyWakeEnqueueFailed: number;
|
|
dependencyWakeIssueIds: string[];
|
|
escalationIssueIds: string[];
|
|
}>(
|
|
"/instance/settings/experimental/issue-graph-liveness-auto-recovery/run",
|
|
input,
|
|
),
|
|
};
|