From 2674ecd615ff54c260b898fa00ed2335f462467b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 19:33:21 +0000 Subject: [PATCH] Spread attributes in markup, `{...e}`; release 0.3.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Parse `{...e}` among an element's attributes (ref + crate). On an HTML element it is Solid's spread; on a component, a function source of Solid's merge between runs of named props: the last part with a field wins, and each field keeps updating. - Checker: a spread on a component is the "*" argument; its failures, holds and O-3 stores go wherever any field goes (R-2, R-f-5). - 07 §4, S-surface-5 (all five runners, crate parity incl. --lower and --project), guide/language.md. - Site: the props example spreads the rest onto the element. - Bump everything to 0.3.1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0117BoWNkVdujweQpFRfpvoj --- CHANGELOG.md | 7 ++ compiler/Cargo.lock | 2 +- compiler/Cargo.toml | 2 +- compiler/npm/darwin-arm64/package.json | 2 +- compiler/npm/darwin-x64/package.json | 2 +- compiler/npm/linux-arm64-gnu/package.json | 2 +- compiler/npm/linux-x64-gnu/package.json | 2 +- compiler/npm/wasm32-wasi/package.json | 2 +- compiler/npm/win32-x64-msvc/package.json | 2 +- compiler/package-lock.json | 4 +- compiler/package.json | 2 +- compiler/src/check.rs | 39 +++++++---- compiler/src/emit.rs | 4 +- compiler/src/oxc.rs | 4 ++ compiler/src/parse.rs | 16 +++++ compiler/src/project.rs | 5 ++ guide/language.md | 3 +- ref/compile/emit.ts | 4 +- ref/oracle/lower.ts | 15 +++-- ref/src/check.ts | 20 +++--- ref/src/parse.ts | 4 ++ ref/src/runtime.ts | 17 ++++- ref/types/project.ts | 2 +- runtime/package-lock.json | 4 +- runtime/package.json | 2 +- scenarios/S-surface-5.md | 68 ++++++++++++++++++++ site/index.html | 8 +-- spec/07-surface.md | 8 +++ tooling/rx-language-server/package-lock.json | 4 +- tooling/rx-language-server/package.json | 2 +- tooling/vscode-rx/README.md | 2 +- tooling/vscode-rx/package-lock.json | 4 +- tooling/vscode-rx/package.json | 2 +- 33 files changed, 206 insertions(+), 60 deletions(-) create mode 100644 scenarios/S-surface-5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index db2ddee8..56ef6e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.3.1 + +**New** + +- Spread attributes in markup, ` +} + +export default component App() { + source extra: { count: number; tone?: string; title: string } := { count: 1, tone: "warn", title: "a" } + + + +} +``` + +The first badge's `title` comes after the spread, so it is `fixed` whatever +`extra` holds; its `tone` comes from `extra`. In the second, `tone="calm"` +comes before the spread, so `extra.tone` wins while it has one. + +## Frames + +| instant | action | frame | +| ------- | ---------------------------------------------- | -------------------------------------- | +| 0 | mount | `x:warn:1:fixedopeny:warn:1:aopen` | +| 1 | handler `extra := { count: 2, title: "b" }` | `x:info:2:fixedopeny:calm:2:bopen` | + +Instant 1: `extra` no longer has `tone`, so the first badge falls back to +its default and the second to the attribute before the spread. + +## Rejected variants + +```rx +-- reject: PARSE +export default component App() { + source extra := { title: "a" } +

hi

+} +``` + +An attribute in braces is a spread: `{...extra}`. + +```rx +-- reject: UNHANDLED_FAILURE +component Card(props: { title: string }) { +

{props.title}

+} + +export default component App() { + info = await fetch("info") + loading fallback {

…

} { + + } +} +``` + +A spread passes `info`'s failure on with its fields: `Card` reads `title`, +so the read can fail with `Error`, and nothing above it catches. diff --git a/site/index.html b/site/index.html index 73dea7a6..e372f052 100644 --- a/site/index.html +++ b/site/index.html @@ -103,15 +103,15 @@

Mistakes of shape become errors

counter.rx:5:3 - error WRITE_TO_NON_SOURCE: double is an equation: only sources are written (W-1)

Destructure props, keep them live

-

In Solid, destructuring props reads them once and the component stops updating. In .rx, destructure them right in the parameter list: every name follows its prop, defaults and rest included.

-
inbox.rxcomponent Badge({ label, tone = "info", ...rest }: { label: string; tone?: string; count: number }) {
-  <span class={tone}>{label}: {rest.count}</span>
+  

In Solid, destructuring props reads them once and the component stops updating. In .rx, destructure them right in the parameter list: every name follows its prop, defaults and rest included, and the rest spreads onto an element as in JSX.

+
inbox.rxcomponent Badge({ label, tone = "info", ...rest }: { label: string; tone?: string; title?: string }) {
+  <span class={tone} {...rest}>{label}</span>
 }
 
 export default component Inbox() {
   source user := { name: "Ada", unread: 3 }
 
-  <Badge label={user.name} count={user.unread} />   // Badge updates when user changes
+  <Badge label={`${user.unread} unread`} title={user.name} />   // Badge updates when user changes
   <button onClick={() => { user := { ...user, unread: 0 } }}>Mark read</button>
 }
diff --git a/spec/07-surface.md b/spec/07-surface.md index 4f13591e..06027bcc 100644 --- a/spec/07-surface.md +++ b/spec/07-surface.md @@ -169,6 +169,14 @@ attributes are equations, children are readers, components are applied as element and bare at block level. `portal to(e) { … }` renders into `e` (RG-8). Each block is a region: declarations, then markup (RG-1, RG-2, RG-5). `if x { … }` narrows `x` inside (RG-1c). +- `{...e}` among the attributes is a spread, as JSX's: every field of `e`. + Parts merge in order and the last one that has a field wins (Solid's + `merge`), so an attribute after the spread overrides it and one before + it is a default. A spread is an equation like any attribute: its fields + keep updating. On a component it is passed, not read: its fields are + read where the component reads its props, and its failures and holds go + where any of those reads go (R-2, R-f-5). On an HTML element it is read + where the element is (S-surface-5). - `for x in e` keys rows by identity, or by the collection's `key` (ST-4b). - Text follows JSX: whitespace that contains a line break is not text, a line's leading and trailing whitespace is not text, and text that runs diff --git a/tooling/rx-language-server/package-lock.json b/tooling/rx-language-server/package-lock.json index 2abe1402..2c388831 100644 --- a/tooling/rx-language-server/package-lock.json +++ b/tooling/rx-language-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@reactivescript/language-server", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@reactivescript/language-server", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "@volar/language-core": "2.4.30", diff --git a/tooling/rx-language-server/package.json b/tooling/rx-language-server/package.json index 2b35163e..70596071 100644 --- a/tooling/rx-language-server/package.json +++ b/tooling/rx-language-server/package.json @@ -1,6 +1,6 @@ { "name": "@reactivescript/language-server", - "version": "0.3.0", + "version": "0.3.1", "description": "The .rx language server (spec/11 CP-8): Volar over the crate's TSX projection, TypeScript's answers mapped back onto .rx, the crate's own diagnostics; rx-tsc type-checks .rx from the command line.", "license": "MIT", "repository": { diff --git a/tooling/vscode-rx/README.md b/tooling/vscode-rx/README.md index 9446f3aa..7f5990c4 100644 --- a/tooling/vscode-rx/README.md +++ b/tooling/vscode-rx/README.md @@ -27,7 +27,7 @@ artifact of a run of the repository's `editor` workflow, or build one (below), then run **Extensions: Install from VSIX…** in the command palette, or ```sh -code --install-extension reactivescript-0.3.0.vsix +code --install-extension reactivescript-0.3.1.vsix ``` The project needs a `tsconfig.json` with Solid's JSX (`"jsx": "preserve"`, diff --git a/tooling/vscode-rx/package-lock.json b/tooling/vscode-rx/package-lock.json index 08c52182..9eae14a7 100644 --- a/tooling/vscode-rx/package-lock.json +++ b/tooling/vscode-rx/package-lock.json @@ -1,12 +1,12 @@ { "name": "reactivescript", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "reactivescript", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "highlight.js": "^11.11.1", diff --git a/tooling/vscode-rx/package.json b/tooling/vscode-rx/package.json index 2d113776..95668161 100644 --- a/tooling/vscode-rx/package.json +++ b/tooling/vscode-rx/package.json @@ -2,7 +2,7 @@ "name": "reactivescript", "displayName": "ReactiveScript", "description": "ReactiveScript (.rx, .d.rx): highlighting, and TypeScript's types, hover, go to definition, completion and errors through the .rx language server, with the compiler's own diagnostics; ```rx blocks highlighted in Markdown.", - "version": "0.3.0", + "version": "0.3.1", "publisher": "solidjs", "license": "MIT", "icon": "icon.png",