mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
## Thinking Path > - Paperclip is the open source control plane people use to manage AI agents for work. > - Environment variables and secrets sit in the configuration surfaces that let agents, projects, routines, and company environments run with the right runtime inputs. > - The previous editor was a single legacy component with cramped row behavior, weak secret conversion affordances, and duplicated handling across several call sites. > - Operators need a clearer editor that handles text values, secret references, draft rows, and sensitive-value warnings consistently wherever environment variables are configured. > - This pull request replaces the legacy editor with a reusable environment variables editor and migrates the existing configuration surfaces to it. > - The benefit is a more reliable editing workflow with targeted test coverage around row state, dotenv parsing, secret selection, and affected page integrations. ## Linked Issues or Issue Description No public GitHub issue exists, so this PR describes the issue inline following the feature request template. ### Subsystem affected ui/ — React + Vite board UI ### Problem or motivation Environment variables are edited in several Paperclip configuration surfaces, including agent config, project properties, stage secrets, routine sections, company environments, and company settings. The legacy editor made common operator work difficult: rows could feel cramped, secret conversion was inconsistent, draft rows and imported dotenv data were easy to mishandle, and sensitive-value warnings did not have a consistent place in the workflow. ### Proposed solution Introduce a reusable environment variables editor component that consistently supports text values, secret references, draft rows, dotenv import parsing, sensitive-value hints, secret picking, secret creation, and conversion to stored secrets. Migrate the existing environment-variable call sites to the shared editor so behavior and tests live in one component family. ### Alternatives considered Keeping the existing `EnvVarEditor` and patching individual call sites would preserve duplication and leave each surface responsible for its own row and secret handling. This PR instead centralizes the behavior so future fixes cover all migrated surfaces. ### Roadmap alignment Checked `ROADMAP.md`; this does not duplicate a named roadmap item. It supports the existing local-first and deployment-oriented product direction by improving the UI where operators configure runtime environment values. ### Additional context The PR includes targeted tests for the editor model, dotenv parsing, sensitive-value detection, component behavior, affected page integrations, and Greptile review regressions around external saves and bulk import immutability. ## What Changed - Replaced the legacy `EnvVarEditor` with a reusable `environment-variables-editor` component family. - Added editor model helpers for draft rows, dotenv parsing, sensitive-value detection, secret picking, secret creation, and conversion to secret references. - Migrated agent config, project properties, stage secrets, routine editable sections, company environments, company settings, design guide examples, and Storybook stories to the new editor. - Added targeted tests for the editor model, parsing, sensitive-value detection, component behavior, and affected company environment/settings integrations. - Fixed the company settings test harness to use the repo’s `flushSync`-based React test helper pattern under the current React build. - Addressed Greptile feedback by flushing pending editor drafts before enclosing form submits or external save-button clicks, cloning bulk-import rows before mutation, and deferring the overflow store-as-secret popover open path. ## Verification - `pnpm exec vitest run ui/src/components/environment-variables-editor/EnvironmentVariablesEditor.test.tsx ui/src/pages/CompanyEnvironments.test.tsx ui/src/components/AgentConfigForm.render.test.tsx` — passed, 3 files / 40 tests. - Earlier focused Vitest coverage for model, dotenv parsing, sensitive detection, company environments, and company settings passed, 6 files / 78 tests. - `pnpm --filter @paperclipai/ui typecheck` — passed. - GitHub PR checks on head `0aa49c6f8afed1f62d9e26da07fc466fbf299850` — passed; CI checks green, security review neutral, Greptile check success. - Greptile review — 5/5 confidence on head `0aa49c6f8afed1f62d9e26da07fc466fbf299850`; 0 unresolved review threads. ## Risks - Medium UI risk: several environment-variable entry surfaces now share the new editor, so regressions could affect multiple configuration workflows at once. - Secret conversion, draft-row behavior, external save flushing, and bulk import behavior are covered by targeted tests, but reviewer attention should still focus on manual editing flows, focus retention, and save/cancel affordances. - No database or API contract changes. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI Codex, GPT-5-based coding agent with tool use and local command execution; medium reasoning mode. Exact context-window metadata is not exposed in this runtime. ## 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: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Paperclip <noreply@paperclip.ing>
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
/**
|
|
* Minimal `.env`-style parser for bulk-pasting environment variables into the
|
|
* editor (plan §6.9). Deliberately forgiving: it ignores comments and blank
|
|
* lines, tolerates a leading `export `, strips one layer of matching quotes,
|
|
* and returns key/value pairs in source order. It is NOT a full dotenv
|
|
* implementation (no variable interpolation, no multi-line values).
|
|
*/
|
|
export interface ParsedEnvPair {
|
|
key: string;
|
|
value: string;
|
|
}
|
|
|
|
/** A KEY at the start of a line, optionally prefixed with `export `. */
|
|
const LINE_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/;
|
|
|
|
function stripInlineComment(value: string): string {
|
|
// Only strip a ` #comment` when the value is unquoted. Quoted values are
|
|
// handled by the caller before this runs, so here we only see bare values.
|
|
const hashIndex = value.indexOf(" #");
|
|
if (hashIndex >= 0) return value.slice(0, hashIndex);
|
|
return value;
|
|
}
|
|
|
|
function unquote(raw: string): string {
|
|
const trimmed = raw.trim();
|
|
if (trimmed.length >= 2) {
|
|
const first = trimmed[0];
|
|
const last = trimmed[trimmed.length - 1];
|
|
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
return trimmed.slice(1, -1);
|
|
}
|
|
}
|
|
return stripInlineComment(trimmed).trim();
|
|
}
|
|
|
|
/**
|
|
* Returns the parsed pairs, or an empty array when nothing looks like an env
|
|
* assignment. Callers can treat an empty result as "not a dotenv paste" and
|
|
* fall through to the browser's default paste handling.
|
|
*/
|
|
export function parseDotenv(text: string): ParsedEnvPair[] {
|
|
const pairs: ParsedEnvPair[] = [];
|
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith("#")) continue;
|
|
const match = LINE_RE.exec(line);
|
|
if (!match) continue;
|
|
const key = match[1];
|
|
const value = unquote(match[2]);
|
|
pairs.push({ key, value });
|
|
}
|
|
return pairs;
|
|
}
|
|
|
|
/**
|
|
* Heuristic: does a paste look like a `.env` block worth importing? We require
|
|
* either more than one parsed pair, or a single pair that spans a genuine
|
|
* `KEY=VALUE` (so a lone `FOO=bar` typed into an empty name still imports, but
|
|
* pasting a bare token like `ghp_xxx` does not hijack the field).
|
|
*/
|
|
export function looksLikeDotenv(text: string): boolean {
|
|
if (!text.includes("=")) return false;
|
|
return parseDotenv(text).length > 0;
|
|
}
|