diff --git a/src/app/components/AppNavbar.tsx b/src/app/components/AppNavbar.tsx
index e2cc16d..3598166 100644
--- a/src/app/components/AppNavbar.tsx
+++ b/src/app/components/AppNavbar.tsx
@@ -55,7 +55,7 @@ const NAV_ITEMS = [
},
{
to: appPath("/job-os/companies"),
- label: "System",
+ label: "Companies & System",
icon: FolderOpen,
matches: (pathname: string) =>
pathname.startsWith(appPath("/job-os/companies")) ||
diff --git a/src/app/components/job-os/JobOsLayout.tsx b/src/app/components/job-os/JobOsLayout.tsx
index f593d83..34f81a4 100644
--- a/src/app/components/job-os/JobOsLayout.tsx
+++ b/src/app/components/job-os/JobOsLayout.tsx
@@ -6,6 +6,7 @@ import {
FileSpreadsheet,
FileText,
FolderOpen,
+ LayoutDashboard,
Megaphone,
Settings,
ShieldCheck,
@@ -35,6 +36,7 @@ function getActiveSection(pathname: string): "action" | "pipeline" | "system" {
const SECTION_NAV = {
action: [
+ { to: appPath(), label: "Command Center", icon: LayoutDashboard },
{ to: appPath("/job-os/sources"), label: "Sources", icon: Compass },
],
pipeline: [
@@ -43,7 +45,7 @@ const SECTION_NAV = {
{ to: appPath("/job-os/outreach"), label: "Outreach", icon: Megaphone },
],
system: [
- { to: appPath("/job-os/companies"), label: "Target Companies", icon: Building2 },
+ { to: appPath("/job-os/companies"), label: "Companies", icon: Building2 },
{ to: appPath("/job-os/assets"), label: "Assets", icon: FolderOpen },
{ to: appPath("/cv-optimizer"), label: "CV Optimizer", icon: WandSparkles },
{ to: appPath("/compliance/afa"), label: "AfA Compliance", icon: ShieldCheck },
diff --git a/src/app/pages/dashboard/DashboardPage.tsx b/src/app/pages/dashboard/DashboardPage.tsx
index fef1450..28cd595 100644
--- a/src/app/pages/dashboard/DashboardPage.tsx
+++ b/src/app/pages/dashboard/DashboardPage.tsx
@@ -1,9 +1,9 @@
import { useMemo, useState } from "react";
-import { useNavigate } from "react-router";
-import { ChevronDown, ChevronUp, Layers3 } from "lucide-react";
+import { NavLink, useNavigate } from "react-router";
+import { ChevronDown, ChevronUp, Compass, LayoutDashboard, Layers3 } from "lucide-react";
import { useApp } from "../../context";
import { useJobOs } from "../../hooks/useJobOs";
-import { UnifiedAddModal } from "../../components/job-os/UnifiedAddModal";
+
import { useJobOsSyncSnapshot } from "../../services/jobOsSync";
import {
getNextActions,
@@ -20,7 +20,7 @@ import { TodayPanel } from "../../components/dashboard/TodayPanel";
import { HotOpportunities } from "../../components/dashboard/HotOpportunities";
import { PipelineStats } from "../../components/dashboard/PipelineStats";
import { ProbabilityPanel } from "../../components/dashboard/ProbabilityPanel";
-import { QuickActions } from "../../components/dashboard/QuickActions";
+
import { FirstRunScreen } from "../../components/dashboard/FirstRunScreen";
import { AppPageShell } from "../../components/layout/AppPageShell";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../components/ui/collapsible";
@@ -32,7 +32,6 @@ export function DashboardPage() {
const navigate = useNavigate();
const jobOsSync = useJobOsSyncSnapshot();
const [supportOpen, setSupportOpen] = useState(false);
- const [addModalOpen, setAddModalOpen] = useState(false);
const {
companies,
roles,
@@ -116,6 +115,32 @@ export function DashboardPage() {
showSync
settingsContent={settingsContent}
/>
+
+
+
+
+
{holdDashboard ? (
@@ -156,7 +181,6 @@ export function DashboardPage() {
-
setAddModalOpen(true)} />
@@ -197,14 +221,6 @@ export function DashboardPage() {
- setAddModalOpen(false)}
- companies={companies}
- addCompany={addCompany}
- addRole={addRole}
- addApplication={addApplication}
- />
);
}
diff --git a/src/app/pages/job-os/JobOsCompaniesPage.tsx b/src/app/pages/job-os/JobOsCompaniesPage.tsx
index de96e0f..ebbd7d9 100644
--- a/src/app/pages/job-os/JobOsCompaniesPage.tsx
+++ b/src/app/pages/job-os/JobOsCompaniesPage.tsx
@@ -549,8 +549,8 @@ export default function JobOsCompaniesPage() {
return (
}
>
diff --git a/src/app/pages/job-os/JobOsRolesPage.tsx b/src/app/pages/job-os/JobOsRolesPage.tsx
index cf43b54..662bfff 100644
--- a/src/app/pages/job-os/JobOsRolesPage.tsx
+++ b/src/app/pages/job-os/JobOsRolesPage.tsx
@@ -1,6 +1,5 @@
import { Link } from "react-router";
import { useEffect, useMemo, useState } from "react";
-import { useUnsavedChanges } from "../../hooks/useUnsavedChanges";
import { toast } from "sonner";
import {
AlertDialog,
@@ -18,12 +17,11 @@ import {
ArrowUp,
ArrowUpDown,
BriefcaseBusiness,
- Check,
CheckCircle2,
- ChevronDown,
- ChevronRight,
Pencil,
Rocket,
+ Search,
+ SlidersHorizontal,
Trash2,
WandSparkles,
X,
@@ -87,7 +85,6 @@ export default function JobOsRolesPage() {
applications,
assets,
cvProfiles,
- addRole,
updateRole,
addApplication,
removeRole,
@@ -96,12 +93,8 @@ export default function JobOsRolesPage() {
replaceState,
} = useJobOsContext();
- const [addFormOpen, setAddFormOpen] = useState(false);
- const [companySearch, setCompanySearch] = useState("");
- const [companyOptionsOpen, setCompanyOptionsOpen] = useState(false);
const [editingRoleId, setEditingRoleId] = useState
(null);
const [editDraft, setEditDraft] = useState | null>(null);
- const [formNotice, setFormNotice] = useState(null);
const [actionNotice, setActionNotice] = useState<{ tone: "success" | "error"; message: string } | null>(null);
const [sortKey, setSortKey] = useState("createdAt");
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
@@ -114,45 +107,26 @@ export default function JobOsRolesPage() {
fitMin: "all",
status: "all",
});
+ const [unifiedSearch, setUnifiedSearch] = useState("");
+ const [showMoreFilters, setShowMoreFilters] = useState(false);
const [selectedJdRoleId, setSelectedJdRoleId] = useState(null);
- const [draft, setDraft] = useState>({
- companyId: "",
- title: "",
- url: "",
- location: "",
- seniority: "",
- track: "TPM",
- fitScore: 3,
- status: "to_apply",
- origin: "self_sourced",
- jobDescription: "",
- jobDescriptionUpdatedAt: undefined,
- });
const companiesById = useMemo(
() => new Map(companies.map((company) => [company.id, company])),
[companies]
);
- const selectedDraftCompany = draft.companyId ? companiesById.get(draft.companyId) : null;
- const filteredCompanyOptions = useMemo(() => {
- const query = companySearch.trim().toLowerCase();
- const matches = query
- ? companies.filter((company) => company.name.toLowerCase().includes(query))
- : companies;
-
- return matches.slice(0, 50);
- }, [companies, companySearch]);
const filtered = useMemo(() => {
- const companyQuery = tableFilters.company.trim().toLowerCase();
- const titleQuery = tableFilters.title.trim().toLowerCase();
+ const searchQuery = unifiedSearch.trim().toLowerCase();
const locationQuery = tableFilters.location.trim().toLowerCase();
return roles.filter((role) => {
const companyName = companiesById.get(role.companyId)?.name ?? "";
- if (companyQuery && !companyName.toLowerCase().includes(companyQuery)) return false;
- if (titleQuery && !role.title.toLowerCase().includes(titleQuery)) return false;
+ if (searchQuery) {
+ const haystack = `${companyName} ${role.title}`.toLowerCase();
+ if (!haystack.includes(searchQuery)) return false;
+ }
if (locationQuery && !role.location.toLowerCase().includes(locationQuery)) return false;
if (
tableFilters.seniority !== "all" &&
@@ -168,7 +142,7 @@ export default function JobOsRolesPage() {
return true;
});
- }, [companiesById, roles, tableFilters]);
+ }, [companiesById, roles, tableFilters, unifiedSearch]);
const sortedRoles = useMemo(() => {
const statusRank: Record = {
@@ -235,7 +209,7 @@ export default function JobOsRolesPage() {
useEffect(() => {
resetRolesPage();
- }, [resetRolesPage, sortDir, sortKey, tableFilters]);
+ }, [resetRolesPage, sortDir, sortKey, tableFilters, unifiedSearch]);
const applicationRoleIds = useMemo(
() => new Set(applications.map((application) => application.roleId).filter(Boolean)),
@@ -311,51 +285,6 @@ export default function JobOsRolesPage() {
toast.success("Role updated");
}
- async function handleAddRole(): Promise {
- const missingFields: string[] = [];
- if (!draft.companyId) missingFields.push("company");
- if (!draft.title.trim()) missingFields.push("role title");
-
- if (missingFields.length > 0) {
- setFormNotice(`Add a ${missingFields.join(" and ")} before creating the role.`);
- return;
- }
-
- try {
- setFormNotice("Saving role...");
- const createdId = await addRole({
- ...draft,
- title: draft.title.trim(),
- seniority: normalizeSeniority(draft.seniority),
- jobDescriptionUpdatedAt: draft.jobDescription?.trim() ? new Date().toISOString() : undefined,
- });
-
- if (!createdId) {
- setFormNotice("Role could not be created.");
- return;
- }
-
- setDraft({
- companyId: "",
- title: "",
- url: "",
- location: "",
- seniority: "",
- track: "TPM",
- fitScore: 3,
- status: "to_apply",
- origin: "self_sourced",
- jobDescription: "",
- jobDescriptionUpdatedAt: undefined,
- });
- setCompanySearch("");
- setFormNotice("");
- toast.success("Role added");
- } catch (error) {
- setFormNotice(error instanceof Error ? error.message : "Role could not be created.");
- }
- }
-
async function handleAddApplication(role: JobOsRole): Promise {
if (applicationRoleIds.has(role.id)) {
setActionNotice({
@@ -396,21 +325,6 @@ export default function JobOsRolesPage() {
}
}
- const isAddFormDirty =
- addFormOpen &&
- (draft.companyId !== "" ||
- draft.title.trim() !== "" ||
- (draft.url ?? "").trim() !== "" ||
- (draft.location ?? "").trim() !== "" ||
- (draft.jobDescription ?? "").trim() !== "");
- const { confirmDiscard: confirmAddFormDiscard } = useUnsavedChanges(isAddFormDirty);
-
- function handleToggleAddForm() {
- if (addFormOpen && !confirmAddFormDiscard()) return;
- setAddFormOpen((value) => !value);
- setCompanyOptionsOpen(false);
- }
-
return (
}
>
-
-
-
- {addFormOpen ? (
-
-
-
{
- setCompanySearch(event.target.value);
- setCompanyOptionsOpen(true);
- setDraft((current) => ({ ...current, companyId: "" }));
- }}
- onFocus={() => {
- setCompanySearch(selectedDraftCompany?.name ?? companySearch);
- setCompanyOptionsOpen(true);
- }}
- onBlur={() => {
- window.setTimeout(() => setCompanyOptionsOpen(false), 120);
- }}
- placeholder="Company"
- autoComplete="off"
- />
- {companyOptionsOpen ? (
-
- {filteredCompanyOptions.length > 0 ? (
- filteredCompanyOptions.map((company) => (
-
event.preventDefault()}
- onClick={() => {
- setDraft((current) => ({ ...current, companyId: company.id }));
- setCompanySearch(company.name);
- setCompanyOptionsOpen(false);
- }}
- >
-
- {company.name}
-
- ))
- ) : (
-
- No company found.
-
- )}
-
- ) : null}
-
- setDraft((current) => ({ ...current, title: event.target.value }))} placeholder="Role title" />
-
- setDraft((current) => ({ ...current, url: event.target.value }))}
- placeholder={draft.origin === "recruiter" ? "Posting URL (optional)" : "Role URL"}
- />
- setDraft((current) => ({ ...current, location: event.target.value }))}
- placeholder="Location"
- />
-
+ {showMoreFilters && (
+
+
updateTableFilter("location", event.target.value)}
+ placeholder="Location"
+ className="h-9 w-[150px] text-sm"
+ />
+
+
updateTableFilter("track", value)}>
+
+
+
+
+ All tracks
+ TPM
+ Product Engineer
+ Systems PM
+
+
+
updateTableFilter("fitMin", value)}>
+
+
+
+
+ All fit
+ {FIT_FILTER_VALUES.filter((value) => value !== "all").map((value) => (
+ {value}+ fit
+ ))}
+
+
+
+ )}
@@ -583,105 +451,6 @@ export default function JobOsRolesPage() {
JD
Actions
-
-
- updateTableFilter("company", event.target.value)}
- placeholder="Filter company"
- className="h-8 min-w-[140px]"
- />
-
-
- updateTableFilter("title", event.target.value)}
- placeholder="Filter title"
- className="h-8 min-w-[160px]"
- />
-
-
- updateTableFilter("location", event.target.value)}
- placeholder="Filter location"
- className="h-8 min-w-[130px]"
- />
-
-
- updateTableFilter("seniority", value)}
- >
-
-
-
-
- All
- {SENIORITY_OPTIONS.map((option) => (
-
- {option}
-
- ))}
-
-
-
-
- updateTableFilter("track", value)}
- >
-
-
-
-
- All tracks
- TPM
- Product Engineer
- Systems PM
-
-
-
-
- updateTableFilter("fitMin", value)}
- >
-
-
-
-
- All
- {FIT_FILTER_VALUES.filter((value) => value !== "all").map((value) => (
-
- {value}+
-
- ))}
-
-
-
-
- updateTableFilter("status", value)}
- >
-
-
-
-
- All statuses
- {ROLE_STATUSES.map((status) => (
-
- {status}
-
- ))}
-
-
-
-
-
-
-
{pagedRoles.map((role) => (
diff --git a/src/app/pages/job-os/JobOsSourcesPage.tsx b/src/app/pages/job-os/JobOsSourcesPage.tsx
index 651dc9e..a0d9d12 100644
--- a/src/app/pages/job-os/JobOsSourcesPage.tsx
+++ b/src/app/pages/job-os/JobOsSourcesPage.tsx
@@ -1,18 +1,20 @@
import { Link } from "react-router";
import { useMemo, useState } from "react";
-import { Archive, CheckCheck, Compass, ExternalLink, Globe2, ListChecks, Plus, Search } from "lucide-react";
+import { Archive, CheckCheck, ExternalLink, HelpCircle, Plus, Search, WandSparkles } from "lucide-react";
import { toast } from "sonner";
import { AppPageShell } from "../../components/layout/AppPageShell";
import { JobOsLayout } from "../../components/job-os/JobOsLayout";
import { JobOsTransferControls } from "../../components/job-os/JobOsTransferControls";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card";
+import { Card, CardContent, CardHeader } from "../../components/ui/card";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../../components/ui/dialog";
import { Input } from "../../components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select";
import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../../components/ui/sheet";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/ui/tabs";
+import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip";
import { Textarea } from "../../components/ui/textarea";
import { useJobOsContext } from "../../context/JobOsContext";
import { appPath } from "../../routing";
@@ -256,6 +258,22 @@ export default function JobOsSourcesPage() {
const [sourceEditDraft, setSourceEditDraft] = useState(null);
const [editingSavedSearch, setEditingSavedSearch] = useState(null);
const [savedSearchEditDraft, setSavedSearchEditDraft] = useState(null);
+ const [activeTab, setActiveTab] = useState("due-today");
+ const [showAllDue, setShowAllDue] = useState(false);
+ const [sourcesSearch, setSourcesSearch] = useState("");
+ const [sourcesPriorityFilter, setSourcesPriorityFilter] = useState("all");
+ const [sourcesCategoryFilter, setSourcesCategoryFilter] = useState("all");
+ const [sourcesActiveFilter, setSourcesActiveFilter] = useState<"all" | "active" | "inactive">("all");
+ const [searchesSearch, setSearchesSearch] = useState("");
+ const [searchesPriorityFilter, setSearchesPriorityFilter] = useState("all");
+ const [searchesSourceFilter, setSearchesSourceFilter] = useState("all");
+ const [searchesActiveFilter, setSearchesActiveFilter] = useState<"all" | "active" | "inactive">("all");
+ const [queueSearch, setQueueSearch] = useState("");
+ const [queueFitFilter, setQueueFitFilter] = useState("all");
+ const [queueStatusFilter, setQueueStatusFilter] = useState("all");
+ const [dueSearch, setDueSearch] = useState("");
+ const [duePriorityFilter, setDuePriorityFilter] = useState("all");
+ const [dueTypeFilter, setDueTypeFilter] = useState<"all" | "source" | "savedSearch">("all");
const companiesById = useMemo(
() => new Map(companies.map((company) => [company.id, company])),
@@ -314,16 +332,69 @@ export default function JobOsSourcesPage() {
[roles]
);
- const searchesDueToday = useMemo(
- () => activeSavedSearches.filter((savedSearch) => isDue(savedSearch.lastCheckedAt, savedSearch.cadence)).length,
- [activeSavedSearches]
+ const dueScanItems = useMemo(
+ () => scanItems.filter((entry) => isDue(entry.item.lastCheckedAt, entry.item.cadence)),
+ [scanItems]
);
+ const filteredDueItems = useMemo(() => {
+ const base = showAllDue ? scanItems : dueScanItems;
+ return base.filter((entry) => {
+ if (dueSearch && !entry.item.name.toLowerCase().includes(dueSearch.toLowerCase())) return false;
+ if (duePriorityFilter !== "all" && entry.item.priority !== duePriorityFilter) return false;
+ if (dueTypeFilter !== "all" && entry.kind !== dueTypeFilter) return false;
+ return true;
+ });
+ }, [showAllDue, scanItems, dueScanItems, dueSearch, duePriorityFilter, dueTypeFilter]);
+
const filteredSavedSearchOptions = useMemo(() => {
if (!captureDraft.sourceId) return activeSavedSearches;
return activeSavedSearches.filter((savedSearch) => savedSearch.sourceId === captureDraft.sourceId);
}, [activeSavedSearches, captureDraft.sourceId]);
+ const filteredSources = useMemo(() => {
+ return sources.filter((source) => {
+ if (
+ sourcesSearch &&
+ !source.name.toLowerCase().includes(sourcesSearch.toLowerCase()) &&
+ !source.bestFor.toLowerCase().includes(sourcesSearch.toLowerCase())
+ ) return false;
+ if (sourcesPriorityFilter !== "all" && source.priority !== sourcesPriorityFilter) return false;
+ if (sourcesCategoryFilter !== "all" && source.category !== sourcesCategoryFilter) return false;
+ if (sourcesActiveFilter === "active" && !source.active) return false;
+ if (sourcesActiveFilter === "inactive" && source.active) return false;
+ return true;
+ });
+ }, [sources, sourcesSearch, sourcesPriorityFilter, sourcesCategoryFilter, sourcesActiveFilter]);
+
+ const filteredSavedSearches = useMemo(() => {
+ return savedSearches.filter((savedSearch) => {
+ if (
+ searchesSearch &&
+ !savedSearch.name.toLowerCase().includes(searchesSearch.toLowerCase()) &&
+ !savedSearch.query.toLowerCase().includes(searchesSearch.toLowerCase())
+ ) return false;
+ if (searchesPriorityFilter !== "all" && savedSearch.priority !== searchesPriorityFilter) return false;
+ if (searchesSourceFilter !== "all" && savedSearch.sourceId !== searchesSourceFilter) return false;
+ if (searchesActiveFilter === "active" && !savedSearch.active) return false;
+ if (searchesActiveFilter === "inactive" && savedSearch.active) return false;
+ return true;
+ });
+ }, [savedSearches, searchesSearch, searchesPriorityFilter, searchesSourceFilter, searchesActiveFilter]);
+
+ const filteredQueue = useMemo(() => {
+ return qualificationQueue.filter((role) => {
+ if (queueSearch) {
+ const company = companiesById.get(role.companyId);
+ const searchText = `${role.title} ${company?.name ?? ""}`.toLowerCase();
+ if (!searchText.includes(queueSearch.toLowerCase())) return false;
+ }
+ if (queueFitFilter !== "all" && String(role.fitScore) !== queueFitFilter) return false;
+ if (queueStatusFilter !== "all" && role.discoveryStatus !== queueStatusFilter) return false;
+ return true;
+ });
+ }, [qualificationQueue, queueSearch, queueFitFilter, queueStatusFilter, companiesById]);
+
function openUrl(url?: string): void {
if (!url) return;
window.open(url, "_blank", "noopener,noreferrer");
@@ -568,465 +639,687 @@ export default function JobOsSourcesPage() {
>
-
-
-
-
-
-
- Active Sources
-
-
{activeSources.length}
-
-
-
-
-
-
-
-
- Searches Due Today
-
-
{searchesDueToday}
-
-
-
-
-
-
-
-
- Captured Roles
+
+
+
+ {activeSources.length} active sources
+
+ ·
+
+ {dueScanItems.length} due today
+
+ ·
+
+ {capturedRolesCount} captured roles
+
+ ·
+
+ {qualificationQueue.length} in queue
+
+
+
+
+
+
+
+ Due Today{dueScanItems.length > 0 ? ` (${dueScanItems.length})` : ""}
+
+
+ Sources ({activeSources.length}/{sources.length})
+
+
+ Saved Searches ({activeSavedSearches.length}/{savedSearches.length})
+
+
+ Queue{qualificationQueue.length > 0 ? ` (${qualificationQueue.length})` : ""}
+
+
+
+
+
+
+
+
+
+
+
Due Today — sources and saved searches overdue for their scan cadence, sorted by priority. Filter by type or priority.
+
Sources — your full discovery stack. Add, edit, enable or disable sources.
+
Saved Searches — keyword searches tied to specific sources. Filter by source or track.
+
Queue — roles captured from sources awaiting qualification before applying.
- {capturedRolesCount}
-
-
-
-
-
-
-
-
- Qualified Queue
+
+
+
+
+
+
+
+
+
+
+ setDueSearch(e.target.value)}
+ />
+
+
setDuePriorityFilter(v as JobSourcePriority | "all")}
+ >
+
+
+
+
+ All priorities
+ {SOURCE_PRIORITIES.map((p) => (
+ Priority {p}
+ ))}
+
+
+
setDueTypeFilter(v as "all" | "source" | "savedSearch")}
+ >
+
+
+
+
+ All types
+ Sources only
+ Saved searches only
+
+
+
setShowAllDue((v) => !v)}
+ className="text-xs text-muted-foreground"
+ >
+ {showAllDue ? "Due only" : `All (${scanItems.length})`}
+
- {qualificationQueue.length}
-
-
-
-
-
-
-
-
-
- Today's Scan
-
-
- {scanItems.length > 0 ? (
- scanItems.map((entry) => {
- const isSource = entry.kind === "source";
- const sourceName = isSource
- ? entry.item.name
- : sourcesById.get(entry.item.sourceId)?.name ?? "Unknown source";
-
- return (
-
-
-
-
-
{entry.item.name}
-
- {entry.item.priority}
-
-
- {isSource ? "Source" : "Saved Search"}
-
- {isDue(entry.item.lastCheckedAt, entry.item.cadence) ? (
-
- Due
-
- ) : null}
-
-
- {isSource ? (entry.item as JobSource).bestFor : sourceName}
-
-
-
Cadence: {CADENCE_LABELS[entry.item.cadence]}
-
Last checked: {formatDateLabel(entry.item.lastCheckedAt)}
+
+
+ {scanItems.length === 0 ? (
+
+ Source Hub is ready. Your default source stack should appear here as soon as sync finishes.
+
+ ) : filteredDueItems.length === 0 ? (
+
+ {dueScanItems.length === 0 && !showAllDue
+ ? "Nothing is due right now."
+ : "No items match the current filters."}
+
+ ) : (
+ <>
+ {filteredDueItems.map((entry) => {
+ const isSource = entry.kind === "source";
+ const sourceName = isSource
+ ? entry.item.name
+ : sourcesById.get(entry.item.sourceId)?.name ?? "Unknown source";
+
+ return (
+
+
+
+
+
{entry.item.name}
+
+ {entry.item.priority}
+
+
+ {isSource ? "Source" : "Saved Search"}
+
+ {isDue(entry.item.lastCheckedAt, entry.item.cadence) ? (
+
+ Due
+
+ ) : null}
+
+
+ {isSource ? (entry.item as JobSource).bestFor : sourceName}
+
+
+ Cadence: {CADENCE_LABELS[entry.item.cadence]}
+ Last checked: {formatDateLabel(entry.item.lastCheckedAt)}
+
+
+
+
+ openCapture(
+ isSource
+ ? { sourceId: entry.item.id, sourceUrl: entry.item.url }
+ : {
+ sourceId: (entry.item as SavedSearch).sourceId,
+ savedSearchId: entry.item.id,
+ sourceUrl: entry.item.url,
+ }
+ )
+ }
+ >
+
+ Capture role
+
+
+ isSource
+ ? void handleMarkSourceChecked(entry.item as JobSource)
+ : void handleMarkSavedSearchChecked(entry.item as SavedSearch)
+ }
+ >
+
+ Mark checked
+
+
+
+ openUrl(entry.item.url)} aria-label="Open in browser">
+
+
+
+ Open in browser
+
+
-
-
openUrl(entry.item.url)}>
-
- Open
-
-
- isSource
- ? void handleMarkSourceChecked(entry.item as JobSource)
- : void handleMarkSavedSearchChecked(entry.item as SavedSearch)
- }
- >
-
- Mark checked
-
-
- openCapture(
- isSource
- ? {
- sourceId: entry.item.id,
- sourceUrl: entry.item.url,
- }
- : {
- sourceId: (entry.item as SavedSearch).sourceId,
- savedSearchId: entry.item.id,
- sourceUrl: entry.item.url,
- }
- )
- }
- >
-
- Capture role
-
+ );
+ })}
+
+ {showAllDue
+ ? `All ${scanItems.length} scan items`
+ : `${dueScanItems.length} item${dueScanItems.length !== 1 ? "s" : ""} due`}
+ {filteredDueItems.length !== (showAllDue ? scanItems : dueScanItems).length
+ ? ` · ${filteredDueItems.length} shown after filtering`
+ : ""}
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+ setSourcesSearch(e.target.value)}
+ />
+
+
setSourcesPriorityFilter(v as JobSourcePriority | "all")}
+ >
+
+
+
+
+ All priorities
+ {SOURCE_PRIORITIES.map((p) => (
+ Priority {p}
+ ))}
+
+
+
setSourcesCategoryFilter(v as JobSourceCategory | "all")}
+ >
+
+
+
+
+ All categories
+ {SOURCE_CATEGORIES.map((c) => (
+ {SOURCE_CATEGORY_LABELS[c]}
+ ))}
+
+
+
setSourcesActiveFilter(v as "all" | "active" | "inactive")}
+ >
+
+
+
+
+ All
+ Active
+ Inactive
+
+
+
+
+
+
+
+
+
+ Source
+ Category
+ Best for
+ Priority
+ Cadence
+ Last checked
+ Actions
+
+
+
+ {filteredSources.map((source) => (
+
+ {source.name}
+ {SOURCE_CATEGORY_LABELS[source.category]}
+ {source.bestFor}
+
+
+ {source.priority}
+
+
+ {CADENCE_LABELS[source.cadence]}
+ {formatDateLabel(source.lastCheckedAt)}
+
+
+ openSourceEditor(source)}>Edit
+ void handleMarkSourceChecked(source)}>Mark checked
+ void handleToggleSource(source)}>
+ {source.active ? "Disable" : "Enable"}
+
+
+
+ openUrl(source.url)} aria-label="Open source">
+
+
+
+ Open source
+
+
+
+
+ ))}
+ {filteredSources.length === 0 && (
+
+
+ No sources match the current filters.
+
+
+ )}
+
+
+
+
+ {filteredSources.map((source) => (
+
+
+
+
{source.name}
+
{source.bestFor}
+
{source.priority}
+
+
+ {SOURCE_CATEGORY_LABELS[source.category]}
+ {CADENCE_LABELS[source.cadence]}
+ {formatDateLabel(source.lastCheckedAt)}
+
+
+ openSourceEditor(source)}>Edit
+ void handleMarkSourceChecked(source)}>Mark checked
+ void handleToggleSource(source)}>
+ {source.active ? "Disable" : "Enable"}
+
+ openUrl(source.url)}>
+
+ Open
+
- );
- })
- ) : (
-
- Source Hub is ready. Your default source stack should appear here as soon as sync finishes.
+ ))}
+ {filteredSources.length === 0 && (
+
+ No sources match the current filters.
+
+ )}
- )}
-
-
-
-
-
- Qualification Queue
-
-
- {qualificationQueue.length > 0 ? (
- qualificationQueue.map((role) => {
- const company = companiesById.get(role.companyId);
- const sourceName =
- (role.savedSearchId
- ? sourcesById.get(savedSearchesById.get(role.savedSearchId)?.sourceId ?? "")
- : undefined)?.name ??
- (role.sourceId ? sourcesById.get(role.sourceId)?.name : undefined) ??
- "Manual capture";
-
- return (
- = 4
- ? "border-foreground/20 bg-neutral-100/70 dark:bg-neutral-900/60"
- : "border-border bg-background/80"
- }`}
- >
-
-
-
-
{role.title}
-
- {company?.name ?? "Unknown company"} · {sourceName}
-
-
-
-
Fit {role.fitScore}/5
-
- {DISCOVERY_STATUS_LABELS[role.discoveryStatus ?? "discovered"]}
+
+
+
+
+
+
+
+
+
+
+ setSearchesSearch(e.target.value)}
+ />
+
+
setSearchesPriorityFilter(v as JobSourcePriority | "all")}
+ >
+
+
+
+
+ All priorities
+ {SOURCE_PRIORITIES.map((p) => (
+ Priority {p}
+ ))}
+
+
+
+
+
+
+
+ All sources
+ {sources.map((s) => (
+ {s.name}
+ ))}
+
+
+
setSearchesActiveFilter(v as "all" | "active" | "inactive")}
+ >
+
+
+
+
+ All
+ Active
+ Inactive
+
+
+
+
+
+
+
+
+
+ Search name
+ Source
+ Target track
+ Priority
+ Cadence
+ Last checked
+ Actions
+
+
+
+ {filteredSavedSearches.map((savedSearch) => (
+
+ {savedSearch.name}
+ {sourcesById.get(savedSearch.sourceId)?.name ?? "Unknown source"}
+ {savedSearch.targetTrack}
+
+
+ {savedSearch.priority}
- {role.track}
-
-
-
-
- Notes:{" "}
- {role.qualificationNotes || "No qualification notes yet."}
-
-
-
Next step:{" "}
- {role.nextStep || "Clarify next move."}
+
+
{CADENCE_LABELS[savedSearch.cadence]}
+
{formatDateLabel(savedSearch.lastCheckedAt)}
+
+
+ openCapture({ sourceId: savedSearch.sourceId, savedSearchId: savedSearch.id, sourceUrl: savedSearch.url })}>Capture role
+ openSavedSearchEditor(savedSearch)}>Edit
+ void handleMarkSavedSearchChecked(savedSearch)}>Mark checked
+ void handleToggleSavedSearch(savedSearch)}>
+ {savedSearch.active ? "Disable" : "Enable"}
+
+
+
+ openUrl(savedSearch.url)} aria-label="Open search">
+
+
+
+ Open search
+
+
+
+
+ ))}
+ {filteredSavedSearches.length === 0 && (
+
+
+ No saved searches match the current filters.
+
+
+ )}
+
+
+
+
+ {filteredSavedSearches.map((savedSearch) => (
+
+
+
+
{savedSearch.name}
+
+ {sourcesById.get(savedSearch.sourceId)?.name ?? "Unknown source"} · {savedSearch.targetTrack}
-
-
- void handleQueueAction(
- role,
- { discoveryStatus: "qualified" },
- `${role.title} marked qualified`
- )
- }
- >
- Qualify
-
-
- void handleQueueAction(
- role,
- { discoveryStatus: "to_apply", status: "to_apply" },
- `${role.title} moved to apply queue`
- )
- }
- >
- Move to Apply Queue
-
-
- void handleQueueAction(
- role,
- { discoveryStatus: "archived", status: "closed" },
- `${role.title} archived`
- )
- }
- >
-
- Archive
-
-
openUrl(role.url)}
- >
- Open role URL
-
-
- Open CV Optimizer
-
-
+
{savedSearch.priority}
+
+
+ {CADENCE_LABELS[savedSearch.cadence]}
+ {formatDateLabel(savedSearch.lastCheckedAt)}
+
+
+ openCapture({ sourceId: savedSearch.sourceId, savedSearchId: savedSearch.id, sourceUrl: savedSearch.url })}>Capture role
+ openSavedSearchEditor(savedSearch)}>Edit
+ void handleMarkSavedSearchChecked(savedSearch)}>Mark checked
+ void handleToggleSavedSearch(savedSearch)}>
+ {savedSearch.active ? "Disable" : "Enable"}
+
+ openUrl(savedSearch.url)}>
+
+ Open
+
- );
- })
- ) : (
-
- Captured roles that are still worth qualifying will show up here.
-
- )}
-
-
-
-
-
-
- Source Library
- setAddSourceOpen(true)} className="gap-2">
-
- Add Source
-
-
-
-
-
-
-
- Source
- Category
- Best for
- Priority
- Cadence
- Last checked
- Actions
-
-
-
- {sources.map((source) => (
-
- {source.name}
- {SOURCE_CATEGORY_LABELS[source.category]}
- {source.bestFor}
-
-
- {source.priority}
-
-
- {CADENCE_LABELS[source.cadence]}
- {formatDateLabel(source.lastCheckedAt)}
-
-
- openUrl(source.url)}>
- Open
-
- void handleMarkSourceChecked(source)}>
- Mark checked
-
- openSourceEditor(source)}>
- Edit
-
- void handleToggleSource(source)}>
- {source.active ? "Disable" : "Enable"}
-
-
-
-
))}
-
-
-
-
- {sources.map((source) => (
-
-
-
-
{source.name}
-
{source.bestFor}
+ {filteredSavedSearches.length === 0 && (
+
+ No saved searches match the current filters.
-
- {source.priority}
-
-
-
- {SOURCE_CATEGORY_LABELS[source.category]}
- {CADENCE_LABELS[source.cadence]}
- {formatDateLabel(source.lastCheckedAt)}
-
-
- openUrl(source.url)}>
- Open
-
- void handleMarkSourceChecked(source)}>
- Mark checked
-
- openSourceEditor(source)}>
- Edit
-
- void handleToggleSource(source)}>
- {source.active ? "Disable" : "Enable"}
-
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ setQueueSearch(e.target.value)}
+ />
+
+
+
+
+
+ All fit scores
+ {[1, 2, 3, 4, 5].map((n) => (
+ Fit {n}/5
+ ))}
+
+
+
setQueueStatusFilter(v as DiscoveryStatus | "all")}
+ >
+
+
+
+
+ All statuses
+ {DISCOVERY_QUEUE_STATUSES.map((s) => (
+ {DISCOVERY_STATUS_LABELS[s]}
+ ))}
+
+
- ))}
-
-
-
-
-
-
- Saved Searches
-
-
-
-
-
-
- Search name
- Source
- Target track
- Priority
- Cadence
- Last checked
- Actions
-
-
-
- {savedSearches.map((savedSearch) => (
-
- {savedSearch.name}
-
- {sourcesById.get(savedSearch.sourceId)?.name ?? "Unknown source"}
-
- {savedSearch.targetTrack}
-
-
- {savedSearch.priority}
-
-
- {CADENCE_LABELS[savedSearch.cadence]}
- {formatDateLabel(savedSearch.lastCheckedAt)}
-
-
-
openUrl(savedSearch.url)}>
- Open
-
-
void handleMarkSavedSearchChecked(savedSearch)}
- >
- Mark checked
-
-
openCapture({ sourceId: savedSearch.sourceId, savedSearchId: savedSearch.id, sourceUrl: savedSearch.url })}>
- Capture role
-
-
openSavedSearchEditor(savedSearch)}>
- Edit
-
-
void handleToggleSavedSearch(savedSearch)}>
- {savedSearch.active ? "Disable" : "Enable"}
-
+
+
+ {filteredQueue.length > 0 ? (
+ filteredQueue.map((role) => {
+ const company = companiesById.get(role.companyId);
+ const sourceName =
+ (role.savedSearchId
+ ? sourcesById.get(savedSearchesById.get(role.savedSearchId)?.sourceId ?? "")
+ : undefined)?.name ??
+ (role.sourceId ? sourcesById.get(role.sourceId)?.name : undefined) ??
+ "Manual capture";
+
+ return (
+ = 4
+ ? "border-foreground/20 bg-neutral-100/70 dark:bg-neutral-900/60"
+ : "border-border bg-background/80"
+ }`}
+ >
+
+
+
+
{role.title}
+
+ {company?.name ?? "Unknown company"} · {sourceName}
+
+
+
+ Fit {role.fitScore}/5
+
+ {DISCOVERY_STATUS_LABELS[role.discoveryStatus ?? "discovered"]}
+
+ {role.track}
+
+
+
+
+ Notes:{" "}
+ {role.qualificationNotes || "No qualification notes yet."}
+
+
+ Next step:{" "}
+ {role.nextStep || "Clarify next move."}
+
+
+
+
+ void handleQueueAction(
+ role,
+ { discoveryStatus: "to_apply", status: "to_apply" },
+ `${role.title} moved to apply queue`
+ )
+ }
+ >
+ Move to apply queue
+
+
+ void handleQueueAction(
+ role,
+ { discoveryStatus: "qualified" },
+ `${role.title} marked qualified`
+ )
+ }
+ >
+ Qualify
+
+
+ void handleQueueAction(
+ role,
+ { discoveryStatus: "archived", status: "closed" },
+ `${role.title} archived`
+ )
+ }
+ >
+
+ Archive
+
+
+
+ openUrl(role.url)} aria-label="Open role URL">
+
+
+
+ Open role URL
+
+
+
+
+
+
+
+
+
+ Open CV Optimizer
+
+
-
-
- ))}
-
-
-
-
- {savedSearches.map((savedSearch) => (
-
-
-
-
{savedSearch.name}
-
- {sourcesById.get(savedSearch.sourceId)?.name ?? "Unknown source"} · {savedSearch.targetTrack}
-
-
- {savedSearch.priority}
-
+ );
+ })
+ ) : (
+
+ {qualificationQueue.length === 0
+ ? "Captured roles that are still worth qualifying will show up here."
+ : "No roles match the current filters."}
-
- {CADENCE_LABELS[savedSearch.cadence]}
- {formatDateLabel(savedSearch.lastCheckedAt)}
-
-
- openUrl(savedSearch.url)}>
- Open
-
- void handleMarkSavedSearchChecked(savedSearch)}>
- Mark checked
-
- openCapture({ sourceId: savedSearch.sourceId, savedSearchId: savedSearch.id, sourceUrl: savedSearch.url })}>
- Capture role
-
- openSavedSearchEditor(savedSearch)}>
- Edit
-
- void handleToggleSavedSearch(savedSearch)}>
- {savedSearch.active ? "Disable" : "Enable"}
-
-
-
- ))}
-
-
-
+ )}
+
+
+
+
diff --git a/tests/e2e/command-centre.spec.ts b/tests/e2e/command-centre.spec.ts
index c644366..f897c74 100644
--- a/tests/e2e/command-centre.spec.ts
+++ b/tests/e2e/command-centre.spec.ts
@@ -15,16 +15,13 @@ test.describe("Command Centre", () => {
await expect(page.getByText("Pipeline Snapshot")).toBeVisible();
});
- test("renders Quick Actions", async ({ page }) => {
- await expect(page.getByText("Quick Actions")).toBeVisible();
+ test("renders Weekly Execution panel", async ({ page }) => {
+ await expect(page.getByText("Weekly Execution")).toBeVisible();
});
- test("Quick Actions contains the expected actions", async ({ page }) => {
- // "Add Role" is now a modal-trigger button (not a nav link)
- await expect(page.getByRole("button", { name: /Add Role/i })).toBeVisible();
- await expect(page.getByRole("link", { name: /Log Application/i })).toBeVisible();
- await expect(page.getByRole("link", { name: /Tailor CV/i })).toBeVisible();
- await expect(page.getByRole("link", { name: /Browse Roles/i })).toBeVisible();
+ test("renders Pipeline Snapshot stats", async ({ page }) => {
+ await expect(page.getByText("To Apply")).toBeVisible();
+ await expect(page.getByText("Applied")).toBeVisible();
});
test("Probability Engine toggle expands the panel", async ({ page }) => {
diff --git a/tests/e2e/job-os-navigation.spec.ts b/tests/e2e/job-os-navigation.spec.ts
index 45bb797..f5e0dcd 100644
--- a/tests/e2e/job-os-navigation.spec.ts
+++ b/tests/e2e/job-os-navigation.spec.ts
@@ -8,7 +8,7 @@ test.describe("Job OS navigation", () => {
test("navigates to Companies page", async ({ page }) => {
await page.goto("/job-os/companies");
- await expect(page.getByText("Company Engine")).toBeVisible();
+ await expect(page.getByRole("heading", { name: "Companies", exact: true })).toBeVisible();
});
test("navigates to Roles page", async ({ page }) => {
@@ -24,7 +24,7 @@ test.describe("Job OS navigation", () => {
test("AppNavbar contains all top-level navigation links", async ({ page }) => {
await expect(page.getByRole("link", { name: "Action", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "Pipeline", exact: true })).toBeVisible();
- await expect(page.getByRole("link", { name: "System", exact: true })).toBeVisible();
+ await expect(page.getByRole("link", { name: "Companies & System", exact: true })).toBeVisible();
});
});