a2ui-project / a2ui-project/a2ui
[BUG]: non-ASCII data model keys are unreachable from templates in Dart, web_core and Python
- Dominant language
- TypeScript
- Stars
- 16.4k
- Forks
- 1.3k
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 134
Description
- [x] I have searched the existing issues to make sure this bug has not already been reported.
## Describe the Bug
A data model key containing any non-ASCII character cannot be referenced from a template. `${señor}`, `${café/precio}` and `${日本}` are parse errors in Dart, TypeScript and Python, and parse correctly in Swift.
| template | Dart | TypeScript | Python | Swift |
| --- | --- | --- | --- | --- |
| `${señor}` | error | error | error | `{path: "señor"}` |
| `${café/precio}` | error | error | error | `{path: "café/precio"}` |
| `${日本}` | error | error | error | `{path: "日本"}` |
| `hola ${señor} qué tal` | error | error | error | parses, literals intact |
| `${a_b-c.d}` | ok | ok | ok | ok |
So an agent that names its data with anything but English ASCII produces a surface that renders on iOS and fails everywhere else.
The data model itself has no such limit, which makes the state reachable but undisplayable:
```dart
final m = DataModel({});
m.set('/señor', 'Diego'); // fine
m.get('/señor'); // 'Diego'
ExpressionParser().parse(r'${señor}'); // A2uiExpressionError
```
An agent can write the key, and read it back through `getClientDataModel`, but cannot put it on screen.
## Root cause
The identifier scanners disagree, and the pattern is worth noting: the three that fail carry a hand-written ASCII range, while the one that works uses its language's own Unicode-aware predicate.
- `dart/a2ui_core/lib/src/processing/expressions.dart` — `_isAlnum` tests code units `0x30–0x39`, `0x41–0x5A`, `0x61–0x7A`.
- `renderers/web_core/.../expression_parser.ts` — `isAlnum` tests `c >= 'a' && c <= 'z'` and friends.
- `agent_sdks/python/a2ui_core/.../expression_parser.py` — `is_alnum` tests `"a" <= c <= "z"` and friends.
- `swift/core/Sources/BasicCatalog/Functions/ExpressionParser.swift` — `scanPathOrIdentifier` tests `c.isLetter || c.isNumber`, which is Unicode-aware in Swift.
The ASCII range looks like a literal port of the same three lines between the first three implementations; Swift's reads like the idiomatic translation, and is the one that comes out right.
## Expected Behavior
I would suggest aligning the other three with Swift rather than the reverse:
- The data model is JSON, and JSON object keys are Unicode strings.
- Paths are JSON Pointer (RFC 6901), which places no restriction on the characters in a key.
- Nothing in the v0.9 specification or in `basic_catalog_implementation_guide.md` says an identifier inside `${...}` must be ASCII.
- Restricting it makes part of a legal data model unreachable from the UI, which is the asymmetry shown above.
Each fix is a couple of lines: Dart has `RegExp(r'\p{L}|\p{N}', unicode: true)` or `String.fromCharCode` category checks, TypeScript has `/[\p{L}\p{N}]/u`, Python has `str.isalnum()`.
If instead the intent is that identifiers are ASCII-only, then that belongs in the guide, and Swift should be tightened — but then agents need a documented way to reach a key that ASCII cannot spell.
## Steps to Reproduce
```
${señor}
```
through each implementation's `ExpressionParser.parse`. Full table above.
## Environment Details
- **OS**: macOS
- **Browser/Platform**: Dart 3.12.2, Node.js 25.2.1, Python 3.13, Swift 6.2.1
- **SDK/Package Name & Version**: all four at main (676a899)
- **Protocol Version**: v0.9
## Additional Context
Found by running all four parser implementations over the same corpus, the method used for #2496 and #2498, now extended from two implementations to four. Two other results from the same run:
- On the `null` keyword left open in #2496, Dart is alone: TypeScript, Python and Swift all produce `''`. That is worth weighing in that discussion, though the guide's wording still points the other way.
- The Python parser raises bare `ValueError` for every parse failure, rather than the `A2uiParseError` its own SDK defines in `a2ui/core/exceptions.py`. Since the conformance schema's error categories map onto that hierarchy, a Python harness for a parser suite cannot currently distinguish a protocol error from any other `ValueError`. Filing separately if useful.
Contributor guide
Assessment
This issue has not been assessed yet.