From f44a002b8d3e3e9fbe5fb1c5ab24fa58af6aff4a Mon Sep 17 00:00:00 2001 From: Jakub Mikiciuk <6577419+qbamca@users.noreply.github.com> Date: Thu, 16 Jul 2026 05:52:40 +0200 Subject: [PATCH] fix(ui): restore prefix-aware company export/import links (#6648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Each company workspace in the web UI is mounted under a URL prefix (e.g. `/NEU/company/...`), and `Link` from `@/lib/router` applies that prefix automatically via `applyCompanyPrefix` > - Company Settings rendered its Org Chart, Export, Import, and Cloud Upstream buttons as raw `` anchors, which drop the prefix, so those pages 404 on prefixed instances (#2910); `CompanyExport.filePathFromLocation` also failed to locate `/company/export/files/` inside prefixed URLs, breaking export file previews > - #2951 fixed the Settings links with `` plus tests, but the sandbox-settings work in #4415 reverted the links back to ``, silently reintroducing the bug (#6647) > - This pull request restores the prefix-aware `` for all four Settings links, normalizes the pathname with `toCompanyRelativePath()` before matching the export-files marker, and adds regression tests covering every route so the fix cannot be lost again > - The benefit is that export/import/org-chart/cloud-upstream navigation and export file previews work on every prefixed deployment ## Linked Issues or Issue Description Fixes: #6647 Refs #2910 (original report: `/company/export` → prefix `COMPANY` → not found) Refs #2951 (original fix with `` + tests — merged, then lost) Refs #4415 (sandbox settings PR that reverted Settings back to ``) ## What Changed - **`ui/src/pages/CompanySettings.tsx`**: use `Link` from `@/lib/router` for the Org Chart, Export, Import, and Cloud Upstream buttons (replacing raw ``) - **`ui/src/pages/CompanyExport.tsx`**: resolve file paths from prefixed URLs by normalizing with `toCompanyRelativePath()` before matching `/company/export/files/` - **`ui/src/lib/company-routes.test.ts`**: regression tests for export/import/cloud-upstream/org prefix rewriting, double-prefix prevention, and export file URL normalization ## Verification ```bash pnpm vitest run ui/src/lib/company-routes.test.ts ``` Manual: 1. Open `http://localhost:3100/NEU/company/settings` (or your company prefix). 2. Click **Export** / **Import** — URL should stay under `/:prefix/company/...`. 3. On export, select a file — URL should be `/:prefix/company/export/files/...` and preview should load. The change is navigation-target-only (no visual/layout changes), so before/after is shown as the resolved URLs: | Link | Before (prefix dropped → not found) | After | |------|-------------------------------------|-------| | Export | `/company/export` | `/NEU/company/export` | | Import | `/company/import` | `/NEU/company/import` | | Org Chart | `/org` | `/NEU/org` | | Cloud Upstream | `/company/settings/cloud-upstream` | `/NEU/company/settings/cloud-upstream` | ## Risks Low — same approach as #2951; only navigation/parsing, no API changes. ## Model Used - Original implementation: authored by @qbamca in Cursor (agentic editor; the session's exact model ID was not recorded) - Follow-up commit (merge-conflict resolution) and this description update: Claude Fable 5 (Anthropic, `claude-fable-5`, extended thinking, agentic tool use), operated by the Commit Capital triage team ## 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 (no doc changes required — behavior matches documented routing) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending re-review of the conflict-resolution commit) - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Jakub Mikiciuk Co-authored-by: Cursor Co-authored-by: Andrew Aymeloglu Co-authored-by: Claude Fable 5 --- ui/src/lib/company-routes.test.ts | 19 +++++++++++++++++++ ui/src/pages/CompanyExport.tsx | 6 ++++-- ui/src/pages/CompanySettings.tsx | 15 ++++++++------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/ui/src/lib/company-routes.test.ts b/ui/src/lib/company-routes.test.ts index 0c0b1be277..cce73805ea 100644 --- a/ui/src/lib/company-routes.test.ts +++ b/ui/src/lib/company-routes.test.ts @@ -36,6 +36,25 @@ describe("company routes", () => { expect(toCompanyRelativePath("/PAP/search?q=foo")).toBe("/search?q=foo"); }); + it("rewrites company package paths with the active prefix", () => { + expect(applyCompanyPrefix("/company/export", "NEU")).toBe("/NEU/company/export"); + expect(applyCompanyPrefix("/company/import", "NEU")).toBe("/NEU/company/import"); + expect(applyCompanyPrefix("/company/settings/cloud-upstream", "NEU")).toBe( + "/NEU/company/settings/cloud-upstream", + ); + expect(applyCompanyPrefix("/org", "NEU")).toBe("/NEU/org"); + }); + + it("does not double-apply the company prefix", () => { + expect(applyCompanyPrefix("/NEU/company/export", "NEU")).toBe("/NEU/company/export"); + }); + + it("normalizes prefixed company export file URLs for parsing", () => { + expect(toCompanyRelativePath("/NEU/company/export/files/agents/ceo/AGENTS.md")).toBe( + "/company/export/files/agents/ceo/AGENTS.md", + ); + }); + // Regression for PAP-10257: Team Catalog navigation (auto-select + row/file // clicks) produces company-relative `/teams-catalog/` paths. Without // `teams-catalog` in the board-route allowlist, `extractCompanyPrefixFromPath` diff --git a/ui/src/pages/CompanyExport.tsx b/ui/src/pages/CompanyExport.tsx index 7bca198761..bad6bc3dce 100644 --- a/ui/src/pages/CompanyExport.tsx +++ b/ui/src/pages/CompanyExport.tsx @@ -20,6 +20,7 @@ import { Button } from "@/components/ui/button"; import { EmptyState } from "../components/EmptyState"; import { PageSkeleton } from "../components/PageSkeleton"; import { MarkdownBody } from "../components/MarkdownBody"; +import { toCompanyRelativePath } from "@/lib/company-routes"; import { cn } from "../lib/utils"; import { queryKeys } from "../lib/queryKeys"; import { createZipArchive } from "../lib/zip"; @@ -559,9 +560,10 @@ function ExportPreviewPane({ /** Extract the file path from the current URL pathname (after /company/export/files/) */ function filePathFromLocation(pathname: string): string | null { const marker = "/company/export/files/"; - const idx = pathname.indexOf(marker); + const relativePathname = toCompanyRelativePath(pathname); + const idx = relativePathname.indexOf(marker); if (idx === -1) return null; - const filePath = decodeURIComponent(pathname.slice(idx + marker.length)); + const filePath = decodeURIComponent(relativePathname.slice(idx + marker.length)); return filePath || null; } diff --git a/ui/src/pages/CompanySettings.tsx b/ui/src/pages/CompanySettings.tsx index a174b63366..25862d5e8d 100644 --- a/ui/src/pages/CompanySettings.tsx +++ b/ui/src/pages/CompanySettings.tsx @@ -10,6 +10,7 @@ import { companiesApi } from "../api/companies"; import { assetsApi } from "../api/assets"; import { instanceSettingsApi } from "../api/instanceSettings"; import { queryKeys } from "../lib/queryKeys"; +import { Link } from "@/lib/router"; import { Button } from "@/components/ui/button"; import { Settings, CloudUpload, Download, Upload } from "lucide-react"; import { CompanyPatternIcon } from "../components/CompanyPatternIcon"; @@ -374,28 +375,28 @@ export function CompanySettings() {

Import and export have moved to dedicated pages accessible from the{" "} - Org Chart header. + Org Chart header.

{cloudSyncEnabled ? ( ) : null}