feat(brief): tolerate a 404 on GET /brief with a one-shot reset (RB-22)
BriefStore.load() now treats a 404 from GET /brief as "no brief exists yet" and calls the existing reset() command once, instead of showing the generic load-failed error. BriefAdapter.load() gains a BriefLoadFailure error channel (notFound | error) so the store can tell a 404 apart from every other failure; every other adapter method stays on runSubmit, unchanged. The once-only bound is a field on the store, not a comment: a second 404 (from a later load() call) always falls through to the ordinary error path, and the recovery path never calls load() again, so no loop can form. This is the expand half of CQ-007's split (04-cqrs-light.md). Today's backend never 404s GET /brief, so the new branch is dead code until RB-23 (the backend contract half) ships in a later merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
import { BriefMsg, BriefState, initial, reduce } from '@brief/domain/brief.machine';
|
||||
import { BlockDiffKind, changedBlocks, diffBlocks } from '@brief/domain/brief-diff';
|
||||
import { OrgTemplate } from '@brief/domain/org-template';
|
||||
import { BriefAdapter, BriefView } from '@brief/infrastructure/brief.adapter';
|
||||
import { BRIEF_LOAD_FAILED, BriefAdapter, BriefView } from '@brief/infrastructure/brief.adapter';
|
||||
import { LetterPreviewAdapter } from '@brief/infrastructure/letter-preview.adapter';
|
||||
import { RevealBigNummerAdapter } from '@brief/infrastructure/reveal-bignummer.adapter';
|
||||
import { uploadContentUrl } from '@shared/upload/upload.adapter';
|
||||
@@ -119,13 +119,40 @@ export class BriefStore implements PendingSave {
|
||||
return !!b && canSubmit(b) && !hasBlockingErrors(this.diagnostics());
|
||||
});
|
||||
|
||||
/** True once a 404-triggered recovery has been attempted (RB-22, CQ-007's expand
|
||||
half — see `recoverFromMissingBrief`). This is the structural once-only bound:
|
||||
a repeated 404 falls straight to the `error` branch below and can never reach
|
||||
`adapter.reset()` a second time, regardless of how many times `load()` runs. */
|
||||
private hasRecoveredFromMissingBrief = false;
|
||||
|
||||
async load() {
|
||||
const r = await this.adapter.load();
|
||||
if (r.ok) {
|
||||
this.orgTemplate.set(r.value.orgTemplate);
|
||||
this.caseContext.set(r.value.caseContext);
|
||||
this.history.clear();
|
||||
this.store.dispatch({ tag: 'BriefLoaded', ...r.value });
|
||||
this.applyLoadedView(r.value);
|
||||
} else if (r.error.tag === 'notFound' && !this.hasRecoveredFromMissingBrief) {
|
||||
this.hasRecoveredFromMissingBrief = true;
|
||||
await this.recoverFromMissingBrief();
|
||||
} else {
|
||||
const reason = r.error.tag === 'notFound' ? BRIEF_LOAD_FAILED : r.error.reason;
|
||||
this.store.dispatch({ tag: 'BriefLoadFailed', reason });
|
||||
}
|
||||
}
|
||||
|
||||
private applyLoadedView(view: BriefView) {
|
||||
this.orgTemplate.set(view.orgTemplate);
|
||||
this.caseContext.set(view.caseContext);
|
||||
this.history.clear();
|
||||
this.store.dispatch({ tag: 'BriefLoaded', ...view });
|
||||
}
|
||||
|
||||
/** `GET /brief` 404'd — no brief exists yet for this owner. Recover by calling the
|
||||
existing `reset()` command directly (the same POST `resetDemo()` uses) and
|
||||
applying whatever it returns; this NEVER calls `load()` again, so a second 404
|
||||
(e.g. `reset()` itself failing) cannot loop back into this method. */
|
||||
private async recoverFromMissingBrief() {
|
||||
const r = await this.adapter.reset();
|
||||
if (r.ok) {
|
||||
this.applyLoadedView(r.value);
|
||||
} else {
|
||||
this.store.dispatch({ tag: 'BriefLoadFailed', reason: r.error });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user