Skip to content

feat(a2ui): add A2UI 0.9 functions support#2608

Open
PupilTong wants to merge 2 commits into
lynx-family:mainfrom
PupilTong:claude/goofy-rubin-cf8a05
Open

feat(a2ui): add A2UI 0.9 functions support#2608
PupilTong wants to merge 2 commits into
lynx-family:mainfrom
PupilTong:claude/goofy-rubin-cf8a05

Conversation

@PupilTong
Copy link
Copy Markdown
Collaborator

@PupilTong PupilTong commented May 12, 2026

Summary

Adds the A2UI 0.9 "functions" concept end-to-end: extractor discovery, a client-side function registry, validation checks evaluation, and handshake announcement to the agent.

The repo already depends on @a2ui/web_core@0.9.1 and imports FunctionCall/CheckRule types, but the runtime was only partially scaffolded — no registry, no checks support, no extraction, no handshake announcement. This PR fills those gaps.

Scope (per design plan)

Plumbed This PR Deferred
FunctionRegistry
Extractor @a2uiFunction discovery
Handshake functions array
14 basic-catalog function impls
checks validation on inputs (CheckBox, RadioGroup)
Action-side functionCall local execution follow-up
Dynamic-property FunctionCall evaluation follow-up

Formatters (formatString, formatNumber, formatCurrency, formatDate, pluralize) and openUrl are 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

  • New @a2uiFunction JSDoc tag discovers function declarations and type-aliased functions.
  • Single-object-parameter unwrapping for the natural function fn(args: { ... }) convention.
  • Relaxed unknown/any handling for function parameters (validators like required(value)).
  • Rejects Promise<T> returns (A2UI 0.9 calls are synchronous).
  • New writeCatalogArtifacts emits both component and function files in one TypeDoc pass.

Renderer — @lynx-js/a2ui-reactlynx

Verification

  • ✅ 9/9 extractor tests pass (including new extractCatalogFunctions happy path, full-pipeline write, and Promise<T> rejection).
  • ✅ 46/46 renderer tests pass (FunctionRegistry, executeFunctionCall, FormController, defineCatalog + serialize, 14 basic functions).
  • ✅ Both packages build cleanly; the renderer build emits 10 component manifests + 14 function manifests.
$ pnpm --filter @lynx-js/a2ui-catalog-extractor test
 Test Files  2 passed (9 tests)
$ pnpm --filter @lynx-js/a2ui-reactlynx test
 Test Files  7 passed (46 tests)
$ pnpm --filter @lynx-js/a2ui-reactlynx build
Generated 10 A2UI component catalog files.
Generated 14 A2UI function definition files.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required) — type-level docs on exported APIs; deferred-scope notes in code comments.
  • Changeset added — not required (both packages are private: true).

Summary by CodeRabbit

  • New Features

    • CLI and catalog tooling now generate and emit both component catalogs and function definitions.
    • Built-in function set added (validators, formatters, logic ops, openUrl) with registration and runtime APIs; public exports expose function registry, registration, and helpers.
    • Catalog model extended to include separate components and functions and to serialize function schemas.
  • UX Improvements

    • Form validation: new form controller/context and useChecks hook; CheckBox and RadioGroup display validation state/messages.
  • Tests

    • New suites covering function extraction, catalogs, execution, registry, and form/validation.

Review Change Stack

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

⚠️ No Changeset found

Latest commit: fe2df24

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds TypeDoc-based extraction of @a2uiFunction to emit per-function JSON artifacts; extends the catalog model to include functions and defineFunction; implements a FunctionRegistry and dynamic-call resolvers; adds FormController/FormContext/useChecks for validation; provides built-in validators, logic operators, formatters, and openUrl; wires checks into CheckBox/RadioGroup; updates CLI/package exports; and adds tests.

Changes

Function Catalog System

Layer / File(s) Summary
Extractor CLI & bootstrap
packages/genui/a2ui-catalog-extractor/src/cli.ts, packages/genui/a2ui-catalog-extractor/src/index.ts
CLI and TypeDoc bootstrap updated to discover @a2uiFunction and emit both component and function artifacts; CLI prints function/component summaries and fails only when both are empty.
Extractor types & signature logic
packages/genui/a2ui-catalog-extractor/src/index.ts
Adds CatalogFunction, TypeDoc signature typings, signature selection logic, parameter JSON-Schema inference (including single-object unwrapping), and constrained return-type mapping with Promise rejection.
Writers & artifacts + extractor tests
packages/genui/a2ui-catalog-extractor/src/index.ts, packages/genui/a2ui-catalog-extractor/test/*
Adds per-function writers, writeCatalogFunctions, and writeCatalogArtifacts to emit components and functions in a single pass, validating filesystem-safe names and writing ${outDir}/functions/<name>.json. Tests and fixtures validate formatString/required extraction, emitted JSON equality, and async-function rejection.
Catalog model & API
packages/genui/a2ui/src/catalog/defineCatalog.ts, packages/genui/a2ui/src/catalog/index.ts
Replaces array-only Catalog with { components, functions }; introduces FunctionManifest, CatalogFunctionEntry, and defineFunction; defineCatalog partitions inputs, enforces per-kind duplicate-name rejection, registers functions into functionRegistry, and serializeCatalog may include functions.
FunctionRegistry & resolvers
packages/genui/a2ui/src/store/FunctionRegistry.ts, packages/genui/a2ui/src/store/resolveFunctionCall.ts
Implements FunctionRegistry singleton, type-guards, resolveDynamicValue, resolveFunctionArguments, and executeFunctionCall that resolves arguments, invokes registered implementations, and deduplicates missing-function warnings.
FormController, FormContext, useChecks
packages/genui/a2ui/src/store/FormController.ts, packages/genui/a2ui/src/react/FormContext.ts, packages/genui/a2ui/src/react/useChecks.ts
Adds reactive FormController (Preact Signals), FormContext, and useChecks hook to evaluate checks (boolean/function-call/data-binding), register per-component outcomes, compute overall isValid, and expose first-failure messages.
Built-in functions
packages/genui/a2ui/src/functions/*
Adds validators (required, regex, length, numeric, email), logic operators (and, or, not), formatters (formatString, formatNumber, formatCurrency, formatDate, pluralize), side-effect openUrl, basicFunctions array, and registerBasicFunctions().
Component validation integration
packages/genui/a2ui/src/catalog/CheckBox/index.tsx, packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
Integrates useChecks into CheckBox and RadioGroup, applying invalid CSS and rendering first-failure messages.
Store/main exports & package
packages/genui/a2ui/src/store/index.ts, packages/genui/a2ui/src/index.ts, packages/genui/a2ui/package.json
Adds store re-exports (registry/resolvers/form controller), expands main public exports to include checks and functions, adds ./functions package export, and updates the build script to generate function artifacts.
Unit tests & catalog test updates
packages/genui/a2ui/test/*, packages/genui/a2ui-catalog-extractor/test/*
Adds/updates unit tests for extraction, registry, execution, form controller, defineCatalog behavior, basic function behavior, and updates catalog tests to use Catalog.components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • lynx-family/lynx-stack#2562: Modifies the extractor CLI's failure guard related to emitted catalogs (overlaps CLI failure condition changes).
  • lynx-family/lynx-stack#2560: Prior work on catalog definition/merge logic that this changeset extends to support functions.
  • lynx-family/lynx-stack#2527: Adds the original TypeDoc-based catalog extractor for components; this PR extends it to extract function definitions.

Suggested reviewers

  • HuJean
  • Sherry-hue
  • gaoachao
  • fzx2666-fz

Poem

🐰 I found functions hidden in docs and code,

JSON files hop out, neatly bestowed.
Validators guard, formatters shape the view,
Signals keep forms true — a catalog anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(a2ui): add A2UI 0.9 functions support' is concise, uses the conventional 'feat' prefix, and accurately summarizes the main change: adding A2UI 0.9 functions support across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/genui/a2ui/src/functions/index.ts (1)

33-50: ⚡ Quick win

Document the necessity of type assertions in the function registry.

Every entry in basicFunctionEntries uses as unknown as FunctionImpl to force compatibility between strongly-typed function signatures and the generic FunctionImpl type. 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 value

Consider removing the void keyword prefix.

The void keyword before beforeEach and afterEach is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 460ddbd and 92de353.

📒 Files selected for processing (30)
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/all.ts
  • packages/genui/a2ui/src/catalog/defineCatalog.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/core/FormContext.ts
  • packages/genui/a2ui/src/core/FormController.ts
  • packages/genui/a2ui/src/core/FunctionRegistry.ts
  • packages/genui/a2ui/src/core/resolveFunctionCall.ts
  • packages/genui/a2ui/src/core/useAction.ts
  • packages/genui/a2ui/src/core/useChecks.ts
  • packages/genui/a2ui/src/functions/formatters.ts
  • packages/genui/a2ui/src/functions/index.ts
  • packages/genui/a2ui/src/functions/logic.ts
  • packages/genui/a2ui/src/functions/sideEffects.ts
  • packages/genui/a2ui/src/functions/validators.ts
  • packages/genui/a2ui/test/basicFunctions.test.ts
  • packages/genui/a2ui/test/defineCatalogFunctions.test.ts
  • packages/genui/a2ui/test/executeFunctionCall.test.ts
  • packages/genui/a2ui/test/formContext.test.ts
  • packages/genui/a2ui/test/functionRegistry.test.ts

Comment thread packages/genui/a2ui-catalog-extractor/src/index.ts
Comment thread packages/genui/a2ui/src/react/useChecks.ts Outdated
Comment thread packages/genui/a2ui/src/react/useChecks.ts
Comment on lines +52 to +56
if (!isPresent(args.value)) return true;
const value = String(args.value);
try {
return new RegExp(args.pattern, args.flags ?? '').test(value);
} catch {
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 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:


🏁 Script executed:

cat -n packages/genui/a2ui/src/functions/validators.ts | head -70

Repository: 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:

  1. Pattern validation: Use the safe-regex library to detect potentially vulnerable patterns before compilation.
  2. Linear-time engine: Replace direct RegExp with re2js (works browser + Node.js, O(n) matching guarantee, eliminates ReDoS entirely).
  3. 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch from 92de353 to 0a82223 Compare May 12, 2026 11:08
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
packages/genui/a2ui/src/react/useChecks.ts (2)

62-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard checks with Array.isArray before iterating.

Line 66 iterates checks with for...of, which will throw if a malformed non-array value is passed. Line 62's current guard checks truthiness and .length, but doesn't ensure checks is 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 win

Skip form registration when componentId is empty.

Line 124 registers outcomes under the provided componentId, but if componentId is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 92de353 and 0a82223.

📒 Files selected for processing (32)
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/defineCatalog.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/functions/formatters.ts
  • packages/genui/a2ui/src/functions/index.ts
  • packages/genui/a2ui/src/functions/logic.ts
  • packages/genui/a2ui/src/functions/sideEffects.ts
  • packages/genui/a2ui/src/functions/validators.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/FormContext.ts
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useChecks.ts
  • packages/genui/a2ui/src/store/FormController.ts
  • packages/genui/a2ui/src/store/FunctionRegistry.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/resolveFunctionCall.ts
  • packages/genui/a2ui/test/basicFunctions.test.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/defineCatalogFunctions.test.ts
  • packages/genui/a2ui/test/executeFunctionCall.test.ts
  • packages/genui/a2ui/test/formContext.test.ts
  • packages/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

@PupilTong PupilTong self-assigned this May 13, 2026
@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch from 0a82223 to 3c8927d Compare May 13, 2026 05:15
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
5003 1 5002 74
View the top 1 failed test(s) by shortest run time
__test__/element-template/test-utils/mock/mockNativePapi/templateTree.test.ts > mock native template tree spread slots > expands spread slots in descriptor order
Stack Traces | 0.00921s run time
AssertionError: expected { id: 'spread-id', class: 'primary' } to deeply equal { id: 'slot-id', class: 'primary' }

- Expected
+ Received

  {
    "class": "primary",
-   "id": "slot-id",
+   "id": "spread-id",
  }

 ❯ .../mock/mockNativePapi/templateTree.test.ts:25:29

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a82223 and 3c8927d.

📒 Files selected for processing (32)
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/defineCatalog.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/functions/formatters.ts
  • packages/genui/a2ui/src/functions/index.ts
  • packages/genui/a2ui/src/functions/logic.ts
  • packages/genui/a2ui/src/functions/sideEffects.ts
  • packages/genui/a2ui/src/functions/validators.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/FormContext.ts
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useChecks.ts
  • packages/genui/a2ui/src/store/FormController.ts
  • packages/genui/a2ui/src/store/FunctionRegistry.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/resolveFunctionCall.ts
  • packages/genui/a2ui/test/basicFunctions.test.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/defineCatalogFunctions.test.ts
  • packages/genui/a2ui/test/executeFunctionCall.test.ts
  • packages/genui/a2ui/test/formContext.test.ts
  • packages/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

Comment thread packages/genui/a2ui/src/functions/formatters.ts Outdated
Comment thread packages/genui/a2ui/src/functions/formatters.ts Outdated
Comment thread packages/genui/a2ui/src/functions/sideEffects.ts Outdated
@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch from 3c8927d to 9375686 Compare May 13, 2026 05:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/genui/a2ui/src/functions/formatters.ts (1)

33-38: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Past review suggestion not fully applied: type should include null if data bindings can produce it.

The previous review suggested value?: string | null, but the current signature only has value?: string. In TypeScript strict mode, this means string | undefined and will reject null at compile time. However, line 37's ?? operator handles both undefined and null at runtime. If dynamic data bindings can produce null values, 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 value

Consider using unknown for count to match other formatters' pattern.

Other formatters (formatNumber, formatCurrency) accept value: unknown and coerce via toNumber. This function declares count: number (line 123) but still passes it to toNumber (line 129). For consistency and robustness—especially if data bindings can provide coercible values like strings—consider changing count: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3c8927d and 9375686.

📒 Files selected for processing (32)
  • packages/genui/a2ui-catalog-extractor/src/cli.ts
  • packages/genui/a2ui-catalog-extractor/src/index.ts
  • packages/genui/a2ui-catalog-extractor/test/extractor-functions.test.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/formatString.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/expected-functions/required.json
  • packages/genui/a2ui-catalog-extractor/test/fixtures/functions/basicFunctions.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/invalid/AsyncFunction.ts
  • packages/genui/a2ui-catalog-extractor/test/fixtures/tsconfig.json
  • packages/genui/a2ui/package.json
  • packages/genui/a2ui/src/catalog/CheckBox/index.tsx
  • packages/genui/a2ui/src/catalog/RadioGroup/index.tsx
  • packages/genui/a2ui/src/catalog/defineCatalog.ts
  • packages/genui/a2ui/src/catalog/index.ts
  • packages/genui/a2ui/src/functions/formatters.ts
  • packages/genui/a2ui/src/functions/index.ts
  • packages/genui/a2ui/src/functions/logic.ts
  • packages/genui/a2ui/src/functions/sideEffects.ts
  • packages/genui/a2ui/src/functions/validators.ts
  • packages/genui/a2ui/src/index.ts
  • packages/genui/a2ui/src/react/FormContext.ts
  • packages/genui/a2ui/src/react/index.ts
  • packages/genui/a2ui/src/react/useChecks.ts
  • packages/genui/a2ui/src/store/FormController.ts
  • packages/genui/a2ui/src/store/FunctionRegistry.ts
  • packages/genui/a2ui/src/store/index.ts
  • packages/genui/a2ui/src/store/resolveFunctionCall.ts
  • packages/genui/a2ui/test/basicFunctions.test.ts
  • packages/genui/a2ui/test/catalog.test.ts
  • packages/genui/a2ui/test/defineCatalogFunctions.test.ts
  • packages/genui/a2ui/test/executeFunctionCall.test.ts
  • packages/genui/a2ui/test/formContext.test.ts
  • packages/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

Comment thread packages/genui/a2ui/src/functions/formatters.ts Outdated
@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch from 9375686 to 0b1857a Compare May 13, 2026 05:31
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 13, 2026

Merging this PR will degrade performance by 76.53%

❌ 1 regressed benchmark
✅ 80 untouched benchmarks
⏩ 26 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
basic-performance-large-css 16.1 ms 68.6 ms -76.53%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing PupilTong:claude/goofy-rubin-cf8a05 (fe2df24) with main (9449860)2

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (353b1b7) during the generation of this report, so 9449860 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 13, 2026

React Example with Element Template

#591 Bundle Size — 199.95KiB (0%).

fe2df24(current) vs 353b1b7 main#569(baseline)

Bundle metrics  Change 2 changes
                 Current
#591
     Baseline
#569
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
Change  Modules 89(-2.2%) 91
No change  Duplicate Modules 27 27
Change  Duplicate Code 40.06%(+0.07%) 40.03%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#591
     Baseline
#569
No change  IMG 145.76KiB 145.76KiB
No change  Other 54.19KiB 54.19KiB

Bundle analysis reportBranch PupilTong:claude/goofy-rubin-cf8...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 13, 2026

Web Explorer

#9896 Bundle Size — 901.35KiB (0%).

fe2df24(current) vs 353b1b7 main#9874(baseline)

Bundle metrics  no changes
                 Current
#9896
     Baseline
#9874
No change  Initial JS 45.06KiB 45.06KiB
No change  Initial CSS 2.22KiB 2.22KiB
No change  Cache Invalidation 0% 0%
No change  Chunks 9 9
No change  Assets 11 11
No change  Modules 229 229
No change  Duplicate Modules 11 11
No change  Duplicate Code 27.22% 27.22%
No change  Packages 10 10
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#9896
     Baseline
#9874
No change  JS 497.08KiB 497.08KiB
No change  Other 402.06KiB 402.06KiB
No change  CSS 2.22KiB 2.22KiB

Bundle analysis reportBranch PupilTong:claude/goofy-rubin-cf8...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 13, 2026

React MTF Example

#1455 Bundle Size — 208.1KiB (0%).

fe2df24(current) vs 353b1b7 main#1434(baseline)

Bundle metrics  no changes
                 Current
#1455
     Baseline
#1434
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 192 192
No change  Duplicate Modules 77 77
No change  Duplicate Code 44.4% 44.4%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1455
     Baseline
#1434
No change  IMG 111.23KiB 111.23KiB
No change  Other 96.86KiB 96.86KiB

Bundle analysis reportBranch PupilTong:claude/goofy-rubin-cf8...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 13, 2026

React Example

#8323 Bundle Size — 237.15KiB (0%).

fe2df24(current) vs 353b1b7 main#8301(baseline)

Bundle metrics  no changes
                 Current
#8323
     Baseline
#8301
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 197 197
No change  Duplicate Modules 80 80
No change  Duplicate Code 44.89% 44.89%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#8323
     Baseline
#8301
No change  IMG 145.76KiB 145.76KiB
No change  Other 91.39KiB 91.39KiB

Bundle analysis reportBranch PupilTong:claude/goofy-rubin-cf8...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented May 13, 2026

React External

#1437 Bundle Size — 695.33KiB (0%).

fe2df24(current) vs 353b1b7 main#1415(baseline)

Bundle metrics  no changes
                 Current
#1437
     Baseline
#1415
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#1437
     Baseline
#1415
No change  Other 695.33KiB 695.33KiB

Bundle analysis reportBranch PupilTong:claude/goofy-rubin-cf8...Project dashboard


Generated by RelativeCIDocumentationReport issue

@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch 3 times, most recently from 128b873 to ca7af55 Compare May 13, 2026 08:58
@PupilTong
Copy link
Copy Markdown
Collaborator Author

Spec-alignment pass (ca7af554)

Ran a self-review against the A2UI 0.9 basic-catalog JSON Schema and found that 8 of my 14 function signatures didn't match the spec. Fixed in this push.

Signature changes

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's required / additionalProperties / parameters shape for the 13 shipped functions.
  • Tests rewritten for the new signatures: 78 renderer + 9 extractor pass.
  • pnpm biome check, pnpm dprint check, pnpm eslint all 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 (currently useAction still forwards to the agent).
  • Dynamic-property FunctionCall evaluation in useDataBinding.
  • email reintroduction with a ReDoS-safe validator.

@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch 2 times, most recently from e2980ba to 55f72d1 Compare May 13, 2026 11:19
HuJean
HuJean previously approved these changes May 14, 2026
@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch 2 times, most recently from 87345fd to ac57b58 Compare May 15, 2026 10:48
PupilTong added 2 commits May 16, 2026 02:15
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.
@PupilTong PupilTong force-pushed the claude/goofy-rubin-cf8a05 branch from ac57b58 to fe2df24 Compare May 15, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants