feat(ai): AI Workspace panel on every job application
CI and Deploy / test (push) Failing after 1m54s
CI and Deploy / deploy (push) Has been skipped

Phase 5 frontend. A new "AI Workspace" tab in the job details dialog hosts the
five suggestion modules (Job Analysis, Career Match, Cover Letter with tone,
Interview Prep, Application Review) with a generate flow, a dependency-free
markdown renderer for results, and a history sidebar (reuse / compare / copy /
delete). Everything is suggestion-only — copy to keep; nothing auto-applies.

- aiWorkspace.ts (types + API), components/AiWorkspacePanel.tsx,
  components/Markdown.tsx (no HTML injection surface — renders React nodes)
- mounted as the last tab in JobDetailsDialog (index-safe, no reindexing)
- 3 tests (generate flow, cover-letter mode, markdown); tsc + build clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-18 15:23:23 +02:00
parent f299d7be7c
commit bb0c0feb4c
5 changed files with 347 additions and 0 deletions
@@ -28,6 +28,7 @@ import { emptyTailoredCvDraft, joinLines, normalizeTailoredCvDraft, splitLines }
import Correspondence from "./Correspondence";
import Attachments from "./Attachments";
import AiWorkspacePanel from "./AiWorkspacePanel";
import JobFlowBar from "./JobFlowBar";
import GradientButton from "./GradientButton";
import { useI18n } from "../i18n/I18nProvider";
@@ -697,6 +698,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
<Tab label={t("jobDetailsTabInterviewPrep")} />
<Tab label={t("jobTableReadiness")} />
{isAdmin ? <Tab label={t("jobDetailsTabHistory")} /> : null}
<Tab label="AI Workspace" />
</Tabs>
{attachmentPicker}
@@ -1270,6 +1272,8 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
{history.length === 0 ? <Typography sx={{ color: "text.secondary" }}>{t("jobDetailsNoHistory")}</Typography> : history.map((entry) => <PaperRow key={entry.id} type={entry.type} oldValue={entry.oldValue} newValue={entry.newValue} at={entry.at} note={entry.note} />)}
</Box>
)}
{/* AI Workspace is the last tab: index depends on whether the admin History tab is present. */}
{tab === (isAdmin ? 10 : 9) && jobId && <AiWorkspacePanel jobId={jobId} />}
</DialogContent>
</Dialog>
);