mirror of
https://github.com/paperclipai/paperclip
synced 2026-03-25 11:21:48 +00:00
Default recurring task exports to checked
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
41
ui/src/lib/company-export-selection.test.ts
Normal file
41
ui/src/lib/company-export-selection.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildInitialExportCheckedFiles } from "./company-export-selection";
|
||||
|
||||
describe("buildInitialExportCheckedFiles", () => {
|
||||
it("checks non-task files and recurring task packages by default", () => {
|
||||
const checked = buildInitialExportCheckedFiles(
|
||||
[
|
||||
"README.md",
|
||||
".paperclip.yaml",
|
||||
"tasks/one-off/TASK.md",
|
||||
"tasks/recurring/TASK.md",
|
||||
"tasks/recurring/notes.md",
|
||||
],
|
||||
[
|
||||
{ path: "tasks/one-off/TASK.md", recurring: false },
|
||||
{ path: "tasks/recurring/TASK.md", recurring: true },
|
||||
],
|
||||
new Set<string>(),
|
||||
);
|
||||
|
||||
expect(Array.from(checked).sort()).toEqual([
|
||||
".paperclip.yaml",
|
||||
"README.md",
|
||||
"tasks/recurring/TASK.md",
|
||||
"tasks/recurring/notes.md",
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves previous manual selections for one-time tasks", () => {
|
||||
const checked = buildInitialExportCheckedFiles(
|
||||
["README.md", "tasks/one-off/TASK.md"],
|
||||
[{ path: "tasks/one-off/TASK.md", recurring: false }],
|
||||
new Set(["tasks/one-off/TASK.md"]),
|
||||
);
|
||||
|
||||
expect(Array.from(checked).sort()).toEqual([
|
||||
"README.md",
|
||||
"tasks/one-off/TASK.md",
|
||||
]);
|
||||
});
|
||||
});
|
||||
56
ui/src/lib/company-export-selection.ts
Normal file
56
ui/src/lib/company-export-selection.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { CompanyPortabilityIssueManifestEntry } from "@paperclipai/shared";
|
||||
|
||||
function isTaskPath(filePath: string): boolean {
|
||||
return /(?:^|\/)tasks\//.test(filePath);
|
||||
}
|
||||
|
||||
function buildRecurringTaskPrefixes(
|
||||
issues: Array<Pick<CompanyPortabilityIssueManifestEntry, "path" | "recurring">>,
|
||||
): Set<string> {
|
||||
const prefixes = new Set<string>();
|
||||
|
||||
for (const issue of issues) {
|
||||
if (!issue.recurring) continue;
|
||||
|
||||
const filePath = issue.path.trim();
|
||||
if (!filePath) continue;
|
||||
|
||||
prefixes.add(filePath);
|
||||
|
||||
const lastSlash = filePath.lastIndexOf("/");
|
||||
if (lastSlash >= 0) {
|
||||
prefixes.add(`${filePath.slice(0, lastSlash + 1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
function isRecurringTaskFile(filePath: string, recurringTaskPrefixes: Set<string>): boolean {
|
||||
for (const prefix of recurringTaskPrefixes) {
|
||||
if (filePath === prefix || filePath.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function buildInitialExportCheckedFiles(
|
||||
filePaths: string[],
|
||||
issues: Array<Pick<CompanyPortabilityIssueManifestEntry, "path" | "recurring">>,
|
||||
previousCheckedFiles: Set<string>,
|
||||
): Set<string> {
|
||||
const next = new Set<string>();
|
||||
const recurringTaskPrefixes = buildRecurringTaskPrefixes(issues);
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
if (previousCheckedFiles.has(filePath)) {
|
||||
next.add(filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isTaskPath(filePath) || isRecurringTaskFile(filePath, recurringTaskPrefixes)) {
|
||||
next.add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { PageSkeleton } from "../components/PageSkeleton";
|
||||
import { MarkdownBody } from "../components/MarkdownBody";
|
||||
import { cn } from "../lib/utils";
|
||||
import { createZipArchive } from "../lib/zip";
|
||||
import { buildInitialExportCheckedFiles } from "../lib/company-export-selection";
|
||||
import { getPortableFileDataUrl, getPortableFileText, isPortableImageFile } from "../lib/portable-files";
|
||||
import {
|
||||
Download,
|
||||
@@ -34,11 +35,6 @@ import {
|
||||
PackageFileTree,
|
||||
} from "../components/PackageFileTree";
|
||||
|
||||
/** Returns true if the path looks like a task file (e.g. tasks/slug/TASK.md or projects/x/tasks/slug/TASK.md) */
|
||||
function isTaskPath(filePath: string): boolean {
|
||||
return /(?:^|\/)tasks\//.test(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the set of agent/project/task slugs that are "checked" based on
|
||||
* which file paths are in the checked set.
|
||||
@@ -588,14 +584,13 @@ export function CompanyExport() {
|
||||
}),
|
||||
onSuccess: (result) => {
|
||||
setExportData(result);
|
||||
setCheckedFiles((prev) => {
|
||||
const next = new Set<string>();
|
||||
for (const filePath of Object.keys(result.files)) {
|
||||
if (prev.has(filePath)) next.add(filePath);
|
||||
else if (!isTaskPath(filePath)) next.add(filePath);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setCheckedFiles((prev) =>
|
||||
buildInitialExportCheckedFiles(
|
||||
Object.keys(result.files),
|
||||
result.manifest.issues,
|
||||
prev,
|
||||
),
|
||||
);
|
||||
// Expand top-level dirs (except tasks — collapsed by default)
|
||||
const tree = buildFileTree(result.files);
|
||||
const topDirs = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user