@abide/abide 0.122.0 → 0.123.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/docs/REGISTRY.md +11 -11
- package/docs/RULEBOOK.md +26 -13
- package/docs/abide.md +12 -12
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +4 -2
- package/src/compiler/REACTIVE_MEMBERS.ts +2 -3
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/TypeSession.ts +1 -1
- package/src/compiler/classifyRead.ts +18 -4
- package/src/compiler/projectShadow.ts +4 -1
- package/src/compiler/reactiveTypes.ts +2 -2
- package/src/compiler/readsIn.ts +144 -49
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/assistant.ts +25 -4
- package/src/server/derivedArgs.ts +71 -2
- package/src/shared/index.ts +2 -1
- package/src/shared/propCell.ts +18 -0
- package/src/shared/rpc.ts +9 -2
- package/src/shared/writePath.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.123.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- the args-as-sent walk returns the args it does not copy, walks a cycle once, and keeps a parsed __proto__ a member (RULEBOOK 19.25) ([`ee02cac`](https://github.com/briancray/abide/commit/ee02cacbb64f00374ae98154e788c59c5a4ec49b))
|
|
8
|
+
- bf77c28: `abide check` no longer fails when an inline component is passed to a component that sits inside another component's body: the outer close tag's read in the check's projection now ends with a `;`, so the braces that scope the passed component are not parsed as an arrow function.
|
|
9
|
+
- 7c24094: A no-argument mutation behind `middleware` no longer starts its body before its rungs run. Dropping the previous call's answer built the request's instance of the handler, which started the body ahead of every rung, so a refusing rung (`signedIn`) answered after the body had already run. Calling the same mutation twice in one request now writes twice behind rungs as well (RULEBOOK 16.7, 16.11, 16.16).
|
|
10
|
+
- 94fbea7: A handler's derived args schema reads a member left `undefined` as absent, so an in-process call like `getResearch({ home, topic: undefined })` is answered as the wire answers it rather than refused 422 (RULEBOOK 19.25, 15.8).
|
|
11
|
+
|
|
12
|
+
## 0.123.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 0e5f83e: an unfinished conversation's log line says why — aborted, failed with the throw, or stopped (RULEBOOK 44.31) ([`042aea3`](https://github.com/briancray/abide/commit/042aea3150ae0fd29959bbe1346d1595b86617bc))
|
|
17
|
+
- 0e5f83e: Breaking: a member the value's type declares is the value's, and a read prop holds a Reactive where one is due (RULEBOOK 31.5, 31.6, 33.27) ([`50b1151`](https://github.com/briancray/abide/commit/50b1151d46389185df0b61d0dd82241566446275))
|
|
18
|
+
|
|
3
19
|
## 0.122.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/docs/REGISTRY.md
CHANGED
|
@@ -168,7 +168,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
168
168
|
|
|
169
169
|
| Name | Signature | Meaning | Rules |
|
|
170
170
|
| --- | --- | --- | --- |
|
|
171
|
-
| `Selection` | `Reactive<any, any, any, any> \| Keyed<any, any, any, any> \| Rpc<any, any, any, any> \| { tags: string[] }` | What is being asked about. | 13.1, 13.8, 13.9, 13.13, 31.
|
|
171
|
+
| `Selection` | `Reactive<any, any, any, any> \| Keyed<any, any, any, any> \| Rpc<any, any, any, any> \| { tags: string[] }` | What is being asked about. | 13.1, 13.8, 13.9, 13.13, 31.13 |
|
|
172
172
|
| `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | 13.2, 13.3, 13.5, 13.12, 41.28, 41.31, 46.11 |
|
|
173
173
|
| `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | 13.3, 13.5, 13.11, 13.12, 41.28, 41.31 |
|
|
174
174
|
| `synced` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection holds what its producer made. | 13.12, 13.18 |
|
|
@@ -273,7 +273,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
273
273
|
|
|
274
274
|
| Name | Signature | Meaning | Rules |
|
|
275
275
|
| --- | --- | --- | --- |
|
|
276
|
-
| `Schema<T>` | `((value: unknown) => T) \| StandardSchemaV1<T>` | The two forms a shape may be declared in. | 19.2, 19.3, 19.4, 19.6, 19.8, 19.9, 19.10, 19.18, 19.11, 19.12 |
|
|
276
|
+
| `Schema<T>` | `((value: unknown) => T) \| StandardSchemaV1<T>` | The two forms a shape may be declared in. | 19.2, 19.3, 19.4, 19.6, 19.8, 19.9, 19.10, 19.18, 19.25, 19.11, 19.12 |
|
|
277
277
|
| `JsonValue` | `null \| boolean \| number \| string \| JsonValue[] \| { [k: string]: JsonValue }` | What an `Args` may hold. | — |
|
|
278
278
|
| `Issues<T>` | `T extends object ? Partial<Record<Paths<T> \| '', string[]>> : string[]` | What was wrong, keyed by where. | 19.4, 19.19 |
|
|
279
279
|
| `Paths<T>` | `string` | The dot-joined leaf paths of a type. | 19.5 |
|
|
@@ -349,7 +349,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
349
349
|
|
|
350
350
|
| Name | Signature | Meaning | Rules |
|
|
351
351
|
| --- | --- | --- | --- |
|
|
352
|
-
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | 44.5, 44.13, 44.17, 44.18, 44.19, 44.24, 44.25, 44.27, 44.30, 44.33 |
|
|
352
|
+
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | 44.5, 44.13, 44.17, 44.18, 44.19, 44.24, 44.25, 44.27, 44.30, 44.33, 44.45, 44.46 |
|
|
353
353
|
| `ClaudeCodeConfig` | `{ model?: string; fallbackModel?: string; systemPrompt?: string; appendSystemPrompt?: string; effort?: 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'; maxBudgetUsd?: number; betas?: string[]; disallowedTools?: string[]; tools?: string[] }` | The Claude Code options a site may set for a turn, and the built-in tools it asks the visitor for. | 44.16, 44.29 |
|
|
354
354
|
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | 44.14, 44.32, 44.33, 44.40, 44.41, 44.42, 44.43, 44.44 |
|
|
355
355
|
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | 44.20, 44.22, 44.32, 44.33, 44.34, 44.40 |
|
|
@@ -380,11 +380,11 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
380
380
|
|
|
381
381
|
| Name | Signature | Meaning | Rules |
|
|
382
382
|
| --- | --- | --- | --- |
|
|
383
|
-
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | 23.1, 23.2, 23.3, 23.4, 30.20, 30.21, 30.23, 30.33, 19.16, 31.
|
|
384
|
-
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | 23.1, 23.5, 23.9, 30.23, 31.
|
|
383
|
+
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | 23.1, 23.2, 23.3, 23.4, 30.20, 30.21, 30.23, 30.33, 19.16, 31.15 |
|
|
384
|
+
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | 23.1, 23.5, 23.9, 30.23, 31.15 |
|
|
385
385
|
| `Params` | `Record<string, string \| string[]>` | What a matched route's segments are. | 23.5 |
|
|
386
|
-
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | 23.1, 23.6, 23.14, 31.
|
|
387
|
-
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | 23.1, 31.
|
|
386
|
+
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | 23.1, 23.6, 23.14, 31.15 |
|
|
387
|
+
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | 23.1, 31.15 |
|
|
388
388
|
|
|
389
389
|
## `online`
|
|
390
390
|
|
|
@@ -533,12 +533,12 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
533
533
|
| `foo` wherever the declared type of its position is not a `Reactive` | a live read | 31.1, 31.2, 31.10 |
|
|
534
534
|
| `const x: Reactive<…> = foo` / a `Reactive<…>`-typed parameter, return or `bind:` operand | the `Reactive` | 31.3 |
|
|
535
535
|
| `await foo` | the settling of the `Reactive`, which is its value | 1.4, 2.7, 31.3 |
|
|
536
|
-
| `refresh(foo)` / a `Selection`-typed parameter | the `Reactive` | 31.
|
|
536
|
+
| `refresh(foo)` / a `Selection`-typed parameter | the `Reactive` | 31.13 |
|
|
537
537
|
| `foo = bar` | a write | 31.4, 11.61 |
|
|
538
|
-
| `foo.bar = v` | a write through a path | 31.8, 31.
|
|
538
|
+
| `foo.bar = v` | a write through a path | 31.8, 31.14 |
|
|
539
539
|
| `foo.push(v)` | the same | 31.8, 31.9 |
|
|
540
540
|
| `const [a, b] = foo` / `...foo` | one read of the value | 31.12 |
|
|
541
|
-
| `foo.bar` | the value's `bar`, or the `Reactive`'s own `bar` where it declares one | 31.5
|
|
541
|
+
| `foo.bar` | the value's `bar`, or the `Reactive`'s own `bar` where it declares one the value does not | 31.5 |
|
|
542
542
|
| `foo.bar(…)` | the `Reactive` API where `bar` is a member | 31.6 |
|
|
543
543
|
| `foo()` / `foo.set(v)` | the explicit forms | 31.7, 31.11 |
|
|
544
544
|
|
|
@@ -596,7 +596,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
596
596
|
|
|
597
597
|
| Name | Signature | Meaning | Rules |
|
|
598
598
|
| --- | --- | --- | --- |
|
|
599
|
-
| `props` | `<Props>() => { [K in keyof Props]: Props[K] }` | One live accessor per prop the component declares. | 33.1, 33.2, 33.3, 33.4, 33.5, 33.6, 33.7, 33.8, 33.9, 33.10, 33.11, 33.14, 33.15, 33.20, 33.22, 33.25 |
|
|
599
|
+
| `props` | `<Props>() => { [K in keyof Props]: Props[K] }` | One live accessor per prop the component declares. | 33.1, 33.2, 33.3, 33.4, 33.5, 33.6, 33.7, 33.8, 33.9, 33.10, 33.11, 33.14, 33.15, 33.20, 33.22, 33.25, 33.27 |
|
|
600
600
|
| `Attributes<Tag>` | `Attributes<Tag extends keyof HTMLElementTagNameMap \| keyof SVGElementTagNameMap>` | The attributes an HTML or SVG element takes, typed as a component's props. | 33.23, 33.24, 33.26 |
|
|
601
601
|
| `children` | always accepted | What a caller passes between the tags. | 33.16, 32.18 |
|
|
602
602
|
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -1335,6 +1335,9 @@ to, and MUST NOT change the type of the value that slot gates.
|
|
|
1335
1335
|
19.24 The value a body produces MUST be typed from the body alone, and a `schema` declared beside it
|
|
1336
1336
|
MUST NOT change that type.
|
|
1337
1337
|
|
|
1338
|
+
19.25 A `Schema` derived per 19.9 MUST read a member left `undefined`, at any depth of a record, as
|
|
1339
|
+
absent — in process as the wire carries it (30.28).
|
|
1340
|
+
|
|
1338
1341
|
# 20. Generated surfaces
|
|
1339
1342
|
|
|
1340
1343
|
20.1 Every surface `Clients` names MUST be derived from the route table, and MUST NOT be authored
|
|
@@ -1871,16 +1874,17 @@ the routes 30.9 ranks after that route.
|
|
|
1871
1874
|
|
|
1872
1875
|
31.3 A name MUST hold the `Reactive` only where the declared type of its position is a `Reactive`: an
|
|
1873
1876
|
annotated binding, an annotated return, a `Reactive`-typed parameter, a `bind:` operand, an `await`
|
|
1874
|
-
operand — or where 31.
|
|
1877
|
+
operand — or where 31.13 holds it.
|
|
1875
1878
|
|
|
1876
1879
|
31.4 Assignment to such a name MUST be a write, and the assigned value MAY be settled, a load, or a
|
|
1877
1880
|
`Reactive`.
|
|
1878
1881
|
|
|
1879
1882
|
31.5 A property access on such a name MUST reach the value, except a member declared on the
|
|
1880
|
-
`Reactive` type, which MUST reach that member bound to the
|
|
1883
|
+
`Reactive` type that the value's type does not declare, which MUST reach that member bound to the
|
|
1884
|
+
`Reactive`.
|
|
1881
1885
|
|
|
1882
|
-
31.6 A call on such a name MUST reach the `Reactive` API where
|
|
1883
|
-
|
|
1886
|
+
31.6 A call on such a name MUST reach the `Reactive` API where 31.5 reaches the `Reactive`'s
|
|
1887
|
+
member, and the value's method otherwise.
|
|
1884
1888
|
|
|
1885
1889
|
31.7 `s` and `s.set` MUST keep compiling inside a `.abide` file.
|
|
1886
1890
|
|
|
@@ -1896,17 +1900,14 @@ the member name.
|
|
|
1896
1900
|
|
|
1897
1901
|
31.12 A destructuring pattern or a spread over such a name MUST read it once.
|
|
1898
1902
|
|
|
1899
|
-
31.13
|
|
1900
|
-
naming the explicit read.
|
|
1901
|
-
|
|
1902
|
-
31.14 A name MUST hold the `Reactive` where the declared type of its position is a `Selection`, or a
|
|
1903
|
+
31.13 A name MUST hold the `Reactive` where the declared type of its position is a `Selection`, or a
|
|
1903
1904
|
type parameter constrained by one.
|
|
1904
1905
|
|
|
1905
|
-
31.
|
|
1906
|
+
31.14 A write through a member path that reaches a value which is not plain data — an array, or an
|
|
1906
1907
|
object whose prototype is `Object.prototype` or `null` — MUST assign on that value itself, MUST NOT
|
|
1907
1908
|
copy it or any level above it, and MUST NOT wake a reader.
|
|
1908
1909
|
|
|
1909
|
-
31.
|
|
1910
|
+
31.15 `route.url`, `route.params`, `route.name` and `route.navigating` MUST each be read and written
|
|
1910
1911
|
as a name bound to a `Reactive` is, per 31.1 to 31.7.
|
|
1911
1912
|
|
|
1912
1913
|
# 32. Templating
|
|
@@ -2219,6 +2220,10 @@ that element as its `currentTarget`.
|
|
|
2219
2220
|
included, under the names SVG spells them, and a tag both HTML and SVG name MUST be typed as the HTML
|
|
2220
2221
|
element.
|
|
2221
2222
|
|
|
2223
|
+
33.27 A name `props` binds MUST hold a `Reactive` wherever 31.3 or 31.13 has a name hold its
|
|
2224
|
+
`Reactive` or 31.5 has it reach its `Reactive`'s member, and that `Reactive`'s probes MUST answer for
|
|
2225
|
+
every `Reactive` the caller's expression read.
|
|
2226
|
+
|
|
2222
2227
|
# 34. Script and style blocks
|
|
2223
2228
|
|
|
2224
2229
|
34.1 A `<script>` block MUST be per-instance component setup.
|
|
@@ -3355,8 +3360,8 @@ run no turn for 30 seconds.
|
|
|
3355
3360
|
44.23 A post to `/__abide/bridge` carrying a line that is not JSON MUST be refused 400, and none of
|
|
3356
3361
|
its lines MUST be relayed.
|
|
3357
3362
|
|
|
3358
|
-
44.24 A bridge MUST answer an MCP request its Claude Code made with a failure where
|
|
3359
|
-
|
|
3363
|
+
44.24 A bridge MUST answer an MCP request its Claude Code made with a failure where
|
|
3364
|
+
`/__abide/bridge` sent no reply within 120 seconds.
|
|
3360
3365
|
|
|
3361
3366
|
44.25 A bridge whose stream from `/__abide/bridge` ends unasked MUST redial it with its pairing's
|
|
3362
3367
|
token, each wait twice the last, until it is refused or 60 seconds have passed.
|
|
@@ -3379,7 +3384,9 @@ before it exits.
|
|
|
3379
3384
|
|
|
3380
3385
|
44.31 A conversation MUST write one `debug` line on `abide:assistant` when its events end, naming
|
|
3381
3386
|
its stop reason, the transcript's length, the tool calls it made, its usage and how long it took,
|
|
3382
|
-
and naming it `unfinished` where it ended without a `done
|
|
3387
|
+
and naming it `unfinished` where it ended without a `done`, followed by why — `aborted` where the
|
|
3388
|
+
request's signal aborted, else `failed` where its provider threw, with the throw's message ending the
|
|
3389
|
+
line, else `stopped` where its caller stopped reading.
|
|
3383
3390
|
|
|
3384
3391
|
44.32 A command `claudeCode.bridgeCommand` or `claudeCode.bridge` mints with `tools` MUST grant
|
|
3385
3392
|
those built-in tools after its `--` as `--tools`, and one minted without `tools` MUST carry no `--`.
|
|
@@ -3427,6 +3434,12 @@ install wrote.
|
|
|
3427
3434
|
NAME=VALUE` it is given, and a bridge opening an `abide-bridge` link MUST run Claude Code with those
|
|
3428
3435
|
options ahead of the link's built-in tools and with those variables set, and a link MUST NOT set either.
|
|
3429
3436
|
|
|
3437
|
+
44.45 A bridge MUST say that `/__abide/bridge` did not take a report once, and not again until a
|
|
3438
|
+
report is taken, and MUST NOT say it while it redials per 44.25.
|
|
3439
|
+
|
|
3440
|
+
44.46 A bridge MUST end a turn once `/__abide/bridge` did not take a report carrying it, and MUST
|
|
3441
|
+
report that turn failed.
|
|
3442
|
+
|
|
3430
3443
|
# 45. Offline
|
|
3431
3444
|
|
|
3432
3445
|
45.1 Where `ABIDE_OFFLINE` is on, a page `abide build` built MUST register the worker
|
package/docs/abide.md
CHANGED
|
@@ -625,7 +625,7 @@ test('typing writes the value, and the value fills the field', async () => {
|
|
|
625
625
|
| `const [a, b] = foo` / `...foo` | one read, taken apart |
|
|
626
626
|
| `foo = bar` | a write |
|
|
627
627
|
| `foo.bar` | the value's `bar` |
|
|
628
|
-
| `foo.bar(…)` | the value's method,
|
|
628
|
+
| `foo.bar(…)` | the value's method where it has one, and otherwise a member of the `Reactive` such as `busy` |
|
|
629
629
|
| `foo()` / `foo.set(v)` | the explicit read and write |
|
|
630
630
|
| `await foo` | the value, once it has settled |
|
|
631
631
|
| `refresh(foo)` / a `Selection`-typed parameter | the `Reactive` itself |
|
|
@@ -7482,7 +7482,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7482
7482
|
|
|
7483
7483
|
| Name | Signature | Meaning | Rules |
|
|
7484
7484
|
| --- | --- | --- | --- |
|
|
7485
|
-
| `Selection` | `Reactive<any, any, any, any> \| Keyed<any, any, any, any> \| Rpc<any, any, any, any> \| { tags: string[] }` | What is being asked about. | [13.1](/rulebook#13.1), [13.8](/rulebook#13.8), [13.9](/rulebook#13.9), [13.13](/rulebook#13.13), [31.
|
|
7485
|
+
| `Selection` | `Reactive<any, any, any, any> \| Keyed<any, any, any, any> \| Rpc<any, any, any, any> \| { tags: string[] }` | What is being asked about. | [13.1](/rulebook#13.1), [13.8](/rulebook#13.8), [13.9](/rulebook#13.9), [13.13](/rulebook#13.13), [31.13](/rulebook#31.13) |
|
|
7486
7486
|
|
|
7487
7487
|
## Taught in
|
|
7488
7488
|
|
|
@@ -8067,7 +8067,7 @@ Transports: what is written in place rather than imported.
|
|
|
8067
8067
|
|
|
8068
8068
|
| Name | Signature | Meaning | Rules |
|
|
8069
8069
|
| --- | --- | --- | --- |
|
|
8070
|
-
| `Schema<T>` | `((value: unknown) => T) \| StandardSchemaV1<T>` | The two forms a shape may be declared in. | [19.2](/rulebook#19.2), [19.3](/rulebook#19.3), [19.4](/rulebook#19.4), [19.6](/rulebook#19.6), [19.8](/rulebook#19.8), [19.9](/rulebook#19.9), [19.10](/rulebook#19.10), [19.18](/rulebook#19.18), [19.11](/rulebook#19.11), [19.12](/rulebook#19.12) |
|
|
8070
|
+
| `Schema<T>` | `((value: unknown) => T) \| StandardSchemaV1<T>` | The two forms a shape may be declared in. | [19.2](/rulebook#19.2), [19.3](/rulebook#19.3), [19.4](/rulebook#19.4), [19.6](/rulebook#19.6), [19.8](/rulebook#19.8), [19.9](/rulebook#19.9), [19.10](/rulebook#19.10), [19.18](/rulebook#19.18), [19.25](/rulebook#19.25), [19.11](/rulebook#19.11), [19.12](/rulebook#19.12) |
|
|
8071
8071
|
| `JsonSchema` | `JsonValue` | A JSON Schema document, the native form. | [19.7](/rulebook#19.7), [19.14](/rulebook#19.14) |
|
|
8072
8072
|
|
|
8073
8073
|
## Taught in
|
|
@@ -8418,7 +8418,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8418
8418
|
|
|
8419
8419
|
| Name | Signature | Meaning | Rules |
|
|
8420
8420
|
| --- | --- | --- | --- |
|
|
8421
|
-
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.27](/rulebook#44.27), [44.30](/rulebook#44.30), [44.33](/rulebook#44.33) |
|
|
8421
|
+
| `claudeCode` | `(config?: ClaudeCodeConfig) => Provider` | The visitor's own Claude Code, reached through the bridge paired with their browser, as a provider. | [44.5](/rulebook#44.5), [44.13](/rulebook#44.13), [44.17](/rulebook#44.17), [44.18](/rulebook#44.18), [44.19](/rulebook#44.19), [44.24](/rulebook#44.24), [44.25](/rulebook#44.25), [44.27](/rulebook#44.27), [44.30](/rulebook#44.30), [44.33](/rulebook#44.33), [44.45](/rulebook#44.45), [44.46](/rulebook#44.46) |
|
|
8422
8422
|
| `claudeCode.bridgeCommand` | `(options?: { tools?: string[] }) => { command: string; link: string; expires: number }` | The command and the link that pair a bridge with this browser, granting the built-in tools it is given. | [44.14](/rulebook#44.14), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.40](/rulebook#44.40), [44.41](/rulebook#44.41), [44.42](/rulebook#44.42), [44.43](/rulebook#44.43), [44.44](/rulebook#44.44) |
|
|
8423
8423
|
| `claudeCode.bridge` | `(options?: { tools?: string[] }) => AsyncGenerator<BridgeStatus>` | This browser's pairing, as it changes. | [44.20](/rulebook#44.20), [44.22](/rulebook#44.22), [44.32](/rulebook#44.32), [44.33](/rulebook#44.33), [44.34](/rulebook#44.34), [44.40](/rulebook#44.40) |
|
|
8424
8424
|
| `/__abide/bridge` | `GET`, `POST` | Where a bridge pairs, and is relayed its turns. | [36.7](/rulebook#36.7), [44.14](/rulebook#44.14), [44.15](/rulebook#44.15), [44.19](/rulebook#44.19), [44.23](/rulebook#44.23), [44.26](/rulebook#44.26), [16.57](/rulebook#16.57), [16.78](/rulebook#16.78) |
|
|
@@ -8529,10 +8529,10 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8529
8529
|
|
|
8530
8530
|
| Name | Signature | Meaning | Rules |
|
|
8531
8531
|
| --- | --- | --- | --- |
|
|
8532
|
-
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | [23.1](/rulebook#23.1), [23.2](/rulebook#23.2), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.23](/rulebook#30.23), [30.33](/rulebook#30.33), [19.16](/rulebook#19.16), [31.
|
|
8533
|
-
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | [23.1](/rulebook#23.1), [23.5](/rulebook#23.5), [23.9](/rulebook#23.9), [30.23](/rulebook#30.23), [31.
|
|
8534
|
-
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | [23.1](/rulebook#23.1), [23.6](/rulebook#23.6), [23.14](/rulebook#23.14), [31.
|
|
8535
|
-
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | [23.1](/rulebook#23.1), [31.
|
|
8532
|
+
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | [23.1](/rulebook#23.1), [23.2](/rulebook#23.2), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.23](/rulebook#30.23), [30.33](/rulebook#30.33), [19.16](/rulebook#19.16), [31.15](/rulebook#31.15) |
|
|
8533
|
+
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | [23.1](/rulebook#23.1), [23.5](/rulebook#23.5), [23.9](/rulebook#23.9), [30.23](/rulebook#30.23), [31.15](/rulebook#31.15) |
|
|
8534
|
+
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | [23.1](/rulebook#23.1), [23.6](/rulebook#23.6), [23.14](/rulebook#23.14), [31.15](/rulebook#31.15) |
|
|
8535
|
+
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | [23.1](/rulebook#23.1), [31.15](/rulebook#31.15) |
|
|
8536
8536
|
|
|
8537
8537
|
## Taught in
|
|
8538
8538
|
|
|
@@ -9215,12 +9215,12 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
9215
9215
|
| `foo` wherever the declared type of its position is not a `Reactive` | a live read | [31.1](/rulebook#31.1), [31.2](/rulebook#31.2), [31.10](/rulebook#31.10) |
|
|
9216
9216
|
| `const x: Reactive<…> = foo` / a `Reactive<…>`-typed parameter, return or `bind:` operand | the `Reactive` | [31.3](/rulebook#31.3) |
|
|
9217
9217
|
| `await foo` | the settling of the `Reactive`, which is its value | [1.4](/rulebook#1.4), [2.7](/rulebook#2.7), [31.3](/rulebook#31.3) |
|
|
9218
|
-
| `refresh(foo)` / a `Selection`-typed parameter | the `Reactive` | [31.
|
|
9218
|
+
| `refresh(foo)` / a `Selection`-typed parameter | the `Reactive` | [31.13](/rulebook#31.13) |
|
|
9219
9219
|
| `foo = bar` | a write | [31.4](/rulebook#31.4), [11.61](/rulebook#11.61) |
|
|
9220
|
-
| `foo.bar = v` | a write through a path | [31.8](/rulebook#31.8), [31.
|
|
9220
|
+
| `foo.bar = v` | a write through a path | [31.8](/rulebook#31.8), [31.14](/rulebook#31.14) |
|
|
9221
9221
|
| `foo.push(v)` | the same | [31.8](/rulebook#31.8), [31.9](/rulebook#31.9) |
|
|
9222
9222
|
| `const [a, b] = foo` / `...foo` | one read of the value | [31.12](/rulebook#31.12) |
|
|
9223
|
-
| `foo.bar` | the value's `bar`, or the `Reactive`'s own `bar` where it declares one | [31.5](/rulebook#31.5)
|
|
9223
|
+
| `foo.bar` | the value's `bar`, or the `Reactive`'s own `bar` where it declares one the value does not | [31.5](/rulebook#31.5) |
|
|
9224
9224
|
| `foo.bar(…)` | the `Reactive` API where `bar` is a member | [31.6](/rulebook#31.6) |
|
|
9225
9225
|
| `foo()` / `foo.set(v)` | the explicit forms | [31.7](/rulebook#31.7), [31.11](/rulebook#31.11) |
|
|
9226
9226
|
|
|
@@ -9330,7 +9330,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
9330
9330
|
|
|
9331
9331
|
| Name | Signature | Meaning | Rules |
|
|
9332
9332
|
| --- | --- | --- | --- |
|
|
9333
|
-
| `props` | `<Props>() => { [K in keyof Props]: Props[K] }` | One live accessor per prop the component declares. | [33.1](/rulebook#33.1), [33.2](/rulebook#33.2), [33.3](/rulebook#33.3), [33.4](/rulebook#33.4), [33.5](/rulebook#33.5), [33.6](/rulebook#33.6), [33.7](/rulebook#33.7), [33.8](/rulebook#33.8), [33.9](/rulebook#33.9), [33.10](/rulebook#33.10), [33.11](/rulebook#33.11), [33.14](/rulebook#33.14), [33.15](/rulebook#33.15), [33.20](/rulebook#33.20), [33.22](/rulebook#33.22), [33.25](/rulebook#33.25) |
|
|
9333
|
+
| `props` | `<Props>() => { [K in keyof Props]: Props[K] }` | One live accessor per prop the component declares. | [33.1](/rulebook#33.1), [33.2](/rulebook#33.2), [33.3](/rulebook#33.3), [33.4](/rulebook#33.4), [33.5](/rulebook#33.5), [33.6](/rulebook#33.6), [33.7](/rulebook#33.7), [33.8](/rulebook#33.8), [33.9](/rulebook#33.9), [33.10](/rulebook#33.10), [33.11](/rulebook#33.11), [33.14](/rulebook#33.14), [33.15](/rulebook#33.15), [33.20](/rulebook#33.20), [33.22](/rulebook#33.22), [33.25](/rulebook#33.25), [33.27](/rulebook#33.27) |
|
|
9334
9334
|
| `children` | always accepted | What a caller passes between the tags. | [33.16](/rulebook#33.16), [32.18](/rulebook#32.18) |
|
|
9335
9335
|
|
|
9336
9336
|
## Taught in
|
package/package.json
CHANGED
package/src/compiler/Lowering.ts
CHANGED
|
@@ -686,7 +686,8 @@ export class Lowering {
|
|
|
686
686
|
/**
|
|
687
687
|
* The read's name as emitted: `name()` for a written prop, whose cell its accessor hands back.
|
|
688
688
|
*
|
|
689
|
-
* 33.6 — a written prop's cell is reached through its accessor.
|
|
689
|
+
* 33.6 — a written prop's cell is reached through its accessor. 33.27 — a read prop where a
|
|
690
|
+
* `Reactive` is due, a cell over its accessor.
|
|
690
691
|
*/
|
|
691
692
|
private nameOf(read: Read): string {
|
|
692
693
|
const spelled = this.projection.text.slice(read.start, read.end)
|
|
@@ -694,6 +695,7 @@ export class Lowering {
|
|
|
694
695
|
// reads the one `route` every page shares.
|
|
695
696
|
if (this.routed !== undefined && read.routeParams)
|
|
696
697
|
return `(${spelled} as unknown as () => __RouteParams<${JSON.stringify(this.routed.route)}, ${JSON.stringify(this.routed.kind)}>)`
|
|
698
|
+
if (read.held) return `${this.use('heldProp')}(${spelled})`
|
|
697
699
|
return read.through ? `${spelled}()` : spelled
|
|
698
700
|
}
|
|
699
701
|
|
|
@@ -734,7 +736,7 @@ export class Lowering {
|
|
|
734
736
|
const name = this.nameOf(read)
|
|
735
737
|
switch (read.shape) {
|
|
736
738
|
case 'asWritten':
|
|
737
|
-
return read.through ? name : undefined
|
|
739
|
+
return read.through || read.held ? name : undefined
|
|
738
740
|
// 31.5 — the member BOUND to the `Reactive`, arguments and all.
|
|
739
741
|
case 'member':
|
|
740
742
|
return this.checking
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* `Reactive` itself, which reaches that. Without the list every `n.foo` reads as the API and the
|
|
9
9
|
* value's own members are unreachable, which is what `regions.join is not a function` was.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* resolves to the `Reactive`'s, and 31.13 asks for a warning naming exactly that case.
|
|
11
|
+
* The list names the candidates; the value's TYPE decides (`declaresMember` in `readsIn`): a value
|
|
12
|
+
* whose own type declares `set` or `error` keeps its own, so a `Map`'s `set` is the map's.
|
|
14
13
|
*
|
|
15
14
|
* WHICH IS WHY A NAME THE `Reactive` DOES NOT DECLARE MUST NOT BE HERE: it takes a member the
|
|
16
15
|
* value really has and resolves it to nothing. This list is exactly `MEMBERS` in
|
|
@@ -29,6 +29,8 @@ export const SHARED_NAMES: ReadonlySet<string> = new Set([
|
|
|
29
29
|
// 32.6, 33.6, 33.7 — a component's `bind:`, and the cell a child writing that prop builds.
|
|
30
30
|
'boundProp',
|
|
31
31
|
'propCell',
|
|
32
|
+
// 33.27 — a read prop where its position holds the `Reactive`.
|
|
33
|
+
'heldProp',
|
|
32
34
|
'propCells',
|
|
33
35
|
// 33.20 — a default answering where the caller's accessor reads `undefined`.
|
|
34
36
|
'propOr',
|
|
@@ -625,7 +625,7 @@ export class TypeSession {
|
|
|
625
625
|
* shadow is asked is `readsIn`'s.
|
|
626
626
|
*
|
|
627
627
|
* WHAT IT HAS TO SAY THAT IS NOT A TYPE ERROR comes back beside the reads: the compiler's own
|
|
628
|
-
* warnings (14.14 to 14.17,
|
|
628
|
+
* warnings (14.14 to 14.17, 32.14), each on the `.abide` line where the source's text
|
|
629
629
|
* was handed in.
|
|
630
630
|
*/
|
|
631
631
|
async readsFor(
|
|
@@ -52,8 +52,7 @@ export type Read = {
|
|
|
52
52
|
* that brackets a binding element's initializer, which together make a default an accessor;
|
|
53
53
|
* `absent`, which gives a binding with no default an accessor of `undefined` (33.4); and
|
|
54
54
|
* `restMember`, a member of the rest element, which calls that key's accessor (33.15), beside
|
|
55
|
-
* `restWhole`, the rest element handed on as one value (33.22). The
|
|
56
|
-
* pair and `absent` are INSERTIONS — zero-width, so what stands between the pair goes on being
|
|
55
|
+
* `restWhole`, the rest element handed on as one value (33.22). The pair and `absent` are INSERTIONS — zero-width, so what stands between the pair goes on being
|
|
57
56
|
* lowered by the same walk, and a default reading a `Reactive` still reads it.
|
|
58
57
|
*/
|
|
59
58
|
shape:
|
|
@@ -96,6 +95,12 @@ export type Read = {
|
|
|
96
95
|
* pair, that the default is one too.
|
|
97
96
|
*/
|
|
98
97
|
through: boolean
|
|
98
|
+
/**
|
|
99
|
+
* 33.27 — the name is a prop the child only reads, standing where a `Reactive` is due.
|
|
100
|
+
*
|
|
101
|
+
* So the `Reactive` every rewrite reaches is a cell over its accessor, `heldProp(name)`.
|
|
102
|
+
*/
|
|
103
|
+
held: boolean
|
|
99
104
|
/** 32.32 — the name is bound to a `reactive(…)` whose options declare `by`. */
|
|
100
105
|
byValue: boolean
|
|
101
106
|
/** 14.8 — the name is declared by a branch-local `<script>`, inside the body it scopes. */
|
|
@@ -136,6 +141,7 @@ export function newRead(
|
|
|
136
141
|
path: [],
|
|
137
142
|
value: undefined,
|
|
138
143
|
through: false,
|
|
144
|
+
held: false,
|
|
139
145
|
byValue: false,
|
|
140
146
|
local: false,
|
|
141
147
|
keys: [],
|
|
@@ -235,7 +241,10 @@ export function classifyPropRead(node: Node): Read | undefined {
|
|
|
235
241
|
* `undefined` for an occurrence that is not a read at all: the declaration itself, an import or
|
|
236
242
|
* export specifier, and a `typeof n` in a type.
|
|
237
243
|
*/
|
|
238
|
-
export function classifyRead(
|
|
244
|
+
export function classifyRead(
|
|
245
|
+
node: Node,
|
|
246
|
+
valueMember = false,
|
|
247
|
+
): Read | undefined {
|
|
239
248
|
const parent = node.parent
|
|
240
249
|
if (isNotARead(parent)) return undefined
|
|
241
250
|
if (
|
|
@@ -278,7 +287,12 @@ export function classifyRead(node: Node): Read | undefined {
|
|
|
278
287
|
if (isPropertyAccessExpression(up) && up.expression === top) {
|
|
279
288
|
// 31.5 — the `Reactive`'s own member is the API, and no path. Called, it stays as
|
|
280
289
|
// written; REFERENCED, it is handed over bound, so `promise.then(n.set)` writes `n`.
|
|
281
|
-
|
|
290
|
+
// `valueMember` where the value's own type declares one of that name (31.5).
|
|
291
|
+
if (
|
|
292
|
+
top === node &&
|
|
293
|
+
!valueMember &&
|
|
294
|
+
REACTIVE_MEMBERS.has(up.name.text)
|
|
295
|
+
) {
|
|
282
296
|
const outer = up.parent
|
|
283
297
|
if (
|
|
284
298
|
(isCallExpression(outer) && outer.expression === up) ||
|
|
@@ -391,11 +391,13 @@ function project(file: AbideFile): Projection {
|
|
|
391
391
|
}
|
|
392
392
|
text += '}))\n'
|
|
393
393
|
// 38.116 — a close tag of its own is a second read of the name, so it is renamed with it.
|
|
394
|
+
// Ended by its `;`: the braces a passed component is scoped in (32.51) can follow, and
|
|
395
|
+
// `(Name)\n{` parses as an arrow function's head.
|
|
394
396
|
const closed = file.origins.closes.get(node)
|
|
395
397
|
if (closed !== undefined) {
|
|
396
398
|
text += ';('
|
|
397
399
|
copied(node.tag, closed)
|
|
398
|
-
text += ')
|
|
400
|
+
text += ');\n'
|
|
399
401
|
}
|
|
400
402
|
for (const attribute of refused)
|
|
401
403
|
slots.set(
|
|
@@ -697,6 +699,7 @@ declare function ${BOUND}<Value>(value: ${REACTIVE}<Value, any, any, any>): Valu
|
|
|
697
699
|
declare function ${BOUND}<Value>(value: { get(): Value; set(value: Value): unknown }): Value
|
|
698
700
|
declare function __restValues<Accessors>(record: Accessors): { [Key in keyof Accessors]: NonNullable<Accessors[Key]> extends () => infer Value ? Value : never }
|
|
699
701
|
declare function __propCell<Value>(read: () => Value): () => ${REACTIVE}<Value>
|
|
702
|
+
declare function __heldProp<Value>(read: () => Value): ${REACTIVE}<Value, Value, any, any>
|
|
700
703
|
declare function __raw(value: unknown): unknown
|
|
701
704
|
declare function __writePath<Root>(root: Root, path: readonly PropertyKey[], value: unknown): Root
|
|
702
705
|
declare type __RouteParams<Pattern extends string, Kind extends string> = Kind extends 'layout' ? ${ROUTE_PARAMS}<Pattern> & { [name: string]: string | string[] | undefined } : ${ROUTE_PARAMS}<Pattern>
|
|
@@ -111,7 +111,7 @@ export type Abide = {
|
|
|
111
111
|
route: string | undefined
|
|
112
112
|
reactive: CheckedSymbol | undefined
|
|
113
113
|
/**
|
|
114
|
-
* 31.
|
|
114
|
+
* 31.13 — abide's `Selection` alias, which a position may be typed as rather than as a `Reactive`.
|
|
115
115
|
*/
|
|
116
116
|
selection: CheckedSymbol | undefined
|
|
117
117
|
props: string | undefined
|
|
@@ -185,7 +185,7 @@ export async function isReactiveType(
|
|
|
185
185
|
/**
|
|
186
186
|
* Whether a type is abide's `Selection`, or a type parameter constrained by one.
|
|
187
187
|
*
|
|
188
|
-
* 31.
|
|
188
|
+
* 31.13 — A POSITION TYPED AS A `Selection`, or as a type parameter constrained by one (the free
|
|
189
189
|
* `refresh`, `invalidate`, `busy` and `ready` take `Chosen extends Selection`). A value is never a
|
|
190
190
|
* selection of itself, so a read there hands `refresh` the invoice rather than the thing to reload.
|
|
191
191
|
*/
|
package/src/compiler/readsIn.ts
CHANGED
|
@@ -246,27 +246,22 @@ export async function readsIn(
|
|
|
246
246
|
)
|
|
247
247
|
}),
|
|
248
248
|
)
|
|
249
|
+
const owned = await ownMembers(checker, nodes.flat(), (node) =>
|
|
250
|
+
valueTypeOf(checker, node),
|
|
251
|
+
)
|
|
249
252
|
const holding: Promise<void>[] = []
|
|
250
253
|
const located: { node: Node; read: Read }[] = []
|
|
251
254
|
for (let index = 0; index < nodes.length; index += 1)
|
|
252
255
|
for (const node of nodes[index] as (Node | undefined)[]) {
|
|
253
256
|
if (node === undefined) continue
|
|
254
|
-
const read = classifyRead(node)
|
|
257
|
+
const read = classifyRead(node, owned.has(node))
|
|
255
258
|
if (read === undefined) continue
|
|
256
259
|
read.byValue = byValue[index] as boolean
|
|
257
260
|
read.local = index >= topLevel
|
|
258
261
|
reads.push(read)
|
|
259
262
|
located.push({ node, read })
|
|
260
263
|
if (read.shape === 'value')
|
|
261
|
-
holding.push(
|
|
262
|
-
declaredType(checker, node).then(async (type) => {
|
|
263
|
-
if (
|
|
264
|
-
type !== undefined &&
|
|
265
|
-
(await isReactiveType(checker, abide, type, true))
|
|
266
|
-
)
|
|
267
|
-
read.shape = 'asWritten'
|
|
268
|
-
}),
|
|
269
|
-
)
|
|
264
|
+
holding.push(holdIn(checker, abide, node, read))
|
|
270
265
|
}
|
|
271
266
|
await Promise.all(holding)
|
|
272
267
|
await props
|
|
@@ -392,7 +387,7 @@ async function bindingsOfReads(
|
|
|
392
387
|
* The tracking and type warnings a file's reactive reads earn.
|
|
393
388
|
*
|
|
394
389
|
* The syntax alone decides 14.14 to 14.16 (`trackingWarnings`);
|
|
395
|
-
* 14.17
|
|
390
|
+
* 14.17 asks what a read hands back, which is the return of the `Reactive`'s call.
|
|
396
391
|
*/
|
|
397
392
|
async function warningsFor(
|
|
398
393
|
checker: Checker,
|
|
@@ -424,45 +419,16 @@ async function warningsFor(
|
|
|
424
419
|
isBinaryExpression(parent) &&
|
|
425
420
|
parent.left === node &&
|
|
426
421
|
parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken
|
|
427
|
-
|
|
428
|
-
isPropertyAccessExpression(parent) &&
|
|
429
|
-
parent.expression === node &&
|
|
430
|
-
isIdentifier(parent.name) &&
|
|
431
|
-
REACTIVE_MEMBERS.has(parent.name.text)
|
|
432
|
-
? parent.name.text
|
|
433
|
-
: undefined
|
|
434
|
-
if (!nullish && member === undefined) continue
|
|
422
|
+
if (!nullish) continue
|
|
435
423
|
typed.push(
|
|
436
424
|
valueTypeOf(checker, node).then(async (value) => {
|
|
437
|
-
if (value === undefined) return
|
|
438
425
|
// 14.17 — the fallback of a read that is never nullish is never taken: an unlanded
|
|
439
426
|
// read is a hole the position fills, not an `undefined` (2.2).
|
|
440
|
-
if (
|
|
427
|
+
if (value !== undefined && !(await admitsNullish(value)))
|
|
441
428
|
warnings.push({
|
|
442
429
|
at: read.start,
|
|
443
430
|
message: `abide: \`${name} ?? …\` never takes its fallback — a read of \`${name}\` is not undefined while it loads, and its type says it is not undefined after. Ask \`${name}.ready()\` to tell the two apart. RULEBOOK 14.17.`,
|
|
444
431
|
})
|
|
445
|
-
if (member === undefined) return
|
|
446
|
-
// Per member, because a union's properties are only those every member shares, and
|
|
447
|
-
// `undefined` shares none — so `HTMLVideoElement | undefined` would declare no `error`.
|
|
448
|
-
for (const held of await unionMembers(value)) {
|
|
449
|
-
if ((held.flags & NULLISH) !== 0) continue
|
|
450
|
-
for (const property of await checker.getPropertiesOfType(
|
|
451
|
-
held,
|
|
452
|
-
))
|
|
453
|
-
if (property.name === member) {
|
|
454
|
-
const dot = (
|
|
455
|
-
parent as { questionDotToken?: unknown }
|
|
456
|
-
).questionDotToken
|
|
457
|
-
? '?.'
|
|
458
|
-
: '.'
|
|
459
|
-
warnings.push({
|
|
460
|
-
at: read.start,
|
|
461
|
-
message: `abide: \`${name}${dot}${member}\` reaches the Reactive's own \`${member}\`, and the value it holds has one too. Write \`${name}()${dot}${member}\` for the value's. RULEBOOK 31.13.`,
|
|
462
|
-
})
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
432
|
}),
|
|
467
433
|
)
|
|
468
434
|
}
|
|
@@ -779,7 +745,7 @@ function valueLeaves(node: Node, leaves: Node[]): void {
|
|
|
779
745
|
const ROUTE_MEMBERS = new Set(['url', 'params', 'name', 'navigating'])
|
|
780
746
|
|
|
781
747
|
/**
|
|
782
|
-
* 31.
|
|
748
|
+
* 31.15 — `route`'s four values, each read by name as a name bound to a `Reactive` is.
|
|
783
749
|
*
|
|
784
750
|
* `route.params.id` reads `route.params().id`, and the explicit call keeps compiling (31.7). Told
|
|
785
751
|
* by the symbol, as `props` is, so an app's own `route` is not abide's.
|
|
@@ -1142,13 +1108,18 @@ async function propsReads(
|
|
|
1142
1108
|
),
|
|
1143
1109
|
),
|
|
1144
1110
|
)
|
|
1111
|
+
const owned = await ownMembers(checker, found.flat(), (node) =>
|
|
1112
|
+
checker.getTypeAtLocation(node),
|
|
1113
|
+
)
|
|
1114
|
+
// 33.27 — each prop asked whether its position holds the `Reactive`, all at once.
|
|
1115
|
+
const holding: Promise<void>[] = []
|
|
1145
1116
|
for (let at = 0; at < found.length; at += 1) {
|
|
1146
1117
|
const binding = bindings[at] as (typeof bindings)[number]
|
|
1147
1118
|
if (binding.rest) {
|
|
1148
1119
|
restReads(found[at] as (Node | undefined)[], reads)
|
|
1149
1120
|
continue
|
|
1150
1121
|
}
|
|
1151
|
-
const uses: Read[] = []
|
|
1122
|
+
const uses: { node: Node; read: Read }[] = []
|
|
1152
1123
|
let written = false
|
|
1153
1124
|
for (const node of found[at] as (Node | undefined)[]) {
|
|
1154
1125
|
// The binding itself is the pattern's, and not a use of the name.
|
|
@@ -1157,15 +1128,16 @@ async function propsReads(
|
|
|
1157
1128
|
node.parent?.kind === SyntaxKind.BindingElement
|
|
1158
1129
|
)
|
|
1159
1130
|
continue
|
|
1160
|
-
const read = classifyRead(node)
|
|
1131
|
+
const read = classifyRead(node, owned.has(node))
|
|
1161
1132
|
if (read === undefined) continue
|
|
1162
1133
|
if (
|
|
1163
1134
|
read.shape === 'write' ||
|
|
1164
1135
|
read.shape === 'update' ||
|
|
1165
|
-
isBindOperand(positions, node)
|
|
1136
|
+
isBindOperand(positions, node) ||
|
|
1137
|
+
writesThrough(node, owned)
|
|
1166
1138
|
)
|
|
1167
1139
|
written = true
|
|
1168
|
-
uses.push(read)
|
|
1140
|
+
uses.push({ node, read })
|
|
1169
1141
|
}
|
|
1170
1142
|
if (!written) {
|
|
1171
1143
|
for (const node of found[at] as (Node | undefined)[]) {
|
|
@@ -1173,18 +1145,141 @@ async function propsReads(
|
|
|
1173
1145
|
const read = classifyPropRead(node)
|
|
1174
1146
|
if (read === undefined) continue
|
|
1175
1147
|
read.local = binding.local
|
|
1176
|
-
|
|
1148
|
+
if (read.shape !== 'value') {
|
|
1149
|
+
reads.push(read)
|
|
1150
|
+
continue
|
|
1151
|
+
}
|
|
1152
|
+
holding.push(
|
|
1153
|
+
holdsProp(checker, abide, node, owned.has(node)).then(
|
|
1154
|
+
(held) => {
|
|
1155
|
+
if (!held) return void reads.push(read)
|
|
1156
|
+
// Classified as a page's own name is, reaching a cell over the accessor.
|
|
1157
|
+
const asHeld = classifyRead(node) as Read
|
|
1158
|
+
if (asHeld.shape === 'value')
|
|
1159
|
+
asHeld.shape = 'asWritten'
|
|
1160
|
+
asHeld.held = true
|
|
1161
|
+
asHeld.local = binding.local
|
|
1162
|
+
reads.push(asHeld)
|
|
1163
|
+
},
|
|
1164
|
+
),
|
|
1165
|
+
)
|
|
1177
1166
|
}
|
|
1178
1167
|
continue
|
|
1179
1168
|
}
|
|
1180
1169
|
binding.call.path.push({ name: binding.key, start: 0, end: 0 })
|
|
1181
1170
|
for (const read of binding.defaults) read.through = true
|
|
1182
|
-
|
|
1171
|
+
// A written prop's cell is already a `Reactive`, and a position due one holds it.
|
|
1172
|
+
for (const { node, read } of uses) {
|
|
1183
1173
|
read.through = true
|
|
1184
1174
|
read.local = binding.local
|
|
1185
1175
|
reads.push(read)
|
|
1176
|
+
if (read.shape === 'value')
|
|
1177
|
+
holding.push(holdIn(checker, abide, node, read))
|
|
1186
1178
|
}
|
|
1187
1179
|
}
|
|
1180
|
+
await Promise.all(holding)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* 31.5 — the `Reactive`'s member a property access reaches by name, or `undefined` where the name
|
|
1185
|
+
* is not one — `n.ready`, `n.set`. Whether it is the `Reactive`'s is the value's type's to say
|
|
1186
|
+
* (`ownMembers`).
|
|
1187
|
+
*/
|
|
1188
|
+
function memberNamed(node: Node): string | undefined {
|
|
1189
|
+
const parent = node.parent
|
|
1190
|
+
return isPropertyAccessExpression(parent) &&
|
|
1191
|
+
parent.expression === node &&
|
|
1192
|
+
isIdentifier(parent.name) &&
|
|
1193
|
+
REACTIVE_MEMBERS.has(parent.name.text)
|
|
1194
|
+
? parent.name.text
|
|
1195
|
+
: undefined
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* 31.5 — the occurrences among `nodes` reaching a member the VALUE's own type declares, where the
|
|
1200
|
+
* access is the value's rather than the `Reactive`'s — a `Map`'s `set` is the map's. Asked before
|
|
1201
|
+
* any is classified, so each is classified once.
|
|
1202
|
+
*
|
|
1203
|
+
* Per union member, because a union's properties are only those every member shares, and
|
|
1204
|
+
* `undefined` shares none — so `HTMLVideoElement | undefined` would declare no `error`. A value
|
|
1205
|
+
* typed `any` or `unknown` declares none: its type says nothing of what it holds.
|
|
1206
|
+
*/
|
|
1207
|
+
async function ownMembers(
|
|
1208
|
+
checker: Checker,
|
|
1209
|
+
nodes: readonly (Node | undefined)[],
|
|
1210
|
+
valueOf: (node: Node) => Promise<Type | undefined>,
|
|
1211
|
+
): Promise<Set<Node>> {
|
|
1212
|
+
const owned = new Set<Node>()
|
|
1213
|
+
const asked: Promise<void>[] = []
|
|
1214
|
+
for (const node of nodes) {
|
|
1215
|
+
if (node === undefined) continue
|
|
1216
|
+
const member = memberNamed(node)
|
|
1217
|
+
if (member === undefined) continue
|
|
1218
|
+
asked.push(
|
|
1219
|
+
valueOf(node).then(async (value) => {
|
|
1220
|
+
if (value === undefined) return
|
|
1221
|
+
for (const held of await unionMembers(value))
|
|
1222
|
+
if (
|
|
1223
|
+
(held.flags & NULLISH) === 0 &&
|
|
1224
|
+
(await checker.getPropertyOfType(held, member)) !==
|
|
1225
|
+
undefined
|
|
1226
|
+
)
|
|
1227
|
+
return void owned.add(node)
|
|
1228
|
+
}),
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
await Promise.all(asked)
|
|
1232
|
+
return owned
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* 31.7, 33.6, 33.7 — `n.set(v)` and `n.patch(…)` are writes by name, called or handed on, where the
|
|
1237
|
+
* value's own type declares no member of that name — a `Map`'s `set` writes the map, not the prop.
|
|
1238
|
+
*/
|
|
1239
|
+
function writesThrough(node: Node, owned: ReadonlySet<Node>): boolean {
|
|
1240
|
+
const member = memberNamed(node)
|
|
1241
|
+
return (member === 'set' || member === 'patch') && !owned.has(node)
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* 33.27 — whether a prop the child only reads stands where its `Reactive` is due, as a page's own
|
|
1246
|
+
* name would: an `await` operand (31.3), a `Reactive`'s member its value does not declare (31.5),
|
|
1247
|
+
* or a position typed as a `Reactive` or a `Selection` (31.3, 31.13).
|
|
1248
|
+
*/
|
|
1249
|
+
async function holdsProp(
|
|
1250
|
+
checker: Checker,
|
|
1251
|
+
abide: Abide,
|
|
1252
|
+
node: Node,
|
|
1253
|
+
ownsMember: boolean,
|
|
1254
|
+
): Promise<boolean> {
|
|
1255
|
+
if (node.parent.kind === SyntaxKind.AwaitExpression) return true
|
|
1256
|
+
if (memberNamed(node) !== undefined) return !ownsMember
|
|
1257
|
+
return typedReactive(checker, abide, node)
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* 31.3, 31.13 — `read` holds the `Reactive` where `node` stands in a position whose declared type
|
|
1262
|
+
* is a `Reactive` or a `Selection`.
|
|
1263
|
+
*/
|
|
1264
|
+
async function holdIn(
|
|
1265
|
+
checker: Checker,
|
|
1266
|
+
abide: Abide,
|
|
1267
|
+
node: Node,
|
|
1268
|
+
read: Read,
|
|
1269
|
+
): Promise<void> {
|
|
1270
|
+
if (await typedReactive(checker, abide, node)) read.shape = 'asWritten'
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/** 31.3, 31.13 — whether `node`'s position is declared a `Reactive` or a `Selection`. */
|
|
1274
|
+
async function typedReactive(
|
|
1275
|
+
checker: Checker,
|
|
1276
|
+
abide: Abide,
|
|
1277
|
+
node: Node,
|
|
1278
|
+
): Promise<boolean> {
|
|
1279
|
+
const type = await declaredType(checker, node)
|
|
1280
|
+
return (
|
|
1281
|
+
type !== undefined && (await isReactiveType(checker, abide, type, true))
|
|
1282
|
+
)
|
|
1188
1283
|
}
|
|
1189
1284
|
|
|
1190
1285
|
/**
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The `abide-bridge` release a pairing command names, so a visitor's `bunx` runs the relay this
|
|
2
2
|
// server speaks. `packages/abide/tests/claudeCode.test.ts` holds it to the bridge package's own.
|
|
3
|
-
export const BRIDGE_VERSION = '0.5.
|
|
3
|
+
export const BRIDGE_VERSION = '0.5.1'
|
package/src/server/assistant.ts
CHANGED
|
@@ -122,37 +122,58 @@ export async function* assistant(
|
|
|
122
122
|
options.mcp === true ? appSurface() : NO_SURFACE,
|
|
123
123
|
askerOf(request()),
|
|
124
124
|
)
|
|
125
|
+
const signal = request().signal
|
|
125
126
|
const events = provider.run(turns, tools, {
|
|
126
127
|
maxTurns: options.maxTurns ?? MAX_TURNS,
|
|
127
|
-
signal
|
|
128
|
+
signal,
|
|
128
129
|
})
|
|
129
130
|
// 28.7 — a closed namespace runs the provider's stream untouched.
|
|
130
|
-
yield* ASSISTANT_LOG.enabled()
|
|
131
|
+
yield* ASSISTANT_LOG.enabled()
|
|
132
|
+
? traced(events, messages.length, signal)
|
|
133
|
+
: events
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* `events`, and one line on `abide:assistant` once they end, however they end. 44.31.
|
|
138
|
+
*
|
|
139
|
+
* AN UNFINISHED CONVERSATION SAYS WHY, an abort read ahead of a throw: an aborted provider may throw
|
|
140
|
+
* as it unwinds, and the abort is what ended it.
|
|
141
|
+
*/
|
|
134
142
|
async function* traced(
|
|
135
143
|
events: AsyncGenerator<AssistantEvent>,
|
|
136
144
|
messageCount: number,
|
|
145
|
+
signal: AbortSignal,
|
|
137
146
|
): AsyncGenerator<AssistantEvent> {
|
|
138
147
|
const started = performance.now()
|
|
139
148
|
let calls = 0
|
|
140
149
|
let done: Extract<AssistantEvent, { type: 'done' }> | undefined
|
|
150
|
+
let failure: string | undefined
|
|
141
151
|
try {
|
|
142
152
|
for await (const event of events) {
|
|
143
153
|
if (event.type === 'tool') calls += 1
|
|
144
154
|
else if (event.type === 'done') done = event
|
|
145
155
|
yield event
|
|
146
156
|
}
|
|
157
|
+
} catch (thrown) {
|
|
158
|
+
failure = thrown instanceof Error ? thrown.message : String(thrown)
|
|
159
|
+
throw thrown
|
|
147
160
|
} finally {
|
|
161
|
+
const ending =
|
|
162
|
+
done?.stop ??
|
|
163
|
+
(signal.aborted
|
|
164
|
+
? 'unfinished aborted'
|
|
165
|
+
: failure === undefined
|
|
166
|
+
? 'unfinished stopped'
|
|
167
|
+
: 'unfinished failed')
|
|
148
168
|
ASSISTANT_LOG.debug(
|
|
149
|
-
|
|
169
|
+
ending,
|
|
150
170
|
`${messageCount} messages`,
|
|
151
171
|
`${calls} tool calls`,
|
|
152
172
|
done === undefined
|
|
153
173
|
? 'no usage'
|
|
154
174
|
: `${done.usage.input}/${done.usage.output} tokens`,
|
|
155
175
|
`${(performance.now() - started).toFixed(1)}ms`,
|
|
176
|
+
...(ending === 'unfinished failed' ? [failure] : []),
|
|
156
177
|
)
|
|
157
178
|
}
|
|
158
179
|
}
|
|
@@ -15,12 +15,81 @@ import { argsDocumentOf } from './coerceArgs.ts'
|
|
|
15
15
|
* called, whether or not an app mounted it. Not where its `args` gate publishes a document of its
|
|
16
16
|
* own, which holds the args itself (19.7). Args that did not come are held as an empty record: a
|
|
17
17
|
* schema is only derived where the handler's args are a required record, so a required member
|
|
18
|
-
* missing is refused, not read off nothing.
|
|
18
|
+
* missing is refused, not read off nothing. The args are held as SENT (`asSent`): a call in process
|
|
19
|
+
* passing a member `undefined` is the call the wire carries without it.
|
|
19
20
|
*/
|
|
20
21
|
export function derivedArgs(
|
|
21
22
|
handler: HeldToAnnotation & Gated<unknown>,
|
|
22
23
|
schema: JsonValue,
|
|
23
24
|
): void {
|
|
24
25
|
if (argsDocumentOf(handler) !== undefined) return
|
|
25
|
-
handler[DERIVED_ARGS] = (args) => validateJson(schema, args ?? {})
|
|
26
|
+
handler[DERIVED_ARGS] = (args) => validateJson(schema, asSent(args ?? {}))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The records `asSent` is inside of, so a cyclic value is walked once rather than forever. */
|
|
30
|
+
const WALKING: object[] = []
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `value` as the wire carries it, which drops a member left `undefined` (19.25): a URL omits it
|
|
34
|
+
* (30.28) and JSON leaves it out, at any depth of a body. `value` itself wherever it holds none, so
|
|
35
|
+
* the walk allocates nothing for the call that writes no `undefined` member.
|
|
36
|
+
*
|
|
37
|
+
* AN `undefined` IN A LIST IS KEPT. The two encodings part there — JSON writes `null`, a URL the
|
|
38
|
+
* text `undefined` — so neither is the wire's, and the schema answers the value as given.
|
|
39
|
+
*/
|
|
40
|
+
export function asSent(value: unknown): unknown {
|
|
41
|
+
if (value === null || typeof value !== 'object' || WALKING.includes(value))
|
|
42
|
+
return value
|
|
43
|
+
WALKING.push(value)
|
|
44
|
+
try {
|
|
45
|
+
return Array.isArray(value)
|
|
46
|
+
? listAsSent(value)
|
|
47
|
+
: recordAsSent(value as Record<string, unknown>)
|
|
48
|
+
} finally {
|
|
49
|
+
WALKING.pop()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function listAsSent(list: unknown[]): unknown[] {
|
|
54
|
+
let sent: unknown[] | null = null
|
|
55
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
56
|
+
const member = list[index]
|
|
57
|
+
const held = asSent(member)
|
|
58
|
+
if (sent === null && held !== member) sent = list.slice(0, index)
|
|
59
|
+
sent?.push(held)
|
|
60
|
+
}
|
|
61
|
+
return sent ?? list
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function recordAsSent(
|
|
65
|
+
record: Record<string, unknown>,
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
let sent: Record<string, unknown> | null = null
|
|
68
|
+
for (const key in record) {
|
|
69
|
+
if (!Object.hasOwn(record, key)) continue
|
|
70
|
+
const member = record[key]
|
|
71
|
+
const held = member === undefined ? undefined : asSent(member)
|
|
72
|
+
if (sent === null && (member === undefined || held !== member))
|
|
73
|
+
sent = membersBefore(record, key)
|
|
74
|
+
if (sent !== null && held !== undefined) sent[key] = held
|
|
75
|
+
}
|
|
76
|
+
return sent ?? record
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A copy of `record`'s own members ahead of `key`, in their order.
|
|
81
|
+
*
|
|
82
|
+
* NO PROTOTYPE, so an own `__proto__` member JSON parsed stays a member the schema reads rather
|
|
83
|
+
* than becoming the copy's prototype.
|
|
84
|
+
*/
|
|
85
|
+
function membersBefore(
|
|
86
|
+
record: Record<string, unknown>,
|
|
87
|
+
key: string,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const copy: Record<string, unknown> = Object.create(null)
|
|
90
|
+
for (const earlier in record) {
|
|
91
|
+
if (earlier === key) break
|
|
92
|
+
if (Object.hasOwn(record, earlier)) copy[earlier] = record[earlier]
|
|
93
|
+
}
|
|
94
|
+
return copy
|
|
26
95
|
}
|
package/src/shared/index.ts
CHANGED
|
@@ -54,8 +54,9 @@ export { online } from './online.ts'
|
|
|
54
54
|
export { outbox, type QueuedCall } from './outbox.ts'
|
|
55
55
|
export { onMenu } from './onMenu.ts'
|
|
56
56
|
export { type Principal, principal } from './principal.ts'
|
|
57
|
-
// EMITTED CODE'S: a prop the child writes (33.6, 33.7).
|
|
57
|
+
// EMITTED CODE'S: a prop the child writes (33.6, 33.7), and one it hands where a `Reactive` is due (33.27).
|
|
58
58
|
export {
|
|
59
|
+
heldProp,
|
|
59
60
|
propCell,
|
|
60
61
|
propCells,
|
|
61
62
|
propOr,
|
package/src/shared/propCell.ts
CHANGED
|
@@ -21,6 +21,24 @@ export function propCell(read: Accessor): () => Reactive<unknown> {
|
|
|
21
21
|
return () => cell
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Each read prop's `heldProp`, built on the first ask and kept for as long as its accessor is.
|
|
25
|
+
const HELD = new WeakMap<Accessor, Reactive<unknown>>()
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 33.27 — a prop the child only reads, where its position holds the `Reactive` (31.3, 31.13): the
|
|
29
|
+
* one cell `propCell` would have built, over the caller's accessor. So `busy(rows)` asks what the
|
|
30
|
+
* caller's expression read — the caller's own `Reactive` where it bound one — and a plain value's is
|
|
31
|
+
* a cell nothing loads. Built only where one is asked for, and once per accessor.
|
|
32
|
+
*/
|
|
33
|
+
export function heldProp(read: Accessor): Reactive<unknown> {
|
|
34
|
+
let cell = HELD.get(read)
|
|
35
|
+
if (cell === undefined) {
|
|
36
|
+
cell = propCell(read)()
|
|
37
|
+
HELD.set(read, cell)
|
|
38
|
+
}
|
|
39
|
+
return cell
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
/**
|
|
25
43
|
* 33.20 — a defaulted prop's accessor: the caller's, answering the default wherever it reads
|
|
26
44
|
* `undefined`, and the default alone where the caller passed nothing. A bound accessor asked to
|
package/src/shared/rpc.ts
CHANGED
|
@@ -407,6 +407,12 @@ export function declare(method: Method): Declare {
|
|
|
407
407
|
rungs.length === 0
|
|
408
408
|
? null
|
|
409
409
|
: compose(rungs, async (ctx: RpcContext) => {
|
|
410
|
+
// 16.7 behind rungs — the handler's spent answer is dropped HERE, once the
|
|
411
|
+
// rungs let the call through: reading an unkeyed value's cell builds this
|
|
412
|
+
// request's instance, which starts its body (1.7), and dropped in `call` that
|
|
413
|
+
// ran the body ahead of every rung (16.11, 16.16).
|
|
414
|
+
if (cacheOptions !== undefined && !keyed)
|
|
415
|
+
dropAnswered(value)
|
|
410
416
|
const reached = reach(argsOf(ctx))
|
|
411
417
|
const started = performance.now()
|
|
412
418
|
try {
|
|
@@ -464,8 +470,9 @@ export function declare(method: Method): Declare {
|
|
|
464
470
|
}
|
|
465
471
|
// 16.7 — a mutation's call is a write each time, even where the last one settled this
|
|
466
472
|
// turn: a keyed value's reach evicts an outlived entry itself, and an unkeyed one's
|
|
467
|
-
// spent answer is dropped here.
|
|
468
|
-
if (cacheOptions !== undefined && !keyed
|
|
473
|
+
// spent answer is dropped here — or, behind rungs, in the onion's center.
|
|
474
|
+
if (cacheOptions !== undefined && !keyed && wrapped === null)
|
|
475
|
+
dropAnswered(value)
|
|
469
476
|
const reached = callLoad(
|
|
470
477
|
reachThrough,
|
|
471
478
|
args,
|
package/src/shared/writePath.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { putOwn } from './putOwn.ts'
|
|
|
14
14
|
* object where an array was, and `length`, `map` and every later `{#for}` over it go with it. The
|
|
15
15
|
* shape is decided here, where the value is.
|
|
16
16
|
*
|
|
17
|
-
* 31.
|
|
17
|
+
* 31.14 — ONLY PLAIN DATA IS COPIED. An element, a class instance, a `Map`: anything whose prototype
|
|
18
18
|
* is not `Object.prototype`, `null` or `Array.prototype` owns its own state, and its setters say
|
|
19
19
|
* what a write means — `video.currentTime = t` seeks. A spread of one is a plain object holding a
|
|
20
20
|
* few own fields, so the write reached nothing and the `Reactive` came to hold the husk. The write
|