Read-only audit of #2575 at
098330759and #2387 atc81426c44. Saved verbatim as evidence for the harvest map. Feature implementation is on hold; nothing was ported, rebased or closed.
Harvest audit: PR #2575 (QM v2 PR-D, web scaffolds and docs) and PR #2387 (QM child visualisation and assign tree)¶
Read-only audit, 5 October 2026. Specifications read on main at 7673ed0 (worktree
pr4061…, HEAD bc0c947). Nothing was checked out, fetched, built or run. Claims marked
"static reading" come from reading code, not from executing it.
Path prefixes used below: QMV2/ = src/services/web/src/app/project/project-admin/question-management-v2/;
QMS/ = src/services/web/src/app/core/state/question-management-v2/;
AF2/ = src/services/web/src/app/shared/annotation/annotation-form-v2/;
QM/ = src/services/web/src/app/project/project-admin/question-management/;
PKG/ = docs/planning/integrated-review-plan-2026-10/.
1. Scope and state¶
PR #2575 (head 0983307, base feat/qm-v2-c-migration-cutover)¶
- State. Open, not draft, last updated 24 April 2026, 12 commits. GitHub lists 544 files.
- Shape of the branch. First-parent history:
36190433d(PR-A squash),2712901(PR-B),874a421(PR-C), then71b179c(PR-D, 17 April 2026);- then five main merges (
941cfee…0983307, the last on 24 April, bringing in main atde8e312). - Relation to the stack.
- C's head
1b93cbbcontains874a421, but the two branches merged main separately. Their merge-base is the main commitde8e312, and C is not an ancestor of D. - PR-A's head has 45 non-merge commits after
36190433dthat D never absorbed (git rev-list --count --no-merges 36190433d..audit/pr2572). - D's own work.
git diff 874a421 71b179cgives 161 files (+38,515/−4,235). D's main merges changed only one of those files afterwards: merge0983307left conflict markers indocs/architecture/dependency-map.md. - Where GitHub's 544 files come from. The other 383 files are drift between the two branches'
separate main merges (for example 133
docs/planning/auth0-to-openiddictfiles and 125docs/planning/_archivefiles) and A/B/C content. None of it is D's work.git diff de8e312..0983307gives 432 files.
Bucket in D's own commit (874a421..71b179c) |
Files | Lines |
|---|---|---|
Generated (api-client.generated.ts, swagger.json, .generated-checksums.json) |
3 | +7,977/−4,144 |
QM v2 designer code (QMV2/**, QMS/**) |
32 | +4,406 |
| QM v2 designer specs | 10 | +2,308 |
AF2 scaffold (AF2/**, including the version-transition alert and the impact service) |
33 | +2,275 |
| Active-reviewer presence web (study-presence, banners, SignalR, review settings, stage review and overview) | 32 | +2,515/−13 |
| Docs (QM v2 specs, plans, three ADRs, briefs, reviews) | 36 | +13,992/−58 |
Prototype HTML and viewer (prototype.html, idle-presence prototypes) |
4 | +4,786 |
Other web (routes, nav, main.ts, angular.json, theme, appConfig.local.json) |
9 | +181/−20 |
CLAUDE.md, .gitignore |
2 | +75 |
Real size without generated files: 158 files, about +30.5k lines. About 6.7k of those lines
are QM v2 designer code and tests. Nothing was ever wired to production. The designer is gated by
newQuestionManagement, and it sends stage-scoped publishes to /api/v2 (PR-C's API).
Drift from main (7673ed0). GitHub reports the PR as mergeable only against its stacked
base. Against main it conflicts in substance:
- AF2 directory. Main has its own
AF2/at the same path: 157 files, lifted from71b179cin74dffd658(6 August 2026). - Presence. The presence web code is already on main.
- ADR numbers. PR-D's ADR numbers collide with main's two ADR-008s and its ADR-009.
- Flag and path. PR-D reuses
newQuestionManagementandadmin/questions, which main uses for the current new editor. - Platform.
- Main runs Angular 22.1 and
@ngrx/signals21.1.1; the PR uses Angular ^21.2.4. - Change detection on main is chosen at bootstrap (
src/services/web/src/main.ts:525). ThezonelessChangeDetectionflag defaults to false (src/charts/syrf-common/env-mapping.yaml:1529-1533) and no cluster-gitops environment sets it. - So main is zoneless-ready rather than zoneless by default. Its guard specs enforce zoneless
discipline (
hot-hook-zoneless-discipline.spec.ts,*.zoneless.spec.ts), the theme (global-styles/syrf-theme.spec.ts) and help URLs (no-hardcoded-help-urls.spec.ts).
PR #2387 (head c81426c, base main)¶
- State. Open, conflicting. 46 files (+3,819/−1,127).
- History.
- Branch
QMChildQuestionVisualize: work by Mala K and Chris from August–September 2024, a 2026-03-10 feature commit (900076d), then a merge of main (c81426c). - Merge-base
262d6be(8 March 2026); main is 7,742 commits ahead. - Content by area.
- Current new editor
QM/**: 23 files (+2,526/−920). - A template-driven validation framework: 6 files.
- Nav and shell: about 10 files.
- Utilities: 4 files.
- Two stray files committed at repo-root
src/app/.... - Formatting churn. Much of the diff is Prettier reformatting. For example,
annotation-question-tree.feature.ts(+168/−130) is mostly trailing-comma changes. - Drift from main. Of its 46 paths, 27 have changed on main since the merge-base, 10 are
unchanged, 6 are new in the PR, 2 are stray, and
drawer.scsshas been deleted on main. - CI on head. "Test Web (Angular)" and "Test Summary" failed (
gh pr view 2387, read-only).
Coverage of the requested targets (both PRs)¶
- OS-A01 collaborative drafts with presence. Neither PR has presence on design pages or a base revision on edits. PR-D's autosave is last-write-wins: the client sends no base, and the server replaces the draft wholesale.
- Q-20 publication impact dialog. PR-D has a stage-scoped wizard without per-category treatments.
- Q-34 option mapping. PR-D maps option values (string to string), not option IDs.
- AC-R2c-27 version history with diff. No component exists. Only
ui-specification.md§8 describes it, and it hides version numbers on tree nodes, against the criterion. - Material 3 (UI-1 to UI-11). PR-D's designer has:
- 67
var(--mat-sys-*, #fallback)uses (UI-10 forbids them) and 35 bare literal colours; - no
--syrf-*roles and noapp-page-shellorapp-page-state; - 12 native form controls, emoji icons and Title Case labels.
#2387 adds literal colours and Material Design Components (MDC) internals. - "Library". Neither PR uses the reserved "Library" label.
2. Harvest entries¶
| ID | PR | Component (type or file at SHA) | What it does | Verdict | Why (rule IDs, decisions) | Target (spec §, contract, release, tracker row) | Adaptation needed | Tests to carry | Effort | Evidence (path:line @ short SHA) |
|---|---|---|---|---|---|---|---|---|---|---|
| H-WEB-01 | #2575 | QmV2RootStore with withUndoRedo and the saveDraft autosave (QMS/qm-v2-root.store.ts) |
Route-scoped SignalStore holding the questions, versions and details collections. Debounced whole-DraftContent autosave, Ctrl+Z undo and redo, stage publish |
Avoid | Undo is a no-op (D-01). Autosave wipes options (D-02) and drops edits made across questions (D-05). No base revision, so silent overwrites, against RD-R26, RD §4.7 and AC-R2c-34. Bound to the superseded AQ/PQS/SQS API, which C4 replaces. Main already has route-owned SignalStores for the editor (#3778) | RD §3.7, §4.7; C4 (designer drafts); R2a, R2c; T-RD-02, T-RD-05 |
Build on main's editor stores with DesignDraftChange {draftId, itemPath, baseItemRevision, value, clientChangeId} per item. Drop undo, or rebuild it over draft changes |
None (the undo test only checks canUndo is false: qm-v2-root.store.spec.ts:240-242) |
— | QMS/qm-v2-root.store.ts:63-67,126-154,160-175 @0983307 |
| H-WEB-02 | #2575 | Web models and normaliser (QMS/qm-v2.models.ts, qm-v2.normalise.ts) |
Flattens ProjectQuestionSetResponse into three collections |
Avoid | Options have no optionId (C4 option identity; AC-R2c-22). optional and multiple sit on the question; versioning model §12.6 avoids this, and requiredness is form-owned in C4. Two incompatible decision vocabularies. The normaliser hardcodes publishDecision: null (D-04) |
C4 | — | Normaliser spec only as a pattern for DTO-to-entity tests | — | QMS/qm-v2.models.ts:29-33,62-83; qm-v2.normalise.ts:114 @0983307 |
| H-WEB-03 | #2575 | Design view shell (QMV2/design/design-v2.component.*, design-v2.store.ts) |
Six fixed category tabs, split tree and properties layout, selection and expansion state | Reference only | The plan says to extend the existing new editor, not duplicate it (PKG/integrated-plan.md:410-416). The handoff's c2 and DH-C16 use entity types, not six fixed tabs. buildTree filters the whole list per node, so it is O(n²); the 2,023-question form is an acceptance case (AC-R2a-22r) |
Handoff §4.2 (Questions and entity types), DH-C16, DH-C31; R1a, R2a; T-RD-02 |
Layout reference only; build inside QM/design; index children by parent once |
Question-tree spec cases (render, select, expand) as ideas | S | QMV2/design/design-v2.store.ts:44-51,205-218 @0983307 |
| H-WEB-04 | #2575 | Question tree and node (QMV2/design/question-tree/*) |
Recursive OnPush tree with "Draft" and "unpublished changes" badges | Reference only | Its lifecycle states match the plan's lifecycle indicators once publication exists (R2c; UI coverage comparison). But it uses emoji icons and an incomplete ARIA tree (no aria-expanded or aria-level, no arrow keys). It has no virtualisation and no add or reorder. It hides the version number, against AC-R2c-27 |
AC-R2c-27; AC-R1a-12; UI-1, UI-5, UI-7; handoff §7.4 | Add a version chip and a lifecycle state shown by icon shape and text (mat-icon, --mat-sys-*). Use a CDK tree with virtual scroll. Take the keyboard model from H-WEB-18 |
— | S | QMV2/design/question-tree/question-node.component.ts:97-106; question-node.component.html:10-16 @0983307 |
| H-WEB-05 | #2575 | Properties panel (QMV2/design/properties-panel/properties-panel.component.*) |
Edits text, help, control type, optional and multiple | Avoid | No options editor. Native <select>, <textarea> and <input> with Title Case labels (UI-1, UI-2, UI-11; D3-04). optional belongs to the form version (C4). Shows published values and reverts typing (D-06). Its impactSummary input and publishDecisionChange output are never bound |
C4; UI-1 to UI-11 | — | — | — | properties-panel.component.ts:117-139,168-176; design-v2.component.html:32-35 @0983307 |
| H-WEB-06 | #2575 | Impact & Mapping panel and DraftPublishDecision (…/impact-mapping-panel/*) |
Design-time keep, map or re-answer choice, with the answer distribution and value-to-value mapping | Reference only | The concept fits RD §4.8 step 2: declarations and treatments are recorded as attributed draft changes. But the mapping is by value, not option ID (RD-R21; AC-R2c-23). Classification is a binary "may affect" instead of the publisher's compatibility declaration with a rationale (RD-R20; AC-R2c-21r). The effect re-triggers itself (D-07), and the panel is dead code in the Design view | RD §3.4, §4.8; Q-34; handoff §5.9 step 1, DH-C32; R2c; T-RD-05 |
Take only the copy and the distribution display. Vocabulary: keep → doNothing; re-answer → requireReanswer; map → a Q-34 per-option mapping, only where meaning is unchanged |
Panel spec cases (distribution shown, three choices) after renaming | S | impact-mapping-panel.models.ts:7-28; impact-mapping-panel.component.ts:161-176 @0983307 |
| H-WEB-07 | #2575 | Publish wizard (QMV2/assign/publish-wizard/*) |
Five-step mat-stepper, "Publish to |
Reference only | Its shape resembles handoff §5.9 but it is stage-scoped; under SF1 and RD §3.8, publication is per form. It lacks per-category treatments across prior versions and stages, usage freshness, the "what reviewers will see" preview and one publication per form (AC-R2c-01, 04, 14; UX-R33). Defects D-03, D-04 and D-08; literal colours | RD §4.8; UX §3.9; handoff §5.9, DH-C32; AC-R2c-01 to 35; R2c; T-RD-05, T-UX-06 |
Rebuild on the shared impact-preview pattern (UX §3.9) and main's GuardedReviewSettings "Apply anyway" prompts. Use the step titles only as copy input, in sentence case |
None as is. Its spec asserts the decision-free payload (publish-wizard.component.spec.ts:188-195) and provides the store at TestBed root, which hides D-03 (:77) |
S | publish-wizard.component.ts:269-299,336-338,375-387 @0983307 |
| H-WEB-08 | #2575 | Publish concurrency inputs (expectedPublishedSqsVersionId, expectedImpactFingerprint) and extractApiErrorMessage (QMS/qm-v2-root.store.ts) |
Sends the base version and the impact fingerprint the admin saw; shows the API error text | Adapt | The same primitive as the RD §4.8 step 3 digest recheck (PreviewDigestChanged) and UX-R35 / AC-R2c-35 ("The impact changed since you reviewed it") |
RD §4.8; C4, C8; UX §3.9; R2c; T-RD-05, T-UX-06 |
Rename to the form-head compare-and-set plus the preview digest. Route the typed refusal to the recheck screen. Replace the ad hoc error parsing with typed problem codes | New test: a changed digest asks the admin to re-confirm | S | QMS/qm-v2-root.store.ts:160-175,232-265 @0983307 |
| H-WEB-09 | #2575 | Assign view (QMV2/assign/assign-v2.*, assign-tree/*) |
Per-stage assignment with ancestor and descendant cascade, draft-versus-published diff, pending-change summary, stage publish | Avoid | Per-stage question selection is retired (SF1; handoff DH-C17 "Remove"). The UI coverage comparison keeps main's two-tree Assign as the base for composing form versions. Main's assign.store.ts already cascades ancestors and descendants. Reads the stage list once in ngOnInit; untyped inline any selectors |
AC-R2a-24; handoff §4.2; R2a; T-RD-02 |
— (compose forms with main's Assign; take the change summary idea via H-WEB-10) | Its cascade cases duplicate main's | — | assign-v2.store.ts:25-34,359-377; assign-v2.component.ts:159-164,181,247 @0983307; QM/assign/assign.store.ts:474-488 @7673ed0 |
| H-WEB-10 | #2575 | Preview v2 (QMV2/preview/*) |
Published-versus-pending toggle; New, Edited and removed markers; pending-changes banner | Reference only | Main's Preview already uses AF2 preview mode. The UI coverage comparison says to keep it and add the published-versus-pending comparison and change markers (R2c) | AC-R2c-16; R2c; T-RD-05 |
Re-implement the markers in the AF2-based preview against form-version pins, not stage SQS | Summary cases from its store spec as ideas | S | QMV2/preview/preview-v2.store.ts:21,89-133; preview-form/preview-form.component.html:15-30 @0983307 |
| H-WEB-11 | #2575 | Version-transition alert (AF2/version-transition-alert/*) |
Reviewer's in-form alert after a publication: answer mapped (old → new, with "Use previous answer"), re-answer needed, or wording updated | Adapt | The only reviewer-facing post-publication UI in the stack. Main deliberately left it out of the AF2 lift (docs/superpowers/plans/2026-08-06-af2-phase2-foundation.md:87 @7673ed0). Fits UX §3.10 and AC-R2c-07, 16 and 20r |
UX §3.10; RD §4.8 step 5; handoff §5.9 "After"; R2c; T-RD-05 |
Use the copy deck (Needs updating, Outdated answers, Fix; D3-03). Show the generated-version attribution ("generated by publication of …", plus the actor). Offer "Use previous answer" only when that value is valid under the session's pinned version (AC-R2c-22). Material 3 roles, no literal colours. Host it in main's AF2 | Its spec (129 lines), renamed | M | AF2/version-transition-alert/version-transition-alert.component.html:1-60; .ts:93-112 @0983307 |
| H-WEB-12 | #2575 | Web AnnotationImpactService and the StageImpactSummary DTO (AF2/services/annotation-impact.service.ts) |
Stage impact: per-question counts of annotations, studies, annotators and sessions (completed and incomplete), answer distribution, fingerprint | Reference only | Stage-scoped. Lacks draft-only, prior-version and route breakdowns and usage freshness (AC-R2c-01, 04; C8). The hardcoded relative /api/v2 URL bypasses the generated client |
C8; RD §4.8 step 1; R2c; T-RD-05 |
Use the field list as input to the C8 and impact-preview DTO | — | — | annotation-impact.service.ts:7-28,52-55 @0983307 |
| H-WEB-13 | #2575 | AF2 scaffold (AF2/** apart from H-WEB-11 and 12: host, store, six controls, list, field, unit panel, card selector) |
Early AF2 reviewer form | Avoid | Superseded. Lifted from 71b179c into main as 74dffd658 (6 August 2026) and grown to 157 files. Main's plan says PR-D must delete this duplicate |
AF2 (L5) | Delete | — | — | docs/superpowers/plans/2026-08-06-af2-phase2-foundation.md:82,95,125 @7673ed0 |
| H-WEB-14 | #2575 | Active-reviewer tracking web (study presence, SignalR presence, idle and surplus banners and dialog, stage review-settings toggles, stage-review presence, active-sessions table; 32 files) | Study presence, idle release, enforce-target warnings | Avoid | Merged on main by another route (study-presence/ since 19d266c70; the banners and dialog are present). The stage-level "Idle Session Timeout" and "Enforce Annotation Target" settings contradict D2-07, RD-R11 and AC-R2b-10r: the form owns the timeout and limits. Uses fxLayout |
RD §3.11; SP; UX §3.8 | For presence on design pages (OS-A01), add a design group to main's existing hub | — | — | stage/stage-admin/review-settings/review-settings.component.html (+61) @71b179c; src/services/web/src/app/study-presence/ @7673ed0 |
| H-WEB-15 | #2575 | Routing, nav and flag (project-admin.routes.ts, project-nav.component.ts) |
Splits admin/questions on newQuestionManagement and adds a top-level "Questions" section (Design, Assign, Preview) |
Avoid | On main, the same flag and path open the current new editor (the "Question management" item). Turning the flag on with PR-D would swap a working editor for an unwired scaffold. Navigation is now set by handoff §4.1 (Design › Questions and entity types, Annotation forms) and C17, and main has rewritten the nav (#3502, #3867) | C17; handoff §4.1, DH-C11; UI-9 | — | — | — | project-admin.routes.ts:67-89; project-nav.component.ts:470-520 @0983307; project-nav.component.ts:708-716 @7673ed0 |
| H-WEB-16 | #2575 | release-strategy.md: R1 gate checklist and the R1–R3 boundaries |
"At a glance" R1 list, production gate, R1 (design, assign, publish, AF2), R2 (edit published questions, decisions UI), R3 (import and export) | Avoid | Superseded by the plan's releases (R1a, R2a, R2c …) and its G0 and F1a gates. The checklist marks undo/redo and the publish wizard [x] although both are defective. "Cannot edit published questions" (remove and replace) contradicts versioned publication (RD §1; AC-R2c-21r) |
PKG/integrated-plan.md §8; AC-M0-04 |
— | — | — | docs/features/question-management/release-strategy.md:32-123,237-336 @0983307 |
| H-WEB-17 | #2575 | D-RS-01 to D-RS-05 (release-strategy.md) |
D-RS-01 domain-first gate; D-RS-02 projections by migration status; D-RS-03 remove and replace; D-RS-04 per-project migration with rollback; D-RS-05 batched transitions that freeze studies | Reference only | D-RS-01 is already the F1a/G0 model. D-RS-02 is replaced by Study.CanonicalSummary and the C16 readers (AC-R2a-12). D-RS-03 is replaced by versions with "removed question" treatments (AC-R2c-18), and a published question is never hard-deleted (AC-R2a-10). D-RS-04 is replaced by universal conversion with a routing-rollback window (OS-A15; BC) and §12.6's ban on destructive rollback. D-RS-05's batching survives as phase 2 per Study, but freezing review and blocking exports contradicts AC-R2c-15 (reviewers keep saving) |
RD §4.8; BC §10; C16; versioning model §12.6 | Record as superseded. Use D-RS-03's dialog wording only as copy input for AC-R2c-18 | — | — | release-strategy.md:357-408 @0983307 |
| H-WEB-18 | #2575 | Design docs: ui-specification.md, publishing-versioning-ux.md, prototype.html, figma-design-reference.md |
QM v2 designer specification (tree, properties, assign, preview, wizard, versioning UX, keyboard and ARIA model) and the 3,890-line prototype | Reference only | Already catalogued as assets A and A2 in PKG/ui-coverage-comparison.md, and partly harvested into UX strategy §6.4. §8's history timeline with a per-field diff is the only design for AC-R2c-27. §9's keyboard and ARIA tree model serves AC-R1a-12 |
AC-R2c-27; AC-R1a-12; UX §3.8; handoff §5.9 | Show the version number on cards: AC-R2c-27 overrides §8's "not shown on tree nodes". "Restore" creates a new draft. Add "Why it changed" and "What reviewers need to do differently" (AC-R2c-12). Replace "All changes saved" with the design-draft status | — | S | docs/features/question-management/ui-specification.md:748-880 @0983307 |
| H-WEB-19 | #2575 | Planning and history docs (implementation-plan, state-of-play, session-summary, code-review, mental-model-review-decisions, annotation-versioning-integration, migration-strategy, the M011 plan, five April plans, three planning reviews, training-rounds, three feature briefs) |
Records of the April model | Reference only | They describe the superseded AQ/PQS/SQS, extraction and per-project migration model, and the owner session wins. The briefs belong to other lanes now: annotation import → XA1; question export and import → R1a (#3934, #2781); autosave → AF2 plus RD §4.2; training rounds → TI | PKG/decision-register.md §5 |
Don't port them into docs/; cite them by SHA in the harvest map |
— | — | docs/features/question-management/*.md @71b179c |
| H-WEB-20 | #2575 | ADRs, CLAUDE.md and architecture docs (two ADR-008s, ADR-009, the CLAUDE.md QM v2 section, mongodb-reference.md, dependency-map.*), plus appConfig.local.json |
ADRs for the extraction cutover and service classification; repository guidance | Avoid | The ADR numbers collide with main's two ADR-008s, and main's ADR-009 covers the same domain-versus-application topic. They describe the superseded extraction model. Also a stale revert of main docs (D-10), committed conflict markers (D-09), and a committed local config that .gitignore excludes |
Docs governance | — | — | — | docs/architecture/dependency-map.md:29-65 @0983307; docs/decisions/ADR-009-domain-vs-application-service-classification.md @7673ed0 |
| H-TREE-01 | #2387 | Child-validity check (checkChildQuestionValidity, isChildInvalid, editor warning; QM/design/design.store.ts, QM/edit/edit.component.html) |
Flags a parent edit that leaves a child's conditional targetParentOptions naming options the parent no longer has, and warns in the editor |
Adapt | The same rule as AC-R2a-24 and FX-VM-10: refuse a composition whose child condition names an option missing from the pinned parent version, and name the question. Also the plan's broken-conditional marker (UI coverage comparison) | C4; AC-R2a-24; FX-VM-10; R1a (client warning in the current editor), R2a (server refusal); T-RD-02 and the proposed T-RD-10 |
Compare option IDs, not values (C4; ADR-011 multi-option conditions). Re-express it as a pure function over main's current design.store (+349/−98 since the merge-base). The server validator is authoritative. Sentence-case copy from the copy deck naming the parent, child and option |
None in the PR; write unit tests and FX-VM-10 | M | QM/design/design.store.ts:153,189-213,444; QM/edit/edit.component.html:43-55 @c81426c |
| H-TREE-02 | #2387 | "New options untouched on children" (OptionStatus.addedUntouched and addedTouched, newOptionsUntouchedOnChildren, NewOptionComponent) |
When a parent gains options, shows them as indeterminate in the children's conditional-option pickers until the admin decides | Reference only | Useful for filtered-options and condition authoring (AC-R1a-06) and for publication's added-option case (versioning model §3.5). But the code subclasses MatOption, overriding its private _selectViaInteraction and _handleKeydown, with MDC classes; UI-1 forbids private Material internals. It also renames a shared enum that main uses twice |
AC-R1a-06; C4 | Re-express as an explicit "Show for new option?" choice keyed on option IDs, with public components | — | S | QM/edit/new-option/new-option.component.ts:39-62; src/services/web/src/app/core/actions/util.ts:622,651 @c81426c |
| H-TREE-03 | #2387 | Flat assign tree with sticky header and scroll-synced positioning (stage-assign, InViewDirective, NavbarService, no-stage guard) |
Keeps the stage tree aligned with the all-questions tree while scrolling | Avoid | Per-stage selection is legacy-only (SF1; DH-C17). Measuring every node with getBoundingClientRect inside a computed driven by window scroll can't scale or virtualise for 2,023 questions (UI-6; AC-R2a-22r). The head doesn't compile (D-13). Main has since changed stage-assign (#3582, #3594) |
SF1; handoff §4.2 | — | — | — | QM/assign/stage-assign/stage-assign.component.ts:48-49,153-240 @c81426c |
| H-TREE-04 | #2387 | Template-driven validation framework (validation-utils.ts, 550 lines; form-model and error-provider directives; edit.form-validations2.ts) |
Declarative error and warning validators for nested template-driven forms | Avoid | Duplicates Vest, which main already uses in edit.form-validations.ts. Ships TEST: copy and visible debug <pre> blocks (D-14; AC-R1a-07 requires debug text gone). Main modernises touched forms with signals and Signal Forms |
AC-R1a-07; UI-11 | — | — | — | QM/edit/edit.form-validations2.ts:21; QM/edit/edit.component.html:6-27 @c81426c |
| H-TREE-05 | #2387 | Question node and editor tweaks (actions gated by annotation count, "Always with parent question" select, pending move and copy marker) | Count-based locks and display options | Avoid | Superseded on main by live assignment locks and fresh counts (#3582, #3594, #3572, #3778). Canonical forms replace locks with versions. Contains a precedence bug (D-16) | UI coverage comparison, "Question tree and editor" | — | — | — | QM/design/question-node/question-node.component.html:61,114; QM/edit/edit.component.html:71 @c81426c |
| H-TREE-06 | #2387 | Nav and shell (core/nav/*, drawer.scss, page.component, rg-body, app styles) |
Navbar title service, layout tweaks | Avoid | Main has rewritten the top bar and nav for Material 3 (#3502, #3454, #3468, #3867) and deleted drawer.scss. Navigation now follows handoff §4.1 |
C17; UI-5 | — | — | — | core/nav/nav.component.html (+587/−302 on main since 262d6be) @7673ed0 |
| H-TREE-07 | #2387 | core/utils/signal.utils.spec.ts |
Unit tests for computedPrevious and computedFromPrevious |
Adapt | Main has no spec for these helpers, and its editor store still uses them. The computedPrevious cases are correct; the computedFromPrevious cases would fail (D-15) |
Main's core/utils/signal.utils.ts |
Keep the first describe; rewrite the second to recompute through a source signal |
The file itself | S | signal.utils.spec.ts:1-50 @c81426c; signal.utils.ts:43-53 @7673ed0 |
| H-TREE-08 | #2387 | Shared utilities (util.ts mapFn, KeyMap, MergeUnion; flattenedNodes in the tree feature) |
Type helpers and a flat node list | Avoid | Changes mapFn from variadic to unary, and five files on main call it. Leaves scratch types in production code (D-18). Mostly formatting churn. flattenedNodes is a one-liner to re-derive if a virtualised tree needs it |
— | — | — | — | core/actions/util.ts:467-526; QM/annotation-question-tree.feature.ts:133 @c81426c |
3. Defects not to carry over¶
Known D defects (all four confirmed)¶
- D-01:
withUndoRedodoes nothing. CONFIRMED. - The store has only named collections (
questions,versions,details). - Called with no options,
withUndoRedo()tracks the default keysentityMap,ids,selectedIdsandfilter(ngrx-toolkit 21.0.1,getUndoRedoKeys). None of these exist in the store (qm-v2-root.store.ts:63-67). - Even if configured, edits go to the server, so there would be nothing local to undo.
- New: the document-level Ctrl+Z handler calls
preventDefault, which blocks the browser's native text undo inside the question textarea (design-v2.component.ts:89-99). - D-02: autosave clobbers
options[]. CONFIRMED. - The client builds
options: base?.options ?? []andanswerOptionFilters: … ?? null, and never falls back to the question's own options. TheQuestionNodeview model drops options altogether (design-v2.component.ts:131-143;design-v2.store.ts:183-198). - The server replaces the draft wholesale (
Draft = draftContent;QuestionManagementV2Controller.cs:73-86;AnnotationQuestionV2.cs:105-109@0983307). - Result: the first text edit to a published question saves a draft with no options and no filters, and publishing it would publish a version without options.
- D-03: the publish wizard throws
NullInjectorError. CONFIRMED by static reading. QmV2RootStoreis provided only on the lazy route (question-management-v2.routes.ts:19).- The Assign view injects the root
MatDialogand callsopenwith noinjectororviewContainerRef(assign-v2.component.ts:152,259). - The CDK dialog therefore takes its parent injector from root, where the wizard's
inject(QmV2RootStore)(publish-wizard.component.ts:271) has no provider. - The wizard spec provides the store at TestBed root (
spec.ts:77), which hides the defect. - D-04: admin decisions are silently discarded. CONFIRMED, at four points.
- The Design view never binds the panel's
publishDecisionChangeoutput or passes itsimpactSummaryinput, so the panel never renders (design-v2.component.html:32-35). - The Assign view never fills
draftDecisionsin the wizard data (assign-v2.component.ts:259-276). - The wizard's
publish()sends only the change reason, expected version, impact IDs and fingerprint, never its confirmations, handling choices or mappings (publish-wizard.component.ts:375-387). Its spec asserts exactly this payload. - The web
DraftContentV2lacks the server'sdraftPublishDecisionfield (qm-v2.models.ts:62-71againstapi-client.generated.ts:9213), and the normaliser hardcodespublishDecision: null(qm-v2.normalise.ts:114).
New in PR-D¶
- D-05: cross-question edits are lost. All questions share one
debounceTime(1500)andswitchMapstream. Editing question B within 1.5 s drops question A's pending save, and a new save cancels one already in flight (qm-v2-root.store.ts:126-131). - D-06: the properties panel shows published values and reverts typing. Static reading, high confidence.
- An effect copies
q.textand the other fields into the local edit state every timequestion()emits (properties-panel.component.ts:130-139). selectedQuestionbuilds a new object wheneverdetailsEntityMapchanges, and each successful autosave changes it (updateEntity … hasDraftChanges).q.textis the published text; the draft lives indraftContent.- D-07: the Impact & Mapping effect re-triggers itself. It reads and writes
optionMappingsin the same effect, with a new array on each run, so it is expected to loop until Angular's NG0103 guard stops it (impact-mapping-panel.component.ts:161-176). Static reading; not run. - D-08: the wizard is not zoneless-safe, and its conflict step is a stub.
- An effect writes the plain fields
publishingandpublishErrorin an OnPush component (publish-wizard.component.ts:286-299). Under main's zoneless switch the view would not refresh. hasConflictsalways returns false (:336-338), so step 4 never blocks.- D-09: committed conflict markers. Two blocks in
docs/architecture/dependency-map.md:29-65@0983307, introduced by merge0983307. Main has none. - D-10: a stale revert of main's docs. At
0983307,docs/architecture/mongodb-reference.mdlacks main's "Driver Connection Behaviour" section:rg -c directConnectiongives 0, against 5 atde8e312and at main. Itsupdated:date also moves back to 2026-03-07. - D-11: hardcoded relative
/api/v2/...URL. It bypasses the generated client andapiOrigin(annotation-impact.service.ts:52-55), and the Assign view'ssubscribehas no error branch (assign-v2.component.ts:257). - D-12: the server's
SaveDrafthas no base revision or etag, so the last write wins (QuestionManagementV2Controller.cs:58-86@0983307). This is PR-C code, listed here because the designer depends on it. It contradicts RD-R26.
New in #2387¶
- D-13: the head does not compile.
navbar.service.tsandin-view-directive.directive.tswere committed at repo-rootsrc/app/..., an artefact of the repository-restructure merge.- The code imports them through
@core/…and@shared/…, which map tosrc/services/web/src/app/*(src/services/web/tsconfig.json:7-18@c81426c), for example atstage-assign.component.ts:48-49. - CI agrees: "Test Web (Angular)" failed on the head.
- D-14: debug output in the user-facing editor. Visible
<pre>dumps (edit.component.html:6-27) andTEST:validation copy (edit.form-validations2.ts:21). - D-15: wrong
computedFromPrevioustests. They expect a memoisedcomputedwith no dependencies to change between reads (signal.utils.spec.ts:24-50). - D-16: operator-precedence bug.
annotationCount ?? 0 > 0evaluates asannotationCount ?? false(question-node.component.html:114). - D-17: private Material internals overridden in a
MatOptionsubclass (new-option.component.ts:39-62). - D-18: breaking utility change and scratch code. The
mapFnsignature change and the scratch typesOriginalType,const abandconst x(util.ts:467-526).
The earlier "known facts" also list C's and B's defects, which belong to other auditors. Nothing in PR-D's own commit fixes them.
4. Superseded by main since the PRs were written¶
- AF2 scaffold.
- Main lifted D's
AF2/scaffold in74dffd658(6 August 2026); its plan records the source as71b179cb9and says the PR-D recut must delete the duplicate (2026-08-06-af2-phase2-foundation.md:82,95,125). - Main's AF2 now has 157 files:
annotation-form-v2.store.tsalone is 3,123 lines longer than PR-D's. - Active-reviewer tracking web.
study-presence/has been on main since19d266c70(April 2026), as have the idle and surplus banners, the surplus dialog anddocs/features/signalr-active-reviewer-tracking.md.- The new model moves the timeout and limits to the form (D2-07).
- Service-classification ADR. Main has
ADR-009-domain-vs-application-service-classification.md; PR-D adds the same topic as an ADR-008. - The current new editor (
QM/), changed since #2387's merge-base: -
3582 (live question-assignment locks) and #3594 (locks kept on rollback);¶
-
3572 (fresh answer counts) and #3778 (route-owned question-count SignalStore);¶
-
4023 (Material 3 question-preview colours in both modes) and #3454 (theme toggle);¶
-
3406 (category guidance), #3349 and #3362 (zoneless inventory and spec providers);¶
-
2772 and #2739 (answer-label authoring), #2737 (AF2 preview host);¶
design.store.ts+349/−98.
The editor still uses native HTML5 DragEvent (QM/design/annotation-question-tree-drag-drop.feature.ts:125-226),
which AC-R1a-12 replaces.
- Navigation and shell. #3502 (top bar moved to Material), #3454 and #3468 (theme toggle),
#3867 (capabilities), #2712 and #2716 (responsive shell); drawer.scss deleted.
- Help-URL guard. Main removed the hard-coded help.syrf.org.uk links from the QM design and
assign templates (no-hardcoded-help-urls.spec.ts). #2387's base still has them in files it
touches, so a rebase must keep main's version.
- newQuestionManagement. On main this flag shows the current new editor
(project-nav.component.ts:708-716), which makes PR-D's reuse of it a collision.
- Platform.
- Angular 22.1 and ngrx 21.1.1.
- Zoneless-ready bootstrap and zoneless discipline specs.
- Theme-token guard: --syrf-* roles must be defined; no literal colours.
- app-page-shell and app-page-state (shared/page-shell/).
- GuardedReviewSettings "Apply anyway" prompts, which are the reuse target for the impact
preview.
5. Closure-note drafts¶
#2575 (QM v2 PR-D).
Harvested into the October 2026 plan, not merged:
- the reviewer's post-publication alert, as input to the R2c in-form alerts (
T-RD-05);- the publish base-version and impact-fingerprint check, as the R2c preview-digest recheck;
- the QM v2 UI specification, publishing UX and prototype, as design references for designer history and diff (AC-R2c-27) and the keyboard model (AC-R1a-12).
The AF2 scaffold already lives on main (
74dffd658, August 2026). The active-reviewer web code merged by another route. The per-stage Assign and publish model, the root store and the properties panel are superseded by the owner-session specifications (SF1, Q-34, OS-A01), and so are the April release docs. The defects found are recorded in the harvest map so they are not reintroduced: the wizard's injector error, the no-op undo, the autosave that wipes options and the discarded admin decisions.
#2387 (QM child visualisation and assign tree).
Harvested:
- the child-question validity rule (a child condition naming a parent option that no longer exists), as the R1a editor warning and the R2a composition refusal (AC-R2a-24, FX-VM-10), re-keyed on option IDs;
- the
computedPrevioustests, for main's signal utilities.The sticky flat Assign tree, the template-driven validation framework, the
MatOptionsubclass and the nav changes are superseded by main (live assignment locks, Material 3 navigation) or retired because forms replace per-stage question selection. Thanks to Mala K and Chris for the 2024 child-visualisation work.
6. Proposed tracker rows¶
| Suggested ID | Scope | Release | Dependencies | Acceptance evidence |
|---|---|---|---|---|
Attach to T-RD-02 |
Server refusal of compositions with broken child conditions (H-TREE-01); lifecycle indicator and version chip on question cards (H-WEB-04) | R2a | F1a (C4 option IDs) | AC-R2a-24; FX-VM-10; UX-AE26 baselines |
Attach to T-RD-05 |
Add AC-R2c-27 (history with a per-field diff; version number on each card; H-WEB-18 as design input) to the row's evidence. In-form post-publication alert (H-WEB-11), digest recheck (H-WEB-08), preview change markers (H-WEB-10) | R2c | T-RD-04; T-UX-06 |
AC-R2c-07, 16, 20r, 27, 35; RD-AE19; UX-AE22 |
Attach to T-UX-06 |
The impact-preview pattern takes H-WEB-07's step copy and H-WEB-08's recheck flow, built on GuardedReviewSettings |
R2c | T-RD-05 |
UX-R34, UX-R35; AC-R2c-35 |
T-RD-10 (new; no row covers AC-R1a-06, 07 or 12) |
R1a hardening of the current new editor, on the legacy API: filtered-options parity (AC-R1a-06; H-TREE-02 as reference); re-enable the 17 excluded specs and the vitest exclusion and remove debug text (AC-R1a-07); CDK pointer drag with a keyboard alternative and a no-DragEvent guard (AC-R1a-12); client warning for broken child conditions (H-TREE-01); signal.utils.spec (H-TREE-07) |
R1a | G0 dossier item R1a-1 (audit of #3934, #2781, #2387); hold lift | AC-R1a-06, 07, 12; Firefox and WebKit smoke (UX-R44) |
| Existing G0-D4 and R1a-1 items | Closure of #2575 (with the stack) and #2387 using the §5 notes | G0 | Hold lift; Chris's authorisation (PKG/g0-dossier.md G0-D4) |
Closure comments link the harvest map |
7. Owner-level questions¶
None. One premise correction for the harvest map, which needs no owner decision: main is not
zoneless by default. It is zoneless-ready behind zonelessChangeDetection (default false, set in no
environment), with zoneless discipline enforced by spec guards (src/services/web/src/main.ts:525;
env-mapping.yaml:1529-1533 @7673ed0).