fix(autocomplete)!: errors following a full review of the component (#DS-5482) - #1985
fix(autocomplete)!: errors following a full review of the component (#DS-5482)#1985artembelik wants to merge 4 commits into
Conversation
Four accessor inputs and one write-target input survived the automated signal
migration, on the panel and its trigger.
`classList` was the odd one: declared `@Input('class')`, its setter accumulated
class names into an object the panel template binds, and cleared the host's
`className` as a side effect. It is an internal `computed` now, fed by a `class`
signal input — `class="…"` on `<kbq-autocomplete>` keeps working as before.
`isOpen` was asymmetric: the setter stored a flag while the getter returned that
flag *and* `showPanel`, so writing `true` and reading it back returned `false`
whenever the panel had no options. It is a `computed` over `attached()` and
`showPanel()` now, with `attached` as the writable half the trigger owns.
`autoActiveFirstOption`, `openOnFocus` and `kbqAutocompleteDisabled` gained
`booleanAttribute`, and the panel id comes from the CDK `_IdGenerator` — it is the
value of the trigger's `aria-owns`, so its shape changes from
`kbq-autocomplete-1` to `kbq-autocomplete-a1`.
`options` stays a `QueryList` content query: `ActiveDescendantKeyManager` and the
panel-closing stream both rely on its `changes` semantics.
BREAKING CHANGE: `KbqAutocomplete.displayWith`, `autoActiveFirstOption`,
`openOnFocus`, `showPanel` and `isOpen` are signals, `isOpen` is read-only,
`classList` is internal, and `KbqAutocompleteTrigger.autocompleteDisabled` is a
signal input. Generated panel ids changed shape. Reported and partly rewritten by
the `autocomplete-signals` schematic.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Visit the preview URL for this PR (updated for commit 4c25493): https://koobiq-next--prs-1985-khe5ircb.web.app (expires Mon, 07 Sep 2026 12:58:10 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c |
lskramarov
left a comment
There was a problem hiding this comment.
Code review — max effort
Ten finder angles plus a gap sweep over the component, the trigger, the spec and the new autocomplete-signals schematic. 15 findings, most severe first. Verified against the installed @angular/core 20.3.29 / @angular/cdk sources and the PR’s own CI logs.
Headline: the api check is red because tools/check-public-api-any/baseline.json was not re-approved (autocomplete: 9 → 12), and KbqAutocomplete.classList now throws on a non-string [class] binding that the old accessor silently ignored. The rest are in the schematic — it rewrites consumers’ source, so a wrong rewrite is worth more than a missed one.
The unit_tests failure is not this PR: tree-selection.component.spec.ts:1666 is already fixed on main by 0f6a3d9. Rebase.
🤖 Generated with Claude Code
lskramarov
left a comment
There was a problem hiding this comment.
Follow-up: gap sweep
A final pass over the diff, run against a real build of the PR head, turned up three more. Two of them land in the same SUMMARY array as earlier comments — with this one, all three notes ng update prints are factually wrong.
Things the sweep checked and found clean, for the record: api-extractor onlyCheck (the .api.md guard itself is up to date — the red api job is the separate check-public-api-any ratchet), prettier, cspell and eslint --max-warnings=0 on all 15 changed files, and jest on autocomplete.spec.ts (123), tags (222) and autocomplete-signals/index.spec.ts (16). should let the input override the token is not vacuous, zone is correctly reassigned after TestBed.resetTestingModule(), panel ids are unique and stable per instance, and dynamic [class] does correctly add and remove classes on the panel.
🤖 Generated with Claude Code
Classes on the panel used to accumulate: the old setter merged each `class` value into an object it never cleared, so a `[class]` binding that changed from "a" to "b" left the panel with both. The computed rebuilds the map from the current value, which is right — but the change was undocumented and untested, because the existing test binds a fixed class that passes either way. The schematic never counted a template-only consumer, so none of the three summary notes reached the audience they were written for. `attached.set((this.overlayAttached = …))` hid an assignment inside an argument, twice, with opposite values. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same four registry conflicts as the other review branches, at the append point main's six component-review PRs share with this one: - migrations.json / collection.json: kept main's entries and appended autocomplete-signals after them. - migration.en.md / migration.ru.md: kept main's Popover subsection and put Autocomplete before it, so section 18 stays alphabetical. Two things the merge surfaced rather than caused: - The section intro carried the "two waves" wording that the badge review rejected: it listed search-expandable, split-button and title as first-wave while they also have subsections below. Replaced with the wording agreed there. - `check-public-api-any` counted autocomplete 9 -> 12. The three are the `unknown` transform type of the `booleanAttribute` inputs autoActiveFirstOption, openOnFocus and autocompleteDisabled, so the baseline records them. main's Safari fix (preventScroll on the input refocus) merged into the trigger cleanly; its `isOpen` chained assignment is this branch's `attached` signal now, and the 124 autocomplete specs pass on the result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new autocomplete-signals migration can silently miss namespace-qualified type references (e.g. ns.KbqAutocomplete) and also fail to report them as unresolved, leaving broken consumer code without warnings.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR completes a full “component review” pass for autocomplete, finishing the signal-based API migration for both KbqAutocomplete (panel) and KbqAutocompleteTrigger (trigger), and adds an autocomplete-signals schematic to migrate consumer code (TS + templates) automatically as part of ng update.
Changes:
- Finalizes the
KbqAutocomplete/KbqAutocompleteTriggerpublic surface: remaining accessor inputs become signal inputs;isOpenbecomes a computed over new trigger-ownedattachedstate; panel IDs switch to CDK_IdGenerator. - Reworks how host
classis handled: accepts full Angular[class]input shapes via transform, computes internal class map, and preserves host-class clearing behavior. - Adds a new
autocomplete-signalsmigration schematic + tests, updates docs and API snapshots accordingly.
File summaries
| File | Description |
|---|---|
| tools/public_api_guard/components/autocomplete.api.md | Updates public API snapshot for signalized inputs and new attached/isOpen/hostClass surface. |
| tools/check-public-api-any/baseline.json | Adjusts any-usage baseline for the updated autocomplete API surface. |
| packages/schematics/src/migrations/autocomplete-signals/schema.ts | Adds schematic option typings (project + fix). |
| packages/schematics/src/migrations/autocomplete-signals/schema.json | Adds schematic schema (including fix defaulting behavior). |
| packages/schematics/src/migrations/autocomplete-signals/README.md | Documents what the migration rewrites, warns about, and how to run it. |
| packages/schematics/src/migrations/autocomplete-signals/index.ts | Implements the migration: TS receiver-based rewrites + template ref rewrites + warnings/summary. |
| packages/schematics/src/migrations/autocomplete-signals/index.spec.ts | Adds tests covering migration rewriting and warning behavior. |
| packages/schematics/src/migrations/autocomplete-signals/data.ts | Centralizes migration member lists, warning patterns, and summary notes. |
| packages/schematics/src/migrations.json | Registers autocomplete-signals for ng update @koobiq/components@20 (20.3.0-0). |
| packages/schematics/src/collection.json | Exposes autocomplete-signals as a runnable schematic with schema. |
| packages/components/autocomplete/autocomplete.spec.ts | Updates tests for new signals API, adds coverage for new class-handling semantics and token-vs-binding behavior. |
| packages/components/autocomplete/autocomplete.html | Updates panel template binding to use computed classList() signal result. |
| packages/components/autocomplete/autocomplete.component.ts | Implements reviewed autocomplete panel changes: signal inputs, computed state, _IdGenerator id, class normalization/transfer. |
| packages/components/autocomplete/autocomplete-trigger.directive.ts | Migrates trigger to signal inputs, uses attached.set(...), and updates reads (openOnFocus(), showPanel(), etc.). |
| docs/guides/migration.ru.md | Adds an Autocomplete subsection to the 20.3.0 component review migration guide (RU). |
| docs/guides/migration.en.md | Adds an Autocomplete subsection to the 20.3.0 component review migration guide (EN). |
Review details
Suppressed comments (1)
packages/schematics/src/migrations/autocomplete-signals/index.ts:436
- collectUnresolvedMentions only reports unresolved TypeReferenceNodes when the type name is a bare Identifier. Namespace-qualified mentions like
ns.KbqAutocomplete | undefinedwon’t be reported, so the migration can leave broken reads behind without any warning.
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && typeNames.includes(node.typeName.text)) {
if (!resolved.has(node)) report(node);
} else if (
- Files reviewed: 16/16 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /** Whether a type annotation refers to `typeName` directly (not through a union, array or type argument). */ | ||
| function isTypeReference(type: ts.TypeNode | undefined, typeName: string): boolean { | ||
| return !!type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === typeName; | ||
| } |
What
A full review of
autocomplete, in the same shape as the 20.3.0 component reviews. Four accessor inputs and one write-target input survived the automated signal migration, across the panel and its trigger.classListwas an@Input('class')that accumulatedIt mixed three concerns: the consumer's classes, the internal
kbq-autocomplete_visible/_hiddenpair, andcdk-keyboard-focused. Those are three signals and onecomputednow, behind aclasssignal input.class="…"on<kbq-autocomplete>keeps working exactly as before, including the host-clearing side effect.isOpendisagreed with itselfWriting
trueand reading it back returnedfalsewhenever the panel had no options. It is acomputednow —attached() && showPanel()— withattachedas the writable half the trigger owns, which is what the setter always meant.The rest
displayWith,autoActiveFirstOption,openOnFocusand the trigger'skbqAutocompleteDisabledare signal inputs. The last three gainedbooleanAttributein place ofcoerceBooleanPropertyor nothing._IdGeneratorinstead of a module-level counter. It is the value of the trigger'saria-owns, so the shape changes fromkbq-autocomplete-1tokbq-autocomplete-a1.setVisibility,emitSelectEvent,onKeydown,setScrollTop,getScrollTop,keyManagerandshowPanelare marked@docs-private: they are the trigger's interface to the panel, not the consumer's.Deliberately left alone
optionsstays aQueryListcontent query.ActiveDescendantKeyManagertakes one directly, andsubscribeToClosingActionsrebuilds the panel-closing stream offoptions.changes— a signal query would mean atoObservableper panel open, with the effect outliving the subscription. Not worth the risk in this PR.Migration
autocomplete-signalsruns fromng update @koobiq/components@20. It rewrites reads on receivers typedKbqAutocompleteorKbqAutocompleteTriggerand reports the rest.It also reports a gotcha worth calling out: binding
[autoActiveFirstOption]overridesKBQ_AUTOCOMPLETE_DEFAULT_OPTIONSeven when the bound value isundefined. The token default only applies to an input nobody bound. This one is easy to hit when converting a field write into a binding — it caught me while updating the spec.Documented in
docs/guides/migration.{en,ru}.md, section 18.Testing
autocomplete.spec.ts: 122 → 123 tests, all existing ones kept.SimpleAutocomplete, because a bound[autoActiveFirstOption]there silently defeats the test that checksKBQ_AUTOCOMPLETE_DEFAULT_OPTIONS. Preselection has its own host now, plus a new test that the input really does override the token.openOnFocusdescribe declaredComponentFixture<AutocompleteWithDisabledItems>while creatingAutocompleteWithOpenOnFocus.autocomplete-signals/index.spec.ts: 16 tests, covering both receiver types.packages/components(4994 tests) andpackages/schematics(450 tests) suites pass.check-apiis in sync.BREAKING CHANGE
🤖 Generated with Claude Code