fix(planning): react only to calculations started by the mounted calendar

Re-mounting the calendar no longer records a duration sample or
announces the previous result, explanation or rejected explanation.
Starting a calculation during a cancellation announces loading and
keeps focus where it is. Only rejected explanation deliveries are
announced, as before. Replacement tests now replace a real plan.

Refs #275, #274

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nessarandClaude Opus 5.5 committed 2026-10-17 11:22:38 +02:00
1 parent 43677ff434
commit feb651148e
4 files changed
+218 -10

No files matched your search

@@ -17,6 +17,7 @@ import { UserInputService } from './user-input-service';
type Horizon = { start: string; end: string };
interface PendingCalculation {
id: number;
horizon: Horizon;
progress: number;
cancelling: boolean;
@@ -78,6 +79,7 @@ export class PlanningCalculationService {
private readonly acceptedState = signal<AcceptedCalculation | null>(null);
private readonly explanationState = signal<PlanningExplanation | null>(null);
private readonly explanationErrorState = signal<PlanningReason | null>(null);
private readonly explanationRejectedState = signal(false);
readonly request = computed(() => {
const horizon = this.settings.planningHorizon();
@@ -101,6 +103,8 @@ export class PlanningCalculationService {
readonly accepted = this.acceptedState.asReadonly();
readonly explanation = this.explanationState.asReadonly();
readonly explanationError = this.explanationErrorState.asReadonly();
readonly explanationRejected = this.explanationRejectedState.asReadonly();
readonly pendingId = computed(() => this.pendingState()?.id ?? null);
readonly isCalculating = computed(() => this.pendingState() !== null);
readonly isCancelling = computed(() => this.pendingState()?.cancelling ?? false);
readonly progress = computed(() => this.pendingState()?.progress ?? 0);
@@ -124,7 +128,12 @@ export class PlanningCalculationService {
const requestedAt = performance.now();
const version = ++this.version;
this.userInput.declarationEntrySignal.set(false);
this.pendingState.set({ horizon: request.horizon, progress: 0, cancelling: false });
this.pendingState.set({
id: version,
horizon: request.horizon,
progress: 0,
cancelling: false,
});
let calculation: PlanningCalculationContext;
try {
calculation = this.calendar.createPlanningCalculation({
@@ -193,6 +202,7 @@ export class PlanningCalculationService {
const cancelling = this.pendingState()?.cancelling ?? false;
this.explanationState.set(null);
this.explanationErrorState.set(null);
this.explanationRejectedState.set(false);
this.pendingState.set(null);
this.acceptedState.set({
calculation,
@@ -209,7 +219,9 @@ export class PlanningCalculationService {
private loadArtifacts(calculation: PlanningCalculationContext): void {
const current = () => this.acceptedState()?.calculation === calculation;
const fail = (error: unknown) => {
if (current()) this.explanationErrorState.set(explanationFailure(error));
if (!current()) return;
this.explanationRejectedState.set(true);
this.explanationErrorState.set(explanationFailure(error));
};
calculation.explanation
.then((explanation) => current() && this.explanationState.set(explanation ?? null))