Commit Graph

15 Commits

Author SHA1 Message Date
Devin Foley bc1a21564f
Remove the company brand color and per-company attachment limit (#12291)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A company is the top-level container, and the company General page
holds its settings
> - Two of those settings did almost nothing: the brand color only
tinted the generated company icon, and the attachment size limit sat
under the deployment-level `PAPERCLIP_ATTACHMENT_MAX_BYTES` cap that
already bounded every upload
> - A setting that changes one icon hue, and a setting that can only
lower a limit the operator already set, are not worth the page space or
the code that carries them
> - This pull request deletes both settings from the UI, the validators,
the API contract, the server, and the database
> - With the deployment cap as the only limit left, the message a person
sees when an upload is rejected has to name that limit in terms they can
act on, so the raw byte count becomes a human-readable size
> - The benefit is a shorter company General page for every deployment,
one attachment limit instead of two, and less code between an upload and
its ceiling

## Linked Issues or Issue Description

No existing issue. The description below follows
`.github/ISSUE_TEMPLATE/enhancement.yml`.

**What existing behavior does this improve?**

The company General page (`/company/settings`), the `PATCH
/api/companies/{companyId}` and `PATCH
/api/companies/{companyId}/branding` request contracts, and the
attachment upload limit on task, case, and company-import uploads.

**Subsystem affected**

Cross-cutting: `ui/`, `server/`, `packages/shared`, `packages/db`.

**Current behavior**

The company General page shows an "Appearance" section with three
controls: Logo, Brand color, and Attachment size limit. The brand color
is a hex value that feeds one thing — the hue of the generated company
pattern icon. Companies that never set one already get a hue derived
from the company name. The attachment size limit is a per-company byte
count stored on `companies.attachment_max_bytes`. Every upload path
clamps it against the deployment-level `PAPERCLIP_ATTACHMENT_MAX_BYTES`
cap, so the per-company value can only lower a limit the operator
already chose.

**Proposed behavior**

The Appearance section keeps the Logo control only. The company pattern
icon always derives its hue from the company name. Every attachment path
reads the deployment cap directly, so `PAPERCLIP_ATTACHMENT_MAX_BYTES`
is the single limit. An upload rejected by that limit says so in human
units — "File is larger than the 10 MB limit" rather than a raw byte
count. The `companies.brand_color` and `companies.attachment_max_bytes`
columns are dropped, and both fields leave the company API contract.

**Reason and benefit**

Both settings ask an operator to make a decision that changes almost
nothing. The brand color moves one icon hue on a page that also lets you
upload a real logo, which overrides the icon entirely. The attachment
limit reads as a real control but cannot raise anything, so it is a
second place to look when an upload is rejected. Removing both shortens
the page every deployment sees, removes a company-scoped read from the
task attachment upload path, and leaves one attachment limit to reason
about instead of two.

**Breaking changes**

The company API responses no longer include `brandColor` or
`attachmentMaxBytes`, and `GET /api/invites/{token}` no longer includes
`companyBrandColor`. `PATCH /api/companies/{companyId}/branding` is
strict, so a request that sends `brandColor` now returns 400; the
non-strict `PATCH /api/companies/{companyId}` schema strips it. Company
packages exported by older versions still import: the portability
company manifest schema is non-strict, so the retired keys are stripped
and ignored rather than rejected. Companies that stored a brand color
lose it — their icon reverts to the name-derived hue that every company
without a color already used.

## What Changed

- Removed the "Brand color" and "Attachment size limit" fields from the
company General page, along with their state, dirty checks, save
payload, and Save-button gating.
- Removed `brandColor` and `attachmentMaxBytes` from
`createCompanySchema`, `updateCompanySchema`, and
`updateCompanyBrandingSchema`, and deleted the now-orphaned
`DEFAULT_COMPANY_ATTACHMENT_MAX_BYTES` and
`MAX_COMPANY_ATTACHMENT_MAX_BYTES` constants.
- Removed both fields from the `Company` type, the portability manifest
type and schema, and the `companiesApi.update` payload allowlist.
- Dropped `brandColor` from `CompanyPatternIcon` and its callers, so the
icon hue always comes from the company name. Deleted the now-unused
`hexToHue` helper and the now-unused `pickTextColorForSolidBg` export.
- Stopped emitting `brandColor` from the company service selection and
from the invite-summary and invite-branding payloads in
`server/src/routes/access.ts`.
- Replaced `normalizeIssueAttachmentMaxBytes` with the deployment cap:
task attachments, case attachments, and company import now use
`MAX_ATTACHMENT_BYTES` directly. The helper is deleted.
- Added `formatAttachmentSize()` next to `MAX_ATTACHMENT_BYTES` and
routed every over-limit message through it, so a rejected upload names
the limit in human units instead of raw bytes: `Image exceeds 10485760
bytes` becomes `Image is larger than the 10 MB limit`. Enforcement is
unchanged — the same single cap, the same multer limits, the same status
codes and response shapes.
- Added migration
`0229_drop_company_brand_color_and_attachment_max_bytes.sql` and removed
both columns from the Drizzle `companies` schema.
- Kept legacy imports working: the portability company manifest schema
is non-strict, so older packages carrying the retired keys still import
with the keys ignored.
- Updated the skill API reference and the implementation spec, and
pruned the token-extraction allowlist entries that the removed code made
stale.

## Verification

Commands run from the repository root:

- `pnpm --filter @paperclipai/shared typecheck` — pass
- `pnpm --filter @paperclipai/db typecheck` — pass (includes
`check:migrations`, which validates the new migration number and journal
entry)
- `pnpm --filter @paperclipai/ui typecheck` — pass
- server typecheck via `node_modules/.bin/tsc --noEmit` in `server/` —
pass. `pnpm --filter @paperclipai/server typecheck` could not run
locally because it builds the Rust runner first and `cargo` is not
installed on this machine; the TypeScript step it wraps is the command
above.
- `npx vitest run packages/shared/src/validators/company.test.ts` — 6
passed
- `npx vitest run server/src/__tests__/company-portability.test.ts` — 90
passed
- `npx vitest run server/src/__tests__/attachment-types.test.ts
server/src/__tests__/assets.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/company-portability.test.ts
server/src/__tests__/cases-routes.test.ts` — 165 passed (the
human-readable limit messages)
- `npx vitest run server/src/__tests__/company-branding-route.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/invite-summary-route.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/companies-route-cross-company-authz.test.ts` — all
passed
- `npx vitest run cli/src/__tests__/company.test.ts
cli/src/__tests__/company-delete.test.ts` — 27 passed
- `npx vitest run` in `ui/` — 4425 passed, 1 pre-existing failure
unrelated to this change (`OnboardingWizard.test.tsx` "renders instead
of throwing when the browser denies storage access", which also fails on
`master`)
- `npx vitest run` in `server/` — see the note below
- `node scripts/check-token-gates.mjs` — no new violations; the only
reported violations are the pre-existing `PillGuy.tsx` ones present on
`master`

New tests added:

- `packages/shared/src/validators/company.test.ts` — the create and
update schemas strip the retired keys, the strict branding schema
rejects `brandColor`, and the portability manifest schema accepts a
legacy entry carrying both keys and drops them.
- `server/src/__tests__/company-branding-route.test.ts` — `PATCH
/api/companies/{companyId}/branding` returns 400 for `brandColor` and
does not call the company service.
- `server/src/__tests__/company-portability.test.ts` — a legacy package
that declares `brandColor` and `attachmentMaxBytes` imports
successfully, and neither key reaches `companies.create`.
- `server/src/__tests__/issue-attachment-routes.test.ts` — the effective
task attachment limit is the deployment cap, and the route no longer
loads the company to size an upload.
- `server/src/__tests__/attachment-types.test.ts` —
`formatAttachmentSize()` renders the default cap as `10 MB`, keeps one
decimal place for fractional sizes and drops a trailing `.0`, falls back
to KB and bytes for small caps, steps up to GB, and never emits `NaN`
for a degenerate input.
- `server/src/__tests__/assets.test.ts` — the asset-image and
company-logo routes both return the human-readable limit message on an
over-cap upload.

## Merge with master

`master` moved while this was open, and the merge needed two
resolutions:

- **`ui/src/pages/CompanySettings.tsx`.** #12243 reworded the
user-facing
copy from "company" to "organization", and that rewording landed inside
  the "Brand color" and "Attachment size limit" hints — the two fields
this change deletes. Both fields are removed, so the conflicted block is
dropped whole. The Logo field and every other copy change from #12243
are
  kept.
- **Migration renumbered 0228 -> 0229.** #12307 landed
  `0228_nasty_grim_reaper`, so this migration is now
`0229_drop_company_brand_color_and_attachment_max_bytes`. Its snapshot
is
rebuilt from master's `0228_snapshot.json` with only the two `companies`
  columns removed, and `meta/_journal.json` is master's journal plus a
single `idx: 229` entry. `pnpm --filter @paperclipai/db
check:migrations`
  passes.

The snapshot was rebuilt by hand rather than taken from `drizzle-kit
generate`, because master's `0228_snapshot.json` has drifted from
master's
own schema: `issue_question_response_deliveries.error_count` is created
by
master's 0228 SQL but missing from its snapshot, and the snapshot still
carries `decision_archive_notification_outbox.error_count`. Regenerating
folds both into this migration, and the resulting `ADD COLUMN
error_count`
would fail on a fresh database where master's 0228 already created that
column. Rebuilding from master's snapshot leaves that drift exactly
where
it is and keeps this migration to the two column drops. The drift is
pre-existing on master and is not addressed here.

## Risks

- **The migration is a destructive column drop.**
`0229_drop_company_brand_color_and_attachment_max_bytes.sql` removes
`companies.brand_color` and `companies.attachment_max_bytes`. It is safe
because both features are removed in the same change and nothing reads
either column after it. The statements use `DROP COLUMN IF EXISTS`,
matching the convention of the recent drop migrations in this
repository. The drop is not reversible: a downgrade after this migration
loses any stored values.
- **Stored brand colors are lost.** A company that had set a color now
renders the name-derived icon hue that every company without a color
already used. No other surface changes, and an uploaded logo still
overrides the icon.
- **API response shape narrows.** `brandColor` and `attachmentMaxBytes`
leave the company payloads, and `companyBrandColor` leaves the invite
summary payload. A client reading those fields now sees `undefined`. The
bundled UI and CLI are updated in this change.
- **Legacy imports are covered.** Packages exported by older versions
still carry both keys. The manifest schema is non-strict, so the keys
are stripped rather than rejected, and a test locks that in.
- **The over-limit message strings changed.** Anything matching on the
old `... exceeds N bytes` text — a test, a script, or a client that
string-matches `body.error` — needs updating. The status codes (422) and
response shapes are unchanged, so structured clients are unaffected.
- **Attachment limits can only widen.** A deployment that had lowered a
company below the deployment cap now allows uploads up to the cap for
that company. Lower `PAPERCLIP_ATTACHMENT_MAX_BYTES` if a smaller
ceiling is needed.
- **Storybook visual baselines shift** for the `CompanyPatternIcon`
matrix story, because those fixtures had brand colors. That workflow
runs only on a PR labeled `storybook-visual`, so it does not gate this
PR; regenerate the baselines if the label is added.

## Model Used

Claude (Anthropic), Claude Opus, agentic tool use via Claude Code.

## 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
2026-08-27 12:11:05 -07:00
Devin Foley a4f1b3c533
Move company invites into a tab of the Members page (#12289)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Company settings include member management and invites, which are
one workflow: invite someone, watch the join request, manage the
membership
> - Today invites sit on a standalone settings page, costing a sidebar
entry and forcing people to bounce between two pages for one task
> - Merging invites into the Members page keeps the workflow in one
place without losing any capability
> - This pull request turns the Members page into a Members/Invites tab
bar, redirects the old URL, and keeps both operator-visibility keys
meaningful
> - The benefit is a tighter settings surface for every deployment, with
`company.invites` now hiding just the tab while `company.members` hides
the whole page

## Linked Issues or Issue Description

No public issue exists; describing the issue inline per the enhancement
template:

**What existing behavior does this improve?**
Company invites live on a standalone settings page separate from the
Members page, even though inviting someone and managing the resulting
membership are one workflow.

**Subsystem affected**
Web UI (company settings)

**Current behavior**
`/company/settings/invites` is its own page with its own sidebar entry
(gated by `company.invites`); the Members page (`company.members`) is
separate. The sidebar company menu's "Invite people" shortcut links to
the invites page and is not gated by the hidden-settings mechanism at
all.

**Proposed behavior**
The Members page carries a Members/Invites tab bar, addressable via
`?tab=invites`. The invite creation flow, latest-link panel, and invite
history move unchanged into an `InvitesSection` component. The old URL
redirects to the tab (still behind its `HiddenSettingsPageGate`).
`company.members` hides the whole page; `company.invites` hides just the
Invites tab, and the tab bar collapses when only Members remains. The
"Invite people" shortcut points at the tab and hides when either surface
is operator-hidden.

**Reason and benefit**
One settings surface for one workflow: fewer sidebar entries and no
bouncing between two pages to invite someone and then manage the
membership. Operators keep the same visibility controls, with a sharper
meaning for each key.

**Breaking changes**
None. Bookmarks to the old invites URL redirect to the tab, invite and
membership APIs are unchanged, and both hidden-settings keys keep
working.

## What Changed

- `ui/src/pages/CompanyInvites.tsx` →
`ui/src/components/access/InvitesSection.tsx` (page chrome and
breadcrumbs dropped; content unchanged), with its tests moved alongside.
- `ui/src/pages/CompanyAccess.tsx`: Members/Invites tabs via the shared
`PageTabBar`, `?tab=invites` search param, `company.invites` gating with
tab snap-back; legacy "Open Invites" button retargeted.
- `ui/src/App.tsx`: the invites route becomes a gated redirect to
`/company/settings/members?tab=invites`.
- `CompanySettingsSidebar` / `CompanySettingsNav`: standalone Invites
entry/tab removed; the old path maps to the members tab.
- `ui/src/components/SidebarCompanyMenu.tsx`: "Invite people" now links
to the tab and hides when `company.members` or `company.invites` is
hidden (closes an existing gating gap).
- Tests: moved invites tests, new tab coverage (default tab, deep link,
operator-hidden tab skips the invites fetch), sidebar/nav suites
updated.

## Verification

- `npx vitest run ui/src/components/CompanySettingsSidebar.test.tsx
ui/src/components/access/CompanySettingsNav.test.tsx
ui/src/pages/CompanyAccess.test.tsx
ui/src/components/access/InvitesSection.test.tsx` — 24 tests passing.
- `pnpm --filter @paperclipai/ui typecheck` — clean.

## Risks

- Low. Pure UI restructure: invite APIs, membership APIs, and the
hidden-settings registry keys are unchanged. Bookmarks to the old
invites URL redirect (and stay gated). The `company.invites` key's
meaning narrows from "hide the page" to "hide the tab", which is the
same effective surface.

## Model Used

Claude (Anthropic), model id `claude-fable-5`, extended thinking,
agentic tool use via Claude Code.

## 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-08-27 11:34:32 -07:00
scotttong 4d82f5eaea
copy: unify user-facing "company" wording to "organization" (#12243) 2026-08-27 01:04:55 -07:00
Tonio ac91b7f3b2
fix(ui): scope the company selection to the signed-in account (#11430)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Every company-scoped screen reads the active company from
`CompanyProvider`, which picks one from the `["companies"]` list and
remembers it in localStorage
> - That cache entry is shared app-wide and carries no account identity,
so it survives a change of account in the tab
> - The provider therefore auto-selects from whatever list is cached,
which can belong to the account that just went away
> - This pull request makes the provider watch the account and refuse to
derive a selection from a list fetched for a different one
> - The benefit is that the app stops pointing at a company the
signed-in account may not be able to see

## Linked Issues or Issue Description

No public issue exists. Refs #11380, #11382, #11417. The problem
follows.

**What happened?**

`CompanyProvider` auto-selects a company from the shared `["companies"]`
cache entry and writes that id to `localStorage`. Nothing ties that
entry to an account. When the account changes in the tab, the previous
account's list is still served, so the provider can select — and persist
— a company belonging to the account that just went away. Company-scoped
screens then render against a company the current account may not be
able to see.

Signing in through `Auth.tsx` invalidates the entry, so the in-app
sign-in path is covered. Two paths are not: a session that lapses
server-side, and a second account signing in on another tab. The
sign-out sweep in #11380 does not cover them either, because neither
presses the sign-out button.

**Expected behavior**

The company selection is derived only from a company list fetched for
the account that is signed in now.

**Steps to reproduce**

1. On a self-hosted instance in `authenticated` mode, sign in as account
A, which belongs to company X.
2. In a second tab, sign in as account B, which does not belong to
company X.
3. Return to the first tab. The session query refetches and reports
account B, while the company list is still account A's.
4. The provider keeps company X selected and leaves its id in
`localStorage`.

**Paperclip version or commit**

`master` at `6542ad1f4`.

## What Changed

- `ui/src/context/CompanyContext.tsx` — the provider observes
`queryKeys.auth.session`. On a change of session user it clears the live
selection, removes the shared company list, and holds auto-select until
a list fetched for the new account lands. The stored id is left alone on
purpose: `resolveBootstrapCompanySelection` re-validates it, so an
account signing back in keeps its company while an unrelated account
cannot inherit it.
- `ui/src/context/CompanyContext.tsx` — an errored list is treated as
undecided rather than as "no companies". With `retry: false` a single
network blip sticks, and the empty-list branch read it as proof the
account owns nothing and cleared the stored selection.
- `ui/src/api/client.ts` — new `detachInflightGet(path)`. GET coalescing
keys on the request path alone, so a `/companies` request issued under
the previous session could be joined by the replacement fetch and answer
it with the previous account's companies. Detaching leaves that request
to settle for its own callers and makes the next call issue a fresh one.
- `ui/src/api/companies.ts` — `companiesApi.detachInflightList()` wraps
that for the list path.
- `ui/src/context/CompanyContext.tsx` — `companyListUnavailable`
separates "no usable list because a request failed" from "this account
owns nothing", and `retryCompanies` gives a recovery action that
fetches. Both are derived from the query rather than tracked beside it;
a second copy of "did the last attempt succeed" drifted out of step
during review, reporting a failure over a later empty list that was
simply the truth.
- `ui/src/components/SidebarCompanyMenu.tsx` — renders "Couldn't load
companies" and a Try again item in place of "No companies", which is a
claim about the account that a failed request cannot support. This is
the menu `Sidebar` mounts, so it is the only place a customer can act on
the failure.
- `ui/src/components/CompanySwitcher.tsx` — the same treatment. The
application does not render this component (its only mount is a
Storybook story), so it is kept in step rather than relied on.
- `ui/src/context/CompanyContext.test.tsx`,
`ui/src/components/SidebarCompanyMenu.test.tsx`,
`ui/src/api/client.test.ts` — coverage for the account switch, a
same-account re-observation not churning, the detached GET, the failed
replacement and its recovery, a single blip self-healing, unavailability
not outliving the failure, and the sidebar rendering the recovery action
for a failure but plain "No companies" for an account that owns nothing.

### No `retry` override on the replacement fetch

The obvious fix for a failed replacement is a retry, and it is not
load-bearing here. A transient failure already gets a second attempt:
the observer rebinds to a fresh query on the render those state updates
schedule, and issues its own request — measured as two attempts with or
without the option. Retries would only add failed round trips before a
real outage is reported, and the outage is what needs a way out, which
is what `companyListUnavailable` and `retryCompanies` provide.

### Why `removeQueries` here, and why that does not generalise

Removal notifies no observer. What rebinds them at this call site is the
render the surrounding state updates schedule; every observer re-binds
to a fresh query on the next render. A caller without that guarantee
would leave mounted observers serving the previous account's value, so
this is not a pattern to lift elsewhere — the sign-out sweep in #11380
must use `resetQueries` instead, and its measurements are at
[#11380](https://github.com/paperclipai/paperclip/pull/11380#issuecomment-5300984911).

The inverse caveat holds for a local reset under an observer that stays
mounted, which is why #11417 and #11382 avoid `resetQueries`.

## Verification

- `pnpm vitest run` in `ui`: **4014 passed, 1 failed**.
- `pnpm tsc -b` in `ui`: clean.
- `CompanyContext.test.tsx`: 16 passed. `SidebarCompanyMenu.test.tsx`:
15 passed. `client.test.ts`: 9 passed.

The failure is pre-existing and unrelated: `IssueProperties.test.tsx`
expects `4:08 PM` and gets `9:08 AM`, a timezone-dependent assertion. It
reproduces on a tree without this change, and #11478 fixes it.

Each new test was confirmed to fail against the implementation it
covers, by reverting that change and re-running rather than by assuming.
The account-switch test fails without the fix (the selection stays on
the previous account's company and no refetch is issued); the
flag-clearing test fails without its clause (an empty list keeps reading
as "couldn't load").

**Not done:** no manual two-account run in a browser. The path needs two
accounts on an `authenticated` instance, which a local dev instance
cannot exercise.

## Risks

Low. The failure direction is a company selection withheld for one extra
round trip, which resolves when the list arrives. The direction it
removes is one account's company selected and persisted for another.

It adds one company-list request per account change, on a query key the
app already uses. It adds no request at boot: the session query it
observes is already fetched app-wide.

**This does not close the class.** Company-scoped entries other than the
list — `["companies", id]`, stats, and the rest of the per-account cache
— still survive an account change. That is the cache-lifetime work in
#11380, not this provider's.

## Model Used

Claude Opus 5 (`claude-opus-5`), through Claude Code. Extended thinking
enabled. Tool use enabled: file read and edit, shell for typecheck and
test runs, and a scratch vitest harness to measure `removeQueries` and
`resetQueries` notification behaviour against the installed
`@tanstack/query-core` 5.101.4.

## 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 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 11:51:03 -07:00
scotttong 815e49bb7c
feat: make chat-style tasks the default experience (#11101) 2026-08-11 09:06:21 -07:00
Dotta f258b34bbd
fix(ui): unify cloud-managed sign-out (#10994)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Paperclip can run as a self-hosted app or as a Cloud-managed tenant.
> - These modes need different sign-out sequences because Cloud owns
three sessions.
> - Several visible controls implemented sign-out separately and could
choose different paths.
> - This pull request adds one Cloud-aware sign-out action and moves
every visible control to it.
> - The benefit is one safe sign-out path in Cloud and unchanged local
sign-out in self-hosted deployments.

## Linked Issues or Issue Description

Refs #2073. That older PR adds a separate company-settings sign-out
surface. This change centralizes the existing account, company, and
instance-settings surfaces and preserves the self-hosted behavior
described there.

**What happened?**

Visible sign-out controls used separate implementations. A Cloud-managed
control could call the app-local sign-out endpoint and open the local
auth page. That path did not enter the Cloud-owned logout sequence for
the tenant, Cloud, and identity sessions.

**Expected behavior**

Every visible sign-out control must use one action. Cloud-managed
instances must navigate the top-level window to the same-origin
`/cloud/logout` route without a local sign-out call first. Authenticated
self-hosted instances must keep the local sign-out API and cache
invalidation behavior.

**Steps to reproduce**

1. Open a Cloud-managed tenant.
2. Use the account menu, company menu, or instance-settings sign-out
control.
3. Observe that independently implemented controls can enter different
sign-out paths.

**Paperclip version or commit**

The problem reproduces at `656ecfa585b31938e2685ffab3db22e794474803`.

**Deployment mode**

Cloud-managed tenant built from source. The regression tests also cover
authenticated self-hosted mode.

## What Changed

- Added `useSignOut` as the shared Cloud-aware sign-out action.
- Navigated Cloud-managed sessions to `/cloud/logout` exactly once
without calling local auth first.
- Preserved local API sign-out and cache invalidation for authenticated
self-hosted sessions.
- Migrated the account menu, company menu, and instance general settings
to the shared action.
- Added focused tests for mode selection, menu closure, pending state,
failure state, and settings behavior.

## Verification

- `pnpm exec vitest run ui/src/hooks/useSignOut.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/pages/InstanceGeneralSettings.test.tsx` — 26 tests passed.
- `pnpm --filter @paperclipai/ui typecheck` — passed.
- `pnpm check:token-gates` — passed.
- `git diff --check origin/master..HEAD` — passed.

## Risks

- Low risk. The change centralizes existing behavior and adds no schema,
API, telemetry, or style-token changes.
- Cloud mode depends on the existing health decision. Tests pin both
mode branches.
- The change does not alter Fetch Metadata, CSRF, cookie, prefetch, or
return-URL protections owned by the Cloud logout route.

> 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. The runtime does not expose a more specific model
ID or context-window size. The agent used high-reasoning mode, shell
tools, and API tools.

## 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>
2026-08-06 12:13:14 -05:00
Dotta 427509e6e0
fix(ui): show the synced company logo on the Cloud org switcher trigger (#10917)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - On Paperclip Cloud a tenant instance holds exactly one company, and
the cloud control plane pushes the stack's uploaded workspace icon into
that company's branding.
> - The cloud-mode organization switcher trigger always rendered the
deterministic monogram, so an uploaded organization logo never appeared
in the app chrome.
> - This pull request renders the trigger through the tenant company's
logo and brand color, with the monogram as the fallback.
> - The benefit is that the logo a customer uploads for their
organization actually shows up inside their Paperclip app.

## Linked Issues or Issue Description

No existing public issue found (searched open/closed PRs and issues for
"organization logo", "switcher logo", "company logo cloud" — closest
related PR is #10850, which introduced the cloud-mode switcher).
Describing in-PR:

**Subsystem affected**

Board UI: the sidebar organization switcher in Paperclip Cloud mode
(`SidebarCompanyMenu`).

**Problem or motivation**

A Cloud customer uploads an organization logo when creating their
workspace; the control plane syncs it into the tenant company's branding
(`company.logoUrl`). But the cloud branch of the switcher trigger
rendered `StackIcon` — monogram-only by design for stack rows — for the
trigger too, ignoring `selectedCompany.logoUrl`. Result: the uploaded
logo never appears in the app chrome; users see a letter tile instead.

**Proposed solution**

Add a `CurrentStackIcon` for the trigger that passes the selected
company's `logoUrl`/`brandColor` into `CompanyPatternIcon`, seeded by
the stack display name. Falls back to the exact previous monogram when
no logo is set. Stack rows are unchanged: the portfolio payload
deliberately carries no hot-linkable icon URL for other stacks.

**Alternatives considered**

Fetching per-stack icons for the rows was rejected: the cloud portfolio
payload carries no icon URLs (embedding signed, expiring control-plane
URLs would be wrong), and the defect is the current organization's
chrome, which the already-synced company logo covers.

## What Changed

- `ui/src/components/SidebarCompanyMenu.tsx`: cloud-mode trigger renders
the tenant company logo (fallback: monogram); stack rows untouched;
self-hosted path untouched.
- `ui/src/components/SidebarCompanyMenu.test.tsx`: new regression test
that the trigger carries the company logo while stack rows keep
monograms; the `CompanyPatternIcon` mock now exposes `logoUrl`.

## Verification

- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx`: 12/12 pass (11 existing + 1
new).
- `pnpm --dir ui exec tsc --noEmit`: clean.

## Risks

- Cloud-only rendering branch; self-hosted trigger rendering is
untouched.
- If the branding sync has not run yet, the trigger shows the same
monogram as before — no regression, and it upgrades in place once
`logoUrl` arrives.

## Model Used

- Claude Fable 5 (`claude-fable-5`), Anthropic. The run used extended
reasoning, repository tools, shell execution, and GitHub integration.

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:30:59 -05:00
Dotta 5858ccb981
feat: make in-app features cloud-aware (#10850)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Operators use the same board application in self-hosted and
Paperclip Cloud deployments.
> - A Cloud tenant contains one company, so an in-app company switch
does not change the active Cloud stack.
> - Cloud operators need the sidebar and company surfaces to use the
signed-in user's stack portfolio.
> - The server must derive Cloud identity and links from trusted
instance context instead of client input.
> - This pull request adds canonical Cloud context, a trusted stack
portfolio proxy, and Cloud-aware navigation.
> - The benefit is consistent stack switching on Cloud while self-hosted
company behavior stays unchanged.

## Linked Issues or Issue Description

**Subsystem affected**

Cross-cutting: server REST routes and the React board UI.

**Problem or motivation**

A Cloud-managed instance contains one company. The existing company
switcher could only switch records inside that tenant. It could not move
the operator to another Cloud stack. The existing header also gave long
organization names too little width.

**Proposed solution**

Expose a canonical public Cloud context in health data. Add a trusted
server proxy for the current user's stack portfolio. Use that data in
the board UI to switch stacks with top-level navigation. Keep the
existing company behavior on self-hosted instances. Move search into the
navigation and keep long organization names inside the sidebar panel.

**Alternatives considered**

An in-app `/stacks` route was rejected because Cloud tenant hosts
reserve that path and stack selection must wake or authenticate another
tenant. Client-supplied user identity was rejected because the server
can derive the trusted Cloud actor.

**Roadmap alignment**

This change advances the Cloud deployments milestone. It keeps the
product local-first and Cloud-ready without changing the self-hosted
mental model.

## What Changed

- Added canonical Cloud instance context and public health metadata.
- Added a Cloud-only stack portfolio proxy with trusted actor forwarding
and per-user caching.
- Prevented normal company creation on Cloud-managed instances.
- Switched the sidebar and Companies page from company actions to stack
actions on Cloud.
- Added full-page stack navigation and Cloud create-stack links.
- Moved search into the sidebar navigation so the organization name
keeps more width.
- Added truncation and hover recovery for long organization and stack
names.
- Added server and UI regression coverage for Cloud and self-hosted
behavior.
- Updated the implementation specification for the Cloud contracts.

## Verification

- `node scripts/check-token-gates.mjs` passed. All three token gates are
clean.
- `pnpm --dir server exec vitest run src/__tests__/health.test.ts
src/__tests__/cloud-instance.test.ts src/__tests__/cloud-routes.test.ts
src/__tests__/company-cloud-floor.test.ts
src/__tests__/company-portability-routes.test.ts` passed: 5 files and 66
tests.
- `pnpm --dir ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx` passed: 1 file and 11 tests.
- Pre-PR QA report `7da87ca7` passed all 8 acceptance criteria with real
HTTP route factories and real Chromium screenshots in Cloud and
self-hosted modes.
- Security reviews passed for the canonical Cloud context and stack
portfolio proxy.

## Risks

- Cloud stack switching depends on the configured Cloud application and
tenant portfolio URLs.
- The new health `cloud` block is public by design, but it contains only
canonical public instance metadata.
- The stack proxy fails closed on self-hosted instances and derives the
user identity from the trusted actor.
- Self-hosted navigation and company creation retain their existing
paths and behavior.

> 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, model `gpt-5`. The run used reasoning, repository tools,
shell execution, and GitHub integration. The deployment did not expose
its context-window size.

## 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-08-04 23:00:14 -05:00
Dotta c79d347abe
Update company creation copy (#8653)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The onboarding and company switcher UI are the first places users
create or select an organization
> - Some of that surface still used older team/workspace wording even
though the product model is company-centric
> - Mixed wording makes the setup path feel inconsistent and can make
users wonder whether they are creating a team, workspace, or company
> - This pull request updates the affected UI copy to consistently say
company
> - The benefit is a clearer first-run and navigation experience without
changing behavior

## Linked Issues or Issue Description

No public issue exists for this small UI polish change.

### Problem or motivation

The company creation and switcher surfaces used mixed
team/workspace/company wording for the same concept, which makes the
setup path feel inconsistent.

### Proposed solution

Update the visible copy, accessibility label, inline comment, e2e
expectations, and matching test expectations to use company-centric
language consistently.

### Alternatives considered

Leave the existing wording alone, but that preserves inconsistent
terminology in a high-traffic setup path.

### Roadmap alignment

This is focused UI polish and does not overlap with a roadmap-level core
feature.

### Additional context

The create action keeps its trailing ellipsis because it opens the
onboarding wizard rather than completing immediately.

## What Changed

- Updated front door and onboarding wizard labels from team-oriented
copy to company-oriented copy.
- Updated the sidebar company menu from workspace/team wording to
company wording, including the trigger accessibility label and empty
fallback text.
- Kept the sidebar create action ellipsis for the dialog/wizard
affordance.
- Updated component and Playwright test expectations for the new copy.

## Verification

- `pnpm exec vitest run ui/src/components/SidebarCompanyMenu.test.tsx`
- Attempted `npx playwright test --config tests/e2e/playwright.config.ts
tests/e2e/onboarding.spec.ts tests/e2e/nux-phase4-screenshots.spec.ts
tests/e2e/planning-mode-visual-verification.spec.ts
tests/e2e/conference-room-typing-intro.spec.ts`; local browser launch is
blocked by missing host Chromium dependencies (`libatk1.0-0t64`,
`libatspi2.0-0t64`, `libxcomposite1`, `libxdamage1`, `libxfixes3`,
`libxrandr2`, `libgbm1`, `libasound2t64`).
- Screenshots intentionally omitted because this is a copy-only change
and no design screenshots are needed for review.

## Risks

Low risk. This is copy-only UI polish plus matching test updates; no
data model, API, migration, workflow, lockfile, or behavior changes are
included.

## Model Used

OpenAI GPT-5 Codex (`gpt-5`) via the Paperclip Codex agent, with
tool-assisted repository inspection, GitHub CLI usage, and local command
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
- [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-06-26 12:01:02 -05:00
Dotta 841742fc1a
[codex] Graduate experimental conference room defaults (#8628)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI has been graduating experimental conference-room task
experiences into the default issue and onboarding flows
> - Several task UI improvements were still coupled to the Conference
Room Chat experimental flag even though they are useful outside chat
itself
> - That coupling meant disabling chat also reverted unrelated defaults
such as work-mode labels, task status colors, team creation copy, and
the graduated issue thread
> - This pull request keeps chat-specific gating scoped to chat while
making the graduated task UI the default experience
> - The benefit is that operators can use the newer task workflows
without needing to enable the separate chat experiment

## Linked Issues or Issue Description

No public GitHub issue exists for this exact change.

### Problem or motivation

The Conference Room Chat experimental flag was controlling unrelated
task UI defaults, which made non-chat workflows regress when chat was
disabled.

### Proposed solution

Remove that flag from task-thread, work-mode, onboarding, status, and
team-creation presentation paths while leaving chat-specific behavior
separately gated.

### Alternatives considered

Keeping the flag as a broad umbrella until chat graduates would avoid a
behavior change, but it keeps unrelated UI improvements hidden behind
the wrong capability switch.

### Roadmap alignment

Checked `ROADMAP.md`; this is focused graduation/polish for existing UI
surfaces rather than a new roadmap-level core feature.

Related search:

- Searched open PRs and issues for `conference room chat experimental
flag`: no matches.
- Searched open PRs and issues for `graduated issue thread`: no matches.

## What Changed

- Removes Conference Room Chat flag branching from task-thread
rendering, work-mode labels, task status colors, and team creation copy.
- Makes the onboarding completion path create/reuse an onboarding
project, create the first assigned task, and send the user to the
dashboard instead of chat.
- Deletes the legacy onboarding wizard and classic task-thread files now
that the graduated flow is the default.
- Updates focused UI tests and affected E2E specs for the default task
experience.
- Adds a user-visible onboarding error if restored state is missing the
company or agent required for launch.

## Verification

- `pnpm run preflight:workspace-links && pnpm exec vitest run
ui/src/components/NewIssueDialog.test.tsx
ui/src/components/OnboardingWizardVariant.test.tsx
ui/src/components/RunChatSurface.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/components/StatusBadge.test.tsx ui/src/lib/agent-order.test.ts
ui/src/lib/onboarding-launch.test.ts ui/src/lib/work-mode-meta.test.ts
ui/src/pages/IssueDetail.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `npx playwright test --config tests/e2e/playwright.config.ts --list
tests/e2e/conference-room-typing-intro.spec.ts
tests/e2e/planning-mode-visual-verification.spec.ts`
- Attempted targeted Playwright execution locally, but this host is
missing Chromium system libraries (`libatk1.0-0t64`, `libatspi2.0-0t64`,
`libxcomposite1`, `libxdamage1`, `libxfixes3`, `libxrandr2`, `libgbm1`,
`libasound2t64`). CI runs the specs in the proper Actions environment.

## Risks

- Medium UI behavior risk: this intentionally changes the default
experience for users who have not enabled Conference Room Chat.
- Medium onboarding risk: completion now creates/reuses a project and
creates the first task instead of only navigating.
- Low migration risk: no database schema or migration changes are
included.
- The PR avoids `pnpm-lock.yaml` and `.github/workflows` changes.

## Model Used

OpenAI Codex, GPT-5-class coding model, tool-enabled local repository
workflow with shell, git, and 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
- [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-06-25 11:52:36 -05:00
scotttong 6f9801a46b
feat(ui): NUX rework behind enableConferenceRoomChat experimental flag — capsule onboarding, conference-room chat, unified composer (#8000)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The first-run experience (onboarding wizard) and the chat surfaces
(conference-room/board chat, task threads, composers) are the product's
front door — they decide whether a new operator understands "hire
agents, give them work, review results" in the first five minutes
> - Today those surfaces feel ticket-y and form-like: the wizard is a
static multi-step form that ends in an anticlimactic "Launch" screen,
the task composer and board chat behave differently from each other, and
agent-feed issue quicklooks misbehave (multiple flyouts open at once,
cards jump on hover)
> - We wanted to iterate toward a conversational, team-centric NUX — but
without risking the workflows of everyone already running Paperclip
> - This PR reworks the NUX behind a new default-OFF
`enableConferenceRoomChat` experimental flag: a capsule-motif onboarding
wizard that builds your team as you answer, a conference-room chat
surface, one shared ChatComposer across surfaces, brand-accurate status
chips, and feed-quicklook fixes — with the pre-existing UI
fork-and-frozen as `*Classic` components that flag-OFF users keep
> - The benefit is a complete, testable modern NUX that anyone can opt
into from Settings → Experimental, with zero default behavior change and
a clean path to either graduate or drop the experiment

## Linked Issues or Issue Description

No pre-existing GitHub issue — feature description per
`feature_request.yml`:

- **Problem / motivation:** Paperclip's onboarding wizard and chat
surfaces grew up as separate ticket-centric forms. New users get a
form-filling experience rather than the feeling of standing up a team;
the board chat and task threads use different composers with different
affordances; the agent feed's issue quicklook can stack multiple
popovers and shifts cards on hover.
- **Proposed solution:** A coherent NUX experiment behind one
experimental flag (`enableConferenceRoomChat`, Settings → Experimental,
default OFF): capsule onboarding wizard with an evolving team capsule,
conference-room chat, unified `ChatComposer`, team-centric copy, brand
status chips, quicklook single-flight fix. Flag-OFF users get the exact
pre-experiment UI via frozen `*Classic` forks, verified by an on/off
parity test matrix.
- **Alternatives considered:** (a) incremental unflagged restyling —
rejected: the changes interlock across surfaces and would drip risk into
every release; (b) a separate app shell / route for the new NUX —
rejected: too much divergence, the flag + classic-fork pattern keeps the
diff reviewable and reversible.
- **Roadmap alignment:** `ROADMAP.md` lists **CEO Chat** ("a
lighter-weight way to talk to leadership agents... should still resolve
to real work objects"). This experiment is groundwork in that direction
(conference-room chat resolves to issues/tasks via the same composer
used in task threads) and does not change the core task-and-comments
model.

Related PRs found in the dedup search (same area, none duplicate this
work — they target the classic wizard, which this PR intentionally
leaves intact and mergeable):

- #5385 — Coach-driven onboarding: conversational entry +
agent-companies package import
- #5378 — Onboarding wizard: reusable adapter picker + probe card
- #6636 — ui(onboarding): friendly error surface + retry for the wizard
- #7005 — fix(onboarding): explicitly await first-task wake
- #2616 — fix: restore workspace directory config in onboarding wizard

## What Changed

- **Experimental flag plumbing** — `enableConferenceRoomChat` in shared
types/validators, server instance-settings service + API, Settings →
Experimental card with explicit enable/disable copy
- **Onboarding wizard** — classic wizard forked and frozen
(`OnboardingWizardClassic`); flag-ON variant is a 5-step capsule wizard
with a persistent evolving `AgentCapsule` (gradient/glow motif),
team-centric reframed copy, and a typing-dots intro (hardened with
fake-timer tests)
- **Conference-room chat** — flag-ON board-chat surface with agent
bubble name/icon headers and copy/vote/timestamp action rows
(`AgentBubbleActionRow`)
- **Unified composer** — shared `ChatComposer` adopted across surfaces;
translucent surface + scroll-mask removal; "Agent mode"/"Plan mode"
relabels; no-assignee confirmation `AlertDialog` (new
`ui/alert-dialog.tsx` primitive); `@task` reference picker +
linkification in mentions
- **Agent feed** — single-flight issue-quicklook store (one popover at a
time), flyouts open to the left, removed hover translate-y jitter
- **Status chips** — brand-accurate task status chips behind the flag
(light/dark, 1px borders per paperclip.ing/brand)
- **Tests** — flag on/off parity matrix across IssueDetail,
NewIssueDialog, Sidebar, wizard, gate components; component tests for
all new pieces
- **Merge with `master`** — one conflict in
`ui/src/components/IssueChatThread.tsx`, resolved by keeping master's
new `AssigneeChip`/`HandoffWakeRow`/`RunStatusBadge` components inside
the flag-gated metadata-row chrome (details in commit `21a5642a`);
post-merge fixes: vitest 4 mock typing in `MarkdownEditor.test.tsx`,
flag hook made safe for provider-less mounts (master's new isolated
component tests)
- **Branch hygiene** — internal design wireframes/mockups stripped
before the PR (they live in the Paperclip issue threads)
- No user-facing documentation changes required: the flag is
intentionally experimental and self-described in the Settings card; no
existing docs reference the affected surfaces

## Verification

- `pnpm run typecheck` — green across the workspace (ui, server, shared,
plugins)
- Full UI suite (`vitest run` in `ui/`, clean worktree at this HEAD):
**1593/1595 passing, 223/224 files** — the 2 remaining failures are in
`src/components/artifacts/ArtifactCard.test.tsx` and **fail identically
on pristine `origin/master`** (pre-existing upstream, unrelated to this
branch)
- Full server suite (`vitest run` in `server/`, same clean worktree):
results in PR checks; flag plumbing covered by instance-settings tests
- Targeted post-merge resolution check: `IssueChatThread`,
`IssueChatThreadSystemNotice`, `IssueDetail`, `Sidebar`,
`ConferenceRoomChatGate`, `OnboardingWizardVariant`, `NewIssueDialog`,
`InstanceExperimentalSettings`, `MarkdownEditor` — 172/172 passing
- Manual walkthrough: flag OFF (default) → onboarding wizard, task
thread, board chat, composer all render the classic UI; flag ON via
Settings → Experimental → capsule wizard, conference-room chat, unified
composer, status chips active
- Screenshots: see below

**Flag on/off screenshots** (committed on this branch under
`screenshots/PR-8000-*`):

| Surface | Flag OFF (classic, default) | Flag ON (experimental) |
| --- | --- | --- |
| Settings → Experimental | ![settings
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-settings-experimental-flag-off.png)
| ![settings
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-settings-experimental-flag-on.png)
|
| Task thread | ![thread
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-task-thread-flag-off.png)
| ![thread
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-task-thread-flag-on.png)
|
| Home / nav | ![home
off](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-home-flag-off.png)
| ![home
on](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-home-flag-on.png)
|
| Conference Room (flag-ON only surface) | — | ![conference
room](https://raw.githubusercontent.com/paperclipai/paperclip/9b4f02708383031d3124b68ca3ed9ab437ea3501/screenshots/PR-8000-conference-room-flag-on.png)
|

Capsule onboarding wizard walkthrough screenshots (flag ON) are attached
to the Paperclip design/implementation threads; the wizard requires a
fresh instance so it is captured via the e2e harness
(`tests/e2e/nux-phase4-screenshots.spec.ts`).


## Risks

- **Large surface, but gated:** all new behavior sits behind
`enableConferenceRoomChat`, default OFF; flag-OFF rendering is locked by
frozen `*Classic` forks plus an on/off parity test suite
- **Classic forks are frozen at the fork point (`e3aada1d`):** master
features added to the live thread component after that point (assignee
handoff chips, run status badge, composer mention coach) render in the
flag-ON path; the flag-OFF task thread keeps the fork-point behavior
until the experiment graduates (forks deleted) or is dropped (forks
restored as canonical). Called out for reviewer attention.
- **Merge-conflict resolution in `IssueChatThread.tsx`** (commit
`21a5642a`) deserves reviewer eyes: master's new handoff/run-status
components were kept; the base toast-style no-assignee flow remains
replaced by the AlertDialog flow introduced on this branch
- Schema/server changes are additive (one optional boolean instance
setting); no migrations of existing data

## Model Used

- Claude (Anthropic) via Claude Code running in the Paperclip agent
harness (agent: ClaudeCoder)
- Branch implemented across multiple agent sessions on Claude Opus-class
models with extended thinking + tool use (file edits, shell, Playwright
screenshots); merge/PR session model ID as reported by the harness:
`claude-fable-5` (Claude Code CLI)
- All code was agent-authored and board-reviewed through Paperclip issue
threads (plans, wireframes, confirmations) before merging

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes (none
required — experimental flag, self-documenting Settings card; noted
above)
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green (run 3 on `8af3041a`: all 16
gates SUCCESS, incl. e2e and all 4 serialized-suite shards)
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
(re-review verdict: Confidence 5/5, “Safe to merge”; all 4 round-1
findings fixed + confirmed resolved; both summary notes addressed in
`8af3041a`)
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:32:55 -05:00
Dotta 4103978578
Polish operator sidebar and issue property controls (#5355)
## Thinking Path

> - Paperclip orchestrates AI agents for zero-human companies
> - Operators use the board sidebar and issue properties panel to move
between companies and understand task metadata
> - Small UI regressions in these controls make repeated board operation
slower and less predictable
> - The local branch already contained targeted fixes for company
ordering, issue date display, and sidebar rail sizing
> - This pull request isolates those operator UI quality-of-life fixes
into a standalone branch against `origin/master`
> - The benefit is a focused, reviewable PR that can merge independently
of the issue-thread activity work

## What Changed

- Shows issue property timestamps with time, not just dates.
- Adds edit-mode support for ordering companies in the sidebar company
menu.
- Fixes a workspace switcher rail regression and keeps the account menu
aligned with the rail width.
- Includes focused component coverage for the touched controls.

## Verification

- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run ui/src/components/IssueProperties.test.tsx
ui/src/components/SidebarCompanyMenu.test.tsx
ui/src/components/Layout.test.tsx
ui/src/components/SidebarAccountMenu.test.tsx` — 4 files passed, 29
tests passed.
- `pnpm --filter /ui typecheck`
- PR checks on `a4030f7a` are green: policy, verify, serialized server
suites 1/4-4/4, e2e, Canary Dry Run, Greptile Review, and Snyk.
- Captured a local Storybook screenshot of `Product/Navigation & Layout`
after the sidebar polish:
`/tmp/pap-3659-screenshots/navigation-layout-after.png`.
- Confirmed the PR changes 8 files and does not include `pnpm-lock.yaml`
or `.github/workflows/*`.

## Risks

- Low to moderate UI risk: this touches shared sidebar components and
issue metadata rendering.
- The company ordering behavior depends on existing query/cache
behavior, so stale cache bugs would show up as ordering inconsistencies.
- No database, API, workflow, or lockfile changes are included.

> 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 coding agent, shell/tool-use enabled, used to
split the existing branch, verify the isolated PR branch, and create
this PR.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 08:59:39 -05:00
Dotta 424e81d087
Improve operator workflow QoL (#5291)
## Thinking Path

> - Paperclip is a control plane operators use repeatedly to supervise
agent companies.
> - Common operator workflows depend on fast scanning of inboxes, issue
sidebars, workspaces, cost totals, and runtime services.
> - Several small UI and service gaps made those workflows slower or
less clear.
> - This pull request groups the operator-facing QoL changes that can
stand alone from recovery and adapter work.
> - The benefit is a denser, clearer board experience for issue triage
and workspace operation.

## What Changed

- Added inbox assignee/project grouping and issue list token/runtime
totals.
- Improved issue properties with removable blocker chips and workspace
task links.
- Improved execution workspace layout, runtime controls, issues tab
default, and stopped-port reuse behavior.
- Added mobile markdown/routine dialog fixes, page title company names,
sidebar polish, and dashboard run task label cleanup.

## Verification

- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run ui/src/lib/inbox.test.ts
ui/src/components/IssueProperties.test.tsx
ui/src/components/WorkspaceRuntimeControls.test.tsx
server/src/__tests__/workspace-runtime.test.ts
server/src/__tests__/costs-service.test.ts`

## Risks

- Medium UI risk because this touches several operator surfaces. The
branch is intentionally grouped around workflow/QoL files and keeps the
file count below the Greptile limit.

## Model Used

- OpenAI GPT-5 Codex via Paperclip `codex_local` adapter, with
shell/git/GitHub CLI tool use.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-06 06:30:44 -05:00
Dotta 76f09c8eb6
[PAP-3180] Move workspace switcher into sidebar (#4981)
## Thinking Path

> - Paperclip is the control plane for autonomous AI companies.
> - The board UI needs a clear persistent way to move between company
workspaces.
> - The previous layout kept company switching in a separate left rail,
which made the sidebar feel split between workspace selection and
navigation.
> - The workspace switcher belongs in the sidebar header so navigation
and workspace context stay together.
> - This pull request removes the separate company rail from the layout
and turns the sidebar company menu into the primary workspace switcher.
> - The benefit is a cleaner sidebar structure that keeps workspace
identity, switching, company actions, and navigation in one place.

## What Changed

- Removed the standalone `CompanyRail` from the main layout.
- Added the company/workspace switcher to the default, company settings,
and instance settings sidebars.
- Expanded `SidebarCompanyMenu` to list active workspaces, indicate the
current workspace, navigate out of instance settings when switching, and
expose add-company onboarding.
- Updated focused component tests for the new workspace-switcher
behavior.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/SidebarCompanyMenu.test.tsx
src/components/CompanySettingsSidebar.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `git diff --check`
- Visual smoke attempted against the managed dev server at
`http://127.0.0.1:57385`; a fresh browser context reached the
authenticated sign-in screen, so I could not capture an authenticated
sidebar screenshot from this heartbeat.

## Risks

- Low-to-medium UI risk: this changes the primary sidebar structure and
workspace-switching entry point.
- The instance-settings switch behavior now routes back to the selected
company dashboard when a workspace is selected.
- No migrations, API contracts, or lockfile 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 coding agent, tool-enabled, medium reasoning mode.

## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-02 08:13:53 -05:00
Dotta b9a80dcf22
feat: implement multi-user access and invite flows (#3784)
## Thinking Path

> - Paperclip is the control plane for autonomous AI companies.
> - V1 needs to stay local-first while also supporting shared,
authenticated deployments.
> - Human operators need real identities, company membership, invite
flows, profile surfaces, and company-scoped access controls.
> - Agents and operators also need the existing issue, inbox, workspace,
approval, and plugin flows to keep working under those authenticated
boundaries.
> - This branch accumulated the multi-user implementation, follow-up QA
fixes, workspace/runtime refinements, invite UX improvements,
release-branch conflict resolution, and review hardening.
> - This pull request consolidates that branch onto the current `master`
branch as a single reviewable PR.
> - The benefit is a complete multi-user implementation path with tests
and docs carried forward without dropping existing branch work.

## What Changed

- Added authenticated human-user access surfaces: auth/session routes,
company user directory, profile settings, company access/member
management, join requests, and invite management.
- Added invite creation, invite landing, onboarding, logo/branding,
invite grants, deduped join requests, and authenticated multi-user E2E
coverage.
- Tightened company-scoped and instance-admin authorization across
board, plugin, adapter, access, issue, and workspace routes.
- Added profile-image URL validation hardening, avatar preservation on
name-only profile updates, and join-request uniqueness migration cleanup
for pending human requests.
- Added an atomic member role/status/grants update path so Company
Access saves no longer leave partially updated permissions.
- Improved issue chat, inbox, assignee identity rendering,
sidebar/account/company navigation, workspace routing, and execution
workspace reuse behavior for multi-user operation.
- Added and updated server/UI tests covering auth, invites, membership,
issue workspace inheritance, plugin authz, inbox/chat behavior, and
multi-user flows.
- Merged current `public-gh/master` into this branch, resolved all
conflicts, and verified no `pnpm-lock.yaml` change is included in this
PR diff.

## Verification

- `pnpm exec vitest run server/src/__tests__/issues-service.test.ts
ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/plugin-routes-authz.test.ts`
- `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts
server/src/__tests__/workspace-runtime-service-authz.test.ts
server/src/__tests__/access-validators.test.ts`
- `pnpm exec vitest run
server/src/__tests__/authz-company-access.test.ts
server/src/__tests__/routines-routes.test.ts
server/src/__tests__/sidebar-preferences-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/openclaw-invite-prompt-route.test.ts
server/src/__tests__/agent-cross-tenant-authz-routes.test.ts
server/src/__tests__/routines-e2e.test.ts`
- `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts
ui/src/pages/CompanyAccess.test.tsx`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server
typecheck`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm db:generate`
- `npx playwright test --config tests/e2e/playwright.config.ts --list`
- Confirmed branch has no uncommitted changes and is `0` commits behind
`public-gh/master` before PR creation.
- Confirmed no `pnpm-lock.yaml` change is staged or present in the PR
diff.

## Risks

- High review surface area: this PR contains the accumulated multi-user
branch plus follow-up fixes, so reviewers should focus especially on
company-boundary enforcement and authenticated-vs-local deployment
behavior.
- UI behavior changed across invites, inbox, issue chat, access
settings, and sidebar navigation; no browser screenshots are included in
this branch-consolidation PR.
- Plugin install, upgrade, and lifecycle/config mutations now require
instance-admin access, which is intentional but may change expectations
for non-admin board users.
- A join-request dedupe migration rejects duplicate pending human
requests before creating unique indexes; deployments with unusual
historical duplicates should review the migration behavior.
- Company member role/status/grant saves now use a new combined
endpoint; older separate endpoints remain for compatibility.
- Full production build was not run locally in this heartbeat; CI should
cover the full matrix.

## Model Used

- OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use
environment. Exact deployed model identifier and context window were not
exposed by the 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge

Note on screenshots: this is a branch-consolidation PR for an
already-developed multi-user branch, and no browser screenshots were
captured during this heartbeat.

---------

Co-authored-by: dotta <dotta@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:44:19 -05:00