Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6414978
feat(task-persistence): add TaskOrganizationStore with atomic persist…
Aug 1, 2026
2926587
feat(task-org-ipc): add task organization IPC message handler and pro…
Aug 2, 2026
e139962
feat(task-organization): add DnD folder management and task grouping
k1yt Jul 25, 2026
33c76d7
fix(history): prevent workspace cross-contamination of tasks, pins, a…
Jul 27, 2026
f6cefb1
fix(history): hide workspace-specific folders when no workspace is open
Jul 27, 2026
963495c
fix: resolve TaskOrganizationStore test failures
Jul 31, 2026
e40f92d
fix(knip): ignore B10 unused file TaskStatusBadge and dnd-kit depende…
Aug 2, 2026
03d93b3
fix(history): align DraggableTaskEntry tests with role-stripping, add…
Aug 2, 2026
d8e7d6e
fix(task-organization): resolve lock bypass, root-task orphaning, sta…
Aug 3, 2026
733ec57
fix(task-organization): resolve lock bypass, root-task orphaning, sta…
Aug 3, 2026
fb167e2
fix(task-organization): reject same-revision writes and harden watche…
Aug 3, 2026
345f92b
fix(task-organization): guard reconcile against constructor-order race
Aug 3, 2026
33e19a8
fix(task-organization): guard taskOrganization revision in full-state…
Aug 3, 2026
df8ecea
fix(history): scope folder pins to the workspace filter and align emp…
Aug 3, 2026
3388c90
feat(history): add pin toggles to grouped-mode task rows for Welcome/…
Aug 3, 2026
1c07838
feat(history): show pinned shortcuts at the top of Welcome Recent Tasks
Aug 4, 2026
f4b6b94
ci: retrigger workflow after transient infra outage
Aug 6, 2026
73be783
fix: prune stale eslint-suppressions.json entries
Aug 6, 2026
a64cacd
chore: remove temp file progress.txt
Aug 6, 2026
837fdc7
feat(ci-precheck): upgrade from 7 to 12 checks with conditional execu…
Aug 6, 2026
5a5b996
fix(ci): strip BOM in ExtensionStateContext and boost test coverage t…
Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
435 changes: 435 additions & 0 deletions .roo/skills/local-ci-precheck/SKILL.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"webview-ui": {
"entry": ["src/index.tsx"],
"project": ["src/**/*.{ts,tsx}", "../src/shared/*.ts"],
"ignore": [
"src/components/history/TaskStatusBadge.tsx"
],
"ignoreDependencies": [
"@roo-code/config-typescript",
"@types/katex",
Expand All @@ -32,7 +35,9 @@
"source-map",
"tailwindcss",
"tailwindcss-animate",
"monocart-reporter"
"monocart-reporter",
"@dnd-kit/sortable",
"@dnd-kit/utilities"
]
},
"apps/cli": {
Expand Down
213 changes: 213 additions & 0 deletions packages/types/src/__tests__/task-organization.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { describe, it, expect } from "vitest"
import {
createEmptyTaskOrganizationState,
taskOrganizationTargetSchema,
pinnedItemSchema,
manualTaskFolderSchema,
taskOrganizationStateSchema,
taskOrganizationMutationSchema,
taskOrganizationMutationRequestSchema,
taskOrganizationMutationResultSchema,
MAX_PINNED_TARGETS,
} from "../task-organization.js"

describe("task-organization types and schemas", () => {
describe("createEmptyTaskOrganizationState", () => {
it("creates default state using Date.now when no clock is provided", () => {
const state = createEmptyTaskOrganizationState()
expect(state.schemaVersion).toBe(1)
expect(state.revision).toBe(0)
expect(state.folders).toEqual([])
expect(state.pins).toEqual([])
expect(typeof state.updatedAt).toBe("number")
expect(state.updatedAt).toBeGreaterThan(0)
})

it("uses injected custom clock function when provided", () => {
const customNow = () => 123456789
const state = createEmptyTaskOrganizationState(customNow)
expect(state.updatedAt).toBe(123456789)
})
})

describe("taskOrganizationTargetSchema", () => {
it("parses valid task target", () => {
const parsed = taskOrganizationTargetSchema.safeParse({ kind: "task", taskId: "t1" })
expect(parsed.success).toBe(true)
})

it("parses valid autoGroup target", () => {
const parsed = taskOrganizationTargetSchema.safeParse({ kind: "autoGroup", rootTaskId: "root1" })
expect(parsed.success).toBe(true)
})

it("parses valid folder target", () => {
const parsed = taskOrganizationTargetSchema.safeParse({ kind: "folder", folderId: "f1" })
expect(parsed.success).toBe(true)
})

it("rejects invalid target kind", () => {
const parsed = taskOrganizationTargetSchema.safeParse({ kind: "unknown", id: "123" })
expect(parsed.success).toBe(false)
})
})

describe("pinnedItemSchema", () => {
it("parses a valid pinned item", () => {
const item = {
target: { kind: "task", taskId: "t1" },
pinnedAt: 1000,
}
const parsed = pinnedItemSchema.safeParse(item)
expect(parsed.success).toBe(true)
})
})

describe("manualTaskFolderSchema", () => {
it("parses a valid manual folder", () => {
const folder = {
folderId: "f1",
name: "My Folder",
taskIds: ["t1", "t2"],
createdAt: 100,
updatedAt: 200,
}
const parsed = manualTaskFolderSchema.safeParse(folder)
expect(parsed.success).toBe(true)
})

it("rejects empty folder name", () => {
const folder = {
folderId: "f1",
name: "",
taskIds: [],
createdAt: 100,
updatedAt: 200,
}
const parsed = manualTaskFolderSchema.safeParse(folder)
expect(parsed.success).toBe(false)
})
})

describe("taskOrganizationStateSchema", () => {
it("enforces max pinned targets limit", () => {
const state = {
schemaVersion: 1,
revision: 0,
folders: [],
pins: Array.from({ length: MAX_PINNED_TARGETS + 1 }, (_, i) => ({
target: { kind: "task", taskId: `t${i}` },
pinnedAt: 1000 + i,
})),
updatedAt: 1000,
}
const parsed = taskOrganizationStateSchema.safeParse(state)
expect(parsed.success).toBe(false)
})

it("accepts positive integer schema versions", () => {
const state = {
schemaVersion: 2,
revision: 5,
folders: [],
pins: [],
updatedAt: 2000,
}
const parsed = taskOrganizationStateSchema.safeParse(state)
expect(parsed.success).toBe(true)
})
})

describe("taskOrganizationMutationSchema", () => {
it("validates all mutation variants", () => {
expect(
taskOrganizationMutationSchema.safeParse({
kind: "createFolder",
folderId: "f1",
name: "Folder",
source: { kind: "task", taskId: "t1" },
destination: { kind: "task", taskId: "t2" },
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "createFolderFromSelection",
folderId: "f1",
name: "Folder",
targets: [
{ kind: "task", taskId: "t1" },
{ kind: "task", taskId: "t2" },
],
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "deleteFolders",
folderIds: ["f1"],
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "renameFolder",
folderId: "f1",
name: "New Name",
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "deleteFolder",
folderId: "f1",
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "moveToFolder",
source: { kind: "task", taskId: "t1" },
folderId: "f1",
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "removeFromFolder",
source: { kind: "task", taskId: "t1" },
folderId: "f1",
}).success,
).toBe(true)

expect(
taskOrganizationMutationSchema.safeParse({
kind: "setPinned",
target: { kind: "task", taskId: "t1" },
pinned: true,
}).success,
).toBe(true)
})
})

describe("taskOrganizationMutationRequestSchema and taskOrganizationMutationResultSchema", () => {
it("parses request and result schemas", () => {
const req = {
requestId: "req-1",
baseRevision: 0,
mutation: {
kind: "deleteFolder",
folderId: "f1",
},
}
expect(taskOrganizationMutationRequestSchema.safeParse(req).success).toBe(true)

const res = {
requestId: "req-1",
success: true,
committedRevision: 1,
}
expect(taskOrganizationMutationResultSchema.safeParse(res).success).toBe(true)
})
})
})
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./followup.js"
export * from "./git.js"
export * from "./global-settings.js"
export * from "./history.js"
export * from "./task-organization.js"
export * from "./image-generation.js"
export * from "./ipc.js"
export * from "./mcp.js"
Expand Down
Loading
Loading