feat(a2ui): add A2UI 0.9 functions support#2608
Conversation
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds TypeDoc-based extraction of ChangesFunction Catalog System
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
packages/genui/a2ui/src/functions/index.ts (1)
33-50: ⚡ Quick winDocument the necessity of type assertions in the function registry.
Every entry in
basicFunctionEntriesusesas unknown as FunctionImplto force compatibility between strongly-typed function signatures and the genericFunctionImpltype. This pattern bypasses TypeScript's type checking.While this is a common trade-off in dynamic function registries (similar to RPC or plugin systems), consider adding a JSDoc comment explaining why these casts are necessary and safe.
📝 Suggested documentation
/** * The 14 A2UI 0.9 basic-catalog functions, packaged for `functionRegistry` * registration. Each entry is announced to the agent during the handshake * via `serializeCatalog` and is invokable via `executeFunctionCall`. + * + * Note: Type assertions are required because individual functions have + * specific typed signatures while FunctionImpl accepts generic args. This + * is safe because the registry routes by name and the protocol ensures + * correct argument shapes at runtime. */ export const basicFunctionEntries: ReadonlyArray<🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/src/functions/index.ts` around lines 33 - 50, basicFunctionEntries currently uses repeated "as unknown as FunctionImpl" casts which suppress TypeScript checks; add a JSDoc comment above the exported basicFunctionEntries explaining why these double-casts are required (dynamic registry of heterogeneous function signatures), why they are safe here (all impl functions conform to expected runtime shape), and note any invariants callers must preserve; reference the exported symbol basicFunctionEntries and the type FunctionImpl and mention that each impl (e.g., required, regex, formatString, formatNumber, etc.) is intentionally cast to FunctionImpl to allow a unified registry.packages/genui/a2ui/test/executeFunctionCall.test.ts (1)
16-16: 💤 Low valueConsider removing the
voidkeyword prefix.The
voidkeyword beforebeforeEachandafterEachis unnecessary. These test lifecycle hooks don't require explicit return-value discarding in modern test frameworks.♻️ Proposed cleanup
- void beforeEach(() => { + beforeEach(() => { functionRegistry.register({- void afterEach(() => { + afterEach(() => { functionRegistry.unregister('identity');Also applies to: 28-28
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/test/executeFunctionCall.test.ts` at line 16, Remove the unnecessary `void` prefix from the test lifecycle hooks in this file: locate the `beforeEach` and `afterEach` usages (functions named beforeEach and afterEach in executeFunctionCall.test.ts) and delete the leading `void` so they are declared normally (e.g., beforeEach(() => { ... }) and afterEach(() => { ... })). This cleans up the code and avoids redundant return-value discarding; no other behavior changes are required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/genui/a2ui-catalog-extractor/src/index.ts`:
- Around line 346-351: The loop in index.ts uses metadata fn.name directly when
writing files (writeFileSync with path.join(functionsDir, `${fn.name}.json`)),
which allows names with "/", ".." or platform separators to escape functionsDir;
update the logic in the for (const fn of functions) block to validate/sanitize
fn.name before using it as a filename: ensure it is non-empty, reject or
normalize "." and "..", remove or replace directory separators (both "/" and
backslash) and other unsafe characters (e.g., replace with "_"), and fall back
to a safe deterministic identifier (e.g., a sanitized name or hash) if needed;
use the sanitized variable in the path.join call so files cannot be written
outside functionsDir.
In `@packages/genui/a2ui/src/core/useChecks.ts`:
- Around line 109-112: The effect currently calls form.setOutcome even when
componentId is an empty string causing registrations under ''. Update the
useEffect in useChecks.ts to short-circuit and do nothing when componentId is
empty (e.g., if (!form || !componentId) return; or explicitly if (componentId
=== '') return;), otherwise call and return form.setOutcome(componentId,
outcome); keep the same dependencies ([form, componentId, outcome]) so cleanup
behavior remains unchanged.
- Around line 51-56: The guard before iterating `checks` should ensure `checks`
is an actual array to avoid runtime throws when a malformed non-array prop is
passed; update the logic in the `useChecks` block (where `failures` is
constructed and `for (const rule of checks)` is used) to either treat invalid
shapes as empty (e.g., check `Array.isArray(checks)` in the initial early-return
condition) or coerce `checks` to an array (e.g., `const checksArr =
Array.isArray(checks) ? checks : []`) and iterate `checksArr` when calling
`evaluateCondition` and building `failures`.
In `@packages/genui/a2ui/src/functions/validators.ts`:
- Around line 52-56: The runtime compilation using new RegExp(args.pattern,
args.flags ?? '') is a ReDoS risk for untrusted patterns; replace this with a
linear-time regex engine such as re2js (use new RE2(args.pattern, args.flags ??
'').test(value)) or, if you cannot add that dependency, validate the pattern
first with a safe-regex check and/or run the match in a cancellable timeout;
modify the code around the RegExp usage (the block that references args.pattern,
args.flags and tests value) to use RE2.test(value) or to short-circuit by
rejecting unsafe patterns and document the trusted-source assumption if you
decide to keep RegExp.
---
Nitpick comments:
In `@packages/genui/a2ui/src/functions/index.ts`:
- Around line 33-50: basicFunctionEntries currently uses repeated "as unknown as
FunctionImpl" casts which suppress TypeScript checks; add a JSDoc comment above
the exported basicFunctionEntries explaining why these double-casts are required
(dynamic registry of heterogeneous function signatures), why they are safe here
(all impl functions conform to expected runtime shape), and note any invariants
callers must preserve; reference the exported symbol basicFunctionEntries and
the type FunctionImpl and mention that each impl (e.g., required, regex,
formatString, formatNumber, etc.) is intentionally cast to FunctionImpl to allow
a unified registry.
In `@packages/genui/a2ui/test/executeFunctionCall.test.ts`:
- Line 16: Remove the unnecessary `void` prefix from the test lifecycle hooks in
this file: locate the `beforeEach` and `afterEach` usages (functions named
beforeEach and afterEach in executeFunctionCall.test.ts) and delete the leading
`void` so they are declared normally (e.g., beforeEach(() => { ... }) and
afterEach(() => { ... })). This cleans up the code and avoids redundant
return-value discarding; no other behavior changes are required.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 79ed225b-cf2c-4c31-b65b-f4be5a291f43
📒 Files selected for processing (30)
packages/genui/a2ui-catalog-extractor/src/cli.tspackages/genui/a2ui-catalog-extractor/src/index.tspackages/genui/a2ui-catalog-extractor/test/extractor-functions.test.tspackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.tspackages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.tspackages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.jsonpackages/genui/a2ui/package.jsonpackages/genui/a2ui/src/catalog/CheckBox/index.tsxpackages/genui/a2ui/src/catalog/RadioGroup/index.tsxpackages/genui/a2ui/src/catalog/all.tspackages/genui/a2ui/src/catalog/defineCatalog.tspackages/genui/a2ui/src/catalog/index.tspackages/genui/a2ui/src/core/FormContext.tspackages/genui/a2ui/src/core/FormController.tspackages/genui/a2ui/src/core/FunctionRegistry.tspackages/genui/a2ui/src/core/resolveFunctionCall.tspackages/genui/a2ui/src/core/useAction.tspackages/genui/a2ui/src/core/useChecks.tspackages/genui/a2ui/src/functions/formatters.tspackages/genui/a2ui/src/functions/index.tspackages/genui/a2ui/src/functions/logic.tspackages/genui/a2ui/src/functions/sideEffects.tspackages/genui/a2ui/src/functions/validators.tspackages/genui/a2ui/test/basicFunctions.test.tspackages/genui/a2ui/test/defineCatalogFunctions.test.tspackages/genui/a2ui/test/executeFunctionCall.test.tspackages/genui/a2ui/test/formContext.test.tspackages/genui/a2ui/test/functionRegistry.test.ts
| if (!isPresent(args.value)) return true; | ||
| const value = String(args.value); | ||
| try { | ||
| return new RegExp(args.pattern, args.flags ?? '').test(value); | ||
| } catch { |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What are the currently recommended JavaScript/TypeScript approaches to prevent ReDoS when constructing RegExp from untrusted input, and which libraries are maintained for browser + Node environments?
💡 Result:
Currently recommended approaches to prevent ReDoS when constructing RegExp from untrusted input in JavaScript/TypeScript include: 1. Use linear-time regex engines that avoid backtracking: re2js (pure JS port of RE2, works in browser + Node) [1][2], re2 (native Node bindings for RE2, Node-only, actively maintained with 2M+ weekly downloads as of 2026) [3][4][5]. These guarantee O(n) time matching, eliminating ReDoS risk [6][7][8]. 2. Validate/sanitize patterns before constructing RegExp using libraries like safe-regex (detects potentially vulnerable patterns) [9][10][11], or @philiprehberger/safe-regex (TypeScript-friendly, checks nested quantifiers) [12]. 3. Escape untrusted input to prevent injection into patterns: Use escape functions to treat input as literals [7][12]. 4. Enforce execution timeouts, especially for untrusted input: super-regex (wraps RegExp with timeouts via workers, supports browser + Node async methods) [13][14], time-limited-regular-expressions (Node worker timeouts) [15]. 5. General best practices: Limit input length, avoid nested quantifiers/overlapping alternations, prefer built-in string methods, run in workers/VM for isolation [9][16][6][7][17]. Maintained libraries for both browser + Node: re2js [1][2], super-regex (3M+ weekly downloads) [14][18]. Regolith (@regolithjs/regolith) is a new Rust-backed linear-time option but low adoption (recent 2025 release) [19][20]. Native re2 is Node-only [3]. Avoid constructing RegExp directly from untrusted strings; if unavoidable, combine validation + timeout [17].
Citations:
- 1: https://www.npmjs.com/package/re2js
- 2: https://github.com/le0pard/re2js?tab=readme-ov-file
- 3: https://www.npmjs.com/package/re2?activeTab=code
- 4: https://registry.npmjs.org/re2
- 5: https://github.com/uhop/node-re2?tab=readme-ov-file
- 6: https://dev.to/pavkode/mitigating-redos-attacks-in-javascript-strategies-to-enhance-regexp-performance-and-security-5141
- 7: https://www.mydevtoolhub.com/blog/regex-security-best-practices
- 8: https://learn.snyk.io/lesson/redos/
- 9: https://medium.com/@arunangshudas/5-tips-to-avoid-regex-dos-attacks-in-node-js-ce1c1d7004a6
- 10: https://registry.npmjs.org/safe-regex2
- 11: https://registry.npmjs.org/safe-regex
- 12: https://registry.npmjs.org/@philiprehberger/safe-regex
- 13: https://github.com/sindresorhus/super-regex
- 14: https://registry.npmjs.org/super-regex
- 15: https://www.npmjs.com/package/time-limited-regular-expressions
- 16: https://www.herodevs.com/blog-posts/preventing-redos-regular-expression-denial-of-service-attacks-in-express
- 17: https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- 18: https://npmx.dev/package/super-regex
- 19: https://registry.npmjs.org/@regolithjs/regolith
- 20: https://github.com/JakeRoggenbuck/regolith/
🏁 Script executed:
cat -n packages/genui/a2ui/src/functions/validators.ts | head -70Repository: lynx-family/lynx-stack
Length of output: 1954
Strengthen regex compilation against ReDoS attacks for untrusted patterns.
Line 55 compiles arbitrary patterns at runtime. If args.pattern originates from untrusted or model-controlled sources, this creates a ReDoS risk—even short patterns like (a+)+b can trigger catastrophic backtracking and freeze the UI.
The suggested length and flag checks provide only partial protection. For comprehensive mitigation of untrusted patterns, consider one of these approaches:
- Pattern validation: Use the
safe-regexlibrary to detect potentially vulnerable patterns before compilation. - Linear-time engine: Replace direct
RegExpwithre2js(works browser + Node.js, O(n) matching guarantee, eliminates ReDoS entirely). - Timeout mechanism: Wrap regex execution in a timeout (e.g.,
super-regex) to abort runaway matching.
If pattern source is known to be trusted, document this assumption. If pattern must come from untrusted input, combine multiple defenses (validation + length limit + timeout).
🧰 Tools
🪛 ast-grep (0.42.2)
[warning] 54-54: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(args.pattern, args.flags ?? '')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/genui/a2ui/src/functions/validators.ts` around lines 52 - 56, The
runtime compilation using new RegExp(args.pattern, args.flags ?? '') is a ReDoS
risk for untrusted patterns; replace this with a linear-time regex engine such
as re2js (use new RE2(args.pattern, args.flags ?? '').test(value)) or, if you
cannot add that dependency, validate the pattern first with a safe-regex check
and/or run the match in a cancellable timeout; modify the code around the RegExp
usage (the block that references args.pattern, args.flags and tests value) to
use RE2.test(value) or to short-circuit by rejecting unsafe patterns and
document the trusted-source assumption if you decide to keep RegExp.
There was a problem hiding this comment.
Partially addressed in ca7af554. The flags arg has been dropped to match the A2UI 0.9 spec (only value + pattern are accepted), but the impl still constructs new RegExp(pattern) directly. Switching to a linear-time engine (re2js) or worker-timeout wrapper is a real concern but out of scope for this PR — it'd require a new runtime dependency and changes coordinated with the A2UI maintainers (they own how regex is spec'd to behave).
In A2UI 0.9 the pattern is agent-supplied, and we're already on the trust boundary that the agent is benign. If untrusted-agent scenarios become real, this should be revisited as a cross-cutting hardening pass on every regex usage in the renderer, not just validators.regex. Tracking as a follow-up.
There was a problem hiding this comment.
@PupilTong, that context makes sense — dropping flags tightens the spec conformance, and relying on the agent-as-trusted-source boundary is a reasonable call for the current scope. The cross-cutting hardening argument is well-taken too; patching just validators.regex in isolation would give a false sense of completeness.
Would you like me to open a GitHub issue to track the ReDoS hardening follow-up? I can link back to this PR and comment so it doesn't get lost.
(っ•́。•́)♪ ← curious rabbit wondering if an issue would help
✏️ Learnings added
Learnt from: PupilTong
Repo: lynx-family/lynx-stack PR: 2608
File: packages/genui/a2ui/src/functions/validators.ts:48-52
Timestamp: 2026-05-13T09:00:09.483Z
Learning: In the lynx-family/lynx-stack repo, A2UI 0.9 `regex` validator patterns are agent-supplied and treated as trusted input. The `flags` argument was intentionally removed to match the A2UI 0.9 spec (only `value` + `pattern` accepted). ReDoS hardening (e.g. switching to re2js) is deferred as a cross-cutting follow-up requiring A2UI maintainer coordination and a new runtime dependency — do not flag the direct `new RegExp(pattern)` construction in validators.ts as a blocking issue for this PR.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: Huxpro
Repo: lynx-family/lynx-stack PR: 2325
File: packages/rspeedy/create-rspeedy/template-react-ts/src/useFlappy.ts:43-45
Timestamp: 2026-03-10T08:25:14.775Z
Learning: In reviews for the lynx-family/lynx-stack repo, accept both 'background only' and 'background-only' spellings for the SWC directive DCE plugin (swc_plugin_directive_dce/lib.rs matches both). Do not flag 'background only' as incorrect; apply this understanding to all TypeScript/TSX files where the directive spelling may appear.
92de353 to
0a82223
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/genui/a2ui/src/react/useChecks.ts (2)
62-67:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard
checkswithArray.isArraybefore iterating.Line 66 iterates
checkswithfor...of, which will throw if a malformed non-array value is passed. Line 62's current guard checks truthiness and.length, but doesn't ensurechecksis actually iterable.🛡️ Proposed fix
function evaluateChecks( processor: MessageProcessor, checks: CheckLike[] | undefined, surface: Surface | undefined, dataContextPath?: string, ): CheckOutcome { - if (!surface || !checks || checks.length === 0) { + if (!surface || !Array.isArray(checks) || checks.length === 0) { return { ok: true, failures: [] }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/src/react/useChecks.ts` around lines 62 - 67, The current guard in useChecks (checking !surface || !checks || checks.length === 0) can still allow a non-array (non-iterable) value for checks and cause a runtime error when the code later does for (const rule of checks); update the guard to validate checks with Array.isArray (e.g. return early when !Array.isArray(checks) or coerce checks to an array) so the subsequent loop and calls like evaluateCondition operate only on a true array; adjust the condition using Array.isArray(checks) to prevent iteration over malformed values.
122-125:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSkip form registration when
componentIdis empty.Line 124 registers outcomes under the provided
componentId, but ifcomponentIdis an empty string, multiple components will collide under the same''key in the form controller, causing incorrect validation state aggregation.🛡️ Proposed fix
// eslint-disable-next-line `@typescript-eslint/no-unsafe-call` -- see comment above on the preceding `useEffect` call. useEffect(() => { - if (!form) return; + if (!form || !componentId) return; return form.setOutcome(componentId, outcome); }, [form, componentId, outcome]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/src/react/useChecks.ts` around lines 122 - 125, The effect currently always registers an outcome with form.setOutcome even when componentId === '', causing key collisions; update the useEffect guard to return early when componentId is falsy (e.g., if (!form || !componentId) return) so you only call form.setOutcome(componentId, outcome) for non-empty componentIds; ensure the effect still returns the cleanup from form.setOutcome when registration happens and returns nothing when skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/genui/a2ui/src/react/useChecks.ts`:
- Around line 62-67: The current guard in useChecks (checking !surface ||
!checks || checks.length === 0) can still allow a non-array (non-iterable) value
for checks and cause a runtime error when the code later does for (const rule of
checks); update the guard to validate checks with Array.isArray (e.g. return
early when !Array.isArray(checks) or coerce checks to an array) so the
subsequent loop and calls like evaluateCondition operate only on a true array;
adjust the condition using Array.isArray(checks) to prevent iteration over
malformed values.
- Around line 122-125: The effect currently always registers an outcome with
form.setOutcome even when componentId === '', causing key collisions; update the
useEffect guard to return early when componentId is falsy (e.g., if (!form ||
!componentId) return) so you only call form.setOutcome(componentId, outcome) for
non-empty componentIds; ensure the effect still returns the cleanup from
form.setOutcome when registration happens and returns nothing when skipped.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 21244a4b-08a8-4923-8abf-fdf2873abfad
📒 Files selected for processing (32)
packages/genui/a2ui-catalog-extractor/src/cli.tspackages/genui/a2ui-catalog-extractor/src/index.tspackages/genui/a2ui-catalog-extractor/test/extractor-functions.test.tspackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.tspackages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.tspackages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.jsonpackages/genui/a2ui/package.jsonpackages/genui/a2ui/src/catalog/CheckBox/index.tsxpackages/genui/a2ui/src/catalog/RadioGroup/index.tsxpackages/genui/a2ui/src/catalog/defineCatalog.tspackages/genui/a2ui/src/catalog/index.tspackages/genui/a2ui/src/functions/formatters.tspackages/genui/a2ui/src/functions/index.tspackages/genui/a2ui/src/functions/logic.tspackages/genui/a2ui/src/functions/sideEffects.tspackages/genui/a2ui/src/functions/validators.tspackages/genui/a2ui/src/index.tspackages/genui/a2ui/src/react/FormContext.tspackages/genui/a2ui/src/react/index.tspackages/genui/a2ui/src/react/useChecks.tspackages/genui/a2ui/src/store/FormController.tspackages/genui/a2ui/src/store/FunctionRegistry.tspackages/genui/a2ui/src/store/index.tspackages/genui/a2ui/src/store/resolveFunctionCall.tspackages/genui/a2ui/test/basicFunctions.test.tspackages/genui/a2ui/test/catalog.test.tspackages/genui/a2ui/test/defineCatalogFunctions.test.tspackages/genui/a2ui/test/executeFunctionCall.test.tspackages/genui/a2ui/test/formContext.test.tspackages/genui/a2ui/test/functionRegistry.test.ts
✅ Files skipped from review due to trivial changes (7)
- packages/genui/a2ui/src/react/index.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
- packages/genui/a2ui/test/basicFunctions.test.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
- packages/genui/a2ui/test/functionRegistry.test.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (14)
- packages/genui/a2ui/test/formContext.test.ts
- packages/genui/a2ui/src/catalog/index.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
- packages/genui/a2ui/package.json
- packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
- packages/genui/a2ui/src/functions/formatters.ts
- packages/genui/a2ui/test/defineCatalogFunctions.test.ts
- packages/genui/a2ui-catalog-extractor/src/cli.ts
- packages/genui/a2ui/src/catalog/defineCatalog.ts
- packages/genui/a2ui/src/functions/sideEffects.ts
- packages/genui/a2ui/src/catalog/CheckBox/index.tsx
- packages/genui/a2ui/src/functions/logic.ts
- packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
- packages/genui/a2ui-catalog-extractor/src/index.ts
0a82223 to
3c8927d
Compare
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/genui/a2ui/src/functions/formatters.ts`:
- Around line 29-34: The function formatString's parameter type is declared as
non-nullable but the implementation uses args.value ?? '' indicating nullish
values are expected; update the args shape so value is optional and nullable
(e.g., value?: string | null) in the formatString signature and adjust the JSDoc
comment to reflect that it may be null/undefined, keeping the implementation
using args.value ?? '' unchanged; refer to formatString and args.value when
making this change.
- Around line 5-9: The toNumber function currently returns numeric inputs
verbatim, allowing NaN and Infinity to pass through; change toNumber to always
coerce via Number(value) and then return the parsed value only if
Number.isFinite(parsed) else return 0 so that NaN/Infinity from numeric or
string inputs are normalized; update the implementation in the toNumber function
used by formatNumber/formatCurrency/pluralize to ensure uniform finite-number
handling.
In `@packages/genui/a2ui/src/functions/sideEffects.ts`:
- Line 22: The open call passes untrusted args.url directly to globalThis.open;
parse args.url with the URL constructor (or reject if it throws) and validate
its protocol against an allowlist (e.g., "http:" and "https:"); if the protocol
is not allowed, do not call globalThis.open and fail closed (return or throw);
update the call site where open(String(args.url ?? ''), args.target) is used
(reference args.url and the globalThis.open invocation in sideEffects.ts) so
only validated URLs are opened.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 887e3aa1-fec7-4592-a35c-8aa8fcee3cab
📒 Files selected for processing (32)
packages/genui/a2ui-catalog-extractor/src/cli.tspackages/genui/a2ui-catalog-extractor/src/index.tspackages/genui/a2ui-catalog-extractor/test/extractor-functions.test.tspackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.tspackages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.tspackages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.jsonpackages/genui/a2ui/package.jsonpackages/genui/a2ui/src/catalog/CheckBox/index.tsxpackages/genui/a2ui/src/catalog/RadioGroup/index.tsxpackages/genui/a2ui/src/catalog/defineCatalog.tspackages/genui/a2ui/src/catalog/index.tspackages/genui/a2ui/src/functions/formatters.tspackages/genui/a2ui/src/functions/index.tspackages/genui/a2ui/src/functions/logic.tspackages/genui/a2ui/src/functions/sideEffects.tspackages/genui/a2ui/src/functions/validators.tspackages/genui/a2ui/src/index.tspackages/genui/a2ui/src/react/FormContext.tspackages/genui/a2ui/src/react/index.tspackages/genui/a2ui/src/react/useChecks.tspackages/genui/a2ui/src/store/FormController.tspackages/genui/a2ui/src/store/FunctionRegistry.tspackages/genui/a2ui/src/store/index.tspackages/genui/a2ui/src/store/resolveFunctionCall.tspackages/genui/a2ui/test/basicFunctions.test.tspackages/genui/a2ui/test/catalog.test.tspackages/genui/a2ui/test/defineCatalogFunctions.test.tspackages/genui/a2ui/test/executeFunctionCall.test.tspackages/genui/a2ui/test/formContext.test.tspackages/genui/a2ui/test/functionRegistry.test.ts
✅ Files skipped from review due to trivial changes (6)
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
- packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
- packages/genui/a2ui/test/basicFunctions.test.ts
- packages/genui/a2ui/test/catalog.test.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
🚧 Files skipped from review as they are similar to previous changes (22)
- packages/genui/a2ui/test/formContext.test.ts
- packages/genui/a2ui/src/react/index.ts
- packages/genui/a2ui/src/store/index.ts
- packages/genui/a2ui/src/react/FormContext.ts
- packages/genui/a2ui/src/catalog/CheckBox/index.tsx
- packages/genui/a2ui-catalog-extractor/src/cli.ts
- packages/genui/a2ui/src/catalog/index.ts
- packages/genui/a2ui/test/functionRegistry.test.ts
- packages/genui/a2ui/src/functions/logic.ts
- packages/genui/a2ui/package.json
- packages/genui/a2ui/src/store/FunctionRegistry.ts
- packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
- packages/genui/a2ui/test/executeFunctionCall.test.ts
- packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
- packages/genui/a2ui/src/store/FormController.ts
- packages/genui/a2ui/test/defineCatalogFunctions.test.ts
- packages/genui/a2ui/src/index.ts
- packages/genui/a2ui/src/react/useChecks.ts
- packages/genui/a2ui/src/functions/index.ts
- packages/genui/a2ui-catalog-extractor/src/index.ts
- packages/genui/a2ui/src/catalog/defineCatalog.ts
- packages/genui/a2ui/src/store/resolveFunctionCall.ts
3c8927d to
9375686
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/genui/a2ui/src/functions/formatters.ts (1)
33-38:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPast review suggestion not fully applied: type should include
nullif data bindings can produce it.The previous review suggested
value?: string | null, but the current signature only hasvalue?: string. In TypeScript strict mode, this meansstring | undefinedand will rejectnullat compile time. However, line 37's??operator handles bothundefinedandnullat runtime. If dynamic data bindings can producenullvalues, the type signature should include it for accuracy.📝 Proposed fix to align type with data-binding contract
export function formatString(args: { /** Template string. May be omitted; treated as the empty string. */ - value?: string; + value?: string | null; }): string { return String(args.value ?? ''); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/src/functions/formatters.ts` around lines 33 - 38, The function signature for formatString doesn't reflect that bindings may produce null values: update the args type so value can be string | null | undefined (e.g., change value?: string to value?: string | null) so the TypeScript type matches the runtime handling at String(args.value ?? '') and avoids compile errors when null is passed; ensure the change is made on the formatString args type (args.value) declaration.
🧹 Nitpick comments (1)
packages/genui/a2ui/src/functions/formatters.ts (1)
121-123: 💤 Low valueConsider using
unknownforcountto match other formatters' pattern.Other formatters (
formatNumber,formatCurrency) acceptvalue: unknownand coerce viatoNumber. This function declarescount: number(line 123) but still passes it totoNumber(line 129). For consistency and robustness—especially if data bindings can provide coercible values like strings—consider changingcount: unknown.♻️ Proposed change for consistency
export function pluralize(args: { /** Quantity used to select the plural category. */ - count: number; + count: unknown; /** Plural-category-keyed forms. `other` is required. */ forms: Record<string, string>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/genui/a2ui/src/functions/formatters.ts` around lines 121 - 123, The pluralize function currently types its parameter as count: number but then coerces it via toNumber; change the parameter to count: unknown to match the pattern used by formatNumber/formatCurrency and allow coercible values (e.g., strings), update the JSDoc if present, and ensure any internal uses still call toNumber(count) (refer to the pluralize function and the toNumber helper) so runtime behavior remains identical while the signature is more robust.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/genui/a2ui/src/functions/formatters.ts`:
- Around line 121-130: The function pluralize' signature claims args.forms is
non-nullable but the implementation treats it as potentially null/undefined via
"args.forms ?? {}". Update the type for the parameter in the pluralize
declaration to reflect nullish values (e.g., make forms optional or allow null:
"forms?: Record<string,string> | null") and update the JSDoc comment to state
null/undefined are allowed; ensure any callers still pass the correct shape and
keep the runtime fallback "args.forms ?? {}" unchanged.
---
Duplicate comments:
In `@packages/genui/a2ui/src/functions/formatters.ts`:
- Around line 33-38: The function signature for formatString doesn't reflect
that bindings may produce null values: update the args type so value can be
string | null | undefined (e.g., change value?: string to value?: string | null)
so the TypeScript type matches the runtime handling at String(args.value ?? '')
and avoids compile errors when null is passed; ensure the change is made on the
formatString args type (args.value) declaration.
---
Nitpick comments:
In `@packages/genui/a2ui/src/functions/formatters.ts`:
- Around line 121-123: The pluralize function currently types its parameter as
count: number but then coerces it via toNumber; change the parameter to count:
unknown to match the pattern used by formatNumber/formatCurrency and allow
coercible values (e.g., strings), update the JSDoc if present, and ensure any
internal uses still call toNumber(count) (refer to the pluralize function and
the toNumber helper) so runtime behavior remains identical while the signature
is more robust.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7206a352-1c64-4e13-b7cc-2b568ebc3ca7
📒 Files selected for processing (32)
packages/genui/a2ui-catalog-extractor/src/cli.tspackages/genui/a2ui-catalog-extractor/src/index.tspackages/genui/a2ui-catalog-extractor/test/extractor-functions.test.tspackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.jsonpackages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.tspackages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.tspackages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.jsonpackages/genui/a2ui/package.jsonpackages/genui/a2ui/src/catalog/CheckBox/index.tsxpackages/genui/a2ui/src/catalog/RadioGroup/index.tsxpackages/genui/a2ui/src/catalog/defineCatalog.tspackages/genui/a2ui/src/catalog/index.tspackages/genui/a2ui/src/functions/formatters.tspackages/genui/a2ui/src/functions/index.tspackages/genui/a2ui/src/functions/logic.tspackages/genui/a2ui/src/functions/sideEffects.tspackages/genui/a2ui/src/functions/validators.tspackages/genui/a2ui/src/index.tspackages/genui/a2ui/src/react/FormContext.tspackages/genui/a2ui/src/react/index.tspackages/genui/a2ui/src/react/useChecks.tspackages/genui/a2ui/src/store/FormController.tspackages/genui/a2ui/src/store/FunctionRegistry.tspackages/genui/a2ui/src/store/index.tspackages/genui/a2ui/src/store/resolveFunctionCall.tspackages/genui/a2ui/test/basicFunctions.test.tspackages/genui/a2ui/test/catalog.test.tspackages/genui/a2ui/test/defineCatalogFunctions.test.tspackages/genui/a2ui/test/executeFunctionCall.test.tspackages/genui/a2ui/test/formContext.test.tspackages/genui/a2ui/test/functionRegistry.test.ts
✅ Files skipped from review due to trivial changes (4)
- packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
- packages/genui/a2ui/src/store/FormController.ts
- packages/genui/a2ui/test/functionRegistry.test.ts
- packages/genui/a2ui/test/catalog.test.ts
🚧 Files skipped from review as they are similar to previous changes (23)
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
- packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
- packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
- packages/genui/a2ui/src/react/index.ts
- packages/genui/a2ui/src/react/FormContext.ts
- packages/genui/a2ui/src/catalog/index.ts
- packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
- packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
- packages/genui/a2ui/src/functions/index.ts
- packages/genui/a2ui/src/functions/logic.ts
- packages/genui/a2ui/src/store/FunctionRegistry.ts
- packages/genui/a2ui/package.json
- packages/genui/a2ui/test/basicFunctions.test.ts
- packages/genui/a2ui/test/executeFunctionCall.test.ts
- packages/genui/a2ui/src/store/resolveFunctionCall.ts
- packages/genui/a2ui-catalog-extractor/src/cli.ts
- packages/genui/a2ui/test/formContext.test.ts
- packages/genui/a2ui/test/defineCatalogFunctions.test.ts
- packages/genui/a2ui/src/catalog/CheckBox/index.tsx
- packages/genui/a2ui/src/index.ts
- packages/genui/a2ui-catalog-extractor/src/index.ts
- packages/genui/a2ui/src/catalog/defineCatalog.ts
9375686 to
0b1857a
Compare
Merging this PR will degrade performance by 76.53%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
React Example with Element Template#591 Bundle Size — 199.95KiB (0%).fe2df24(current) vs 353b1b7 main#569(baseline) Bundle metrics
Bundle size by type
|
| Current #591 |
Baseline #569 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
54.19KiB |
54.19KiB |
Bundle analysis report Branch PupilTong:claude/goofy-rubin-cf8... Project dashboard
Generated by RelativeCI Documentation Report issue
Web Explorer#9896 Bundle Size — 901.35KiB (0%).fe2df24(current) vs 353b1b7 main#9874(baseline) Bundle metrics
|
| Current #9896 |
Baseline #9874 |
|
|---|---|---|
45.06KiB |
45.06KiB |
|
2.22KiB |
2.22KiB |
|
0% |
0% |
|
9 |
9 |
|
11 |
11 |
|
229 |
229 |
|
11 |
11 |
|
27.22% |
27.22% |
|
10 |
10 |
|
0 |
0 |
Bundle size by type no changes
| Current #9896 |
Baseline #9874 |
|
|---|---|---|
497.08KiB |
497.08KiB |
|
402.06KiB |
402.06KiB |
|
2.22KiB |
2.22KiB |
Bundle analysis report Branch PupilTong:claude/goofy-rubin-cf8... Project dashboard
Generated by RelativeCI Documentation Report issue
React MTF Example#1455 Bundle Size — 208.1KiB (0%).fe2df24(current) vs 353b1b7 main#1434(baseline) Bundle metrics
|
| Current #1455 |
Baseline #1434 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
192 |
192 |
|
77 |
77 |
|
44.4% |
44.4% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #1455 |
Baseline #1434 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
96.86KiB |
96.86KiB |
Bundle analysis report Branch PupilTong:claude/goofy-rubin-cf8... Project dashboard
Generated by RelativeCI Documentation Report issue
React Example#8323 Bundle Size — 237.15KiB (0%).fe2df24(current) vs 353b1b7 main#8301(baseline) Bundle metrics
|
| Current #8323 |
Baseline #8301 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
197 |
197 |
|
80 |
80 |
|
44.89% |
44.89% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #8323 |
Baseline #8301 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
91.39KiB |
91.39KiB |
Bundle analysis report Branch PupilTong:claude/goofy-rubin-cf8... Project dashboard
Generated by RelativeCI Documentation Report issue
React External#1437 Bundle Size — 695.33KiB (0%).fe2df24(current) vs 353b1b7 main#1415(baseline) Bundle metrics
|
| Current #1437 |
Baseline #1415 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
17 |
17 |
|
5 |
5 |
|
8.59% |
8.59% |
|
0 |
0 |
|
0 |
0 |
Bundle analysis report Branch PupilTong:claude/goofy-rubin-cf8... Project dashboard
Generated by RelativeCI Documentation Report issue
128b873 to
ca7af55
Compare
Spec-alignment pass (
|
| Function | Before | After (spec-aligned) |
|---|---|---|
regex |
{ value, pattern, flags? } |
{ value, pattern } — flags dropped |
numeric |
parseable-as-number check | range validator with optional min/max |
and / or |
{ values?, a?, b? } overload |
{ values: [...] } only |
formatNumber |
{ value, locale?, options? } |
{ value, decimals?, grouping? } |
formatCurrency |
{ value, currency?, locale? } |
{ value, currency, decimals?, grouping? } — currency now required |
formatDate |
{ value, locale?, options? } w/ Intl.DateTimeFormat |
{ value, format } w/ Unicode TR35 pattern |
pluralize |
{ count, forms, locale? } |
{ value, other, zero?, one?, two?, few?, many? } |
openUrl |
{ url, target? } |
{ url } — target dropped |
email removed (temporarily)
email is excluded from basicFunctions and the package's root re-export for this PR. It'll come back once we've settled on a ReDoS-safe regex strategy (per CodeRabbit's concern on validators.regex).
Verification
- Built manifests (
dist/catalog/functions/*.json) now match the spec'srequired/additionalProperties/parametersshape for the 13 shipped functions. - Tests rewritten for the new signatures: 78 renderer + 9 extractor pass.
pnpm biome check,pnpm dprint check,pnpm eslintall clean.
Deferred to follow-up PRs (consistent with the original deferred-scope plan):
- Full TR35 coverage in
formatDate(era, week-of-year, time zones) — current impl covers the common date/time tokens. formatString${path}/${fn(...)}interpolation — still a passthrough.- Action-side local execution of
FunctionCall(currentlyuseActionstill forwards to the agent). - Dynamic-property
FunctionCallevaluation inuseDataBinding. emailreintroduction with a ReDoS-safe validator.
e2980ba to
55f72d1
Compare
87345fd to
ac57b58
Compare
Adds the A2UI 0.9 "functions" concept end-to-end: extractor discovery,
client-side registry, validation checks via the `Checkable` mixin, and
handshake announcement to the agent.
Scope (per design plan): registry, extractor, handshake, all 14
basic-catalog functions, and the `checks` runtime call site (used by
CheckBox + RadioGroup). Action-side `functionCall` execution and
dynamic-property `FunctionCall` evaluation are deferred to follow-ups.
Extractor (`@lynx-js/a2ui-catalog-extractor`):
- New `@a2uiFunction` JSDoc tag discovers function declarations
- Single-object-parameter unwrapping for the natural
`function fn(args: { ... })` convention
- Relaxed handling of `unknown`/`any` for function parameters
- Rejects `Promise<T>` return types (A2UI 0.9 is synchronous)
- `writeCatalogArtifacts` emits both components and functions in one
TypeDoc pass; functions land under `dist/catalog/functions/<name>.json`
Renderer (`@lynx-js/a2ui-reactlynx`):
- `FunctionRegistry` keyed by `FunctionCall.call`
- `executeFunctionCall` resolves args (data bindings + nested calls) and
invokes the registered impl; unknown names warn once and return
undefined
- `FormController` aggregates per-input check outcomes via Preact signals
- `useChecks` evaluates v0.9 `CheckRule[]` reactively and registers with
the enclosing `FormContext`
- All 14 basic-catalog functions (`required`, `regex`, `length`,
`numeric`, `email`, `and`, `or`, `not`, `formatString`, `formatNumber`,
`formatCurrency`, `formatDate`, `pluralize`, `openUrl`)
- `defineCatalog` / `serializeCatalog` accept function entries and
announce them in the handshake payload
- `CheckBox` and `RadioGroup` wire `useChecks` and render inline error
messages
Tests: 9/9 extractor and 46/46 renderer tests pass. Builds emit
10 component manifests + 14 function manifests.
ac57b58 to
fe2df24
Compare
Summary
Adds the A2UI 0.9 "functions" concept end-to-end: extractor discovery, a client-side function registry, validation
checksevaluation, and handshake announcement to the agent.The repo already depends on
@a2ui/web_core@0.9.1and importsFunctionCall/CheckRuletypes, but the runtime was only partially scaffolded — no registry, nocheckssupport, no extraction, no handshake announcement. This PR fills those gaps.Scope (per design plan)
FunctionRegistry@a2uiFunctiondiscoveryfunctionsarraychecksvalidation on inputs (CheckBox,RadioGroup)functionCalllocal executionFunctionCallevaluationFormatters (
formatString,formatNumber,formatCurrency,formatDate,pluralize) andopenUrlare registered + announced to the agent so the handshake is correct, but they aren't reachable via render paths until the follow-ups land.What changed
Extractor —
@lynx-js/a2ui-catalog-extractor@a2uiFunctionJSDoc tag discovers function declarations and type-aliased functions.function fn(args: { ... })convention.unknown/anyhandling for function parameters (validators likerequired(value)).Promise<T>returns (A2UI 0.9 calls are synchronous).writeCatalogArtifactsemits both component and function files in one TypeDoc pass.Renderer —
@lynx-js/a2ui-reactlynxFunctionRegistry.ts— singleton mappingFunctionCall.call→ impl.resolveFunctionCall.ts—executeFunctionCallresolves args (data bindings + nested calls) and invokes the registered impl.FormController.ts+FormContext.ts— signal-backed per-formisValidaggregation.useChecks.ts— evaluates v0.9CheckRule[]reactively and registers withFormContext.defineCatalogaccepts function entries viadefineFunction(impl, manifest?)andserializeCatalogannounces them in the handshake ({ version: '0.9', components, functions }).CheckBoxandRadioGroupwireuseChecksand render inline error messages.Verification
extractCatalogFunctionshappy path, full-pipeline write, andPromise<T>rejection).Checklist
private: true).Summary by CodeRabbit
New Features
UX Improvements
Tests