@abide/abide 0.116.0 → 0.116.2
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 +14 -0
- package/docs/REGISTRY.md +7 -6
- package/docs/RULEBOOK.md +5 -4
- package/docs/abide.md +35 -14
- package/package.json +1 -1
- package/scaffold/AGENTS.md +12 -0
- package/src/compiler/scopeStyles.ts +4 -1
- package/src/server/OFFLINE_STALE.ts +4 -3
- package/src/server/answerWorker.ts +22 -6
- package/src/shared/OFFLINE_CACHES.ts +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.116.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ef77e8d: a comment directly above an at-rule in a component's `<style>` no longer breaks its scoping — `/* … */ @media (…) { .x {…} }` scopes `.x` as it would with no comment
|
|
8
|
+
- 99631d1: the registry names `isFailed`, says `route.url` includes the fragment and `route.name` is the same under any mount path, and shows a `GET` over a plain function or a value taking `ReactiveOptions`; a scaffolded app's AGENTS.md names the `abide-ignore` escape, exporting a `Reactive` unwrapped, never `raw()` on untrusted markup, and `config` / `cookies()` over `Bun.env` and header parsing
|
|
9
|
+
|
|
10
|
+
## 0.116.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 7dedb9a: the offline worker keeps an answer marked `cache-control: public` — a public file, a font a stylesheet draws with — through a sign-in, a sign-out and a form submitted, which drop only what was saved for the caller
|
|
15
|
+
- 6a69393: a copy the offline worker kept stands in for a year where the network fails, rather than a week — a page, a GET's answer and a public file carry `stale-if-error=31536000`
|
|
16
|
+
|
|
3
17
|
## 0.116.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/docs/REGISTRY.md
CHANGED
|
@@ -183,6 +183,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
183
183
|
| `refuse.named` | `<Name extends string>(name: Name, status?: number, message?: string) => ((data?: undefined) => Failed<Name, undefined>) & { is: (error: unknown) => error is Failed<Name, undefined> }` | A reusable factory for a named, narrowable failure. | 15.3, 15.13, 15.18 |
|
|
184
184
|
| `refuse.named` | `<Name extends string, Data extends JsonValue>(name: Name, status: number \| undefined, message: string \| ((data: Data) => string) \| undefined, options: { schema: Schema<Data> }) => ((data?: Data) => Failed<Name, Data>) & { is: (error: unknown) => error is Failed<Name, Data> }` | The same, carrying data its schema declares. | 15.3, 15.11, 15.12, 15.13, 15.14, 15.15, 15.18 |
|
|
185
185
|
| `Failed<Name, Data>` | `Error & { name: Name; status: number; message: string; data: Data }` | The one refusal type, in-process and over a wire alike. | 15.1, 15.2, 15.6, 16.40, 16.41, 15.21 |
|
|
186
|
+
| `isFailed` | `(value: unknown) => value is Failed<string, unknown>` | Whether a value is a refusal. | 15.6 |
|
|
186
187
|
| `return myError(data)` | `Failed<Name, Data>` | The one spelling for every refusal. | 15.4, 15.5 |
|
|
187
188
|
| `notFound` | `(data?: { path: string; method: string }) => Failed<'NotFound', { path: string; method: string }>` | abide's own refusal for an address nothing answers. | 15.7, 15.9, 15.22 |
|
|
188
189
|
| `validationError` | `<T>(data?: Issues<T>) => Failed<'ValidationError', Issues<T>>` | abide's own refusal for a schema or an `args` gate that refused, or a call's route parameter no segment can spell. | 15.8, 15.9 |
|
|
@@ -219,8 +220,8 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
219
220
|
| `Args` | `Record<string, JsonValue> \| undefined` | The arguments, in the body on a mutation and in the URL on a `GET` or `DELETE`. | 11.24, 16.18, 19.15 |
|
|
220
221
|
| `Middleware` | `<Ctx, Result>(next: (ctx?: Ctx) => Promise<Result>, ctx: Ctx) => Result \| Promise<Result>` | One rung of `middleware`. | 16.14, 16.15, 16.16, 16.21 |
|
|
221
222
|
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: Reactive<Value, Value, Failures, Produced> \| Keyed<Value, Args, Value, Failures, Produced>, options?: RpcOptions<Args, RungFailures>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | Declares a read any surface may call. | 16.1, 16.2, 16.5, 16.6, 16.30, 16.31, 16.32, 16.68, 16.76, 16.34, 16.35, 16.36, 16.37, 16.39, 16.44, 16.45, 16.46, 16.47, 16.75, 16.50, 16.51, 16.52, 16.64, 18.1, 18.3, 18.6, 18.8, 18.10, 18.12, 18.21, 18.24, 18.27 |
|
|
222
|
-
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose
|
|
223
|
-
| `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright. | 16.1, 16.6, 16.7 |
|
|
223
|
+
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose `ReactiveOptions` (`ttl`, `global`, `debounce` and the rest) build the value it becomes beside its address. | 16.1, 16.6, 16.7 |
|
|
224
|
+
| `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright, whose `ReactiveOptions` build the value it becomes. | 16.1, 16.6, 16.7 |
|
|
224
225
|
| `POST` / `PUT` / `PATCH` / `DELETE` | same as `GET` | Declares a mutation. | 16.2, 16.7, 16.32, 16.68, 16.76, 16.38, 16.53 |
|
|
225
226
|
| `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | 13.2, 13.3, 13.12, 46.11 |
|
|
226
227
|
| `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | 13.11, 13.12 |
|
|
@@ -291,8 +292,8 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
291
292
|
| `cache-control: no-store` | `/__abide/openapi.json`, `/__abide/mcp` | Refuses caching for an answer about the app's surfaces. | 21.4 |
|
|
292
293
|
| `cache-control: public, max-age=31536000, immutable` | the built bundle | A chunk addressed by its own content hash. | 21.5 |
|
|
293
294
|
| `cache-control: public, max-age=0, must-revalidate` | a file under a `public` directory | A file at an address the build never chose. | 21.12 |
|
|
294
|
-
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=
|
|
295
|
-
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=
|
|
295
|
+
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=31536000` | a page, a `GET`'s answer, where `ABIDE_OFFLINE` registers its worker | The default that lets a copy kept on the device stand in for the network. | 21.22, 21.11, 21.17 |
|
|
296
|
+
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=31536000` | a file under a `public` directory, where `ABIDE_OFFLINE` registers its worker | The same, for a file at an address the build never chose. | 21.23 |
|
|
296
297
|
| `clear-site-data: "cache"` | an answer carrying `abide-principal`, where pages are kept offline | Drops what the browser's own cache kept for the caller signed in before. | 21.24 |
|
|
297
298
|
| `etag` | a file under a `public` directory | The answered bytes, addressed by their content. | 21.13, 21.14 |
|
|
298
299
|
| `referrer-policy: strict-origin-when-cross-origin` | a page | The browsers' own default, written down. | 21.6 |
|
|
@@ -379,10 +380,10 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
379
380
|
|
|
380
381
|
| Name | Signature | Meaning | Rules |
|
|
381
382
|
| --- | --- | --- | --- |
|
|
382
|
-
| `route.url` | `Reactive<URL>` | Where we are. | 23.1, 23.2, 23.3, 23.4, 30.23, 19.16, 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, 19.16, 31.16 |
|
|
383
384
|
| `route.params` | `Reactive<Params>` | The matched route's segments. | 23.1, 23.5, 23.9, 30.23, 31.16 |
|
|
384
385
|
| `Params` | `Record<string, string \| string[]>` | What a matched route's segments are. | 23.5 |
|
|
385
|
-
| `route.name` | `Reactive<string>` | The resolution path. | 23.1, 23.6, 31.16 |
|
|
386
|
+
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | 23.1, 23.6, 23.14, 31.16 |
|
|
386
387
|
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | 23.1, 31.16 |
|
|
387
388
|
|
|
388
389
|
## `online`
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -1448,11 +1448,11 @@ time those rungs took apart from what they wrapped.
|
|
|
1448
1448
|
|
|
1449
1449
|
21.22 Where 45.1 has a page register the worker `ABIDE_OFFLINE` turns on, a page, and the answer of
|
|
1450
1450
|
a `GET` of the app's own that is neither a refusal nor a stream 16.32 frames, MUST default to `cache-control: private,
|
|
1451
|
-
stale-while-revalidate=604800, stale-if-error=
|
|
1451
|
+
stale-while-revalidate=604800, stale-if-error=31536000`.
|
|
1452
1452
|
|
|
1453
1453
|
21.23 Where 45.1 has a page register the worker `ABIDE_OFFLINE` turns on, a file under a `public`
|
|
1454
1454
|
directory MUST answer `cache-control: public, max-age=0, stale-while-revalidate=604800,
|
|
1455
|
-
stale-if-error=
|
|
1455
|
+
stale-if-error=31536000`.
|
|
1456
1456
|
|
|
1457
1457
|
21.24 Where 45.1 has a page register the worker `ABIDE_OFFLINE` turns on, an answer carrying the
|
|
1458
1458
|
`abide-principal` header 26.24 has it carry MUST carry `clear-site-data: "cache"`.
|
|
@@ -3301,8 +3301,9 @@ rather than one a redirect led to, that is neither a stream 16.32 frames nor a b
|
|
|
3301
3301
|
45.6 The worker `ABIDE_OFFLINE` turns on MUST answer a file of the build's bundle from what it
|
|
3302
3302
|
saved.
|
|
3303
3303
|
|
|
3304
|
-
45.7 The worker `ABIDE_OFFLINE` turns on MUST drop everything it saved
|
|
3305
|
-
|
|
3304
|
+
45.7 The worker `ABIDE_OFFLINE` turns on MUST drop everything it saved, save an answer whose
|
|
3305
|
+
`cache-control` is `public`, where an answer it is handed carries `abide-principal`, where
|
|
3306
|
+
`principal` changes in any tab, and where a form is submitted.
|
|
3306
3307
|
|
|
3307
3308
|
45.8 The worker `ABIDE_OFFLINE` turns on, of a new build, MUST drop everything the one before it
|
|
3308
3309
|
saved, and every file of the bundle the new build does not name.
|
package/docs/abide.md
CHANGED
|
@@ -43,6 +43,7 @@ at its address with `.md` on the end, and the links below point at those files.
|
|
|
43
43
|
- `invalidate` — `reference/invalidate.md`
|
|
44
44
|
- `refuse` — `reference/refuse.md`
|
|
45
45
|
- `Failed` — `reference/types/Failed.md`
|
|
46
|
+
- `isFailed` — `reference/isFailed.md`
|
|
46
47
|
- `notFound` — `reference/notFound.md`
|
|
47
48
|
- `validationError` — `reference/validationError.md`
|
|
48
49
|
- `aborted` — `reference/aborted.md`
|
|
@@ -439,7 +440,7 @@ let starred = reactive(false)
|
|
|
439
440
|
</script>
|
|
440
441
|
|
|
441
442
|
<div class="row">
|
|
442
|
-
<button aria-pressed={
|
|
443
|
+
<button aria-pressed={starred} onclick={() => (starred = !starred)}>
|
|
443
444
|
{starred ? '★ Starred' : '☆ Star'}
|
|
444
445
|
</button>
|
|
445
446
|
<span>Grace Hopper</span>
|
|
@@ -2135,7 +2136,7 @@ const { id, name } = props<{ id: string; name: string }>()
|
|
|
2135
2136
|
let selected = reactive.share('selectedDeal', () => reactive(''))
|
|
2136
2137
|
</script>
|
|
2137
2138
|
|
|
2138
|
-
<button aria-pressed={
|
|
2139
|
+
<button aria-pressed={selected === id} onclick={() => (selected = id)}>
|
|
2139
2140
|
{name}
|
|
2140
2141
|
</button>
|
|
2141
2142
|
```
|
|
@@ -2939,7 +2940,7 @@ const address = reactive(() => url('/companies/[id]', { id: open }))
|
|
|
2939
2940
|
|
|
2940
2941
|
<div class="tabs">
|
|
2941
2942
|
{#for id of IDS}
|
|
2942
|
-
<button aria-selected={
|
|
2943
|
+
<button aria-selected={open === id}
|
|
2943
2944
|
onclick={() => (open = id)}>{id}</button>
|
|
2944
2945
|
{/for}
|
|
2945
2946
|
</div>
|
|
@@ -3087,7 +3088,7 @@ let draft = reactive('')
|
|
|
3087
3088
|
|
|
3088
3089
|
<div class="tabs">
|
|
3089
3090
|
{#for name of TABS}
|
|
3090
|
-
<button aria-selected={
|
|
3091
|
+
<button aria-selected={tab === name}
|
|
3091
3092
|
onclick={() => navigate(`?tab=${name}`, { replace: true })}
|
|
3092
3093
|
>{name}</button>
|
|
3093
3094
|
{/for}
|
|
@@ -3236,7 +3237,7 @@ function followTitle(element: Element) {
|
|
|
3236
3237
|
|
|
3237
3238
|
<div class="tabs">
|
|
3238
3239
|
{#for page of PAGES}
|
|
3239
|
-
<button aria-selected={
|
|
3240
|
+
<button aria-selected={open === page.address}
|
|
3240
3241
|
onclick={() => (open = page.address)}>{page.label}</button>
|
|
3241
3242
|
{/for}
|
|
3242
3243
|
</div>
|
|
@@ -5678,6 +5679,7 @@ import { claudeCode, GET } from '@abide/abide/server'
|
|
|
5678
5679
|
// This browser's pairing, for as long as the page watches it.
|
|
5679
5680
|
export default GET(() => claudeCode.bridge(), {
|
|
5680
5681
|
description: "This browser's Claude Code pairing, as it changes.",
|
|
5682
|
+
// Exempt from an operator's ABIDE_RPC_TIMEOUT: a pairing idles for minutes.
|
|
5681
5683
|
timeout: Infinity,
|
|
5682
5684
|
clients: { mcp: false },
|
|
5683
5685
|
})
|
|
@@ -6541,12 +6543,12 @@ abide start
|
|
|
6541
6543
|
ABIDE_OFFLINE=0 abide start
|
|
6542
6544
|
```
|
|
6543
6545
|
|
|
6544
|
-
Each page the CRM opens is kept on the device, with the answers its handlers gave, the public files it loaded and the bundle it runs, until `ABIDE_OFFLINE=0` turns that off where the CRM is served. A kept page opens at once, however weak the signal, and the network is asked for the next copy behind it. With the network gone, a page opened in the last
|
|
6546
|
+
Each page the CRM opens is kept on the device, with the answers its handlers gave, the public files it loaded and the bundle it runs, until `ABIDE_OFFLINE=0` turns that off where the CRM is served. A kept page opens at once, however weak the signal, and the network is asked for the next copy behind it. With the network gone, a page opened in the last year loads again with its data and works. One never opened says the device is offline, and a link to it leaves the page you are on standing.
|
|
6545
6547
|
|
|
6546
6548
|
| Name | What it does for the CRM |
|
|
6547
6549
|
| --- | --- |
|
|
6548
|
-
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=
|
|
6549
|
-
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=
|
|
6550
|
+
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=31536000` | what a page and a handler's answer carry, so the device shows what it kept while it asks again |
|
|
6551
|
+
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=31536000` | the same, for a public file |
|
|
6550
6552
|
| `clear-site-data: "cache"` | what a sign-in or a sign-out answers, so the browser forgets what it kept for the last person |
|
|
6551
6553
|
|
|
6552
6554
|
What is kept is what those headers allow, and nothing else decides it. A handler that writes `cache-control: no-store` through `response` is never kept, and an answer with a `ttl` is shown from the device until its `max-age` runs out. A refresh always asks the network.
|
|
@@ -6596,6 +6598,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
6596
6598
|
| [`invalidate`](/reference/invalidate.md) | `@abide/abide` |
|
|
6597
6599
|
| [`refuse`](/reference/refuse.md) | `@abide/abide` |
|
|
6598
6600
|
| [`Failed`](/reference/types/Failed.md) | `@abide/abide` |
|
|
6601
|
+
| [`isFailed`](/reference/isFailed.md) | `@abide/abide` |
|
|
6599
6602
|
| [`notFound`](/reference/notFound.md) | `@abide/abide` |
|
|
6600
6603
|
| [`validationError`](/reference/validationError.md) | `@abide/abide` |
|
|
6601
6604
|
| [`aborted`](/reference/aborted.md) | `@abide/abide` |
|
|
@@ -7289,6 +7292,24 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7289
7292
|
|
|
7290
7293
|
---
|
|
7291
7294
|
|
|
7295
|
+
<!-- reference/isFailed.md -->
|
|
7296
|
+
|
|
7297
|
+
# `isFailed`
|
|
7298
|
+
|
|
7299
|
+
`import { isFailed } from '@abide/abide'`
|
|
7300
|
+
|
|
7301
|
+
## Refusals
|
|
7302
|
+
|
|
7303
|
+
| Name | Signature | Meaning | Rules |
|
|
7304
|
+
| --- | --- | --- | --- |
|
|
7305
|
+
| `isFailed` | `(value: unknown) => value is Failed<string, unknown>` | Whether a value is a refusal. | [15.6](/rulebook#15.6) |
|
|
7306
|
+
|
|
7307
|
+
## Taught in
|
|
7308
|
+
|
|
7309
|
+
- [Add pages](/guide/add-pages.md)
|
|
7310
|
+
|
|
7311
|
+
---
|
|
7312
|
+
|
|
7292
7313
|
<!-- reference/notFound.md -->
|
|
7293
7314
|
|
|
7294
7315
|
# `notFound`
|
|
@@ -7441,8 +7462,8 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7441
7462
|
| Name | Signature | Meaning | Rules |
|
|
7442
7463
|
| --- | --- | --- | --- |
|
|
7443
7464
|
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: Reactive<Value, Value, Failures, Produced> \| Keyed<Value, Args, Value, Failures, Produced>, options?: RpcOptions<Args, RungFailures>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | Declares a read any surface may call. | [16.1](/rulebook#16.1), [16.2](/rulebook#16.2), [16.5](/rulebook#16.5), [16.6](/rulebook#16.6), [16.30](/rulebook#16.30), [16.31](/rulebook#16.31), [16.32](/rulebook#16.32), [16.68](/rulebook#16.68), [16.76](/rulebook#16.76), [16.34](/rulebook#16.34), [16.35](/rulebook#16.35), [16.36](/rulebook#16.36), [16.37](/rulebook#16.37), [16.39](/rulebook#16.39), [16.44](/rulebook#16.44), [16.45](/rulebook#16.45), [16.46](/rulebook#16.46), [16.47](/rulebook#16.47), [16.75](/rulebook#16.75), [16.50](/rulebook#16.50), [16.51](/rulebook#16.51), [16.52](/rulebook#16.52), [16.64](/rulebook#16.64), [18.1](/rulebook#18.1), [18.3](/rulebook#18.3), [18.6](/rulebook#18.6), [18.8](/rulebook#18.8), [18.10](/rulebook#18.10), [18.12](/rulebook#18.12), [18.21](/rulebook#18.21), [18.24](/rulebook#18.24), [18.27](/rulebook#18.27) |
|
|
7444
|
-
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose
|
|
7445
|
-
| `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright. | [16.1](/rulebook#16.1), [16.6](/rulebook#16.6), [16.7](/rulebook#16.7) |
|
|
7465
|
+
| `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose `ReactiveOptions` (`ttl`, `global`, `debounce` and the rest) build the value it becomes beside its address. | [16.1](/rulebook#16.1), [16.6](/rulebook#16.6), [16.7](/rulebook#16.7) |
|
|
7466
|
+
| `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright, whose `ReactiveOptions` build the value it becomes. | [16.1](/rulebook#16.1), [16.6](/rulebook#16.6), [16.7](/rulebook#16.7) |
|
|
7446
7467
|
|
|
7447
7468
|
## Taught in
|
|
7448
7469
|
|
|
@@ -7681,8 +7702,8 @@ Transports: what is written in place rather than imported.
|
|
|
7681
7702
|
| `cache-control: no-store` | `/__abide/openapi.json`, `/__abide/mcp` | Refuses caching for an answer about the app's surfaces. | [21.4](/rulebook#21.4) |
|
|
7682
7703
|
| `cache-control: public, max-age=31536000, immutable` | the built bundle | A chunk addressed by its own content hash. | [21.5](/rulebook#21.5) |
|
|
7683
7704
|
| `cache-control: public, max-age=0, must-revalidate` | a file under a `public` directory | A file at an address the build never chose. | [21.12](/rulebook#21.12) |
|
|
7684
|
-
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=
|
|
7685
|
-
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=
|
|
7705
|
+
| `cache-control: private, stale-while-revalidate=604800, stale-if-error=31536000` | a page, a `GET`'s answer, where `ABIDE_OFFLINE` registers its worker | The default that lets a copy kept on the device stand in for the network. | [21.22](/rulebook#21.22), [21.11](/rulebook#21.11), [21.17](/rulebook#21.17) |
|
|
7706
|
+
| `cache-control: public, max-age=0, stale-while-revalidate=604800, stale-if-error=31536000` | a file under a `public` directory, where `ABIDE_OFFLINE` registers its worker | The same, for a file at an address the build never chose. | [21.23](/rulebook#21.23) |
|
|
7686
7707
|
| `clear-site-data: "cache"` | an answer carrying `abide-principal`, where pages are kept offline | Drops what the browser's own cache kept for the caller signed in before. | [21.24](/rulebook#21.24) |
|
|
7687
7708
|
| `etag` | a file under a `public` directory | The answered bytes, addressed by their content. | [21.13](/rulebook#21.13), [21.14](/rulebook#21.14) |
|
|
7688
7709
|
| `referrer-policy: strict-origin-when-cross-origin` | a page | The browsers' own default, written down. | [21.6](/rulebook#21.6) |
|
|
@@ -8175,9 +8196,9 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8175
8196
|
|
|
8176
8197
|
| Name | Signature | Meaning | Rules |
|
|
8177
8198
|
| --- | --- | --- | --- |
|
|
8178
|
-
| `route.url` | `Reactive<URL>` | Where we are. | [23.1](/rulebook#23.1), [23.2](/rulebook#23.2), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [30.23](/rulebook#30.23), [19.16](/rulebook#19.16), [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), [19.16](/rulebook#19.16), [31.16](/rulebook#31.16) |
|
|
8179
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) |
|
|
8180
|
-
| `route.name` | `Reactive<string>` | The resolution path. | [23.1](/rulebook#23.1), [23.6](/rulebook#23.6), [31.16](/rulebook#31.16) |
|
|
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) |
|
|
8181
8202
|
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | [23.1](/rulebook#23.1), [31.16](/rulebook#31.16) |
|
|
8182
8203
|
|
|
8183
8204
|
## Taught in
|
package/package.json
CHANGED
package/scaffold/AGENTS.md
CHANGED
|
@@ -17,6 +17,9 @@ before writing code, rather than guessing from Svelte, Vue or React. It installs
|
|
|
17
17
|
TypeScript service do not read `.abide` files, so a clean `tsc` says nothing about them. Run
|
|
18
18
|
`bun run check` after every change, and treat its warnings as bugs: several of them name a read
|
|
19
19
|
that will not update when its value changes.
|
|
20
|
+
Where the read is meant, `// abide-ignore` and the clause the warning cites, on the line above
|
|
21
|
+
(`// abide-ignore 14.15`), silences that one (RULEBOOK 14.19); a read hoisted out of its place
|
|
22
|
+
only to quiet a warning is the bug it named.
|
|
20
23
|
- `bun run test` — runs `bun test`. `src/getGreeting.test.ts` is the pattern: a test imports a
|
|
21
24
|
handler or a `.abide` file and calls it.
|
|
22
25
|
- `bun run dev` — serves the app on a local port and rebuilds when a file changes.
|
|
@@ -44,3 +47,12 @@ before writing code, rather than guessing from Svelte, Vue or React. It installs
|
|
|
44
47
|
- Import the app's own modules as `#src/…` (see `imports` in `package.json`).
|
|
45
48
|
- A test imports `.abide` files directly: `bunfig.toml` preloads `@abide/harness/abide/preload`,
|
|
46
49
|
a DOM to mount into and then `@abide/abide/test`.
|
|
50
|
+
|
|
51
|
+
## Habits
|
|
52
|
+
|
|
53
|
+
- Export a `Reactive` itself, never wrapped in an object: an `.abide` file reads and writes it by
|
|
54
|
+
name (RULEBOOK 31.1), and one reached through a property is left to `x()` and `x.set(v)` (31.7).
|
|
55
|
+
- Model output and fetched HTML are untrusted. Never hand them to `raw()` (RULEBOOK 32.2)
|
|
56
|
+
unsanitized.
|
|
57
|
+
- Settings come from `onConfig` and `config` (RULEBOOK 27.2, 27.9), which already read the
|
|
58
|
+
environment, and cookies from `cookies()` (22.2) — not `Bun.env` or a parsed `cookie` header.
|
|
@@ -122,6 +122,9 @@ export function namesViewTransition(css: string): boolean {
|
|
|
122
122
|
return false
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// Whitespace and comments ahead of a statement, which do not decide whether it is an at-rule.
|
|
126
|
+
const LEADING_COMMENTS = /^(?:\s|\/\*[\s\S]*?\*\/)*/
|
|
127
|
+
|
|
125
128
|
// The block at-rules that hold rules, which `scopeRules` walks into.
|
|
126
129
|
const NESTING =
|
|
127
130
|
/^@(media|supports|container|layer|document|starting-style|scope)\b/i
|
|
@@ -155,7 +158,7 @@ function scopeRules(css: string, attribute: string): string {
|
|
|
155
158
|
}
|
|
156
159
|
const close = matching(css, open)
|
|
157
160
|
const body = css.slice(open + 1, close)
|
|
158
|
-
const trimmed = prelude.trim()
|
|
161
|
+
const trimmed = prelude.replace(LEADING_COMMENTS, '').trim()
|
|
159
162
|
if (trimmed.startsWith('@'))
|
|
160
163
|
out += NESTING.test(trimmed)
|
|
161
164
|
? `${prelude}{${scopeRules(body, attribute)}}`
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// 21.22, 21.23 — HOW LONG A COPY KEPT ON THE DEVICE STAYS SERVABLE once stale, where `ABIDE_OFFLINE`
|
|
2
|
-
// registers its worker: a week
|
|
3
|
-
//
|
|
2
|
+
// registers its worker: a week while the network answers and the copy is asked for again behind it,
|
|
3
|
+
// and a year where the network fails — a copy of any age beats the page saying nothing was saved, and
|
|
4
|
+
// what may evict it sooner (Safari's week for a site not visited) is the browser's, not this header's.
|
|
4
5
|
//
|
|
5
6
|
// NO `max-age` BESIDE IT on a page or a `GET`: a copy reads `max-age` as its own life (18.14), and
|
|
6
7
|
// a followed copy stale from the moment it landed would reload on every read. With no lifetime and
|
|
7
8
|
// no `Last-Modified`, a cache holds the answer stale at once, which is all the two windows need.
|
|
8
9
|
|
|
9
10
|
export const OFFLINE_STALE =
|
|
10
|
-
'stale-while-revalidate=604800, stale-if-error=
|
|
11
|
+
'stale-while-revalidate=604800, stale-if-error=31536000'
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
OFFLINE_BUNDLE,
|
|
16
16
|
OFFLINE_CACHE_PREFIX,
|
|
17
17
|
OFFLINE_SAVED,
|
|
18
|
+
OFFLINE_SHARED,
|
|
18
19
|
} from '#shared/OFFLINE_CACHES.ts'
|
|
19
20
|
import { PRINCIPAL_HEADER } from '#shared/PRINCIPAL_HEADER.ts'
|
|
20
21
|
import { REACHED_MESSAGE } from '#shared/REACHED_MESSAGE.ts'
|
|
@@ -31,6 +32,7 @@ const STREAMED_TYPES = [
|
|
|
31
32
|
|
|
32
33
|
const CACHES = `const OURS = ${JSON.stringify(OFFLINE_CACHE_PREFIX)}
|
|
33
34
|
const SAVED = ${JSON.stringify(OFFLINE_SAVED)}
|
|
35
|
+
const SHARED = ${JSON.stringify(OFFLINE_SHARED)}
|
|
34
36
|
const BUNDLE = ${JSON.stringify(OFFLINE_BUNDLE)}
|
|
35
37
|
|
|
36
38
|
async function dropOurs(keeping) {
|
|
@@ -112,7 +114,7 @@ async function answer(event, page) {
|
|
|
112
114
|
request.cache === 'no-store')
|
|
113
115
|
const saved = bypass
|
|
114
116
|
? undefined
|
|
115
|
-
: await
|
|
117
|
+
: await savedCopy(request)
|
|
116
118
|
const life = saved === undefined ? undefined : lifeOf(saved)
|
|
117
119
|
if (life !== undefined) {
|
|
118
120
|
if (life.age < life.fresh) return marked(saved)
|
|
@@ -197,7 +199,7 @@ async function keepReached(url) {
|
|
|
197
199
|
if (reaching.has(url)) return
|
|
198
200
|
reaching.add(url)
|
|
199
201
|
try {
|
|
200
|
-
const saved = await
|
|
202
|
+
const saved = await savedCopy(url)
|
|
201
203
|
if (saved === undefined || !standsIn(lifeOf(saved))) await keepPage(url)
|
|
202
204
|
} finally {
|
|
203
205
|
reaching.delete(url)
|
|
@@ -247,9 +249,19 @@ function kept(response) {
|
|
|
247
249
|
return !STREAMED.some((streamed) => type.startsWith(streamed))
|
|
248
250
|
}
|
|
249
251
|
|
|
252
|
+
// The copy saved of an address, a caller's own ahead of one any caller may be answered.
|
|
253
|
+
async function savedCopy(request) {
|
|
254
|
+
return (
|
|
255
|
+
(await caches.match(request, { cacheName: SAVED, ignoreVary: true })) ??
|
|
256
|
+
(await caches.match(request, { cacheName: SHARED, ignoreVary: true }))
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
250
260
|
// 45.7 — EVERY PAGE SAVED DROPPED, and with them every copy asked for before the drop: one asked under
|
|
251
261
|
// the cookies of then would land after it holding the caller signed out since. The drop an answer's
|
|
252
|
-
// own abide-principal makes is the exception, since that answer is the new caller's.
|
|
262
|
+
// own abide-principal makes is the exception, since that answer is the new caller's. What any caller
|
|
263
|
+
// may be answered is kept apart and left standing: it was never the caller's to drop, and a font
|
|
264
|
+
// thrown away at sign-in is a page drawn without it offline.
|
|
253
265
|
let drops = 0
|
|
254
266
|
|
|
255
267
|
function dropSaved() {
|
|
@@ -259,15 +271,19 @@ function dropSaved() {
|
|
|
259
271
|
|
|
260
272
|
// Saves a copy 45.3 and 45.4 let through where no drop has come since its request was asked — after
|
|
261
273
|
// the drop its own answer makes where that answer rotated the principal — and cancels one where one has.
|
|
274
|
+
// One any caller may be answered is saved whenever it was asked, since no caller's cookies made it.
|
|
262
275
|
async function keep(request, copy, since, rotated) {
|
|
263
276
|
if (rotated) {
|
|
264
277
|
await dropSaved()
|
|
265
278
|
since = drops
|
|
266
279
|
}
|
|
267
280
|
if (copy === undefined) return
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
281
|
+
const shared = directivesOf(copy).has('public')
|
|
282
|
+
if (!shared && since !== drops) return copy.body?.cancel()
|
|
283
|
+
// An address whose answer went public or private since leaves no copy behind for savedCopy to
|
|
284
|
+
// prefer over this one.
|
|
285
|
+
await (await caches.open(shared ? SAVED : SHARED)).delete(request)
|
|
286
|
+
await (await caches.open(shared ? SHARED : SAVED)).put(request, copy)
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
function offline() {
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
/** What every cache the worker keeps is named under, so a cache an earlier worker named is found. */
|
|
6
6
|
export const OFFLINE_CACHE_PREFIX = 'abide:'
|
|
7
7
|
|
|
8
|
-
/** The pages
|
|
8
|
+
/** The pages and the handlers' answers the worker saved, by address. */
|
|
9
9
|
export const OFFLINE_SAVED = `${OFFLINE_CACHE_PREFIX}saved`
|
|
10
10
|
|
|
11
|
+
/** What the worker saved that any caller may be answered — `cache-control: public` — by address. */
|
|
12
|
+
export const OFFLINE_SHARED = `${OFFLINE_CACHE_PREFIX}shared`
|
|
13
|
+
|
|
11
14
|
/** The build's bundle, saved as the worker installs. */
|
|
12
15
|
export const OFFLINE_BUNDLE = `${OFFLINE_CACHE_PREFIX}bundle`
|