Files
PaperClipAI/ui
Devin Foley 49d1abc458 fix(ui): detail the env unsaved-changes banner and guard against draft loss (#9391)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Instance settings include an Environments section where operators
configure execution environments, each with an environment-variables
editor for run-time bindings
> - The editor showed a bare "Unsaved changes" banner that never said
which variables changed, sometimes appeared the moment a saved config
was opened (a lossy round-trip through the editor's emit rules made
clean values look dirty), and the environment form let you navigate away
without any confirmation, silently dropping the draft
> - Operators could not tell what was unsaved, distrusted the phantom
banner, and lost half-finished environment edits to a stray click — the
agent configuration page already confirms before discarding, so
environments behaved inconsistently
> - This pull request lists the new/edited/removed variable names under
the banner, normalizes both sides of the dirty comparison so saved
values no longer look dirty on open, and confirms before cancel, in-app
navigation, or tab unload while the form has unsaved changes
> - The benefit is that the banner is trustworthy and specific, and
unsaved environment edits can no longer be lost without an explicit
confirmation

## Linked Issues or Issue Description

Related (not fixed by this PR): #8930 introduced the current
environment-variables editor and its unsaved-changes banner; #9386 moved
environment create/edit from a modal to routed pages, which this PR's
navigation guard builds on.

No existing public issue for the defects themselves; described per the
bug report template:

**What happened?**

The environment-variables editor in Environments settings showed a bare
"Unsaved changes" banner with no indication of which variables changed.
For some saved configurations (names with surrounding whitespace,
incomplete secret references, duplicate names differing only by
whitespace) the banner appeared immediately on opening the edit form,
before any user input. Navigating away from the environment form —
cancel, an in-app link, or closing the tab — silently discarded the
draft with no confirmation.

**Expected behavior**

The banner should say which variables are new, edited, or removed; a
freshly opened saved configuration should show no banner; and leaving
the form with unsaved changes should require an explicit confirmation,
consistent with the agent configuration page.

**Steps to reproduce**

1. Open Settings → Instance settings → Environments and edit an
environment whose saved config round-trips lossily (e.g. an env var name
stored with trailing whitespace) — the "Unsaved changes" banner appears
with no user edits.
2. Add or edit a variable — the banner gives no hint of what is unsaved.
3. With a dirty draft, click any in-app link or Cancel — the draft is
dropped with no confirmation.

**Deployment mode**

Self-hosted (local development instance), reproducible on `master`.

## What Changed

- The unsaved-changes banner in `EnvironmentVariablesEditor` now renders
a change summary line — `New: … · Edited: … · Removed: …` — showing up
to three names per group with a `+N more` overflow and the full list in
a `title` tooltip. A rename shows as one addition plus one removal.
- Dirty detection normalizes both the committed value and the draft
through the same rules the editor uses when emitting values (trimmed
names, incomplete secret refs dropped, last-writer-wins on trimmed
duplicates), so a saved config that round-trips lossily no longer shows
a phantom banner on first open.
- The editor exposes an `onDirtyChange` callback and warns via
`beforeunload` while its local draft is dirty.
- The environment create/edit page (`CompanyEnvironments`) tracks a
payload-level baseline fingerprint of the form as initialized and treats
the page as having unsaved changes when the current form differs from it
or the editor draft is dirty. While dirty it confirms ("Discard unsaved
environment changes?") on Cancel, intercepts same-origin in-app link
clicks, and warns on tab unload.

## Verification

- `node_modules/.bin/vitest run
ui/src/pages/CompanyEnvironments.test.tsx
ui/src/components/environment-variables-editor/EnvironmentVariablesEditor.test.tsx`
— 48 tests pass, including new coverage for: the change-summary banner
text, no phantom banner for lossy round-trip values, beforeunload only
while dirty, cancel confirmation on the edit page, and unload/link-click
warnings after edits are staged into the form.
- `tsc -b` in `ui/` passes.
- Manual: edit an environment, add/edit/remove variables, observe the
summary line; click Cancel or an in-app link and observe the
confirmation; save and observe navigation proceeds without prompting.

## Risks

- Low risk, UI-only. The click interceptor is scoped to same-origin
anchor navigation while the environment form page has unsaved changes
and is removed on cleanup; modified-key/middle-button clicks and
external links are left alone.
- The dirty-normalization intentionally ignores differences the editor
could never persist (incomplete secret refs, untrimmed duplicate names);
those were previously reported as unsaved changes that could not be
saved away.

## Model Used

- Claude Fable 5 (`claude-fable-5`, Anthropic) via Claude Code, with
extended thinking and tool use (code editing, test execution).

## 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
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-07-10 17:14:28 -07:00
..

@paperclipai/ui

Published static assets for the Paperclip board UI.

What gets published

The npm package contains the production build under dist/. It does not ship the UI source tree or workspace-only dependencies.

Storybook

Storybook config, stories, and fixtures live under ui/storybook/.

pnpm --filter @paperclipai/ui storybook
pnpm --filter @paperclipai/ui build-storybook

Typical use

Install the package, then serve or copy the built files from node_modules/@paperclipai/ui/dist.