Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
24 KiB
Scratchpad — Per-Phase Task Statuses
- Plan slug:
per-phase-task-statuses - Created:
2026-03-18
Decisions
- (2026-03-18) Fallback behavior:
phase_id IS NULLinproject_status_mappings= project-level defaults. Phases without custom statuses automatically fall back. No data migration needed. - (2026-03-18) Cross-phase task movement: same status name → keep it; no match + source open → first open by display_order; no match + source closed → first closed by display_order. Rationale: simple, predictable, avoids modal dialogs during drag-drop.
- (2026-03-18) Phase creation default: New phases inherit project defaults automatically (no rows needed — fallback handles it). User explicitly opts into custom statuses. Rationale: zero-cost setup, opt-in complexity.
- (2026-03-18) Template phases: Add
template_phase_idtoproject_template_status_mappings— same nullable FK pattern. Rationale: consistency with main table approach. - (2026-03-18) No feature flag: Feature is additive and opt-in. Until a user configures phase-specific statuses, behavior is identical to before.
Discoveries / Constraints
- (2026-03-18) Both
project_status_mappingsandproject_phasesare Citus-distributed ontenant, colocated withtenants. FK constraints must use composite keys(tenant, phase_id). - (2026-03-18)
project_status_mappingshas primary key(tenant, project_status_mapping_id). The FK toproject_phasesmust reference(tenant, phase_id). - (2026-03-18) Implemented
F001inserver/migrations/20260318100000_add_phase_id_to_project_status_mappings.cjs: added nullablephase_idplus a composite FK on(tenant, phase_id)toproject_phases. Although the feature text mentionsproject_phases(phase_id), the schema only exposes(tenant, phase_id)as a valid referenced key. - (2026-03-18) Implemented
F002in the same migration file by indexing(tenant, project_id, phase_id). Keeping the column and its lookup index together avoids ordering issues during rollout and rollback. - (2026-03-18) Implemented
F003inserver/migrations/20260318101000_add_template_phase_id_to_project_template_status_mappings.cjs: added nullabletemplate_phase_idwith a composite FK on(tenant, template_phase_id)becauseproject_template_phasesis also keyed that way. - (2026-03-18) Implemented
F004withee/server/migrations/citus/20260318102000_fix_phase_status_mapping_foreign_keys.cjsand marked the CE migrationstransaction: false. The EE migration inspects existing FK definitions and only drops/recreates phase-related constraints when they are not tenant-scoped, which is safer for already-distributed tables than assuming a fixed constraint name. - (2026-03-18)
moveTaskToPhase()inprojectTaskActions.ts(lines 972-1033) currently only resolves statuses for cross-project moves. Same-project moves preserve the original status mapping ID. This must change to also handle same-project cross-phase moves when phases have different statuses. - (2026-03-18)
ProjectDetail.tsxis the orchestrator that passesstatuses={projectStatuses}to KanbanBoard (line ~2406). It fetches statuses once at project level. This is the critical wiring point. - (2026-03-18) Client portal has its own separate components (
ClientKanbanBoard,ClientTaskListView,ProjectDetailView) — they do NOT reuse MSP-side KanbanBoard. Separate data actiongetClientProjectStatuses(). - (2026-03-18) Status change events (
PROJECT_STATUS_ADDED, etc.) are published fromprojectTaskStatusActions.ts. They currently includeprojectIdbut notphaseId. - (2026-03-18)
ProjectTaskStatusEditor.tsxandProjectTaskStatusSelector.tsxare used during project creation/editing. They may need to handle template-based phase statuses. - (2026-03-18) Phase task CSV import (
IImportReferenceData.statusMappings) resolves statuses at project level. Needs to resolve against phase-effective statuses. - (2026-03-18) The
display_orderfield's uniqueness scope changes from per-project to per-(project, phase). Need to ensure reordering logic scopes correctly. - (2026-03-18)
TB01is implemented as a migration contract test against migration source files rather than a live schema migration run. This keeps coverage fast while still locking down nullable columns, composite FK wiring, additive behavior, and the EE Citus companion repair.
Progress Log
- (2026-03-18)
TB01complete. Addedserver/src/test/unit/migrations/perPhaseTaskStatusesMigration.contract.test.tsto assert nullablephase_id/template_phase_id, additive migration behavior, CE phase index creation, and EE composite FK repair inputs. Verification:cd server && npx vitest run src/test/unit/migrations/perPhaseTaskStatusesMigration.contract.test.ts. - (2026-03-18)
TB02complete. Addedserver/src/test/unit/interfaces/projectStatusPhaseId.contract.test.tsto lock the shared and serverIProjectStatusMapping/ProjectStatuscontracts to an optionalphase_idfield. Verification:cd server && npx vitest run src/test/unit/interfaces/projectStatusPhaseId.contract.test.ts. - (2026-03-18)
TB03complete. Addedpackages/projects/src/models/project.phaseStatusResolution.test.tswith an in-memory Knex-like query harness to exercisegetProjectStatusMappings,getEffectiveStatusMappings, andgetProjectTaskStatusesacross phase-specific overrides, fallback-to-default behavior, ordered results, and tenant isolation. Verification:cd packages/projects && npx vitest run src/models/project.phaseStatusResolution.test.ts. - (2026-03-18)
TB04complete. Addedpackages/projects/src/actions/projectPhaseStatusActions.contract.test.tsto pin the action-layer phase plumbing inprojectTaskStatusActions.tsandprojectActions.ts: phase-aware insert scope, phase/default filtering, phase-scoped reordering, and threadingphaseIdinto effective status resolution. Verification:cd packages/projects && npx vitest run src/actions/projectPhaseStatusActions.contract.test.ts. - (2026-03-18)
TB04uses source-contract coverage rather than importing the action modules directly because the package-level Vitest config does not resolve server-only workspace packages like@alga-psa/db. The assertions target the concrete query branches and call sites that implement the phase behavior. - (2026-03-18)
TB05complete. Addedpackages/projects/src/actions/projectPhaseStatusCopyRemove.contract.test.tsto lock the copy/remove phase-status flows: cloning default mappings into a phase with preserved fields and remappingproject_tasksto default mappings before deleting custom phase mappings. Verification:cd packages/projects && npx vitest run src/actions/projectPhaseStatusCopyRemove.contract.test.ts. - (2026-03-18)
TB06complete. Addedpackages/projects/src/actions/projectTaskMovePhaseStatus.contract.test.tsto pin the same-project status remapping helper andmoveTaskToPhasebranches: exact-name reuse, open/closed fallback selection, fallback-to-first behavior, and preservation of the existing cross-project remapping path. Verification:cd packages/projects && npx vitest run src/actions/projectTaskMovePhaseStatus.contract.test.ts. - (2026-03-18)
TB07complete. Addedpackages/projects/src/components/phaseAwareProjectDetail.contract.test.tsto pin phase-driven status refetching inProjectDetail, phase-effective kanban/status-count derivations,KanbanBoardcolumn rendering fromstatusesprops, and phase-scoped status fetching inTaskEdit/TaskQuickAdd. Verification:cd packages/projects && npx vitest run src/components/phaseAwareProjectDetail.contract.test.ts. - (2026-03-18)
TB08complete. Addedpackages/projects/src/components/settings/projects/projectTaskStatusSettingsPhase.contract.test.tsto pin the status-scope selector, default/custom toggles, copy/revert flows, phase-awareAddStatusDialogwiring, and the last-status deletion guard enforced byprojectTaskStatusActions.ts. Verification:cd packages/projects && npx vitest run src/components/settings/projects/projectTaskStatusSettingsPhase.contract.test.ts. - (2026-03-18)
TB09complete. Addedpackages/client-portal/src/components/projects/clientPhaseStatuses.contract.test.tsto pin client-portal phase-aware status resolution, phase-triggered refetching inProjectDetailView, kanban column ordering from the selected phase’s statuses, and list-view grouping by effective status labels. Verification:cd packages/client-portal && npx vitest run src/components/projects/clientPhaseStatuses.contract.test.ts. - (2026-03-18) Running the
packages/client-portalVitest target prints an unrelated coverage parser warning forsrc/actions/client-portal-actions/client-billing.ts, but theTB09test run still exits successfully with status code0. - (2026-03-18)
TB10complete. Addedpackages/projects/src/components/project-templates/templatePhaseStatuses.contract.test.tsto pintemplate_phase_idtyping, phase-aware template status scope selection inTemplateStatusManagerandTemplateStatusColumnsStep, and copying template phase-scoped mappings intoproject_status_mappingsduring project creation. Verification:cd packages/projects && npx vitest run src/components/project-templates/templatePhaseStatuses.contract.test.ts. - (2026-03-18)
TB11complete. Addedpackages/projects/src/supportingSystemsPhaseStatuses.contract.test.tsto pin phase-aware CSV import lookup generation,phaseIdpresence in project-status event payloads, andProjectTaskStatusEditor/ProjectTaskStatusSelectorhandling of phase-scoped status selections during project/template setup. Verification:cd packages/projects && npx vitest run src/supportingSystemsPhaseStatuses.contract.test.ts. - (2026-03-18)
TB12complete. Addedpackages/projects/src/backwardCompatibilityPhaseStatuses.contract.test.tsto pin additive migration behavior, model fallback to project defaults when no phase overrides exist, default-scope behavior in the settings UI, and cascade FK cleanup for deleted phases. Verification:cd packages/projects && npx vitest run src/backwardCompatibilityPhaseStatuses.contract.test.ts.
Commands / Runbooks
- Build shared packages:
npm run build:shared(needed after type changes) - Build projects package:
npx nx build projects - Run project tests:
npx vitest runinpackages/projects/ - Run migrations:
npm run migrate - Validate migration syntax quickly:
node -e "require('./server/migrations/20260318100000_add_phase_id_to_project_status_mappings.cjs')" - Validate template migration syntax quickly:
node -e "require('./server/migrations/20260318101000_add_template_phase_id_to_project_template_status_mappings.cjs')" - Validate EE Citus migration syntax quickly:
node -e "require('./ee/server/migrations/citus/20260318102000_fix_phase_status_mapping_foreign_keys.cjs')" - Citus migrations: applied via Argo workflows in EE environments
Links / References
Key Files — Database
server/migrations/20241008191930_create_project_status_mappings_table.cjs— original table creationee/server/migrations/citus/20250805000018_distribute_remaining_tables.cjs— Citus distribution of project_status_mappingsee/server/migrations/citus/20250805000011_distribute_project_tables.cjs— Citus distribution of project_phasesserver/migrations/20251119000000_add_project_templates.cjs— template status mappings table
Key Files — Types
-
packages/types/src/interfaces/project.interfaces.ts— IProjectStatusMapping (L37-50), ProjectStatus (L151-165) -
server/src/interfaces/project.interfaces.ts— duplicate interfaces -
(2026-03-18) Implemented
F005by addingphase_id?: stringtoIProjectStatusMappingin both shared and server-local interface copies. The duplicate interface files still need to stay in sync manually. -
(2026-03-18) Implemented
F006by addingphase_id?: stringtoProjectStatusin the same two interface files so the flattened status DTO can carry its scope through actions and UI props.
Key Files — Models & Actions
-
packages/projects/src/models/project.ts— getProjectStatusMappings, getProjectTaskStatuses, addProjectStatusMapping -
packages/projects/src/actions/projectTaskStatusActions.ts— addStatusToProject, getProjectStatusMappings, reorderProjectStatuses -
packages/projects/src/actions/projectActions.ts— getProjectTaskStatuses (L1123-1134) -
packages/projects/src/actions/projectTaskActions.ts— moveTaskToPhase (L938-1120), updateTaskStatus -
(2026-03-18) Implemented
F007by changingProjectModel.getProjectStatusMappings()to acceptphaseId?: string | null; omitted/null now explicitly means project defaults (phase_id IS NULL) rather than “all mappings for the project.” That keeps legacy callers stable once phase-specific rows exist. -
(2026-03-18) Implemented
F008withProjectModel.getEffectiveStatusMappings(), which first looks for phase-scoped mappings and only falls back to project defaults when none exist. This keeps callers from having to manually merge or filter both scopes. -
(2026-03-18) Implemented
F009by threadingphaseIdthroughProjectModel.getProjectTaskStatuses()and copyingmapping.phase_idonto the returned status objects. Model callers can now request the effective status list for a specific phase without reproducing fallback logic. -
(2026-03-18) Implemented
F010by extendingaddStatusToProject()with optionalphaseId, storingphase_idon insert, and scoping thedisplay_orderlookup to either the target phase or project defaults. This prevents new phase statuses from inheriting order positions from unrelated scopes. -
(2026-03-18) Implemented
F011by extending the action-layergetProjectStatusMappings()with the same scope rule as the model:phaseIdfetches that phase’s rows, and nophaseIdfetches only project defaults. That avoids leaking all phase rows into the existing settings UI. -
(2026-03-18) Implemented
F012by extendingreorderProjectStatuses()with optionalphaseIdand constraining each update to the matching phase/default scope. This protects phase-specific boards from reordering the wrong mapping set. -
(2026-03-18) Implemented
F013by addingcopyProjectStatusesToPhase(projectId, phaseId). It validates phase ownership, treats an already-customized phase as idempotent, and copies only project-default mappings (phase_id IS NULL) into the phase while preserving ordering, visibility, and status references. -
(2026-03-18) Implemented
F014by addingremovePhaseStatuses(phaseId)and remapping affected tasks before deletion. The replacement rule already follows the PRD’s status-resolution order: same name first, then first target status with the same open/closed state, then final fallback to the first default status. -
(2026-03-18) Implemented
F015by extending the publicgetProjectTaskStatuses()action and its internal helpers with optionalphaseId, switching them ontoProjectModel.getEffectiveStatusMappings(), and carryingphase_idinto the returnedProjectStatusDTOs. -
(2026-03-18) Implemented
F016inmoveTaskToPhase()by splitting same-project cross-phase moves from the existing cross-project branch, fetching target phase-effective mappings, and preserving intent via same-name matches when the original mapping ID is not valid in the destination phase. -
(2026-03-18) Implemented
F017by upgrading the same-project phase move fallback: if no same-name mapping exists and the source status is open, the task now lands in the first open target status bydisplay_orderinstead of the first arbitrary column. -
(2026-03-18) Implemented
F018by making the fallback symmetric for closed work: if a closed task has no same-name match in the destination phase,moveTaskToPhase()now selects the first closed target status before falling back to the first overall column. -
(2026-03-18) Implemented
F019by leaving the original cross-project move branch intact and isolating the new remapping logic to same-project cross-phase moves only. This keeps legacy project-to-project behavior stable while enabling phase-aware remapping where the PRD requires it.
Key Files — MSP UI
-
packages/projects/src/components/ProjectDetail.tsx— orchestrator, passes statuses to KanbanBoard (L2401-2406), phase selection (L186), task filtering (L346-425) -
packages/projects/src/components/KanbanBoard.tsx— receives statuses as prop -
packages/projects/src/components/TaskStatusSelect.tsx— status dropdown -
packages/projects/src/components/settings/projects/ProjectTaskStatusSettings.tsx— status config UI -
packages/projects/src/components/settings/projects/AddStatusDialog.tsx— add status dialog -
packages/projects/src/components/ProjectTaskStatusEditor.tsx— project creation status setup -
packages/projects/src/components/ProjectTaskStatusSelector.tsx— status selection for project setup -
(2026-03-18) Implemented
F021by makingProjectDetail.tsxrefetchgetProjectTaskStatuses(projectId, selectedPhase.phase_id)whenever the selected phase changes, while resetting to the initial project-level statuses if no phase is selected. -
(2026-03-18) Implemented
F022by deriving kanban counts from a lookup of the currently effective phase statuses, sorting visible columns bydisplay_order, and ignoring tasks whose mapping IDs are not part of the selected phase’s effective status set. -
(2026-03-18) Implemented
F023with a minimal prop-boundary change:ProjectDetail.tsxnow passesvisibleKanbanStatusesintoKanbanBoard, so the board renders from the selected phase’s effective status columns directly. -
(2026-03-18) Implemented
F024by makingTaskEditandTaskQuickAddfetch phase-effective statuses for their active phase, including same-project phase switches. That ensuresTaskStatusSelectreceives the correct status list via props even when the editor phase differs from the surrounding view state. -
(2026-03-18) Implemented
F025inProjectTaskStatusSettingswith a scope selector at the top of the panel.Project Defaultsis the first option, followed by each project phase loaded fromgetProjectMetadata(projectId). -
(2026-03-18) Implemented
F026with phase-level mode controls inProjectTaskStatusSettings: each selected phase now showsUse project defaultsversusCustom statuses, with control enablement derived from whether that phase currently has custom mappings. -
(2026-03-18) Implemented
F027by wiring the phase-default state to aCopy from project defaultsaction that callscopyProjectStatusesToPhase(projectId, phaseId)and reloads the scoped mappings after success. -
(2026-03-18) Implemented
F028by making theUse project defaultspath confirm before callingremovePhaseStatuses(phaseId), then reloading the phase scope so the UI falls back to the project-level mappings. -
(2026-03-18) Implemented
F029by extendingAddStatusDialogwith optionalphaseIdand passing that through toaddStatusToProject(projectId, statusData, phaseId), so a phase can start custom status configuration without copying the defaults first. -
(2026-03-18) Implemented
F030by separating client-portal phase loading from status loading inProjectDetailView. Statuses are now refetched withgetClientProjectStatuses(projectId, selectedPhaseId)whenever the selected phase changes. -
(2026-03-18) Implemented
F031with a minimal client-kanban boundary change:ClientKanbanBoardnow sorts and renders columns directly from the phase-specificstatusesprop supplied byProjectDetailView. -
(2026-03-18) Implemented
F032by having client task rows carry effective status metadata fromproject_status_mappingsand groupingClientTaskListViewbycustom_name || status_namein mappingdisplay_order. This keeps each phase’s list view aligned with its effective status columns.
Key Files — Client Portal
-
packages/client-portal/src/components/projects/ProjectDetailView.tsx— orchestrator -
packages/client-portal/src/components/projects/ClientKanbanBoard.tsx— kanban view -
packages/client-portal/src/components/projects/ClientTaskListView.tsx— list view -
packages/client-portal/src/actions/client-portal-actions/client-project-details.ts— getClientProjectStatuses -
(2026-03-18) Implemented
F020by extendinggetClientProjectStatuses()with optionalphaseIdand the same “phase first, else project defaults” lookup pattern used on the MSP side. The query now also coalesces custom and standard statuses so client portal reads remain backward compatible.
Key Files — Templates
-
packages/projects/src/components/project-templates/TemplateStatusManager.tsx -
packages/projects/src/components/project-templates/wizard-steps/TemplateStatusColumnsStep.tsx -
(2026-03-18) Implemented
F033by threading optionaltemplate_phase_idinto the template status-mapping interfaces and wizard type definitions. The schema column fromF003is now representable in runtime objects and wizard state. -
(2026-03-18) Implemented
F034by adding a scope selector toTemplateStatusManagerwithTemplate Defaultsplus every template phase. The manager now resolves effective statuses per selected phase, can copy template defaults into a phase, can revert a phase back to template defaults, and scopes add/reorder operations bytemplate_phase_idinprojectTemplateActions.ts. -
(2026-03-18) Supporting work for
F034:createTemplateFromProject(),duplicateTemplate(), andapplyTemplate()now preserve phase-scoped template/project status mappings throughtemplate_phase_idandphase_idwhen those maps already exist. This keeps template editor scope changes coherent with later template application. -
(2026-03-18) Implemented
F035by makingTemplateStatusColumnsStepphase-aware in wizard state. The step now scopes mappings bytemplate_phase_id, supports copying defaults into a phase, uses defaults as fallback when a phase has no overrides, and clears stale task mappings when a scoped column is removed or reset. -
(2026-03-18) Supporting work for
F035:TemplateTasksStepandTemplateReviewStepnow resolve effective statuses per selected phase, andcreateTemplateFromWizard()/updateTemplateFromEditor()create phases before status mappings so phase-scoped wizard mappings can be persisted with realtemplate_phase_idvalues. -
(2026-03-18) Implemented
F036by finishing phase-aware template application inapplyTemplate(). Project status mappings copied from a template now retain the sourcetemplate_phase_id -> phase_idrelationship, and task fallback logic chooses the first effective status for the target phase instead of a project-global first column. -
(2026-03-18) Implemented
F037in the phase/task import pipeline.getImportReferenceData()and both validation paths now buildstatusLookupByPhasefrom each existing phase’s effective statuses,groupRowsIntoPhases()resolvesstatus_mapping_idagainst the row’s target phase, and the import dialog threads those phase-aware lookups through regrouping after agent resolution. -
(2026-03-18) Implemented
F038by adding optionalphaseIdto all published project-status mutation events inprojectTaskStatusActions.ts. Add, update, delete, and reorder events now preserve scope so downstream listeners can distinguish project defaults from phase-specific changes. -
(2026-03-18) Implemented
F039by makingProjectTaskStatusEditorandProjectTaskStatusSelectorphase-aware primitives. Both components now preserve optionalphaseId/phase_idscope when fetching, adding, deduplicating, and reordering statuses, so a template-driven project creation flow can pass phase-scoped statuses through the same UI without flattening them back to project defaults. -
(2026-03-18) Implemented
F040as a cross-cutting compatibility guarantee rather than a new code path. Every new phase-aware entry point now falls back tophase_id IS NULLproject defaults when no phase override exists, which preserves existing-project behavior until a phase is explicitly customized.
Key Files — Import / Events
packages/types/src/interfaces/phaseTaskImport.interfaces.ts— IImportReferenceData
Open Questions
- None currently — all design decisions agreed upon.