Skip to content

AF2 Phase 2 Implementation Plan — Foundation

Goal: Establish the default-off, current-backend-compatible foundation for the Accelerated Annotation Form v2: a generated annotationFormV2 flag, a form-scoped SignalStore, injectable current-v1 data and persistence adapters, and the reusable data-model-neutral scaffold lifted from the dormant Question Management v2 work.

Outcome: Across two sequential PRs, the lifted scaffold and its six controls compile on Angular 22 behind a default-off flag. Draft answers and UI state belong to the form-scoped store, while all reads and writes cross explicit adapter contracts. With the flag off, the existing annotation form remains byte-for-byte on the active path.

PR shape: Two focused, sequential feat(annotation) PRs, as allowed by the programme roadmap:

  1. Phase 2A (#2702): Angular 22 re-baseline, generated false-default flag, neutral contracts/current-v1 adapters, and normalized form-scoped store.
  2. Phase 2B: scaffold/control lift, decoupling, and isolated default-off assembly. Create it only after Phase 2A merges.

Phase 3 will add the first user-visible stage-review integration and is deliberately not part of either Phase 2 PR. The split keeps the 33-file/2,264-line source scaffold review separate from new state and persistence semantics.

Constraints

  • Work only in the isolated Phase 2 PR worktree.
  • Keep the flag false in every generated default and environment value. Do not activate it in preview, staging, or production.
  • Preserve the existing IEmittedAnnotations, ISession, IAnnotation, and IOutcomeData compatibility contract.
  • Use the existing session-submit flow. Do not add backend endpoints, schemas, migrations, per-answer server autosave, question-version pinning, transition alerts, or concurrency behavior.
  • Lift only the reusable scaffold from the dormant QM v2 branch. Do not merge or otherwise revive that backend stack.
  • Do not add Phase 3 routing, stage eligibility, autosave/navigation guards, unit CRUD, source-panel behavior, virtualization, or performance assertions for the unfinished form.

Issue #2495 tracks this foundation slice; the AF2 programme originated from issue #1125.

Gate 0 — Angular 22 performance re-baseline

  • Run the Track 0 Playwright benchmark against current origin/main (b57a75264 or a descendant with no annotation-form behavior change) using local E2E-only infrastructure or the normal full-E2E CI workflow.
  • Verify and update the existing docs/planning/annotation-form-perf-baseline.md with initial render, study navigation, keystroke-to-paint p95, and tab-switch latency; append the Angular 22 row without replacing the pre-upgrade result.
  • Treat this as the comparison baseline for Phase 3; Phase 2 remains default-off and must not change v1 measurements materially.

A local attempt was stopped before any data write after the harness resolved a non-isolated MongoDB configuration. Its services and containers were torn down. Gate 0 was therefore run only through the repository's normal full-E2E CI workflow, where the benchmark has isolated test infrastructure.

The isolated full-E2E run 31059742925 passed on commit 4c1af0a20. Its successful benchmark attempt recorded 8210 ms initial render, 2665 ms study navigation, 111.3 ms keystroke-to-paint p95, and 3679 ms tab switching on GitHub Actions ubuntu-latest / Angular 22.

Task 1 — Generated annotationFormV2 flag

  • Add SYRF__FeatureFlags__AnnotationFormV2 / featureFlags.annotationFormV2 to src/charts/syrf-common/env-mapping.yaml, categorized as a web UI feature with a false default.
  • Run pnpm run generate:flags from src/services/web and commit every generated TypeScript and JSON output plus checksum updates.
  • Verify chart/default/local/E2E configuration all remain false and pnpm run validate:flags passes.
  • Add or update focused config/parser/selector tests where the generator's existing coverage does not prove the new flag.

Task 2 — Adapter seam for the current model

  • Define an injectable AnnotationFormDataSource contract for questions, current annotations, annotation units, and option sources needed by the form.
  • Define an injectable AnnotationFormPersistence contract for save, delete, and revert using the existing form/session payloads and result semantics.
  • Implement current-v1 adapters using existing stage-review selectors, actions, and effects; global ngrx remains load-in/save-out rather than a per-keystroke mirror.
  • Unit-test contract mapping, emitted identifiers and payloads, error propagation, and save/delete/revert dispatch behavior.

Task 3 — Form-scoped SignalStore

  • Define AnnotationFormV2Store as a non-root injectable for one instance at the Phase 2B form-component boundary.
  • Store normalized draft answers and a per-unit draft index, open-unit IDs, selected category, page, and request/error state.
  • Make the store own answer drafts and row-scoped conditional-visibility reads so Phase 2B can remove QuestionListComponent's local Map.
  • Keep immutable updates and identity-preserving computed view models so a single answer change does not rebuild unrelated units.
  • Make page size an input/configured store value rather than a hardcoded constant.
  • Unit-test draft updates, category/page transitions, open-unit behavior, conditional visibility, page-size changes, and adapter hydration.

Phase 2B: Task 4 — Lift and decouple the reusable scaffold

  • Lift the tracked shared/annotation/annotation-form-v2/ scaffold from commit 3142092f7 in PR #2461 (later consolidated at 71b179cb9 in PR #2575) without bringing backend or admin-v2 code with it.
  • Replace its import of QM v2 design-store constants with local, model-neutral annotation-form category definitions.
  • Retain the six standalone controls and their value-in/event-out boundary; update them for stable Angular 22 Signal Forms where required.
  • Convert option inputs from raw string[] assumptions to the current option-object model without discarding stable option identity.
  • Remove placeholder console logging and park Phase 3 actions as typed outputs or unavailable UI rather than fake behavior.
  • Exclude the version-transition alert from the lift: it belongs to later QM v2 versioning semantics, not AF2 Phase 2.
  • Add focused component tests proving the scaffold compiles, renders representative controls, and delegates answers to the store.

Phase 2B: Task 5 — Default-off assembly and compatibility proof

  • Register the adapters and flag-read seam without replacing the active v1 stage-review form.
  • Prove that annotationFormV2=false leaves the v1 component path and existing save/remove/revert behavior unchanged.
  • Provide a test-only or isolated component-harness path that compiles the Phase 2 scaffold without presenting an incomplete form to users.
  • Document that the future QM v2 PR-D recut must remove its duplicate annotation-form-v2/ directory after this PR lands.

Phase 2A validation gates

  • Focused flag, adapter, and store tests pass.
  • Full Angular/Vitest suite with coverage passes.
  • Angular production build passes.
  • Generated-file/pre-commit validation passes with no unexplained diff.
  • Playwright discovery still includes the Track 0 performance scenario.
  • Full E2E smoke or full-E2E label required by review is green.
  • Independent re-review confirms all findings are resolved and there is no active-path behavior, backend, data, UI-scaffold, or deployment change.

Local validation on Node 22.23.0 / pnpm 10.30.0: 30 focused tests passed; the full Angular builder suite passed 1,522/1,522 with coverage before review fixes; the production build passed; generated flags, generated env blocks, repository checksums, documentation, and git diff --check passed; and the full .NET Release build passed with zero errors. The isolated full-E2E workflow passed 14/14 scenarios (two flaky scenarios recovered within the normal retry policy), and its Angular 22 performance measurements are recorded above. Repository-wide ESLint remains unavailable after the Angular 22 / ESLint 9 upgrade because the repository still exposes only the legacy .eslintrc.json; that platform wiring issue predates this diff and is left to its owning track.

Phase 2B validation gates

  • All six lifted controls and the scaffold harness pass focused tests, including explicit execution of specs under shared/annotation.
  • Static checks prove there are no question-management-v2 imports, /api/v2 calls, or version-transition symbols in the lifted subset.
  • Full Angular/Vitest suite with coverage and the Angular production build pass.
  • The active stage-review v1 path remains unchanged and normal E2E smoke passes.
  • Independent review confirms the lift consumes Phase 2A contracts/store and introduces no backend, data, or flag-activation change.

Rollback

Revert the affected Phase 2 PR. Because the flag remains false everywhere and there are no backend or data changes, rollback removes only dormant frontend foundation code and generated false-default configuration.

Dependency handoff

Phase 2B may start only after Phase 2A is merged and cleaned up. Phase 3 may start only after Phase 2B is merged and cleaned up. Its detailed plan must use the Angular 22 baseline recorded in Gate 0, preserve these adapter contracts, and introduce the first stage-scoped, non-experiment user-visible route behind annotationFormV2.

After Phase 2B lands, the dormant QM v2 PR-D recut must delete its duplicate shared/annotation/annotation-form-v2/ scaffold and consume the AF2-owned implementation instead; it must not reintroduce a second copy or couple AF2 to the QM v2 backend stack.

Outcome-data hydration and Experiment controls remain Phase 4 work. Phase 2A's read adapter intentionally covers annotation drafts only; callers must not use the dormant scaffold for extraction stages until the Phase 4 adapter contract is extended and parity-tested.