Skip to content

Read-only audit of #2575 at 098330759 and #2387 at c81426c44. 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), then 71b179c (PR-D, 17 April 2026);
  • then five main merges (941cfee … 0983307, the last on 24 April, bringing in main at de8e312).
  • Relation to the stack.
  • C's head 1b93cbb contains 874a421, but the two branches merged main separately. Their merge-base is the main commit de8e312, and C is not an ancestor of D.
  • PR-A's head has 45 non-merge commits after 36190433d that D never absorbed (git rev-list --count --no-merges 36190433d..audit/pr2572).
  • D's own work. git diff 874a421 71b179c gives 161 files (+38,515/−4,235). D's main merges changed only one of those files afterwards: merge 0983307 left conflict markers in docs/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-openiddict files and 125 docs/planning/_archive files) and A/B/C content. None of it is D's work. git diff de8e312..0983307 gives 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 from 71b179c in 74dffd658 (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 newQuestionManagement and admin/questions, which main uses for the current new editor.
  • Platform.
  • Main runs Angular 22.1 and @ngrx/signals 21.1.1; the PR uses Angular ^21.2.4.
  • Change detection on main is chosen at bootstrap (src/services/web/src/main.ts:525). The zonelessChangeDetection flag 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.scss has 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 no app-page-shell or app-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 ": review, confirm impact, handling, conflicts (a stub), publish 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: withUndoRedo does nothing. CONFIRMED.
  • The store has only named collections (questions, versions, details).
  • Called with no options, withUndoRedo() tracks the default keys entityMap, ids, selectedIds and filter (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 ?? [] and answerOptionFilters: … ?? null, and never falls back to the question's own options. The QuestionNode view 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.
  • QmV2RootStore is provided only on the lazy route (question-management-v2.routes.ts:19).
  • The Assign view injects the root MatDialog and calls open with no injector or viewContainerRef (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 publishDecisionChange output or passes its impactSummary input, so the panel never renders (design-v2.component.html:32-35).
  • The Assign view never fills draftDecisions in 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 DraftContentV2 lacks the server's draftPublishDecision field (qm-v2.models.ts:62-71 against api-client.generated.ts:9213), and the normaliser hardcodes publishDecision: null (qm-v2.normalise.ts:114).

New in PR-D

  • D-05: cross-question edits are lost. All questions share one debounceTime(1500) and switchMap stream. 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.text and the other fields into the local edit state every time question() emits (properties-panel.component.ts:130-139).
  • selectedQuestion builds a new object whenever detailsEntityMap changes, and each successful autosave changes it (updateEntity … hasDraftChanges).
  • q.text is the published text; the draft lives in draftContent.
  • D-07: the Impact & Mapping effect re-triggers itself. It reads and writes optionMappings in 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 publishing and publishError in an OnPush component (publish-wizard.component.ts:286-299). Under main's zoneless switch the view would not refresh.
  • hasConflicts always 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 merge 0983307. Main has none.
  • D-10: a stale revert of main's docs. At 0983307, docs/architecture/mongodb-reference.md lacks main's "Driver Connection Behaviour" section: rg -c directConnection gives 0, against 5 at de8e312 and at main. Its updated: date also moves back to 2026-03-07.
  • D-11: hardcoded relative /api/v2/... URL. It bypasses the generated client and apiOrigin (annotation-impact.service.ts:52-55), and the Assign view's subscribe has no error branch (assign-v2.component.ts:257).
  • D-12: the server's SaveDraft has 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.ts and in-view-directive.directive.ts were committed at repo-root src/app/..., an artefact of the repository-restructure merge.
  • The code imports them through @core/… and @shared/…, which map to src/services/web/src/app/* (src/services/web/tsconfig.json:7-18 @c81426c), for example at stage-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) and TEST: validation copy (edit.form-validations2.ts:21).
  • D-15: wrong computedFromPrevious tests. They expect a memoised computed with no dependencies to change between reads (signal.utils.spec.ts:24-50).
  • D-16: operator-precedence bug. annotationCount ?? 0 > 0 evaluates as annotationCount ?? false (question-node.component.html:114).
  • D-17: private Material internals overridden in a MatOption subclass (new-option.component.ts:39-62).
  • D-18: breaking utility change and scratch code. The mapFn signature change and the scratch types OriginalType, const ab and const 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 in 74dffd658 (6 August 2026); its plan records the source as 71b179cb9 and 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.ts alone is 3,123 lines longer than PR-D's.
  • Active-reviewer tracking web.
  • study-presence/ has been on main since 19d266c70 (April 2026), as have the idle and surplus banners, the surplus dialog and docs/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 computedPrevious tests, for main's signal utilities.

The sticky flat Assign tree, the template-driven validation framework, the MatOption subclass 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).