feat: personal/work area toggle
Global lens in the home header: every list, Today, Projects and Review show only the active area; new rows are born into it. Tasks and projects gain an area column (personal default); the task form's Area chip re-files across areas. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SW5iVXkZ2JBHgyweMCRymY
This commit is contained in:
@@ -21,6 +21,7 @@ const store = await import("./store");
|
||||
beforeEach(async () => {
|
||||
storage.clear();
|
||||
await store.hydrate();
|
||||
store.setArea("personal");
|
||||
});
|
||||
|
||||
const find = (id: string) => store.getState().tasks.find((t) => t.id === id)!;
|
||||
@@ -61,3 +62,66 @@ describe("completeTask", () => {
|
||||
expect(store.completeTask("nope")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("area lens", () => {
|
||||
test("new rows are born into the active area and selectors filter by it", () => {
|
||||
const home = store.createTask({ title: "home" });
|
||||
store.setArea("work");
|
||||
const office = store.createTask({ title: "office" });
|
||||
const proj = store.createProject({ name: "Q4" });
|
||||
expect(home.area).toBe("personal");
|
||||
expect(office.area).toBe("work");
|
||||
expect(proj.area).toBe("work");
|
||||
|
||||
// The store is module state shared across tests, so assert on membership
|
||||
// and on every visible row's area rather than on exact lists.
|
||||
const ids = (s: ReturnType<typeof store.getState>) => store.tasksByStatus(s, "next").map((t) => t.id);
|
||||
let s = store.getState();
|
||||
expect(ids(s)).toContain(office.id);
|
||||
expect(ids(s)).not.toContain(home.id);
|
||||
expect(store.tasksByStatus(s, "next").every((t) => t.area === "work")).toBe(true);
|
||||
expect(store.activeProjects(s).map((p) => p.id)).toEqual([proj.id]);
|
||||
|
||||
store.setArea("personal");
|
||||
s = store.getState();
|
||||
expect(ids(s)).toContain(home.id);
|
||||
expect(ids(s)).not.toContain(office.id);
|
||||
expect(store.activeProjects(s).some((p) => p.id === proj.id)).toBe(false);
|
||||
});
|
||||
|
||||
test("area persists across hydrate", async () => {
|
||||
store.setArea("work");
|
||||
expect(storage.get("area_v1")).toBe("work");
|
||||
});
|
||||
|
||||
test("rows cached without an area count as personal", () => {
|
||||
// Simulate a pre-migration cache row.
|
||||
const legacy = store.createTask({ title: "old" });
|
||||
delete (legacy as { area?: string }).area;
|
||||
store.setArea("work");
|
||||
expect(store.todayTasks(store.getState()).overdue).toEqual([]);
|
||||
store.setArea("personal");
|
||||
expect(store.tasksByStatus(store.getState(), "next").some((t) => t.id === legacy.id)).toBe(true);
|
||||
});
|
||||
|
||||
test("today, review and contexts respect the lens", () => {
|
||||
store.createTask({ title: "p", due_date: "2000-01-01", context: "@home" });
|
||||
store.setArea("work");
|
||||
store.createTask({ title: "w", due_date: "2000-01-01", context: "@office" });
|
||||
const s = store.getState();
|
||||
expect(store.todayTasks(s).overdue.every((t) => t.area === "work")).toBe(true);
|
||||
expect(store.todayTasks(s).overdue.map((t) => t.title)).toContain("w");
|
||||
expect(store.reviewBuckets(s).overdue.map((t) => t.title)).toContain("w");
|
||||
expect(store.reviewBuckets(s).overdue.map((t) => t.title)).not.toContain("p");
|
||||
expect(store.knownContexts(s)).toEqual(["@office"]);
|
||||
});
|
||||
|
||||
test("completing a repeating work task spawns into work", () => {
|
||||
store.setArea("work");
|
||||
const t = store.createTask({ title: "standup", repeat: "daily", due_date: "2000-01-01" });
|
||||
store.setArea("personal");
|
||||
store.completeTask(t.id);
|
||||
const spawned = store.getState().tasks.find((x) => x.title === "standup" && x.status === "next")!;
|
||||
expect(spawned.area).toBe("work");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user