Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion apps/azure/src/components/ProcessHubReviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import type {
ProcessStateNote,
ResponsePathAction,
} from '@variscout/core';
import { ProcessHubCurrentStatePanel } from '@variscout/ui';
import { InboxDigest, ProcessHubCurrentStatePanel, type InboxDigestPrompt } from '@variscout/ui';
import { surveyInboxRules } from '@variscout/core/survey';
import ProcessHubCadenceQuestions from './ProcessHubCadenceQuestions';
import ProcessHubCadenceQueues from './ProcessHubCadenceQueues';
import { formatLatestActivity } from './ProcessHubFormat';
Expand Down Expand Up @@ -75,6 +76,22 @@ const ProcessHubReviewPanel: React.FC<ProcessHubReviewPanelProps> = ({
}) => {
const cadence = buildProcessHubCadence(rollup);
const currentState = buildCurrentProcessState(rollup, cadence);
const inboxPrompts = React.useMemo(
() =>
surveyInboxRules({
hub: rollup.hub,
improvementProjects: rollup.hub.improvementProjects ?? [],
sustainmentRecords: rollup.sustainmentRecords,
sustainmentReviews: rollup.hub.sustainmentReviews ?? [],
now: Date.now(),
}),
[
rollup.hub.id,
rollup.hub.improvementProjects,
rollup.hub.sustainmentReviews,
rollup.sustainmentRecords,
]
);

// Pick the most-recently-modified investigation in this hub as the
// default navigation target for hub-aggregate state items (capability-gap,
Expand All @@ -95,6 +112,40 @@ const ProcessHubReviewPanel: React.FC<ProcessHubReviewPanelProps> = ({
[defaultInvestigationId]
);

const handleInboxNavigate = React.useCallback(
(prompt: InboxDigestPrompt) => {
const surface = prompt.action?.opensSurface;
const targetId = prompt.action?.opensId;
const targetProject = rollup.hub.improvementProjects?.find(
project => project.id === targetId
);
if (surface === 'sustainment' && targetId) {
if (rollup.sustainmentRecords.some(record => record.id === targetId)) {
onLogReview(targetId);
return;
}
if (targetProject?.metadata.investigationId) {
onSetupSustainment(targetProject.metadata.investigationId);
return;
}
onOpenInvestigation(targetId);
return;
}
if (surface === 'improvement-projects' && targetId) {
onOpenInvestigation(targetProject?.metadata.investigationId ?? targetId);
return;
}
if (targetId) onOpenInvestigation(targetId);
},
[
onLogReview,
onOpenInvestigation,
onSetupSustainment,
rollup.hub.improvementProjects,
rollup.sustainmentRecords,
]
);

// Resolver: given a state item, return the investigation IDs whose findings
// should "back" it. Mirrors the spec's Investigation-ID resolver table.
//
Expand Down Expand Up @@ -180,6 +231,10 @@ const ProcessHubReviewPanel: React.FC<ProcessHubReviewPanelProps> = ({
</button>
</div>

<div className="mt-4">
<InboxDigest prompts={inboxPrompts} onNavigate={handleInboxNavigate} />
</div>

<ProcessHubCurrentStatePanel
state={currentState}
actions={{
Expand Down
5 changes: 5 additions & 0 deletions apps/azure/src/components/SustainmentRecordEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ const SustainmentRecordEditor: React.FC<SustainmentRecordEditorProps> = ({
const nowMs = Date.now();
const record: SustainmentRecord = {
id: existingRecord?.id ?? crypto.randomUUID(),
title: existingRecord?.title ?? 'Sustainment cadence',
investigationId,
hubId,
status: existingRecord?.status ?? 'pending',
consecutiveOnTargetTicks: existingRecord?.consecutiveOnTargetTicks ?? 0,
hasOverride: existingRecord?.hasOverride ?? false,
lastEvaluatedSnapshotId: existingRecord?.lastEvaluatedSnapshotId,
cadence,
nextReviewDue: nextReviewDue
? new Date(nextReviewDue + 'T00:00:00.000Z').toISOString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ function makeRecord(
): SustainmentRecord {
return {
id: `rec-${investigationId}`,
title: 'Sustainment cadence',
investigationId,
hubId: 'hub-1',
status: 'pending',
consecutiveOnTargetTicks: 0,
hasOverride: false,
lastEvaluatedSnapshotId: undefined,
cadence: 'monthly',
createdAt: 1735689600000, // 2026-01-01T00:00:00.000Z
updatedAt: 1735689600000, // 2026-01-01T00:00:00.000Z
Expand Down
15 changes: 15 additions & 0 deletions apps/azure/src/components/__tests__/SustainmentEditors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,13 @@ describe('SustainmentRecordEditor', () => {
it("preserves an existing record's next-review-due when cadence changes (treated as user-set)", () => {
const existingRecord: SustainmentRecord = {
id: 'rec-existing',
title: 'Sustainment cadence',
investigationId: 'inv-abc',
hubId: 'hub-1',
status: 'pending',
consecutiveOnTargetTicks: 0,
hasOverride: false,
lastEvaluatedSnapshotId: undefined,
cadence: 'monthly',
nextReviewDue: '2026-12-01T00:00:00.000Z',
createdAt: 1743465600000, // 2026-04-01T00:00:00.000Z
Expand Down Expand Up @@ -242,8 +247,13 @@ describe('SustainmentRecordEditor', () => {
describe('SustainmentReviewLogger', () => {
const baseRecord: SustainmentRecord = {
id: 'rec-1',
title: 'Sustainment cadence',
investigationId: 'inv-abc',
hubId: 'hub-1',
status: 'pending',
consecutiveOnTargetTicks: 0,
hasOverride: false,
lastEvaluatedSnapshotId: undefined,
cadence: 'monthly',
nextReviewDue: '2026-04-27T00:00:00.000Z',
createdAt: 1740787200000, // 2026-03-01T00:00:00.000Z
Expand Down Expand Up @@ -382,8 +392,13 @@ describe('ControlHandoffEditor', () => {
it('updates relatedRecord.controlHandoffId after saving the handoff', async () => {
const relatedRecord: SustainmentRecord = {
id: 'rec-1',
title: 'Sustainment cadence',
investigationId: 'inv-abc',
hubId: 'hub-1',
status: 'pending',
consecutiveOnTargetTicks: 0,
hasOverride: false,
lastEvaluatedSnapshotId: undefined,
cadence: 'monthly',
createdAt: 1740787200000, // 2026-03-01T00:00:00.000Z
updatedAt: 1740787200000,
Expand Down
Loading