Default recurring task exports to checked
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
c41dd2e393
commit
220946b2a1
3 changed files with 105 additions and 13 deletions
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 { MarkdownBody } from "../components/MarkdownBody";
|
||||||
import { cn } from "../lib/utils";
|
import { cn } from "../lib/utils";
|
||||||
import { createZipArchive } from "../lib/zip";
|
import { createZipArchive } from "../lib/zip";
|
||||||
|
import { buildInitialExportCheckedFiles } from "../lib/company-export-selection";
|
||||||
import { getPortableFileDataUrl, getPortableFileText, isPortableImageFile } from "../lib/portable-files";
|
import { getPortableFileDataUrl, getPortableFileText, isPortableImageFile } from "../lib/portable-files";
|
||||||
import {
|
import {
|
||||||
Download,
|
Download,
|
||||||
|
|
@ -34,11 +35,6 @@ import {
|
||||||
PackageFileTree,
|
PackageFileTree,
|
||||||
} from "../components/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
|
* Extract the set of agent/project/task slugs that are "checked" based on
|
||||||
* which file paths are in the checked set.
|
* which file paths are in the checked set.
|
||||||
|
|
@ -588,14 +584,13 @@ export function CompanyExport() {
|
||||||
}),
|
}),
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
setExportData(result);
|
setExportData(result);
|
||||||
setCheckedFiles((prev) => {
|
setCheckedFiles((prev) =>
|
||||||
const next = new Set<string>();
|
buildInitialExportCheckedFiles(
|
||||||
for (const filePath of Object.keys(result.files)) {
|
Object.keys(result.files),
|
||||||
if (prev.has(filePath)) next.add(filePath);
|
result.manifest.issues,
|
||||||
else if (!isTaskPath(filePath)) next.add(filePath);
|
prev,
|
||||||
}
|
),
|
||||||
return next;
|
);
|
||||||
});
|
|
||||||
// Expand top-level dirs (except tasks — collapsed by default)
|
// Expand top-level dirs (except tasks — collapsed by default)
|
||||||
const tree = buildFileTree(result.files);
|
const tree = buildFileTree(result.files);
|
||||||
const topDirs = new Set<string>();
|
const topDirs = new Set<string>();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue