@abide/abide 0.116.4 → 0.117.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @abide/abide
2
2
 
3
+ ## 0.117.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3bb5cc9: `abide dev`, `build`, `start`, `check`, `routes`, `compile`, `bundle`, `openapi`, `mcp` and the shell now refuse to run on a bun outside abide's `engines.bun`, naming the bun running and the range it needs (RULEBOOK 38.123)
8
+
9
+ ### Patch Changes
10
+
11
+ - 3bb5cc9: a component whose markup reads a bare `busy()` or `ready()` is now built on the server after the turn, once the siblings after it have started their loads, so it paints the arm the browser chooses at hydration rather than one that is rebuilt (RULEBOOK 41.28, 41.31)
12
+ - 3bb5cc9: the rulebook now states that the effects one turn wakes, a slot's own update among them, run in no guaranteed order (12.15) — a `watch` that post-processes what a sibling `{raw(...)}` slot renders cannot count on the slot having updated first
13
+ - 3bb5cc9: markdown imported `with { type: 'text' }` into an app's `.ts` now checks as a `string` under `abide check`, as it already did in a `.abide` — no more hand-written `declare module '*.md'`
14
+ - 3bb5cc9: the `bunfig.toml` `abide scaffold` writes now sets `[install] peer = false`, so a dependency's peer on `bun` (`bun-plugin-tailwind`'s) cannot install an older bun that shadows the system one inside `bun run` (RULEBOOK 38.122)
15
+ - 3bb5cc9: the rulebook states three things that held but were never written down: a `raw` slot writes its markup unchecked (32.77), a fragment-only step moves `route.url` (30.33), and a `refresh` over `tags` reruns no synchronous derivation that carries none of them (13.20) — and the registry says `route.params` is `Params` outside a page or layout
16
+
3
17
  ## 0.116.4
4
18
 
5
19
  ### Patch Changes
package/bin/abide.ts CHANGED
@@ -41,12 +41,17 @@ import { executableName } from '#server/executableName.ts'
41
41
  import { iconAccent } from '#server/iconAccent.ts'
42
42
  import { namesUnder } from '#server/namesUnder.ts'
43
43
  import { DEFAULT_DOCUMENT, intoHead } from '#server/render.ts'
44
+ import { runtimeRefusal } from '#server/runtimeRefusal.ts'
44
45
  import { pathsUnder, serveBuild } from '#server/serveBuild.ts'
45
46
  import { serveDev } from '#server/serveDev.ts'
46
47
  import { escapeText } from '#shared/escapeText.ts'
47
48
  import { ICON_ADDRESSES, LAUNCHER_PAGE } from '#shared/LAUNCHER_FILES.ts'
48
49
  import abidePackage from '../package.json' with { type: 'json' }
49
50
 
51
+ // 38.123 — the bun this process runs on, held to abide's `engines.bun` before anything is built or
52
+ // booted. A compiled app carries the bun `abide compile` ran on, which this held already.
53
+ const RUNTIME_REFUSAL = runtimeRefusal(Bun.version, abidePackage.engines.bun)
54
+
50
55
  /** Where the app a command builds or serves is, and where its build goes — both absolute. */
51
56
  type AppDirectories = { root: string; out: string }
52
57
 
@@ -59,14 +64,18 @@ const DISK: Host = {
59
64
  version: abidePackage.version,
60
65
  // 38.100 — built in this process rather than a child, where nobody built it, and said on
61
66
  // stderr: the command's stdout is its own — `openapi`'s document, `mcp`'s messages.
62
- app: ({ values }) =>
63
- appOnDisk(directoriesOf(values), async (root, out) =>
67
+ app: ({ values }) => {
68
+ const handle = appOnDisk(directoriesOf(values), async (root, out) =>
64
69
  (await serveBuild(root, out, (root, out) =>
65
70
  writeBuild({ root, out }, console.error),
66
71
  ))
67
72
  ? out
68
73
  : undefined,
69
- ),
74
+ )
75
+ return RUNTIME_REFUSAL === undefined
76
+ ? handle
77
+ : { ...handle, boot: async () => RUNTIME_REFUSAL }
78
+ },
70
79
  source: {
71
80
  scaffold: scaffoldCommand,
72
81
  dev: inApp(dev),
@@ -645,6 +654,10 @@ function inApp<Answer>(
645
654
  body: (app: AppDirectories, given: Given) => Promise<Answer>,
646
655
  ): (given: Given) => Promise<Answer | number> {
647
656
  return async (given) => {
657
+ if (RUNTIME_REFUSAL !== undefined) {
658
+ console.error(`abide ${given.positionals[0]}: ${RUNTIME_REFUSAL}`)
659
+ return 1
660
+ }
648
661
  const app = directoriesOf(given.values)
649
662
  if (app === undefined) {
650
663
  console.error(
package/docs/REGISTRY.md CHANGED
@@ -160,7 +160,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
160
160
  | --- | --- | --- | --- |
161
161
  | `Disposer` | `() => void` | What tears down the previous run. | 12.1 |
162
162
  | `Effect` | `() => void \| Disposer` | What runs on change. | 12.2, 12.8, 14.6 |
163
- | `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | 12.2, 12.4, 12.5, 12.6, 12.7, 12.10, 12.13 |
163
+ | `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | 12.2, 12.4, 12.5, 12.6, 12.7, 12.10, 12.13, 12.15 |
164
164
  | `watch` | `(sources: Reactive<unknown, never, unknown, unknown> \| Reactive<unknown, never, unknown, unknown>[], effect: Effect) => () => void` | The narrowed form, over named sources. | 12.3 |
165
165
  | `flushSync` | `() => void` | Ends the turn: every `Effect` a write woke runs now. | 9.10, 12.11, 12.12 |
166
166
 
@@ -169,10 +169,10 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
169
169
  | Name | Signature | Meaning | Rules |
170
170
  | --- | --- | --- | --- |
171
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.14 |
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, 46.11 |
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 |
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
+ | `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 |
175
- | `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | 13.4, 13.7, 13.10, 13.12, 13.14, 13.15, 13.16, 13.17 |
175
+ | `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | 13.4, 13.7, 13.10, 13.12, 13.14, 13.15, 13.16, 13.17, 13.20 |
176
176
  | `invalidate` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Marks the selection stale. | 13.4, 13.6, 13.10, 13.12, 13.15, 13.17 |
177
177
 
178
178
 
@@ -380,8 +380,8 @@ 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, 19.16, 31.16 |
384
- | `route.params` | `Reactive<Params>` | The matched route's segments. | 23.1, 23.5, 23.9, 30.23, 31.16 |
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.16 |
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.16 |
385
385
  | `Params` | `Record<string, string \| string[]>` | What a matched route's segments are. | 23.5 |
386
386
  | `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | 23.1, 23.6, 23.14, 31.16 |
387
387
  | `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | 23.1, 31.16 |
@@ -571,7 +571,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
571
571
 
572
572
  | Name | Signature | Meaning | Rules |
573
573
  | --- | --- | --- | --- |
574
- | `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | 32.2, 32.24, 32.26, 32.30, 32.31, 32.70 |
574
+ | `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | 32.2, 32.24, 32.26, 32.30, 32.31, 32.70, 32.77 |
575
575
  | `Raw` | `class Raw { html: string }` | What `raw` hands back. | 32.24 |
576
576
 
577
577
  ### Control flow
@@ -629,7 +629,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
629
629
  | Name | Signature | Meaning | Rules |
630
630
  | --- | --- | --- | --- |
631
631
  | `Component` | `interface Component {}` | A component bound to its props. | 32.17, 34.16, 35.4 |
632
- | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | 29.10, 35.4, 35.5, 35.6, 35.15, 35.16, 41.1, 41.2, 41.3, 41.4, 41.9, 41.13, 41.16, 41.17, 41.18, 41.19, 41.20, 41.28, 35.11, 41.29, 41.30 |
632
+ | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | 29.10, 35.4, 35.5, 35.6, 35.15, 35.16, 41.1, 41.2, 41.3, 41.4, 41.9, 41.13, 41.16, 41.17, 41.18, 41.19, 41.20, 41.28, 35.11, 41.29, 41.30, 41.31 |
633
633
 
634
634
  ## Sinks
635
635
 
@@ -849,20 +849,20 @@ rows a runner prints, and an adapter for an abide app.
849
849
 
850
850
  | Command | Meaning | Rules |
851
851
  | --- | --- | --- |
852
- | `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112 |
852
+ | `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112, 38.122 |
853
853
  | `abide run <file> [args…]` | Runs a script under the abide runtime. | 38.2, 38.57 |
854
- | `abide check [dir…]` | The type-checker over a `.abide` tree. | 38.3, 38.31, 38.32, 38.33, 38.40, 38.105 |
855
- | `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | 38.108 |
856
- | `abide dev [--port <n>]` | Watches the project and keeps the app up. | 16.23, 16.56, 37.8, 37.9, 38.4, 38.14, 38.15, 38.16, 38.17, 38.19, 38.24, 38.25, 38.26, 38.28, 38.30, 38.35, 38.36, 38.37, 38.38, 38.41, 38.43, 38.58, 38.63, 38.64, 38.65, 38.94, 38.96, 38.97 |
857
- | `abide build` | Builds the client into content-hashed chunks and a manifest. | 38.43, 38.5, 38.18, 38.25, 38.26, 38.27, 38.30, 38.66, 38.94 |
858
- | `abide start [--port <n>]` | Boots the built app. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.100, 38.20, 38.21, 38.22, 38.23, 38.25, 38.26, 38.27, 38.28, 38.29, 38.58, 38.67, 38.68, 38.95, 38.97, 38.110 |
859
- | `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.7, 38.53, 38.54, 38.55, 38.58, 38.59, 38.60, 38.62 |
854
+ | `abide check [dir…]` | The type-checker over a `.abide` tree. | 38.3, 38.31, 38.32, 38.33, 38.40, 38.105, 38.123 |
855
+ | `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | 38.108, 38.123 |
856
+ | `abide dev [--port <n>]` | Watches the project and keeps the app up. | 16.23, 16.56, 37.8, 37.9, 38.4, 38.14, 38.15, 38.16, 38.17, 38.19, 38.24, 38.25, 38.26, 38.28, 38.30, 38.35, 38.36, 38.37, 38.38, 38.41, 38.43, 38.58, 38.63, 38.64, 38.65, 38.94, 38.96, 38.97, 38.123 |
857
+ | `abide build` | Builds the client into content-hashed chunks and a manifest. | 38.43, 38.5, 38.18, 38.25, 38.26, 38.27, 38.30, 38.66, 38.94, 38.123 |
858
+ | `abide start [--port <n>]` | Boots the built app. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.100, 38.20, 38.21, 38.22, 38.23, 38.25, 38.26, 38.27, 38.28, 38.29, 38.58, 38.67, 38.68, 38.95, 38.97, 38.110, 38.123 |
859
+ | `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.7, 38.53, 38.54, 38.55, 38.58, 38.59, 38.60, 38.62, 38.123 |
860
860
  | `abide call <address> [args]` | One call to one handler. | 38.8, 38.61, 38.110 |
861
- | `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | 38.9, 38.25, 38.110, 38.111 |
862
- | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13, 38.25, 38.110 |
861
+ | `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | 38.9, 38.25, 38.110, 38.111, 38.123 |
862
+ | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13, 38.25, 38.110, 38.123 |
863
863
  | `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | 38.69 |
864
- | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | 37.8, 37.10, 37.11, 37.12, 38.10, 38.25, 38.26, 38.44, 38.45, 38.46, 38.47, 38.48, 38.49, 38.50, 38.51, 38.52, 38.56, 38.93, 38.97 |
865
- | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | 38.10, 38.25, 38.26, 38.70, 38.71, 38.72, 38.73, 38.74, 38.75, 38.76, 38.77, 38.78, 38.79, 38.80, 38.81, 38.82, 38.83, 38.84, 38.85, 38.86, 38.87, 38.88, 38.89, 38.90, 38.91, 38.92, 38.101, 38.102 |
864
+ | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | 37.8, 37.10, 37.11, 37.12, 38.10, 38.25, 38.26, 38.44, 38.45, 38.46, 38.47, 38.48, 38.49, 38.50, 38.51, 38.52, 38.56, 38.93, 38.97, 38.123 |
865
+ | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | 38.10, 38.25, 38.26, 38.70, 38.71, 38.72, 38.73, 38.74, 38.75, 38.76, 38.77, 38.78, 38.79, 38.80, 38.81, 38.82, 38.83, 38.84, 38.85, 38.86, 38.87, 38.88, 38.89, 38.90, 38.91, 38.92, 38.101, 38.102, 38.123 |
866
866
  | `abide lsp` | The `.abide` language server, over stdio. | 38.11, 38.34, 38.39, 38.98, 38.116, 38.117, 38.118 |
867
867
  | `--help` · `-h` · `abide help` | Usage, generated from the command list. | 38.12, 38.113, 38.114 |
868
868
  | `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | 38.115 |
package/docs/RULEBOOK.md CHANGED
@@ -700,6 +700,9 @@ holding that arm or row is removed.
700
700
  `watch`, MUST stop reading its sources once that owner stops and nothing else reads it, and MUST
701
701
  answer a later read with what its body computes then.
702
702
 
703
+ 12.15 The `Effect` values one turn wakes, a slot's own update among them, MAY run in any order,
704
+ save where a clause orders them.
705
+
703
706
  # 13. Selections
704
707
 
705
708
  13.1 A `Selection` MUST be one `Reactive`, one `Keyed`, one `Rpc`, or a set of `tags`.
@@ -757,6 +760,9 @@ where every value it sweeps is.
757
760
  current scope MUST answer as over a `Selection` with no entries, and reading `busy`, `ready` or
758
761
  `synced` there MUST subscribe the reader to the first call that reaches it in that scope.
759
762
 
763
+ 13.20 `refresh` over a set of `tags` MUST NOT rerun a synchronous derivation that carries none of
764
+ them, which MUST recompute from the load it reads.
765
+
760
766
  # 14. Tracking
761
767
 
762
768
  14.1 A `Reactive` MUST be tracked only where something downstream will be rebuilt from it.
@@ -1819,6 +1825,9 @@ to `middleware`.
1819
1825
  30.32 A `navigate` that is not a history step, whose request gets no answer while `online` is false,
1820
1826
  MUST leave the page and the address where they stand, save where 45.12 holds.
1821
1827
 
1828
+ 30.33 A history step under 30.20, and a `navigate` under 30.21, MUST move `route.url` to the address
1829
+ it steps to.
1830
+
1822
1831
  # 31. Reading and writing by name
1823
1832
 
1824
1833
  31.1 Inside a `.abide` file a name bound to a `Reactive` MUST be read wherever 31.3 does not hold it.
@@ -2099,6 +2108,9 @@ element, written or spread, runs for the same event, wherever the two are writte
2099
2108
  32.76 A `bind:` whose operand is a path into a value that takes an optional step (`?.`) MUST be a
2100
2109
  build error.
2101
2110
 
2111
+ 32.77 A slot holding a value branded by `raw` MUST write its markup as given, with no element or
2112
+ attribute in it removed and no URL in it scheme-checked.
2113
+
2102
2114
  # 33. Props
2103
2115
 
2104
2116
  33.1 `props` MUST be callable from a `<script>` block only, and calling it from a `<script module>`
@@ -2766,6 +2778,12 @@ supplies, and `abide start` MUST NOT supply that secret.
2766
2778
  38.121 Under `mount`, a call the component's browser module makes to a private handler MUST be
2767
2779
  answered in the test's process by the handler the component's server module declares.
2768
2780
 
2781
+ 38.122 The project `abide scaffold` writes MUST NOT install a dependency's peer dependencies.
2782
+
2783
+ 38.123 A command `abide` builds, checks or boots the app for MUST be refused where the bun running it
2784
+ is outside the range abide's `engines.bun` names, and the refusal MUST name that bun's version and
2785
+ the range.
2786
+
2769
2787
  # 39. Head and view transitions
2770
2788
 
2771
2789
  39.1 A `<head>` element in a `.abide` file MUST contribute its children to the document head.
@@ -3031,13 +3049,18 @@ has answered or failed.
3031
3049
  adopts the page, however early its answer arrived.
3032
3050
 
3033
3051
  41.28 A `render` MUST let the turn's queued microtasks run between setting up a `<Name/>` whose setup
3034
- made an `Rpc` call and building that component's markup.
3052
+ made an `Rpc` call, or whose markup reads a bare `busy` or `ready`, and building that component's
3053
+ markup.
3035
3054
 
3036
3055
  41.29 A `render` answering a request whose `sec-fetch-dest` is `empty` MUST NOT fill the seed buffer,
3037
3056
  save one whose `abide-keep` header names `open`.
3038
3057
 
3039
3058
  41.30 A `render` answering a request carrying `abide-keep` MUST write 41.20's script.
3040
3059
 
3060
+ 41.31 On a server, a bare `busy` or `ready` MUST answer over the loads set up before its component's
3061
+ markup is built, and a load a component set up later, one only a pending value places, MAY have it
3062
+ choose another arm at hydration under 41.24.
3063
+
3041
3064
  # 42. The app object
3042
3065
 
3043
3066
  42.1 `createApp` MUST build the route table and compose every `Middleware` rung, per 16.14.
package/docs/abide.md CHANGED
@@ -773,7 +773,7 @@ A hole is `{}` around an expression, and it updates the one thing it names and n
773
773
  | You write | Where it lands |
774
774
  | --- | --- |
775
775
  | `{expr}` | a text node, escaped |
776
- | `{raw(...)}` | markup, unescaped: `raw` brands a string as a `Raw` |
776
+ | `{raw(...)}` | markup, unescaped and unchecked (32.77): `raw` brands a string as a `Raw`, so hand it markup you wrote, never a model's reply or a fetched page as it came |
777
777
  | `name={expr}` | one attribute, whole |
778
778
  | `name="…{expr}…"` | one attribute, interpolated |
779
779
  | `class:name={cond}` | one class, there while the condition holds |
@@ -2996,7 +2996,7 @@ test('one file answers for every company, reading its id', async () => {
2996
2996
  | --- | --- |
2997
2997
  | `route.name` | the pattern that matched, `/companies/[id]` for every company, so a log line keys on the page rather than on each address |
2998
2998
  | `Params` | what the segments matched; a query string is never in it |
2999
- | `RouteParams<P>` | the type `route.params` has in a page filed under `P`, so `id` is text and a key the route lacks is an error |
2999
+ | `RouteParams<P>` | the type `route.params` has in a page or layout filed under `P`, so `id` is text and a key the route lacks is an error. A component under `lib/` has no route of its own and reads the loose `Params`: pass it the value as a prop, or narrow it with `RouteParams<'/…'>` |
3000
3000
 
3001
3001
  ## A layout holds what the companies pages share
3002
3002
 
@@ -3888,7 +3888,7 @@ test('the server paints the density the cookie holds', async () => {
3888
3888
 
3889
3889
  `cookies()` is the request's cookies on the server and the document's in the browser, as a `CookieMap`. Reading a cookie by name follows it, so the list changes on the click. The server render reads the same cookie off the request, so a reload paints compact rather than flashing comfortable first.
3890
3890
 
3891
- The principal cookie is `HttpOnly`, so script never sees the seal. Use `cookies()` for the ones that are yours: a density, a theme, a dismissed banner.
3891
+ `cookies()` reads and writes any cookie the request carries: a density, a theme, or another system's session cookie your server checks. The principal cookie is `HttpOnly`, so in the browser `cookies()` never sees the seal.
3892
3892
 
3893
3893
  ## A handler decides which fields a caller may read
3894
3894
 
@@ -7112,7 +7112,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7112
7112
 
7113
7113
  | Name | Signature | Meaning | Rules |
7114
7114
  | --- | --- | --- | --- |
7115
- | `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | [12.2](/rulebook#12.2), [12.4](/rulebook#12.4), [12.5](/rulebook#12.5), [12.6](/rulebook#12.6), [12.7](/rulebook#12.7), [12.10](/rulebook#12.10), [12.13](/rulebook#12.13) |
7115
+ | `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | [12.2](/rulebook#12.2), [12.4](/rulebook#12.4), [12.5](/rulebook#12.5), [12.6](/rulebook#12.6), [12.7](/rulebook#12.7), [12.10](/rulebook#12.10), [12.13](/rulebook#12.13), [12.15](/rulebook#12.15) |
7116
7116
  | `watch` | `(sources: Reactive<unknown, never, unknown, unknown> \| Reactive<unknown, never, unknown, unknown>[], effect: Effect) => () => void` | The narrowed form, over named sources. | [12.3](/rulebook#12.3) |
7117
7117
 
7118
7118
  ## Taught in
@@ -7167,7 +7167,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7167
7167
 
7168
7168
  | Name | Signature | Meaning | Rules |
7169
7169
  | --- | --- | --- | --- |
7170
- | `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12), [46.11](/rulebook#46.11) |
7170
+ | `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12), [41.28](/rulebook#41.28), [41.31](/rulebook#41.31), [46.11](/rulebook#46.11) |
7171
7171
 
7172
7172
  ## Taught in
7173
7173
 
@@ -7185,7 +7185,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7185
7185
 
7186
7186
  | Name | Signature | Meaning | Rules |
7187
7187
  | --- | --- | --- | --- |
7188
- | `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.11](/rulebook#13.11), [13.12](/rulebook#13.12) |
7188
+ | `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.11](/rulebook#13.11), [13.12](/rulebook#13.12), [41.28](/rulebook#41.28), [41.31](/rulebook#41.31) |
7189
7189
 
7190
7190
  ## Taught in
7191
7191
 
@@ -7221,7 +7221,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
7221
7221
 
7222
7222
  | Name | Signature | Meaning | Rules |
7223
7223
  | --- | --- | --- | --- |
7224
- | `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | [13.4](/rulebook#13.4), [13.7](/rulebook#13.7), [13.10](/rulebook#13.10), [13.12](/rulebook#13.12), [13.14](/rulebook#13.14), [13.15](/rulebook#13.15), [13.16](/rulebook#13.16), [13.17](/rulebook#13.17) |
7224
+ | `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | [13.4](/rulebook#13.4), [13.7](/rulebook#13.7), [13.10](/rulebook#13.10), [13.12](/rulebook#13.12), [13.14](/rulebook#13.14), [13.15](/rulebook#13.15), [13.16](/rulebook#13.16), [13.17](/rulebook#13.17), [13.20](/rulebook#13.20) |
7225
7225
 
7226
7226
  ## Taught in
7227
7227
 
@@ -8196,8 +8196,8 @@ Generated surfaces: what is written in place rather than imported.
8196
8196
 
8197
8197
  | Name | Signature | Meaning | Rules |
8198
8198
  | --- | --- | --- | --- |
8199
- | `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), [19.16](/rulebook#19.16), [31.16](/rulebook#31.16) |
8200
- | `route.params` | `Reactive<Params>` | The matched route's segments. | [23.1](/rulebook#23.1), [23.5](/rulebook#23.5), [23.9](/rulebook#23.9), [30.23](/rulebook#30.23), [31.16](/rulebook#31.16) |
8199
+ | `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.16](/rulebook#31.16) |
8200
+ | `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.16](/rulebook#31.16) |
8201
8201
  | `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.16](/rulebook#31.16) |
8202
8202
  | `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | [23.1](/rulebook#23.1), [31.16](/rulebook#31.16) |
8203
8203
 
@@ -8961,7 +8961,7 @@ Generated surfaces: what is written in place rather than imported.
8961
8961
 
8962
8962
  | Name | Signature | Meaning | Rules |
8963
8963
  | --- | --- | --- | --- |
8964
- | `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | [32.2](/rulebook#32.2), [32.24](/rulebook#32.24), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70) |
8964
+ | `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | [32.2](/rulebook#32.2), [32.24](/rulebook#32.24), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70), [32.77](/rulebook#32.77) |
8965
8965
 
8966
8966
  ## Taught in
8967
8967
 
@@ -9084,7 +9084,7 @@ Pages and routing: what is written in place rather than imported.
9084
9084
 
9085
9085
  | Name | Signature | Meaning | Rules |
9086
9086
  | --- | --- | --- | --- |
9087
- | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11), [41.29](/rulebook#41.29), [41.30](/rulebook#41.30) |
9087
+ | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11), [41.29](/rulebook#41.29), [41.30](/rulebook#41.30), [41.31](/rulebook#41.31) |
9088
9088
 
9089
9089
  ## Taught in
9090
9090
 
@@ -10375,20 +10375,20 @@ CLI: what is written in place rather than imported.
10375
10375
 
10376
10376
  | Command | Meaning | Rules |
10377
10377
  | --- | --- | --- |
10378
- | `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112) |
10378
+ | `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122) |
10379
10379
  | `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
10380
- | `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105) |
10381
- | `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108) |
10382
- | `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97) |
10383
- | `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94) |
10384
- | `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110) |
10385
- | `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.7](/rulebook#38.7), [38.53](/rulebook#38.53), [38.54](/rulebook#38.54), [38.55](/rulebook#38.55), [38.58](/rulebook#38.58), [38.59](/rulebook#38.59), [38.60](/rulebook#38.60), [38.62](/rulebook#38.62) |
10380
+ | `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105), [38.123](/rulebook#38.123) |
10381
+ | `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108), [38.123](/rulebook#38.123) |
10382
+ | `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
10383
+ | `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94), [38.123](/rulebook#38.123) |
10384
+ | `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110), [38.123](/rulebook#38.123) |
10385
+ | `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.7](/rulebook#38.7), [38.53](/rulebook#38.53), [38.54](/rulebook#38.54), [38.55](/rulebook#38.55), [38.58](/rulebook#38.58), [38.59](/rulebook#38.59), [38.60](/rulebook#38.60), [38.62](/rulebook#38.62), [38.123](/rulebook#38.123) |
10386
10386
  | `abide call <address> [args]` | One call to one handler. | [38.8](/rulebook#38.8), [38.61](/rulebook#38.61), [38.110](/rulebook#38.110) |
10387
- | `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | [38.9](/rulebook#38.9), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.111](/rulebook#38.111) |
10388
- | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | [38.9](/rulebook#38.9), [38.13](/rulebook#38.13), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110) |
10387
+ | `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | [38.9](/rulebook#38.9), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.111](/rulebook#38.111), [38.123](/rulebook#38.123) |
10388
+ | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | [38.9](/rulebook#38.9), [38.13](/rulebook#38.13), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.123](/rulebook#38.123) |
10389
10389
  | `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | [38.69](/rulebook#38.69) |
10390
- | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97) |
10391
- | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102) |
10390
+ | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
10391
+ | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102), [38.123](/rulebook#38.123) |
10392
10392
  | `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118) |
10393
10393
  | `--help` · `-h` · `abide help` | Usage, generated from the command list. | [38.12](/rulebook#38.12), [38.113](/rulebook#38.113), [38.114](/rulebook#38.114) |
10394
10394
  | `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | [38.115](/rulebook#38.115) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.116.4",
3
+ "version": "0.117.0",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
@@ -915,8 +915,9 @@ export class TypeSession {
915
915
  checked.push({ source, map, refusals: shadow.refusals })
916
916
  }
917
917
  // 34.6 — WHAT THE BUNDLER RESOLVES AND THE CHECKER CANNOT: a stylesheet imported for its
918
- // effect. Declared in a file of its own at the app's root, which is a script, because a
919
- // pattern module can only be declared outside a module.
918
+ // effect, and (34.21) markdown imported as text into a `.ts`, which bun's own types leave
919
+ // out where they declare `.txt`. Declared in a file of its own at the app's root, which is
920
+ // a script, because a pattern module can only be declared outside a module.
920
921
  this.register(
921
922
  `${this.canonical(within)}/${AMBIENT}`,
922
923
  AMBIENT_DECLARATIONS,
@@ -1484,6 +1485,10 @@ function routesDeclaration(addresses: readonly string[]): string {
1484
1485
  return `export {}\ndeclare module ${JSON.stringify(URL_SOURCE)} {\n interface Routes {\n${keys} }\n}\n`
1485
1486
  }
1486
1487
  const AMBIENT_DECLARATIONS = `declare module '*.css' {}
1488
+ declare module '*.md' {
1489
+ const text: string
1490
+ export default text
1491
+ }
1487
1492
  `
1488
1493
 
1489
1494
  /**
@@ -1,9 +1,9 @@
1
1
  import type { JsonValue } from '#shared/JsonValue.ts'
2
- import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
2
+ import { SERVER_SPECIFIER, SHARED_SPECIFIER } from '#shared/SPECIFIERS.ts'
3
3
  import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
4
4
  import { Cursor, placeTemplate } from './Cursor.ts'
5
5
  import { type LoweredChunk, Lowering, PROPS } from './Lowering.ts'
6
- import type { AbideFile } from './parseAbide.ts'
6
+ import { type AbideFile, scriptBodies } from './parseAbide.ts'
7
7
  import { privateExports } from './projectShadow.ts'
8
8
  import {
9
9
  concat,
@@ -15,7 +15,7 @@ import {
15
15
  SERVER_LANE,
16
16
  } from './renderMarkup.ts'
17
17
  import { insideShare, SETTLED_AT, shareScope } from './shareScope.ts'
18
- import { splitImports, withoutProps } from './splitImports.ts'
18
+ import { importStatements, splitImports, withoutProps } from './splitImports.ts'
19
19
  import type { TemplateNode } from './TemplateNode.ts'
20
20
 
21
21
  // The name the body is bound to while a component's head contributions land (`HEADS`).
@@ -41,7 +41,8 @@ const CALLS_BEFORE = '__callsBefore'
41
41
  * `settles` IS A PAGE OR A LAYOUT, which `render` builds a level at a time (41.19): its setup runs
42
42
  * at the call and its markup is built on the first read of `head` or `body`, so the calls the
43
43
  * setup started are given the turn to answer. Every other component answers the same way only where
44
- * its setup made a call (41.28), and builds where it is called otherwise.
44
+ * its setup made a call or its markup reads a bare `busy` or `ready` (41.28), and builds where it is
45
+ * called otherwise.
45
46
  */
46
47
  export function emitComponent(
47
48
  file: AbideFile,
@@ -144,16 +145,22 @@ ${heldToAnnotations(lowering, derived)}${privateExports(file.servers.join('\n'))
144
145
  // made a call, and then answers as a page does, for `renderComponent` to build after the turn.
145
146
  // THE BUILD IS WRITTEN TWICE rather than always run through `__build`: the closure measured ~4%
146
147
  // a row on `bench:component`'s card under JSC, the second copy nothing but server module bytes.
147
- // One with no setup cannot call, and is written once.
148
- const waits = !settles && componentBody.trim() !== ''
149
- const level = settles
150
- ? deferred()
151
- : waits
152
- ? ` if (${lowering.use('callsInRender')}() !== ${CALLS_BEFORE}) {
148
+ // One with no setup cannot call, and is written once. One whose markup reads a bare `busy` or
149
+ // `ready` answers as a page does always: built where it is called, it would answer before the
150
+ // siblings after it had started a load, where the browser's answers once every setup has run.
151
+ const probes =
152
+ !settles &&
153
+ readsBareProbe(file, `${head}\n${declared}\n${body.join('\n')}`)
154
+ const waits = !settles && !probes && componentBody.trim() !== ''
155
+ const level =
156
+ settles || probes
157
+ ? deferred()
158
+ : waits
159
+ ? ` if (${lowering.use('callsInRender')}() !== ${CALLS_BEFORE}) {
153
160
  ${deferred()}
154
161
  }
155
162
  ${build}`
156
- : build
163
+ : build
157
164
  return `${moduleImports(lowering, () => SERVER_SPECIFIER, [moduleSetup, serverSetup, setup])}
158
165
  ${moduleSetup.body === '' ? '' : `${moduleSetup.body}\n`}${serverBody}export default function Component(${lowering.takesProps() ? `${PROPS} = {}` : ''}) {
159
166
  ${waits ? ` const ${CALLS_BEFORE} = ${lowering.use('callsInRender')}()\n` : ''}${opened}${componentBody}
@@ -162,6 +169,29 @@ ${level}${closed}
162
169
  `
163
170
  }
164
171
 
172
+ /**
173
+ * Whether `markup`, a component's emitted head and body, calls `busy` or `ready` with no argument,
174
+ * under any name the file's scripts import either by from `@abide/abide`. A member's (`s.busy()`)
175
+ * is not bare.
176
+ */
177
+ function readsBareProbe(file: AbideFile, markup: string): boolean {
178
+ let names = ''
179
+ for (const script of scriptBodies(file))
180
+ for (const statement of importStatements(script))
181
+ if (statement.specifier === SHARED_SPECIFIER && !statement.typeOnly)
182
+ for (const binding of statement.bindings)
183
+ if (
184
+ !binding.typeOnly &&
185
+ (binding.imported === 'busy' ||
186
+ binding.imported === 'ready')
187
+ )
188
+ names += `${names === '' ? '' : '|'}${binding.local}`
189
+ return (
190
+ names !== '' &&
191
+ new RegExp(`(?<![\\w$.])(?:${names})\\(\\s*\\)`).test(markup)
192
+ )
193
+ }
194
+
165
195
  /**
166
196
  * 15.8, 19.9 — each private handler held to the schema its annotation derives, once its server block
167
197
  * has built it: the module is what the server mounts and what a component under `mount` calls
@@ -35,15 +35,17 @@ const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
35
35
  * 38.109, 38.119 — the starter's `bunfig.toml`: the harness's abide preload, which is a DOM a test
36
36
  * can mount into and measure, then abide's own — the app's `.abide` files imported as modules, with
37
37
  * nothing booted first. Its test root is `src`, so a browser test under `e2e` is playwright's alone.
38
+ * 38.122 — and no peer installed: `bun-plugin-tailwind` names `bun` as one, and an installed copy
39
+ * lands first on the PATH inside `bun run`, shadowing the bun that runs everything else.
38
40
  */
39
- export const TEST_PRELOAD =
40
- '# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n'
41
+ export const BUNFIG =
42
+ '# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n'
41
43
 
42
44
  // WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
43
45
  // the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file.
44
46
  function filesFor(name: string): Record<string, string> {
45
47
  return {
46
- 'bunfig.toml': TEST_PRELOAD,
48
+ 'bunfig.toml': BUNFIG,
47
49
  '.gitignore': 'node_modules\n.abide\n',
48
50
  'package.json': `${JSON.stringify(
49
51
  {
@@ -1,4 +1,5 @@
1
- // 41.28 — A CHILD WHOSE SETUP MADE A CALL IS BUILT AS A PAGE IS: after the turn.
1
+ // 41.28 — A CHILD WHOSE SETUP MADE A CALL IS BUILT AS A PAGE IS: after the turn — and so is one whose
2
+ // markup reads a bare `busy` or `ready`, which the compiler writes to answer as a page does always.
2
3
  // A component is set up where its parent's markup invokes it, so its build cannot wait inline; it is
3
4
  // left as a marker in the parent's head and body, built once the turn has run, and written in where
4
5
  // its marker stands. A child whose setup made no call builds where it stands, as before, and pays a
@@ -30,8 +30,9 @@ export function renderComponent(
30
30
  const rendered = (
31
31
  component as (props: Record<string, () => unknown>) => Rendered
32
32
  )(props)
33
- // 41.28 — A CHILD WHOSE SETUP MADE A CALL ANSWERS AS A PAGE DOES, with a `scope` and its build
34
- // left for the first read, and is built after the turn where a page is being built.
33
+ // 41.28 — A CHILD WHOSE SETUP MADE A CALL, OR WHOSE MARKUP READS A BARE PROBE, ANSWERS AS A PAGE
34
+ // DOES, with a `scope` and its build left for the first read, and is built after the turn where a
35
+ // page is being built.
35
36
  if ('scope' in rendered) {
36
37
  const marker = deferBuild(rendered, heads, callsFrom)
37
38
  if (marker !== undefined) return marker
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Why the bun at `version` cannot run abide, which asks for `range` — or `undefined` where it can.
3
+ *
4
+ * 38.123 — named in full, both versions and the way out, since an older bun fails later and
5
+ * somewhere unrelated, if at all.
6
+ */
7
+ export function runtimeRefusal(
8
+ version: string,
9
+ range: string,
10
+ ): string | undefined {
11
+ if (Bun.semver.satisfies(version, range)) return undefined
12
+ return `this is bun ${version}, and abide needs bun ${range}. Run \`bun upgrade\`, or install a bun in that range. RULEBOOK 38.123.`
13
+ }