Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac18e77
docs(plans): bite-sized sub-plan for PR-WV1-2 Improve workspace migra…
jukka-matti May 16, 2026
076e4e4
refactor(ui): route IPDetailPage + Charter ACL through canAccess
jukka-matti May 16, 2026
2c8bfbd
docs(ui): explain CharterOverview empty-members escape
jukka-matti May 16, 2026
7a900d5
feat(core): add migrateImprovementProjectMetadata + rename Handoff st…
jukka-matti May 16, 2026
3924e1b
feat(ui,apps): wire stage rename + metadata migration into hydration …
jukka-matti May 16, 2026
2e14427
feat(ui): add ImproveStage with canAccess-gated ActionItem tracker
jukka-matti May 16, 2026
c081fda
feat(ui): add ImproveStageAdvanced mounting PDCA primitives
jukka-matti May 16, 2026
99c2bfb
feat(ui): add Advanced toggle on ImproveStage
jukka-matti May 16, 2026
20c5f32
refactor(ui): fold Handoff close-logic into Sustainment closure
jukka-matti May 16, 2026
0750b55
docs(specs): wedge V1 amendment — Improve top-level tab + Project sin…
jukka-matti May 16, 2026
b9920e3
docs(plans): bite-sized amendment plan for Improve-tab restoration
jukka-matti May 16, 2026
9f8e996
refactor(ui): trim StageName to 3 values — drop 'improve' stage per a…
jukka-matti May 16, 2026
b234fd6
refactor(ui): remove ImproveStage routing from IPDetailPage per amend…
jukka-matti May 16, 2026
94e379f
feat(ui): add NoActiveProjectGuidance empty state for Improve tab
jukka-matti May 16, 2026
ccaa81c
refactor(ui): move ImproveStage to Improve/; retire ImproveStageAdvan…
jukka-matti May 16, 2026
eb15d04
feat(ui): add ImproveTabRoot orchestration component
jukka-matti May 16, 2026
9db0a03
feat(apps): wire ImproveTabRoot into PWA + Azure Improve tab
jukka-matti May 16, 2026
84eb86b
fix(apps): route Azure Improve tab through ImproveTabRoot + tighten P…
jukka-matti May 16, 2026
5e8adfa
refactor(i18n): rename workspace.projects to workspace.project (singu…
jukka-matti May 16, 2026
a6bed5c
docs(wedge): log PR-WV1-2 delivery + Improve-tab amendment
jukka-matti May 16, 2026
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
12 changes: 5 additions & 7 deletions apps/azure/src/components/ProjectsTabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IPDetailPage } from '@variscout/ui/ipDetail';
import type {
CauseProjectionInputs,
CauseRow,
HandoffChecklistInputs,
SustainmentClosureInputs,
} from '@variscout/ui/ipDetail';

interface ProjectsTabViewProps {
Expand All @@ -22,9 +22,9 @@ interface ProjectsTabViewProps {
onOpenCauseWorkbench?: (cause: CauseRow) => void;
sustainmentRecord?: SustainmentRecord;
controlHandoff?: ControlHandoff;
handoffInputs?: HandoffChecklistInputs;
/** Closure checklist derived from controlHandoff (folded in from former Handoff stage). */
closureInputs?: SustainmentClosureInputs;
onOpenLegacySustainment?: () => void;
onOpenLegacyHandoff?: () => void;
onNudgeProcessOwner?: () => void;
onProjectPatch?: (
projectId: ImprovementProject['id'],
Expand Down Expand Up @@ -86,9 +86,8 @@ const ProjectsTabView: React.FC<ProjectsTabViewProps> = ({
onOpenCauseWorkbench,
sustainmentRecord,
controlHandoff,
handoffInputs,
closureInputs,
onOpenLegacySustainment,
onOpenLegacyHandoff,
onNudgeProcessOwner,
onProjectPatch,
onNudgeSignoff,
Expand Down Expand Up @@ -149,9 +148,8 @@ const ProjectsTabView: React.FC<ProjectsTabViewProps> = ({
onOpenCauseWorkbench={onOpenCauseWorkbench}
sustainmentRecord={sustainmentRecord}
controlHandoff={controlHandoff}
handoffInputs={handoffInputs}
closureInputs={closureInputs}
onOpenLegacySustainment={onOpenLegacySustainment}
onOpenLegacyHandoff={onOpenLegacyHandoff}
onNudgeProcessOwner={onNudgeProcessOwner}
activeHub={activeHub}
ideas={approachInputs?.ideas}
Expand Down
225 changes: 22 additions & 203 deletions apps/azure/src/pages/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,16 @@ import { AppHeader } from '../components/AppHeader';
import PasteScreen from '../components/data/PasteScreen';
import ManualEntry from '../components/data/ManualEntry';
import {
ImprovementWorkspaceBase,
ImprovementContextPanel,
WhatIfExplorerPage,
PrioritizationMatrix,
TrackView,
VerificationPrompt,
BrainstormModal,
QuestionLinkPrompt,
SurveyNotebookBase,
DEFAULT_PRESETS,
type ColumnMappingConfirmPayload,
type MatrixDimension,
StageFiveModal,
MatchSummaryCard,
ActiveIPLaunchpadCard,
ActiveIPScopeRibbon,
ImproveTabRoot,
deriveActiveIPCanvasFocus,
deriveActiveIPLineageIds,
deriveActiveIPScopeLabels,
Expand Down Expand Up @@ -621,7 +615,7 @@ export const Editor: React.FC<EditorProps> = ({
const projectsControlHandoff = _azureLiveControlHandoffs.find(
h => h.investigationId === (projectsSustainmentRecord?.investigationId ?? '')
);
const projectsHandoffInputs = projectsControlHandoff
const projectsClosureInputs = projectsControlHandoff
? {
controlPlanDocumented: false,
trainingDelivered: Boolean(projectsControlHandoff.signoff?.approvedBy),
Expand Down Expand Up @@ -1147,26 +1141,11 @@ export const Editor: React.FC<EditorProps> = ({
// Improvement workspace
const {
handleConvertIdeasToActions,
handleOpenImprovementPopout,
handleSynthesisChange,
causeColors,
causeLabels,
causeSummaries,
matrixIdeas,
aggregatedActions,
selectedIdeasForRecap,
projectionReferenceContext,
verificationData: improvVerificationData,
hasVerification: improvHasVerification,
currentOutcome: improvCurrentOutcome,
outcomeNotes: improvOutcomeNotes,
handleOutcomeChange: improvHandleOutcomeChange,
handleOutcomeNotesChange: improvHandleOutcomeNotesChange,
improvementQuestions,
improvementLinkedFindings,
selectedIdeaIds,
projectedCpkMap: improvementProjectedCpkMap,
convertedIdeaIds,
} = useImprovementOrchestration({
questionsState,
findingsState,
Expand All @@ -1178,8 +1157,6 @@ export const Editor: React.FC<EditorProps> = ({
specs,
stagedStats,
});
const activeImprovementView = usePanelsStore(s => s.activeImprovementView);
const highlightedIdeaId = usePanelsStore(s => s.highlightedIdeaId);
const scopedFindings = useMemo(
() =>
activeIPContext.isIPScoped
Expand Down Expand Up @@ -1212,13 +1189,6 @@ export const Editor: React.FC<EditorProps> = ({
: questionsState.questions,
[questionsState.questions, scopedQuestionIds]
);
const scopedImprovementQuestions = useMemo(
() =>
scopedQuestionIds
? improvementQuestions.filter(question => scopedQuestionIds.has(question.id))
: improvementQuestions,
[improvementQuestions, scopedQuestionIds]
);
const scopedQuestionsState = useMemo(
() => (scopedQuestionIds ? { ...questionsState, questions: scopedQuestions } : questionsState),
[questionsState, scopedQuestionIds, scopedQuestions]
Expand Down Expand Up @@ -1255,12 +1225,6 @@ export const Editor: React.FC<EditorProps> = ({
// Verification prompt: show when new data is uploaded while findings are improving
const [showVerificationPrompt, setShowVerificationPrompt] = useState(false);

// Matrix axis state (local — not persisted)
const [matrixXAxis, setMatrixXAxis] = useState<MatrixDimension>('benefit');
const [matrixYAxis, setMatrixYAxis] = useState<MatrixDimension>('timeframe');
const [matrixColorBy, setMatrixColorBy] = useState<MatrixDimension>('cost');
const [matrixPreset, setMatrixPreset] = useState<string>('benefit-time');

// Brainstorm modal state
const [brainstormQuestionId, setBrainstormQuestionId] = useState<string | null>(null);
const brainstormQuestion = improvementQuestions.find(q => q.id === brainstormQuestionId);
Expand Down Expand Up @@ -1886,17 +1850,12 @@ export const Editor: React.FC<EditorProps> = ({
}}
sustainmentRecord={projectsSustainmentRecord}
controlHandoff={projectsControlHandoff}
handoffInputs={projectsHandoffInputs}
closureInputs={projectsClosureInputs}
onOpenLegacySustainment={() =>
usePanelsStore
.getState()
.showSustainment(projectsSustainmentRecord?.investigationId ?? undefined)
}
onOpenLegacyHandoff={() =>
usePanelsStore
.getState()
.showHandoff(projectsControlHandoff?.investigationId ?? undefined)
}
onNudgeProcessOwner={() => {
// Plan 3 will emit EngagementEvent webhook here.
console.info('[handoff] Nudge process owner — Plan 3 will wire EngagementEvent');
Expand All @@ -1920,165 +1879,25 @@ export const Editor: React.FC<EditorProps> = ({
currentUserId={currentUser?.email ?? undefined}
/>
) : activeView === 'improvement' ? (
<div className="flex min-h-0 flex-1 flex-col">
{activeIPScope ? (
<ActiveIPScopeRibbon
title={activeIPScope.title}
labels={activeIPScope.labels}
surface="Improve"
/>
) : null}
<ImprovementWorkspaceBase
synthesis={processContext?.synthesis}
onSynthesisChange={handleSynthesisChange}
questions={scopedImprovementQuestions}
linkedFindings={
activeIPContext.isIPScoped ? scopedFindings : improvementLinkedFindings
}
onToggleSelect={(hId, iId, sel) => questionsState.selectIdea(hId, iId, sel)}
onUpdateTimeframe={(hId, iId, timeframe) =>
questionsState.updateIdea(hId, iId, { timeframe })
}
onUpdateDirection={(hId, iId, dir) =>
questionsState.updateIdea(hId, iId, { direction: dir })
}
onUpdateCost={(hId, iId, cost) => questionsState.updateIdea(hId, iId, { cost })}
onOpenRisk={() => {}}
onRemoveIdea={questionsState.removeIdea}
onOpenWhatIf={(questionId, ideaId) => handleProjectIdea(questionId, ideaId, true)}
onAddIdea={(hId, text) => questionsState.addIdea(hId, text)}
onAskCoScout={aiOrch.handleAskCoScoutFromIdeas}
onConvertToActions={() => {
handleConvertIdeasToActions();
usePanelsStore.getState().setActiveImprovementView('track');
}}
onBack={() => usePanelsStore.getState().showAnalysis()}
onPopout={handleOpenImprovementPopout}
selectedIdeaIds={selectedIdeaIds}
convertedIdeaIds={convertedIdeaIds}
targetCpk={processContext?.targetValue}
activeView={activeImprovementView}
showLeftPanel={true}
renderLeftPanel={() => {
if (projectionTarget) {
return (
<WhatIfExplorerPage
filteredData={filteredData}
rawData={rawData}
outcome={outcome}
specs={specs}
filterCount={0}
onBack={() => clearProjectionTarget()}
cpkTarget={cpkTarget}
activeFactor={viewState?.boxplotFactor}
mode={analysisMode ?? 'standard'}
projectionContext={{
ideaText: projectionTarget.ideaText,
questionText: projectionTarget.questionText,
}}
onSaveProjection={handleSaveIdeaProjection}
referenceContext={projectionReferenceContext}
/>
);
}
return (
<ImprovementContextPanel
problemStatement={processContext?.problemStatement}
currentUnderstanding={processContext?.currentUnderstanding}
targetCpk={processContext?.targetValue}
currentCpk={stats?.cpk}
causes={causeSummaries}
synthesis={processContext?.synthesis}
/>
);
}}
renderMatrix={() => (
<div className="p-4">
<PrioritizationMatrix
ideas={matrixIdeas}
xAxis={matrixXAxis}
yAxis={matrixYAxis}
colorBy={matrixColorBy}
causeColors={causeColors}
causeLabels={causeLabels}
presets={DEFAULT_PRESETS}
activePreset={matrixPreset}
onPresetChange={setMatrixPreset}
onAxisChange={(axis, value) => {
if (axis === 'x') setMatrixXAxis(value);
else if (axis === 'y') setMatrixYAxis(value);
else setMatrixColorBy(value);
}}
onToggleSelect={ideaId => {
const question = improvementQuestions.find(q =>
q.ideas?.some((i: { id: string }) => i.id === ideaId)
);
if (question) {
questionsState.selectIdea(
question.id,
ideaId,
!selectedIdeaIds.has(ideaId)
);
}
}}
highlightedIdeaId={highlightedIdeaId ?? undefined}
onIdeaClick={ideaId => {
const card = document.querySelector(`[data-testid="idea-row-${ideaId}"]`);
card?.scrollIntoView({ behavior: 'smooth', block: 'center' });
usePanelsStore.getState().setHighlightedIdeaId(ideaId);
setTimeout(
() => usePanelsStore.getState().setHighlightedIdeaId(null),
2000
);
}}
onGhostDotClick={ideaId => {
const question = improvementQuestions.find(q =>
q.ideas?.some((i: { id: string }) => i.id === ideaId)
);
if (question) {
handleProjectIdea(question.id, ideaId, true);
}
}}
/>
</div>
)}
onIdeaHover={ideaId => usePanelsStore.getState().setHighlightedIdeaId(ideaId)}
highlightedIdeaId={highlightedIdeaId}
onOpenBrainstorm={questionId => {
setBrainstormQuestionId(questionId);
setBrainstormIdeas([]);
}}
renderTrackView={() => (
<TrackView
selectedIdeas={selectedIdeasForRecap}
onEditSelection={() =>
usePanelsStore.getState().setActiveImprovementView('plan')
}
onBackToPlan={() =>
usePanelsStore.getState().setActiveImprovementView('plan')
}
actions={aggregatedActions}
onToggleComplete={(actionId, findingId) => {
findingsState.toggleActionComplete(findingId, actionId);
}}
verification={improvVerificationData}
hasVerification={improvHasVerification}
selectedOutcome={
improvCurrentOutcome
? improvCurrentOutcome.effective === 'yes'
? 'effective'
: improvCurrentOutcome.effective === 'partial'
? 'partial'
: 'not-effective'
: undefined
}
outcomeNotes={improvOutcomeNotes}
onOutcomeChange={improvHandleOutcomeChange}
onOutcomeNotesChange={improvHandleOutcomeNotesChange}
/>
)}
/>
</div>
<ImproveTabRoot
activeIP={activeIPContext.activeIP ?? null}
actions={[]}
currentUserId={currentUser?.email}
onGoHome={() => usePanelsStore.getState().showDashboard()}
onActionAdd={action =>
console.warn('[wedge V1] ACTION_ITEM_ADD not yet wired (PR-WV1-3 work):', action)
}
onActionUpdate={(id, patch) =>
console.warn(
'[wedge V1] ACTION_ITEM_UPDATE not yet wired (PR-WV1-3 work):',
id,
patch
)
}
onActionRemove={id =>
console.warn('[wedge V1] ACTION_ITEM_REMOVE not yet wired (PR-WV1-3 work):', id)
}
/>
) : activeView === 'report' ? (
<Suspense fallback={null}>
<ReportView
Expand Down
6 changes: 5 additions & 1 deletion apps/azure/src/persistence/AzureHubRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
import type { HubAction } from '@variscout/core/actions';
import type { ActionItem } from '@variscout/core/findings';
import type { ProcessHub } from '@variscout/core/processHub';
import { migrateImprovementProjectMetadata } from '@variscout/core/improvementProject';
import { db } from '../db/schema';
import { saveProcessHubToIndexedDB } from '../services/localDb';
import { applyAction } from './applyAction';
Expand Down Expand Up @@ -350,7 +351,10 @@ async function hydrateHub(hub: ProcessHub): Promise<ProcessHub> {
db.sustainmentReviews.where('hubId').equals(hub.id).toArray(),
db.controlHandoffs.where('hubId').equals(hub.id).toArray(),
]);
const liveIps = ips.filter(p => p.deletedAt === null);
const hydrateAt = Date.now();
const liveIps = ips
.filter(p => p.deletedAt === null)
.map(p => migrateImprovementProjectMetadata(p, hydrateAt));
const liveSustainmentRecords = sustainmentRecords.filter(record => record.deletedAt === null);
const liveSustainmentReviews = sortReviewsDescending(
sustainmentReviews.filter(review => review.deletedAt === null)
Expand Down
Loading