@abide/abide 0.52.0 → 0.53.0

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.
Files changed (34) hide show
  1. package/AGENTS.md +16 -7
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +7 -5
  4. package/package.json +2 -1
  5. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +1 -0
  6. package/src/lib/ui/compile/analyzeComponent.ts +2 -0
  7. package/src/lib/ui/compile/compileComponent.ts +3 -1
  8. package/src/lib/ui/compile/compileSSR.ts +12 -0
  9. package/src/lib/ui/compile/compileShadow.ts +37 -34
  10. package/src/lib/ui/compile/desugarSignals.ts +60 -32
  11. package/src/lib/ui/compile/generateBuild.ts +5 -1
  12. package/src/lib/ui/compile/hasTopLevelAwait.ts +28 -0
  13. package/src/lib/ui/compile/isFunctionScopeBoundary.ts +20 -0
  14. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +2 -2
  15. package/src/lib/ui/compile/lowerContext.ts +4 -0
  16. package/src/lib/ui/compile/lowerScript.ts +11 -1
  17. package/src/lib/ui/compile/renameSignalRefs.ts +18 -0
  18. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  19. package/src/lib/ui/dom/appendText.ts +29 -8
  20. package/src/lib/ui/dom/appendTextAt.ts +21 -4
  21. package/src/lib/ui/dom/attr.ts +16 -1
  22. package/src/lib/ui/dom/awaitBlock.ts +34 -13
  23. package/src/lib/ui/dom/each.ts +10 -2
  24. package/src/lib/ui/dom/eachAsync.ts +9 -1
  25. package/src/lib/ui/dom/readCellBlocking.ts +29 -0
  26. package/src/lib/ui/dom/readTextOrSuspend.ts +21 -0
  27. package/src/lib/ui/dom/spreadAttrs.ts +47 -1
  28. package/src/lib/ui/dom/switchBlock.ts +19 -2
  29. package/src/lib/ui/dom/tryBlock.ts +10 -0
  30. package/src/lib/ui/dom/when.ts +19 -1
  31. package/src/lib/ui/dom/withSuspense.ts +22 -0
  32. package/src/lib/ui/runtime/SuspenseSignal.ts +24 -0
  33. package/src/lib/ui/runtime/flushEffects.ts +18 -10
  34. package/src/lib/ui/watch.ts +7 -2
@@ -1,6 +1,7 @@
1
1
  import { boundaryFor } from './boundaryFor.ts'
2
2
  import { NODE_STATE } from './NODE_STATE.ts'
3
3
  import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
4
+ import { SuspenseSignal } from './SuspenseSignal.ts'
4
5
  import type { ReactiveNode } from './types/ReactiveNode.ts'
5
6
  import { updateIfNecessary } from './updateIfNecessary.ts'
6
7
 
@@ -50,16 +51,23 @@ function drain(): void {
50
51
  try {
51
52
  updateIfNecessary(node)
52
53
  } catch (error) {
53
- /* A node built inside a reactive `{#try}` routes its throw to that boundary
54
- (swap the guarded content to the catch branch) instead of the rethrow
55
- fallback this is the "later re-run throw" the sync boundary could not
56
- catch. Reset it CLEAN either way so a later write to its deps can re-queue
57
- it (otherwise `mark`'s CLEAN→dirty gate leaves it permanently inert). */
58
- const boundary = boundaryFor.get(node)
59
- if (boundary !== undefined) {
60
- boundary.handle(error)
61
- node.status = NODE_STATE.CLEAN
62
- continue
54
+ /* A `SuspenseSignal` is "no value yet", not an error, and must NEVER route to a
55
+ reactive `{#try}` (that would flash the author's `{:catch}` during loading,
56
+ ADR-0042 D3). Every reading region catches its own suspend locally and re-runs on
57
+ settle (the throwing read subscribed its cell), so a `SuspenseSignal` reaches here
58
+ only if it escaped an unguarded read surface it via the error collector below
59
+ rather than leaking it to a boundary. A normal error routes to the node's nearest
60
+ `{#try}` boundary: swap the guarded content to the catch branch instead of the
61
+ rethrow fallback — the "later re-run throw" the sync boundary could not catch.
62
+ Reset it CLEAN either way so a later write to its deps can re-queue it (otherwise
63
+ `mark`'s CLEAN→dirty gate leaves it permanently inert). */
64
+ if (!(error instanceof SuspenseSignal)) {
65
+ const boundary = boundaryFor.get(node)
66
+ if (boundary !== undefined) {
67
+ boundary.handle(error)
68
+ node.status = NODE_STATE.CLEAN
69
+ continue
70
+ }
63
71
  }
64
72
  /* No boundary — one effect throwing must not strand the effects queued behind
65
73
  it (they live in this same `batch`, unreachable once we swap
@@ -3,6 +3,7 @@ import { REMOTE_FUNCTION } from '../shared/REMOTE_FUNCTION.ts'
3
3
  import type { CacheOnContext } from '../shared/types/CacheOnContext.ts'
4
4
  import type { NamedAsyncIterable } from '../shared/types/NamedAsyncIterable.ts'
5
5
  import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
6
+ import { withSuspense } from './dom/withSuspense.ts'
6
7
  import { effect } from './effect.ts'
7
8
  import { generationGuard } from './runtime/generationGuard.ts'
8
9
  import type { EffectResult } from './runtime/types/EffectResult.ts'
@@ -61,9 +62,13 @@ export function watch(
61
62
  also silence the compiler's generated bindings, which build on the client where the test
62
63
  DOM sets no global `window`). SSR-safety comes from the compiler stripping author
63
64
  `watch(...)` calls, and the subscribable branch's own server guard (cache.on). */
64
- /* Compiler binding form watch(thunk): a bare auto-tracked effect (== today's $$effect). */
65
+ /* Compiler binding form watch(thunk): a bare auto-tracked effect (== today's $$effect). A
66
+ pending blocking `await` read throws a `SuspenseSignal` (ADR-0042) — withhold this reaction
67
+ (e.g. leave a `class:`/`style:` binding untouched) until the value resolves; the read tracked
68
+ its cell, so the effect re-runs on settle. Rethrows any real error. */
65
69
  if (argsOrHandler === undefined) {
66
- return effect(source as () => EffectResult)
70
+ const thunk = source as () => EffectResult
71
+ return effect(() => withSuspense<EffectResult>(thunk, undefined))
67
72
  }
68
73
  /* watch(fn, args, handler): an rpc selector with explicit args. */
69
74
  if (maybeHandler !== undefined) {