fix(ui): restore prefix-aware company export/import links (#6648)
## 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 `<a href>` 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 `<Link to>` plus tests, but the sandbox-settings work in #4415 reverted the links back to `<a href>`, silently reintroducing the bug (#6647) > - This pull request restores the prefix-aware `<Link>` 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 `<Link to>` + tests — merged, then lost) Refs #4415 (sandbox settings PR that reverted Settings back to `<a href>`) ## What Changed - **`ui/src/pages/CompanySettings.tsx`**: use `Link` from `@/lib/router` for the Org Chart, Export, Import, and Cloud Upstream buttons (replacing raw `<a href>`) - **`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 <jmikiciuk@igus.net> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
df2404d443
commit
f44a002b8d
|
|
@ -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/<key>` paths. Without
|
||||
// `teams-catalog` in the board-route allowlist, `extractCompanyPrefixFromPath`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<div className="rounded-md border border-border px-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Import and export have moved to dedicated pages accessible from the{" "}
|
||||
<a href="/org" className="underline hover:text-foreground">Org Chart</a> header.
|
||||
<Link to="/org" className="underline hover:text-foreground">Org Chart</Link> header.
|
||||
</p>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||
{cloudSyncEnabled ? (
|
||||
<Button size="sm" asChild>
|
||||
<a href="/company/settings/cloud-upstream">
|
||||
<Link to="/company/settings/cloud-upstream">
|
||||
<CloudUpload className="mr-1.5 h-3.5 w-3.5" />
|
||||
Send to Paperclip Cloud
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<a href="/company/export">
|
||||
<Link to="/company/export">
|
||||
<Download className="mr-1.5 h-3.5 w-3.5" />
|
||||
Export
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" asChild>
|
||||
<a href="/company/import">
|
||||
<Link to="/company/import">
|
||||
<Upload className="mr-1.5 h-3.5 w-3.5" />
|
||||
Import
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue