refactor: fold machine-remote-data into remote-data.ts, PascalCase load lifecycle (RD-11)
`machine-remote-data.ts` defined a third encoding of an in-flight fetch: `LoadLifecycle`. It had three call sites, all one identical line, and the type was never imported by name. Move the mapping into `remote-data.ts` as `fromLoadLifecycle`, beside its neighbour `fromResource` — a `RemoteData` constructor, not a sixth encoding. The lowercase `loading`/`failed`/`loaded` tags on `BriefState`, `OrgTemplateState` and `StamdataEditorState` existed only because `LoadLifecycle` required them. Now that the constraint is inline and PascalCase, the three machines' load-lifecycle tags become `Loading`, `Failed` and `Loaded` — matching their own PascalCase message tags in the same file. `stamdata-editor.machine.spec.ts` no longer asserts a PascalCase message producing a lowercase state. `BriefStatus` (the letter's draft/submitted/approved/rejected/sent status, parsed off the wire from `BriefViewDto`) is a separate tag family and is untouched — its tag count stays 54 before and after this change. Delete `machine-remote-data.ts` and merge its spec into `remote-data.spec.ts`. Regenerate `behaviour-spec.mdx` (the `machineRemoteData` section heading becomes `fromLoadLifecycle`) and confirm `gen:snippets` reports no drift, since `remote-data.ts` carries a showcase region. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# RD-11 — Fold the lifecycle projection into `remote-data.ts`, and PascalCase the 3 machines
|
||||
|
||||
Status: done
|
||||
Source: PLAN.md 1b#3
|
||||
|
||||
## Why
|
||||
|
||||
`machine-remote-data.ts` is 24 lines defining a **third** encoding of "in flight / ok /
|
||||
failed": `LoadLifecycle = { tag: 'loading' } | { tag: 'failed'; reason } | { tag: 'loaded' }`.
|
||||
It has three call sites, all the identical line, and `LoadLifecycle` is never imported by
|
||||
name anywhere — it is a purely structural constraint.
|
||||
|
||||
That constraint is the **only** reason three machines carry lowercase state tags while their
|
||||
message tags are PascalCase in the same file. `stamdata-editor.machine.spec.ts:61` shows the
|
||||
confusion in one line today:
|
||||
|
||||
```ts
|
||||
expect(reduce(seedLoaded(), { tag: 'Loading' })).toEqual({ tag: 'loading' });
|
||||
```
|
||||
|
||||
A PascalCase message producing a lowercase state. Relocate the projection with PascalCase
|
||||
keys and the dialect drift resolves itself — no separate renaming pass, and one named concept
|
||||
disappears.
|
||||
|
||||
## Read first
|
||||
|
||||
- `libs/shared/src/application/machine-remote-data.ts` — all 24 lines
|
||||
- `libs/shared/src/application/machine-remote-data.spec.ts` — 20 lines, to be merged
|
||||
- `libs/shared/src/application/remote-data.ts` — note `fromResource`, the neighbour and
|
||||
precedent for the new function
|
||||
- `libs/shared/docs/remote-data.mdx:72` — teaches `s.tag === 'loaded'`, so it must change too
|
||||
- `apps/ssp/src/app/brief/domain/brief.ts:68` — **`BriefStatus`. Read this before renaming
|
||||
anything.** See decision 4.
|
||||
|
||||
## Decisions (pre-made, don't relitigate)
|
||||
|
||||
1. **Relocate, do not simply delete.** The mapping has to exist somewhere, because
|
||||
`<app-async>` takes a `RemoteData`. Deleting the module re-inlines a 6-line switch in three
|
||||
stores, recreating the duplication WP-31 removed. Move it into `remote-data.ts` as
|
||||
`fromLoadLifecycle`, beside `fromResource`, where it reads as what it is: **a `RemoteData`
|
||||
constructor, not a sixth encoding.** Keep the `Extract<S, { tag: 'Loaded' }>` Success
|
||||
payload so all three call sites stay one line.
|
||||
|
||||
2. **Key it PascalCase**: `Loading | Failed{reason} | Loaded`. Merge
|
||||
`machine-remote-data.spec.ts` into `remote-data.spec.ts` and delete both old files.
|
||||
|
||||
3. **Rename only the three load-lifecycle tags, and catch all four syntactic forms.** Measured
|
||||
counts of the construction form alone (39 across 11 files) understate it. The forms are:
|
||||
|
||||
| Form | Example |
|
||||
| ------------- | ---------------------------------------------- |
|
||||
| construction | `tag: 'loading'` |
|
||||
| comparison | `s.tag === 'loaded'`, `s.tag !== 'loaded'` |
|
||||
| type-level | `Extract<OrgTemplateState, { tag: 'loaded' }>` |
|
||||
| documentation | `remote-data.mdx:72` |
|
||||
|
||||
Files in scope: the three machines (`brief.machine.ts`, `org-template.machine.ts`,
|
||||
`stamdata-editor.machine.ts`), their three specs, `brief.store.ts`, `brief.store.spec.ts`,
|
||||
`org-template.store.ts`, `stamdata.store.ts`, `brief.page.ts`, and `remote-data.mdx`.
|
||||
|
||||
4. **`BriefStatus` IS NOT IN SCOPE. This is the one way to break this ticket.**
|
||||
`brief.machine.ts` contains **two** independent lowercase tag families:
|
||||
- `BriefState`'s load lifecycle — `loading`/`failed`/`loaded` — **rename these**
|
||||
- `BriefStatus`'s letter status — `draft`/`submitted`/`approved`/`rejected`/`sent`, defined
|
||||
in `brief.ts:68` — **leave these alone**
|
||||
|
||||
`brief.machine.ts:278` has both in one line:
|
||||
`if (s.tag !== 'loaded' || s.brief.status.tag !== from …)`. The first is in scope, the
|
||||
second is not. `BriefStatus` is parsed off the wire from `BriefViewDto`, so renaming its
|
||||
tags breaks the parse boundary and the backend contract. **Never rename by "all lowercase
|
||||
tags in this file".**
|
||||
|
||||
5. **Three more collision sites must not be touched.** They use the same words for unrelated
|
||||
things, and anchoring on `tag: '` already excludes them — but verify rather than assume:
|
||||
- `scenario.ts` / `scenario.interceptor.ts` — `'loading'` is a `?scenario=` **URL param
|
||||
value**, not a state tag
|
||||
- `upload.machine.ts` and the four upload UI components — `UploadStatus` discriminates on
|
||||
**`type:`**, not `tag:`, with `'failed'`/`'complete'`/`'uploading'`
|
||||
- `registratie-lookup.store.ts` — `'loading'` is an Angular `resource()` status
|
||||
|
||||
6. **Do not touch `ActionState`, `SaveState`, or `pendingPublish`.** RD-12, RD-13 and RD-14
|
||||
own those, and they must follow this ticket or the same tags get renamed twice.
|
||||
|
||||
## Files
|
||||
|
||||
Add to / edit: `libs/shared/src/application/remote-data.ts` (+ `.spec.ts`),
|
||||
`libs/shared/docs/remote-data.mdx`.
|
||||
Delete: `libs/shared/src/application/machine-remote-data.ts` (+ `.spec.ts`).
|
||||
Rename tags in: `brief.machine.ts` (+ spec), `org-template.machine.ts` (+ spec),
|
||||
`stamdata-editor.machine.ts` (+ spec), `brief.store.ts` (+ spec), `org-template.store.ts`,
|
||||
`stamdata.store.ts`, `brief.page.ts`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Add `fromLoadLifecycle` to `remote-data.ts` with PascalCase keys (decisions 1-2).
|
||||
2. Rename the load-lifecycle tags across the files in decision 3, one file at a time, letting
|
||||
the type-checker find the next site. **Do not blanket-sed.**
|
||||
3. Point the three stores at `fromLoadLifecycle`; delete `machine-remote-data.ts` and merge
|
||||
its spec cases into `remote-data.spec.ts`.
|
||||
4. Update `remote-data.mdx:72`.
|
||||
5. Run `npm run gen:behaviour-spec` — `behaviour-spec.mdx:839` has a `machineRemoteData`
|
||||
section that must become the new name.
|
||||
6. Update this ticket's `Status:` to `done` and the README's RD-11 row to `done`.
|
||||
7. Commit all of it together.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
The third encoding is gone and nothing lowercase survives in the three machines:
|
||||
|
||||
```bash
|
||||
git grep -n "machineRemoteData\|LoadLifecycle" -- apps libs # MUST return nothing
|
||||
ls libs/shared/src/application/machine-remote-data* # MUST be "No such file"
|
||||
|
||||
M="apps/ssp/src/app/brief/domain apps/ssp/src/app/brief/application \
|
||||
apps/ssp/src/app/brief/ui libs/beheer/src/domain libs/beheer/src/application"
|
||||
git grep -n "tag: 'loading'\|tag: 'failed'\|tag: 'loaded'" -- $M # MUST return nothing
|
||||
git grep -n "tag === 'loaded'\|tag !== 'loaded'" -- $M # MUST return nothing
|
||||
```
|
||||
|
||||
`BriefStatus` is untouched — this is the check that matters most (decision 4):
|
||||
|
||||
```bash
|
||||
# Measured before this ticket was written: the total is exactly 54. It MUST still be 54.
|
||||
git grep -c "tag: 'draft'\|tag: 'submitted'\|tag: 'approved'\|tag: 'rejected'\|tag: 'sent'" \
|
||||
-- apps/ssp/src/app/brief | awk -F: '{s+=$NF} END {print s}' # MUST print 54
|
||||
|
||||
git diff --stat apps/ssp/src/app/brief/domain/brief.ts # MUST be empty — brief.ts unchanged
|
||||
```
|
||||
|
||||
If that number moves, you have renamed a wire contract. Stop and revert rather than adjusting
|
||||
the number.
|
||||
|
||||
The collision sites are untouched:
|
||||
|
||||
```bash
|
||||
git diff --name-only | git grep -c "scenario\|upload" || true # expect no such files
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run ci # exits 0
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
`npm run ci`. Also run `npm run ci -- --full` **with an explicit long timeout**: this edits
|
||||
`remote-data.mdx`, which Storybook globs, and a broken MDX import is invisible to plain `ci`.
|
||||
|
||||
Note for whoever runs it: an 8-minute command cannot complete in the default 120s foreground
|
||||
window and the harness will move it to the background. Pass `timeout: 600000` on the Bash
|
||||
call so it runs to completion in the foreground, then commit.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `ActionState` / `SaveState` / `pendingPublish` — RD-12, RD-13, RD-14 (decision 6).
|
||||
- `BriefStatus` (decision 4). If a `BriefStatus` tag changes, the ticket has failed.
|
||||
- `UploadStatus`'s `type:` discriminant — optional RD-35.
|
||||
- The `NO_SUBORGS`/`NO_TABLES`-should-be-`Empty` finding — optional RD-34.
|
||||
|
||||
## Risks
|
||||
|
||||
- **`BriefStatus` (decision 4) is the failure mode to fear.** Its tags are a wire contract.
|
||||
Rename by union, never by file.
|
||||
- **Do not blanket-sed `'loading'`/`'failed'`/`'loaded'`.** Five files legitimately use those
|
||||
words for other purposes (decision 5). Renaming one file at a time and following the
|
||||
type-checker is slower and correct.
|
||||
- **`brief.store.ts` and `brief.page.ts` use only the comparison form**, so a
|
||||
construction-only grep misses them. That is why decision 3 lists four forms.
|
||||
- **`behaviour-spec.mdx` drift**: it has a `machineRemoteData` section heading at :839 which
|
||||
changes with the function name. Run `gen:behaviour-spec` in the same commit.
|
||||
- **`remote-data.ts` carries a `// #region showcase:fold` marker at :30.** If your edit moves
|
||||
or splits that region, run `npm run gen:snippets` in the same commit too.
|
||||
@@ -105,7 +105,7 @@ two. Note that RD-15 exists because 22 abandoned agent worktrees are still on di
|
||||
| RD-08 | Migrate the 3 wizards to the effect map + `Primary` | 07 | yes | done |
|
||||
| RD-09 | Teach the effect map: ARCHITECTURE §2d + fp-tea (2 docs, no generator) | 08 | | done |
|
||||
| RD-10 | `WizardStatus` to a payload-carrying `WizardPhase` | 08 | yes | done |
|
||||
| RD-11 | Fold the lifecycle projection into `remote-data.ts`; PascalCase 3 machines | 01 | | todo |
|
||||
| RD-11 | Fold the lifecycle projection into `remote-data.ts`; PascalCase 3 machines | 01 | | done |
|
||||
| RD-12 | `ActionState` becomes `action` on `BriefState.Loaded` | 11 | | todo |
|
||||
| RD-13 | Same for org-template, folding `pendingPublish` in | 12 | | todo |
|
||||
| RD-14 | Move `SaveState` to `debounced-save.ts`; delete `action-state.ts` | 13 | | todo |
|
||||
|
||||
Reference in New Issue
Block a user