diff --git a/orchestrator/src/client/pages/orchestrator/JobCommandBar.test.tsx b/orchestrator/src/client/pages/orchestrator/JobCommandBar.test.tsx index 4195852..82e7b06 100644 --- a/orchestrator/src/client/pages/orchestrator/JobCommandBar.test.tsx +++ b/orchestrator/src/client/pages/orchestrator/JobCommandBar.test.tsx @@ -148,10 +148,29 @@ describe("JobCommandBar", () => { expect(screen.getByText("Lock to @ready")).toBeInTheDocument(); expect(screen.getByText("Lock to @discovered")).toBeInTheDocument(); expect(screen.getByText("Lock to @applied")).toBeInTheDocument(); + expect(screen.getByText("Lock to @in-progress")).toBeInTheDocument(); expect(screen.getByText("Lock to @skipped")).toBeInTheDocument(); expect(screen.getByText("Lock to @expired")).toBeInTheDocument(); }); + it("creates in-progress lock from @prog + Tab", () => { + render( + , + ); + + openWithKeyboard(); + const input = screen.getByPlaceholderText( + "Search jobs by job title or company name...", + ); + fireEvent.change(input, { target: { value: "@prog" } }); + fireEvent.keyDown(input, { key: "Tab" }); + + expect(screen.getByText("@in-progress")).toBeInTheDocument(); + }); + it("searches by company name and routes to the matched state", () => { const onSelectJob = vi.fn(); const jobs: Job[] = [ @@ -377,11 +396,44 @@ describe("JobCommandBar", () => { fireEvent.keyDown(input, { key: "Tab" }); expect( - screen.queryByText(/^@(ready|discovered|applied|skipped|expired)$/), + screen.queryByText( + /^@(ready|discovered|applied|in-progress|skipped|expired)$/, + ), ).not.toBeInTheDocument(); expect((input as HTMLInputElement).value).toBe("@all"); }); + it("routes in-progress jobs to the all jobs view", () => { + const onSelectJob = vi.fn(); + + render( + , + ); + + openWithKeyboard(); + fireEvent.change( + screen.getByPlaceholderText( + "Search jobs by job title or company name...", + ), + { + target: { value: "Globex" }, + }, + ); + fireEvent.click(screen.getByText("Staff Engineer")); + + expect(onSelectJob).toHaveBeenCalledWith("all", "in-progress-job"); + }); + it("excludes processing jobs from every lock scope", () => { const jobs: Job[] = [ createJob({ diff --git a/orchestrator/src/client/pages/orchestrator/JobCommandBar.tsx b/orchestrator/src/client/pages/orchestrator/JobCommandBar.tsx index 1dd8e35..e340b9a 100644 --- a/orchestrator/src/client/pages/orchestrator/JobCommandBar.tsx +++ b/orchestrator/src/client/pages/orchestrator/JobCommandBar.tsx @@ -51,6 +51,8 @@ export const JobCommandBar: React.FC = ({ "border-sky-500/50 shadow-[0_0_0_1px_rgba(14,165,233,0.2),0_0_36px_-12px_rgba(14,165,233,0.55)]", applied: "border-emerald-500/50 shadow-[0_0_0_1px_rgba(16,185,129,0.2),0_0_36px_-12px_rgba(16,185,129,0.55)]", + in_progress: + "border-cyan-500/50 shadow-[0_0_0_1px_rgba(6,182,212,0.2),0_0_36px_-12px_rgba(6,182,212,0.55)]", skipped: "border-rose-500/50 shadow-[0_0_0_1px_rgba(244,63,94,0.2),0_0_36px_-12px_rgba(244,63,94,0.55)]", expired: diff --git a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts index c41f346..c5228a4 100644 --- a/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts +++ b/orchestrator/src/client/pages/orchestrator/JobCommandBar.utils.ts @@ -6,6 +6,7 @@ export type StatusLock = | "ready" | "discovered" | "applied" + | "in_progress" | "skipped" | "expired"; @@ -21,6 +22,7 @@ const lockAliases: Record = { ready: ["ready", "rdy"], discovered: ["discovered", "discover", "disc"], applied: ["applied", "apply", "app"], + in_progress: ["in-progress", "inprogress", "progress", "prog"], skipped: ["skipped", "skip", "skp"], expired: ["expired", "expire", "exp"], }; @@ -29,6 +31,7 @@ export const lockLabel: Record = { ready: "ready", discovered: "discovered", applied: "applied", + in_progress: "in-progress", skipped: "skipped", expired: "expired", }; @@ -124,6 +127,7 @@ export const jobMatchesLock = (job: JobListItem, lock: StatusLock) => { if (lock === "ready") return job.status === "ready"; if (lock === "discovered") return job.status === "discovered"; if (lock === "applied") return job.status === "applied"; + if (lock === "in_progress") return job.status === "in_progress"; if (lock === "skipped") return job.status === "skipped"; if (lock === "expired") return job.status === "expired"; return false; diff --git a/orchestrator/src/client/pages/orchestrator/useFilteredJobs.test.ts b/orchestrator/src/client/pages/orchestrator/useFilteredJobs.test.ts index a7b1d18..bfee144 100644 --- a/orchestrator/src/client/pages/orchestrator/useFilteredJobs.test.ts +++ b/orchestrator/src/client/pages/orchestrator/useFilteredJobs.test.ts @@ -15,6 +15,35 @@ const baseJob = createJob({ }); describe("useFilteredJobs", () => { + it("keeps in-progress jobs in the all jobs tab", () => { + const jobs: Job[] = [ + { ...baseJob, id: "in-progress", status: "in_progress" }, + { ...baseJob, id: "processing", status: "processing" }, + { + ...baseJob, + id: "closed", + status: "in_progress", + closedAt: 1741996800, + }, + ]; + + const { result } = renderHook(() => + useFilteredJobs( + jobs, + "all", + "all", + "all", + { mode: "at_least", min: null, max: null }, + { + key: "score", + direction: "desc", + }, + ), + ); + + expect(result.current.map((job) => job.id)).toEqual(["in-progress"]); + }); + it("filters by sponsor status categories", () => { const jobs: Job[] = [ { ...baseJob, id: "confirmed", sponsorMatchScore: 99 }, diff --git a/orchestrator/src/client/pages/orchestrator/useFilteredJobs.ts b/orchestrator/src/client/pages/orchestrator/useFilteredJobs.ts index 8dfe8f1..ebebbeb 100644 --- a/orchestrator/src/client/pages/orchestrator/useFilteredJobs.ts +++ b/orchestrator/src/client/pages/orchestrator/useFilteredJobs.ts @@ -24,7 +24,7 @@ export const useFilteredJobs = ( sort: JobSort, ) => useMemo(() => { - let filtered = jobs.filter((job) => job.status !== "in_progress"); + let filtered = [...jobs]; if (activeTab === "ready") { filtered = filtered.filter((job) => job.status === "ready"); @@ -34,6 +34,10 @@ export const useFilteredJobs = ( ); } else if (activeTab === "applied") { filtered = filtered.filter((job) => job.status === "applied"); + } else if (activeTab === "all") { + filtered = filtered.filter( + (job) => job.status !== "processing" && job.closedAt == null, + ); } if (activeTab !== "all") {