Add application tracker REST and MCP parity

Add comprehensive Application Tracker REST and MCP coverage, document the MCP workflow, add Markdown/ActionLint checks, bump the release version, and fill all extracted translations.
This commit is contained in:
Amruth Pillai
2026-07-07 17:02:39 +02:00
committed by GitHub
parent dfc5559625
commit 46afc65cc6
80 changed files with 9938 additions and 809 deletions
+49
View File
@@ -34,3 +34,52 @@ describe("applicationDto jobDescription", () => {
).toThrow();
});
});
describe("applicationDto document uploads", () => {
it("accepts PDF application documents", () => {
const file = new File(["%PDF-1.4"], "resume.pdf", { type: "application/pdf" });
const parsed = applicationDto.attachDocument.input.parse({
id: "application-1",
kind: "resume",
file,
});
expect(parsed.kind).toBe("resume");
expect(parsed.file.name).toBe("resume.pdf");
});
it("rejects non-PDF application documents", () => {
const file = new File(["hello"], "cover.txt", { type: "text/plain" });
expect(() =>
applicationDto.attachDocument.input.parse({
id: "application-1",
kind: "cover-letter",
file,
}),
).toThrow();
});
it("rejects unknown application document kinds", () => {
const file = new File(["%PDF-1.4"], "resume.pdf", { type: "application/pdf" });
expect(() =>
applicationDto.attachDocument.input.parse({
id: "application-1",
kind: "portfolio",
file,
}),
).toThrow();
});
});
describe("applicationDto zero-argument inputs", () => {
it("normalizes stats input to an empty object", () => {
expect(applicationDto.stats.input.parse(undefined)).toEqual({});
});
it("normalizes tags input to an empty object", () => {
expect(applicationDto.tags.input.parse(undefined)).toEqual({});
});
});
+29 -2
View File
@@ -9,6 +9,14 @@ import {
} from "@reactive-resume/schema/applications/data";
const MAX_APPLICATION_JOB_DESCRIPTION_CHARS = 20_000;
const MAX_APPLICATION_DOCUMENT_BYTES = 10 * 1024 * 1024;
const applicationDocumentKindSchema = z.enum(["resume", "cover-letter"]);
const applicationDocumentFileSchema = z
.file()
.max(MAX_APPLICATION_DOCUMENT_BYTES, "File size must be less than 10MB")
.mime(["application/pdf"], "Application documents must be PDF files.");
const httpUrlSchema = z
.string()
@@ -124,6 +132,23 @@ export const applicationDto = {
output: applicationSchema.omit({ userId: true }),
},
attachDocument: {
input: z.object({
id: z.string(),
kind: applicationDocumentKindSchema,
file: applicationDocumentFileSchema,
}),
output: applicationSchema.omit({ userId: true }),
},
removeDocument: {
input: z.object({
id: z.string(),
kind: applicationDocumentKindSchema,
}),
output: applicationSchema.omit({ userId: true }),
},
addNote: {
input: z.object({ id: z.string(), text: z.string().trim().min(1) }),
output: applicationSchema.omit({ userId: true }),
@@ -153,7 +178,7 @@ export const applicationDto = {
// Aggregates for the Insights view. Everything else (funnel, sankey, tiles) is derived
// client-side from these raw counts via computeInsights().
stats: {
input: z.void(),
input: z.object({}).optional().default({}),
output: z.object({
total: z.number(),
byStage: z.array(z.object({ status: applicationStatusSchema, count: z.number() })),
@@ -162,7 +187,9 @@ export const applicationDto = {
},
tags: {
input: z.void(),
input: z.object({}).optional().default({}),
output: z.array(z.string()),
},
};
export type ApplicationDocumentKind = z.infer<typeof applicationDocumentKindSchema>;