feat(workspace): unified application checklist (Phase 5 milestone 2)
Evolve the existing readiness workflow into one persisted, user-controlled checklist rather than adding a second tracker. ApplicationChecklistItem records only completion state and user intent. Each default system item carries a stable SystemKey and an AutoSignal — the same signal /readiness already computed — and re-syncs on every read: a satisfied signal auto-completes the item, a reverted signal reopens it, and a manual tick always wins. Users can add, reorder, dismiss and delete. Readiness is refactored into a projection of the checklist (score = completion percentage, completed/missing = live items by status). Its DTO shape and the workflowSignal/reminders health view are unchanged, so no API contract breaks. The workspace's next recommended action now comes from the first pending checklist item in category priority order (preparation, submission, follow-up, interview, custom), replacing the parallel ruleset — so the overview can never recommend something already ticked off, and a user's own task can be next. The table follows the established MariaDB-safe path: the scaffolded migration is a no-op and the idempotent reconciler owns the DDL for both providers. Verified on MariaDB 11 — auto_increment PK, varchar/datetime(6)/tinyint(1) columns, both indexes inside the key limit, cascade delete, unique system key per application, and NULL system keys not colliding for custom items. 329 backend tests, 94 frontend tests, type check, production build and both Docker builds pass locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -858,6 +858,34 @@ public static class StartupInitializationExtensions
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_AiInteractions_Owner_Job_Module_Created" ON "AiInteractions" ("OwnerUserId", "JobApplicationId", "Module", "CreatedAtUtc");""");
|
||||
}
|
||||
|
||||
// Phase 5 Milestone 2: the application checklist (workflow guidance over readiness signals).
|
||||
static void EnsureApplicationChecklistTable(DbConnection c)
|
||||
{
|
||||
Exec(c, """
|
||||
CREATE TABLE IF NOT EXISTS "ApplicationChecklistItems" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_ApplicationChecklistItems" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"JobApplicationId" INTEGER NOT NULL,
|
||||
"SystemKey" TEXT NULL,
|
||||
"AutoSignal" TEXT NULL,
|
||||
"Title" TEXT NOT NULL,
|
||||
"Description" TEXT NULL,
|
||||
"Category" TEXT NOT NULL,
|
||||
"Status" TEXT NOT NULL,
|
||||
"Section" TEXT NULL,
|
||||
"SortOrder" INTEGER NOT NULL,
|
||||
"IsSystemGenerated" INTEGER NOT NULL,
|
||||
"IsAutoCompleted" INTEGER NOT NULL,
|
||||
"CompletedAt" TEXT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
"UpdatedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_ApplicationChecklistItems_JobApplications_JobApplicationId" FOREIGN KEY ("JobApplicationId") REFERENCES "JobApplications" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""");
|
||||
Exec(c, """CREATE UNIQUE INDEX IF NOT EXISTS "IX_ApplicationChecklistItems_JobApplicationId_SystemKey" ON "ApplicationChecklistItems" ("JobApplicationId", "SystemKey");""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_ApplicationChecklistItems_Owner_Job_Sort" ON "ApplicationChecklistItems" ("OwnerUserId", "JobApplicationId", "SortOrder");""");
|
||||
}
|
||||
|
||||
EnsureGmailConnectionsTable(conn);
|
||||
EnsureMicrosoftGraphConnectionsTable(conn);
|
||||
EnsureImapConnectionsTable(conn);
|
||||
@@ -870,6 +898,7 @@ public static class StartupInitializationExtensions
|
||||
EnsureAiWorkspaceNotesTable(conn);
|
||||
EnsureCvBuilderTables(conn);
|
||||
EnsureAiInteractionsTable(conn);
|
||||
EnsureApplicationChecklistTable(conn);
|
||||
|
||||
// Legacy DB signature: migration history exists (AddCorrespondence applied), but 20260310195000 not recorded,
|
||||
// and at least one of the new columns already exists.
|
||||
@@ -1351,6 +1380,7 @@ public static class StartupInitializationExtensions
|
||||
DropMalformedMySqlTable(conn, "CvVariantVersions", "CreatedAtUtc", "datetime");
|
||||
DropMalformedMySqlTable(conn, "CvVariants", "UpdatedAtUtc", "datetime");
|
||||
DropMalformedMySqlTable(conn, "AiInteractions", "CreatedAtUtc", "datetime");
|
||||
DropMalformedMySqlTable(conn, "ApplicationChecklistItems", "CreatedAtUtc", "datetime");
|
||||
|
||||
if (!HasMySqlTable(conn, "CvVariants"))
|
||||
{
|
||||
@@ -1408,6 +1438,33 @@ public static class StartupInitializationExtensions
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!HasMySqlTable(conn, "ApplicationChecklistItems"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS `ApplicationChecklistItems` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`OwnerUserId` varchar(255) NOT NULL,
|
||||
`JobApplicationId` int NOT NULL,
|
||||
`SystemKey` varchar(64) NULL,
|
||||
`AutoSignal` varchar(64) NULL,
|
||||
`Title` varchar(255) NOT NULL,
|
||||
`Description` longtext NULL,
|
||||
`Category` varchar(32) NOT NULL,
|
||||
`Status` varchar(32) NOT NULL,
|
||||
`Section` varchar(64) NULL,
|
||||
`SortOrder` int NOT NULL,
|
||||
`IsSystemGenerated` tinyint(1) NOT NULL,
|
||||
`IsAutoCompleted` tinyint(1) NOT NULL,
|
||||
`CompletedAt` datetime(6) NULL,
|
||||
`CreatedAtUtc` datetime(6) NOT NULL,
|
||||
`UpdatedAtUtc` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_ApplicationChecklistItems_JobApplications_JobApplicationId` FOREIGN KEY (`JobApplicationId`) REFERENCES `JobApplications` (`Id`) ON DELETE CASCADE
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "ApplicationChecklistItems", "Id");
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "CvVariants", "Id");
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "CvVariantVersions", "Id");
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "AiInteractions", "Id");
|
||||
@@ -1420,6 +1477,8 @@ public static class StartupInitializationExtensions
|
||||
("CvVariantVersions", "IX_CvVariantVersions_CvVariantId_Version", "`CvVariantId`, `Version`", false),
|
||||
("AiInteractions", "IX_AiInteractions_JobApplicationId", "`JobApplicationId`", false),
|
||||
("AiInteractions", "IX_AiInteractions_Owner_Job_Module_Created", "`OwnerUserId`, `JobApplicationId`, `Module`, `CreatedAtUtc`", false),
|
||||
("ApplicationChecklistItems", "IX_ApplicationChecklistItems_JobApplicationId_SystemKey", "`JobApplicationId`, `SystemKey`", true),
|
||||
("ApplicationChecklistItems", "IX_ApplicationChecklistItems_Owner_Job_Sort", "`OwnerUserId`, `JobApplicationId`, `SortOrder`", false),
|
||||
})
|
||||
{
|
||||
if (MySqlIndexExists(conn, ixTable, ixName)) continue;
|
||||
|
||||
Reference in New Issue
Block a user