Conversation
Add the AWOS spec triad for the Forms & Validation roadmap item: functional spec, technical considerations, and the vertically-sliced task list (7 runnable slices). No source changes yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nput/textarea (spec 039 slice 1) Scaffold the ./forms subpath (@Forms alias, package.json exports, rollup entry, root barrel). Implement NgModelController (the $viewValue/$modelValue pipeline, $parsers/$formatters/$render/$setViewValue, state tracking, per-key $setValidity, $isEmpty, model->view feedback guard), the ngModel directive (model write-back via buildParentWriter, $formatters watch), the single input directive + text handler + textarea, ngChange, and append-only state-class toggling. Registered on ngModule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add FormController (child add/remove, per-key validity aggregation, dirty/pristine/submitted propagation, $$renameControl) and the shared form/ngForm directive factory (named-form scope publish, named-control publish onto the form, submit -> $setSubmitted + ngSubmit with preventDefault when no action, nested-form bubbling via ?^^form). Wire ngModel to register with the enclosing form (nullFormCtrl fallback keeps form-less ngModel working) and deregister on $destroy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (spec 039 slice 3) Add the inputType handlers: number/range (numeric parse, invalid keeps bad values out of the model, range clamp), checkbox (boolean + ng-true-value/ ng-false-value), radio (group-shared model), and the date/datetime-local/ time/month/week family (Date model, per-type parse/format in input-date.ts, timezone-parameterized for the Slice 6 handoff), plus no-model hidden/button/ submit/reset and baseline string handlers. Single input directive dispatches on type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the select directive + SelectController (single value, multiple->array, unknown-option handling), ngOptions (grammar parse in ng-options-parse.ts; label/value/group-by/disable-when/track-by over array + object collections, regenerated on $watchCollection), and ngList ($parser/$formatter with configurable/regex delimiter). Fixes uncovered during Slice 4: - ng-options.ts: add missing safeString helper (ngOptions generated zero options due to a ReferenceError swallowed by the digest). - ng-options-parse.ts: bare `expr for item in items` binds the item, not the label expression, as the option value (AngularJS parity). - directive-collector.ts: same-priority sort now tie-breaks on directive name before index (AngularJS byPriority parity), so ngModel links before select and select's `require: ?ngModel` resolves. - ng-event-directives.test: a <form> now suppresses native submit by default via the form directive (forms spec 039 §2.3) when it has no action; updated the spec-026 expectations + added the action-gated case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…slice 5) Extend NgModelController with the validation engine: $validators/ $asyncValidators maps, $validate(), $$runValidators (extracted to validation.ts) — sync-first, async only if all sync pass, tri-state $setValidity (true/false/undefined-pending), $pending + ng-pending, and a generation counter cancelling stale async passes. Invalid values are kept out of the scope model by default (the $$allowInvalid seam defaults false for the Slice 6 ngModelOptions handoff). Add built-in validator directives (required/ngRequired, ngMinlength/ngMaxlength, pattern/ngPattern, min/max) in validators.ts and the email/url/number/date type validators in input-validators.ts. Model->view watch gains the ngModelWatch divergence guard. EXCEPTION_HANDLER_CAUSES stays 13 (validator errors reuse '$compile'). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mezone (spec 039 slice 6) Add the ngModelOptions directive + resolved-options helper and thread it through the pipeline: updateOn (configurable commit events + buffering via $$updateEvents), debounce (number or per-event map, $timeout-backed, supersede-reset, cancel on $destroy, $$phase-guarded), allowInvalid (flips the Slice 5 $$allowInvalid seam), getterSetter (model expression as read/write fn), and timezone (threaded into the date input parse/format). Options inherit from an ancestor ngModelOptions via a $$ngControllers stash walk (parity note: a structural departure from AngularJS's ^^?ngModelOptions require wiring — the same-element ngModel controller seam runs before the options controller stashes). 118 forms tests; src/compiler green; EXCEPTION_HANDLER_CAUSES stays 13. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p (spec 039 slice 7) Add forms-types.test.ts (25 expectTypeOf contract tests) and parity.test.ts (13 upstream ngModelSpec/formSpec edge-case vectors — all green against the shipped runtime, no bugs found). Add src/forms/README.md. Update CLAUDE.md (./forms module row, 8 invariants incl. the ng-submit/form suppression parity change, the directive-collector byPriority name tie-break, ngModelOptions stash-walk resolution, and EXCEPTION_HANDLER_CAUSES still 13; 12 where-to-look rows). Tick the Forms & Validation roadmap block (spec 039 — shipped). 156 forms tests; src/compiler green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All acceptance criteria verified against the 156 forms tests + full green regression (4223 tests). Set functional-spec + technical-considerations Status to Completed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Findings from a four-way adversarial audit of PR #44, all verified and fixed with regression tests (19 new tests; full suite 4242 passing): - transclude-fn: invoke cloneAttachFn BEFORE linking the clone (AngularJS publicLinkFn parity). Structural directives insert the clone inside that callback, so require '^'/'^^' and the $$ngControllers stash walks now resolve real DOM ancestors — an <input ng-model> inside ng-if previously never registered with its enclosing <form>. - form: tri-state FormController.$setValidity — a control's in-flight async rule now populates the form's $pending[key] set + ng-pending (undefined was coerced to valid at the ngModel bubble site); $removeControl clears pending sets; $$renameControl re-slots the named control on the form instance (identity-guarded), and the ngModel $destroy unpublish is identity-guarded too. - input: numeric min/max validate the PARSED modelValue (a '.' view value parses to NaN → empty → passes, upstream parity); radio watches its ng-value expression so a data-driven value re-renders, and commits under the 'change' debounce trigger; native minlength/maxlength attribute spellings now register (the factories already read them). - ngOptions: track-by matching is by track-by key evaluated on the candidate model value (fresh non-identical objects select), the multiple-select hook path is actually reachable (writeMultiValue now routes through ngOptionsHooks), an author-supplied empty option survives regeneration (null model selects it / reads back null), and a mismatched model shows the synthetic unknown option ('?'). - select: interpolated option value="{{…}}" re-keys on change. - ngModelOptions: a lone updateOn: '*' resets to the handler's default commit events instead of making the control uncommittable. - ngModel: the model watch re-runs validators only inside the render branch (upstream ngModelWatch parity) so a live parse error is not cleared when the formatted value matches the on-screen text. - ngList: joins with the RAW attribute value (upstream value.join(ngList)); default is ', '. - validation: the $q.all rejection branch finalizes as failed instead of leaving the control permanently ng-pending. - barrel: root index re-exports the full ./forms surface (SelectController(Impl), validators, model-options helpers, …). - docs: CLAUDE.md invariants (tri-state form validity, cloneAttach ordering) + forms README (ngList join, native length names); ng-options-parse regex-group comment corrected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ceOf util + shared test dom-guards (PR-44 audit) The forms suite (and two forms src sites) narrowed querySelector / firstElementChild / indexed-collection reads with bare `as HTML*Element` casts, silencing both the null/undefined case and the wrong-element case. - @core gains `asInstanceOf(value, ctor)` — the throwing companion to the boolean is* guards (compiler's node-guards.ts precedent), any-free via an `abstract new (...args: never[])` constructor signature. - New shared test helper `@compiler/__tests__/dom-guards` (the test-helpers.ts precedent): asHtmlElement / asInput / asTextarea / asForm / asSelect / asOption wrappers. - All eight forms test files now guard instead of cast; the src-side narrows in input.ts / input-types.ts / select.ts / ng-options.ts go through asInstanceOf, so a violated tag invariant fails loudly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 tasks done
Mgrdich
added a commit
that referenced
this pull request
Jul 6, 2026
…row routing, doc consistency Post-merge cleanup on top of spec 039 (PR #44): - Extract the `$$phase`-guarded `$apply`/`$evalAsync` dispatch into a single shared `src/compiler/apply-phase-guarded.ts` helper. The spec-026 event directives, forms `applyDuringEvent` (input-types), `form` submit, and `select`'s `applyChange` now all route through it with their own cause token instead of duplicating the try/catch. - Fix `select`: `applyChange` previously lacked the try/catch, so a throwing `$parser` during the native change commit escaped the listener without reaching `$exceptionHandler`. It now injects `$exceptionHandler` and routes via `'$compile'`. Adds a regression test in select.test.ts. - De-duplicate `stashController` — remove the local copy in compile.ts and import the shared one from element-slots.ts. - Rename runControllerSeam-widening.test.ts → kebab-case run-controller-seam-widening.test.ts (file-naming convention). - Add inline justifications to the eslint-disable comments in core/utils.ts (isFunction, TypedArray copy cast, dynamic delete). - Doc consistency: update the stale "EXCEPTION_HANDLER_CAUSES stays at 10" comments across compiler/controller/bootstrap error files (the tuple is now 13 after spec 037) and refresh CLAUDE.md + roadmap.md. typecheck + lint clean; 4243 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mgrdich
added a commit
that referenced
this pull request
Jul 6, 2026
…row routing, doc consistency (#45) Post-merge cleanup on top of spec 039 (PR #44): - Extract the `$$phase`-guarded `$apply`/`$evalAsync` dispatch into a single shared `src/compiler/apply-phase-guarded.ts` helper. The spec-026 event directives, forms `applyDuringEvent` (input-types), `form` submit, and `select`'s `applyChange` now all route through it with their own cause token instead of duplicating the try/catch. - Fix `select`: `applyChange` previously lacked the try/catch, so a throwing `$parser` during the native change commit escaped the listener without reaching `$exceptionHandler`. It now injects `$exceptionHandler` and routes via `'$compile'`. Adds a regression test in select.test.ts. - De-duplicate `stashController` — remove the local copy in compile.ts and import the shared one from element-slots.ts. - Rename runControllerSeam-widening.test.ts → kebab-case run-controller-seam-widening.test.ts (file-naming convention). - Add inline justifications to the eslint-disable comments in core/utils.ts (isFunction, TypedArray copy cast, dynamic delete). - Doc consistency: update the stale "EXCEPTION_HANDLER_CAUSES stays at 10" comments across compiler/controller/bootstrap error files (the tuple is now 13 after spec 037) and refresh CLAUDE.md + roadmap.md. typecheck + lint clean; 4243 tests pass. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers the complete Forms & Validation layer (Phase 3 roadmap item, spec 039) — the largest remaining templating gap. A developer can now bind any form control to their data with two-way binding, group controls into forms that aggregate validity, validate with built-in and custom (sync + async) rules, and style controls via the full AngularJS state-class surface. Ships as a new
./formssubpath; all directives are coreng(registered onngModule). Ran through the AWOS triad and marks the spec Completed with every acceptance criterion checked. Full suite green: 211 files, 4223 passed / 26 skipped;lint/format:check/typecheck/buildall pass.EXCEPTION_HANDLER_CAUSESstays at 13 (forms errors reuse'$compile').Built in 7 vertical slices:
NgModelController: the$viewValue/$modelValuepipeline ($parsers/$formatters/$render/$setViewValue), model write-back viabuildParentWriter(non-assignable →'$compile'), the model→view feedback guard, andngChange.FormController+form/ngForm: per-key validity aggregation, dirty/pristine/submitted propagation, named-form/named-control publishing, nested-form bubbling, submit →$setSubmitted+ngSubmit(native submit suppressed by default when the form has noaction), and anullFormCtrlso form-lessngModelworks.inputdirective dispatching ontypeinto an internal handler registry (parity) — number/range→Number, checkbox→boolean (+ng-true-value/ng-false-value), radio→group value, date/datetime-local/time/month/week→Date (timezone-aware), plus no-model hidden/button/submit/reset andtextarea.select+ngOptions+ngList: single +multiple(→array) select, the fullngOptionsgrammar (label/value/group-by/disable-when/track-by over array + object collections), andngListstring↔array.$validators/$asyncValidatorsengine (sync-before-async, tri-state$setValidity,$pending+ng-pending, generation-counter stale-async cancellation) + built-ins (required/ngRequired,ngMinlength/ngMaxlength,pattern/ngPattern,min/max,email/number/url).ngModelOptions:updateOn,debounce($timeout-backed,$$phase-guarded, cancel-on-destroy),allowInvalid,getterSetter,timezone.classListtoggling ofng-valid/ng-invalid,ng-dirty/ng-pristine,ng-touched/ng-untouched,ng-empty/ng-not-empty, per-ruleng-valid-<key>/ng-invalid-<key>,ng-pending, andng-submitted— never stripping author classes.$animatedeferred to Phase 4; toggles are synchronous.New importable subpath
./forms— exports theNgModelController/FormController/NgModelOptions/SelectControllerand validator contract types; emits ESM + CJS +.d.ts, wired throughtsconfig,vitest,package.jsonexports,rollup, and the root barrel.Notes for reviewers
CLAUDE.md):<form>with noactionnow suppresses native submit by default via theformdirective — this supersedes the spec-026 "ng-submitdoes NOT auto-preventDefault" note. ThengSubmitdirective itself still doesn'tpreventDefault; theformdirective owns suppression +$setSubmittedand does not double-run thengSubmitexpression. The spec-026 test was updated accordingly (+ anaction-gated case).directive-collector.tsnow tie-breaks same-priority directives on directive name before collection index (AngularJSbyPriorityparity), songModellinks beforeselectandselect'srequire: '?ngModel'resolves.ngModelOptionsresolution walks the$$ngControllersstash rather than^^?ngModelOptionsrequire (the same-elementngModelcontroller seam runs before the options controller stashes) — the one structural departure from upstream, noted inCLAUDE.md.safeStringinngOptions; barengOptionsvalue binding; the directive-sort tie-break above).Test plan
pnpm lint— cleanpnpm format:check— cleanpnpm typecheck— cleanpnpm test— 4223 passing / 26 skipped (211 files); forms 156/156pnpm build— succeeds (dual ESM/CJS +.d.ts;./formsemits all three)Docs
src/forms/README.md— new: controllers, pipeline, state classes, input-type matrix, validators,ngModelOptions, worked example.CLAUDE.md—./formsmodule row, 8 non-obvious invariants, 12 "where to look" rows.context/product/roadmap.md— Forms & Validation block marked shipped.🤖 Generated with Claude Code