diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index d38de4b0..e07b6d3d 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -117,7 +117,7 @@ function boardRoutes() {
} />
} />
} />
- } />
+ } />
} />
} />
} />
diff --git a/ui/src/pages/CompanyExport.tsx b/ui/src/pages/CompanyExport.tsx
index 32e7458e..922e9c5f 100644
--- a/ui/src/pages/CompanyExport.tsx
+++ b/ui/src/pages/CompanyExport.tsx
@@ -1,6 +1,7 @@
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import type { CompanyPortabilityExportResult, CompanyPortabilityManifest } from "@paperclipai/shared";
+import { useNavigate, useLocation } from "@/lib/router";
import { useCompany } from "../context/CompanyContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { useToast } from "../context/ToastContext";
@@ -495,10 +496,33 @@ function ExportPreviewPane({
// ── Main page ─────────────────────────────────────────────────────────
+/** 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);
+ if (idx === -1) return null;
+ const filePath = decodeURIComponent(pathname.slice(idx + marker.length));
+ return filePath || null;
+}
+
+/** Expand all ancestor directories for a given file path */
+function expandAncestors(filePath: string): string[] {
+ const parts = filePath.split("/").slice(0, -1);
+ const dirs: string[] = [];
+ let current = "";
+ for (const part of parts) {
+ current = current ? `${current}/${part}` : part;
+ dirs.push(current);
+ }
+ return dirs;
+}
+
export function CompanyExport() {
const { selectedCompanyId, selectedCompany } = useCompany();
const { setBreadcrumbs } = useBreadcrumbs();
const { pushToast } = useToast();
+ const navigate = useNavigate();
+ const location = useLocation();
const [exportData, setExportData] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
@@ -507,6 +531,38 @@ export function CompanyExport() {
const [treeSearch, setTreeSearch] = useState("");
const [taskLimit, setTaskLimit] = useState(TASKS_PAGE_SIZE);
const savedExpandedRef = useRef | null>(null);
+ const initialFileFromUrl = useRef(filePathFromLocation(location.pathname));
+
+ // Navigate-aware file selection: updates state + URL without page reload.
+ // `replace` = true skips history entry (used for initial load); false = pushes (used for clicks).
+ const selectFile = useCallback(
+ (filePath: string | null, replace = false) => {
+ setSelectedFile(filePath);
+ if (filePath) {
+ navigate(`/company/export/files/${encodeURI(filePath)}`, { replace });
+ } else {
+ navigate("/company/export", { replace });
+ }
+ },
+ [navigate],
+ );
+
+ // Sync selectedFile from URL on browser back/forward
+ useEffect(() => {
+ if (!exportData) return;
+ const urlFile = filePathFromLocation(location.pathname);
+ if (urlFile && urlFile in exportData.files && urlFile !== selectedFile) {
+ setSelectedFile(urlFile);
+ // Expand ancestors so the file is visible in the tree
+ setExpandedDirs((prev) => {
+ const next = new Set(prev);
+ for (const dir of expandAncestors(urlFile)) next.add(dir);
+ return next;
+ });
+ } else if (!urlFile && selectedFile) {
+ setSelectedFile(null);
+ }
+ }, [location.pathname, exportData]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
setBreadcrumbs([
@@ -535,10 +591,21 @@ export function CompanyExport() {
for (const node of tree) {
if (node.kind === "dir") topDirs.add(node.path);
}
- setExpandedDirs(topDirs);
- // Select first file
- const firstFile = Object.keys(result.files)[0];
- if (firstFile) setSelectedFile(firstFile);
+
+ // If URL contains a deep-linked file path, select it and expand ancestors
+ const urlFile = initialFileFromUrl.current;
+ if (urlFile && urlFile in result.files) {
+ setSelectedFile(urlFile);
+ const ancestors = expandAncestors(urlFile);
+ setExpandedDirs(new Set([...topDirs, ...ancestors]));
+ } else {
+ // Select first file and update URL
+ const firstFile = Object.keys(result.files)[0];
+ if (firstFile) {
+ selectFile(firstFile, true);
+ }
+ setExpandedDirs(topDirs);
+ }
},
onError: (err) => {
pushToast({
@@ -690,7 +757,7 @@ export function CompanyExport() {
);
const skillPath = manifestSkill?.path ?? `skills/${skillKey}/SKILL.md`;
if (!(skillPath in exportData.files)) return;
- setSelectedFile(skillPath);
+ selectFile(skillPath);
setExpandedDirs((prev) => {
const next = new Set(prev);
next.add("skills");
@@ -791,7 +858,7 @@ export function CompanyExport() {
expandedDirs={expandedDirs}
checkedFiles={checkedFiles}
onToggleDir={handleToggleDir}
- onSelectFile={setSelectedFile}
+ onSelectFile={selectFile}
onToggleCheck={handleToggleCheck}
/>
{totalTaskChildren > visibleTaskChildren && !treeSearch && (