@abide/abide 0.32.1 → 0.33.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 (54) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +93 -63
  3. package/package.json +6 -2
  4. package/src/lib/server/runtime/buildCacheSnapshot.ts +5 -4
  5. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  6. package/src/lib/shared/cache.ts +43 -29
  7. package/src/lib/shared/types/CacheEntry.ts +12 -12
  8. package/src/lib/shared/types/CacheOptions.ts +17 -13
  9. package/src/lib/ui/README.md +3 -3
  10. package/src/lib/ui/compile/HTML_TAGS.ts +132 -0
  11. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +4 -1
  12. package/src/lib/ui/compile/componentWrapperTag.ts +13 -10
  13. package/src/lib/ui/compile/generateBuild.ts +265 -121
  14. package/src/lib/ui/compile/generateSSR.ts +78 -37
  15. package/src/lib/ui/compile/parseTemplate.ts +52 -0
  16. package/src/lib/ui/compile/skeletonable.ts +80 -0
  17. package/src/lib/ui/dom/MATHML_NAMESPACE.ts +6 -0
  18. package/src/lib/ui/dom/SVG_NAMESPACE.ts +7 -0
  19. package/src/lib/ui/dom/anchorCursor.ts +24 -0
  20. package/src/lib/ui/dom/appendSnippet.ts +1 -1
  21. package/src/lib/ui/dom/appendTextAt.ts +70 -0
  22. package/src/lib/ui/dom/awaitBlock.ts +27 -7
  23. package/src/lib/ui/dom/cloneStatic.ts +15 -24
  24. package/src/lib/ui/dom/each.ts +44 -25
  25. package/src/lib/ui/dom/eachAsync.ts +6 -2
  26. package/src/lib/ui/dom/effectiveChildNamespace.ts +13 -0
  27. package/src/lib/ui/dom/enterNamespace.ts +20 -0
  28. package/src/lib/ui/dom/fillBefore.ts +20 -3
  29. package/src/lib/ui/dom/foreignWrapperTag.ts +22 -0
  30. package/src/lib/ui/dom/hydrate.ts +1 -1
  31. package/src/lib/ui/dom/inheritedNamespace.ts +19 -0
  32. package/src/lib/ui/dom/mountSlot.ts +32 -0
  33. package/src/lib/ui/dom/openMarker.ts +4 -2
  34. package/src/lib/ui/dom/skeleton.ts +202 -0
  35. package/src/lib/ui/dom/switchBlock.ts +10 -3
  36. package/src/lib/ui/dom/templateFor.ts +28 -0
  37. package/src/lib/ui/dom/tryBlock.ts +7 -5
  38. package/src/lib/ui/dom/types/SkeletonHoles.ts +8 -0
  39. package/src/lib/ui/dom/when.ts +6 -2
  40. package/src/lib/ui/installHotBridge.ts +8 -2
  41. package/src/lib/ui/runtime/HOLE_ATTRIBUTE.ts +9 -0
  42. package/src/lib/ui/runtime/RENDER.ts +7 -0
  43. package/src/lib/ui/runtime/createDoc.ts +11 -8
  44. package/src/lib/ui/runtime/types/PathWalk.ts +10 -0
  45. package/src/lib/ui/runtime/types/UiProps.ts +3 -4
  46. package/src/lib/ui/runtime/walkPath.ts +27 -0
  47. package/template/src/ui/pages/about/page.abide +4 -6
  48. package/template/src/ui/pages/layout.abide +21 -0
  49. package/template/src/ui/pages/page.abide +5 -8
  50. package/src/lib/ui/compile/partitionSlots.ts +0 -36
  51. package/src/lib/ui/dom/openChild.ts +0 -22
  52. package/src/lib/ui/runtime/pathExists.ts +0 -23
  53. package/src/lib/ui/runtime/valueAtPath.ts +0 -18
  54. package/template/src/ui/Layout.abide +0 -19
@@ -18,22 +18,26 @@ for per-request data: the default keeps a per-user response from leaking across
18
18
  requests. Write only `global: true`; there is no `false` form. On the client
19
19
  there is a single tab store, so the flag is a no-op there.
20
20
 
21
- `invalidate` controls how a `cache.invalidate` hit on this key is applied, in ms.
22
- `{ throttle: N }` refetches on the leading edge then at most once per N ms while
23
- invalidations keep arriving; `{ debounce: N }` refetches only after N ms of
24
- quiet. Both coalesce a burst of invalidations (e.g. a socket spraying
25
- `cache.invalidate`) into far fewer calls and keep serving the existing (stale)
26
- value until the refetch resolves stale-while-revalidate. They affect only the
27
- refetch-after-invalidate; the first fetch and arg-change fetches stay immediate.
28
- A policy declares the call safe to re-run unprompted: cache() throws at wrap
29
- time on both set at once, on ttl: 0 (nothing retained, nothing to revalidate),
30
- and on a non-replayable remote method (replaying a write is a state change
31
- disguised as a refresh). Producers are uncheckable declare a policy only on
32
- a producer that is a pure read.
21
+ `swr` is stale-while-revalidate: it changes what a `cache.invalidate` hit does
22
+ to this key. Without it, an invalidate drops the entry and the next read shows
23
+ `pending()`. With it, the entry is kept and refetched in the background — the
24
+ existing (stale) value stays visible and `refreshing()` reports the in-flight
25
+ reload so the reader never blanks. It governs only the refetch-after-invalidate;
26
+ the first fetch and arg-change fetches stay immediate regardless.
27
+
28
+ `swr: true` refetches immediately on every invalidate. An optional window
29
+ coalesces a burst (e.g. a socket spraying `cache.invalidate`) into far fewer
30
+ calls: `swr: { throttle: N }` refetches on the leading edge then at most once
31
+ per N ms while invalidations keep arriving; `swr: { debounce: N }` refetches
32
+ only after N ms of quiet. `swr` declares the call safe to re-run unprompted:
33
+ cache() throws at wrap time on throttle+debounce set at once, on ttl: 0 (nothing
34
+ retained, nothing to revalidate), and on a non-replayable remote method
35
+ (replaying a write is a state change disguised as a refresh). Producers are
36
+ uncheckable — set `swr` only on a producer that is a pure read.
33
37
  */
34
38
  export type CacheOptions = {
35
39
  ttl?: number
36
40
  scope?: string | string[]
37
41
  global?: boolean
38
- invalidate?: { throttle?: number; debounce?: number }
42
+ swr?: boolean | { throttle?: number; debounce?: number }
39
43
  }
@@ -46,8 +46,8 @@ every page, and `.abide` files are the only component format.
46
46
  `<template await>`/`then`/`catch`, `<template switch>`/`case`/`default`.
47
47
  - **Components** are capitalised tags (`<Layout title="…">`); children fill the
48
48
  child's `<slot>`. Props are reactive (passed as thunks).
49
- - **Scoped styles**: a `<style>` block is scoped per component via a
50
- `[data-b-<hash>]` attribute.
49
+ - **Scoped styles**: a `<style>` block is scoped via a `[data-a-<hash>]`
50
+ attribute — per component, or per control-flow branch when nested in one.
51
51
 
52
52
  ## Substrate (why it's fast)
53
53
 
@@ -70,7 +70,7 @@ write-path microbench this runs ~20× faster than a deep-proxy signal baseline.
70
70
  ```
71
71
  .abide → analyzeComponent (split script/style/template, desugar signals → doc,
72
72
  lower data access, scope CSS)
73
- → generateBuild (client: openChild/appendText/attr/on/each/when/…)
73
+ → generateBuild (client: skeleton/cloneStatic/appendText/attr/on/each/when/…)
74
74
  → generateSSR (server: HTML-string back-end, await markers)
75
75
  → hoistCells (static paths → cells)
76
76
  → compileModule (ES module: default mount + render() for SSR)
@@ -0,0 +1,132 @@
1
+ /*
2
+ Every standard HTML element name (lowercase), plus the two foreign-content roots
3
+ `svg`/`math`. A component wrapper tag that collides with one of these is a real
4
+ element with a content model and parser quirks — `<button>`/`<a>` reject interactive
5
+ descendants, table/list/select families foster or auto-close non-conforming children,
6
+ void elements self-close, and `<svg>`/`<math>` switch the parser into SVG/MathML
7
+ namespace so the wrapper's HTML children break out (foster-parent) of it entirely —
8
+ so the parser reparents the component's own markup out of the wrapper and hydration
9
+ claims `null`. `componentWrapperTag` remaps any such name to a transparent custom
10
+ element instead. Pure SVG/MathML descendant names (`circle`, `path`, `mrow`, …) are
11
+ NOT here: in HTML context they are inert unknown elements that hold children fine, so
12
+ a `<Circle>` component stays as-is like any non-element name. Superset of VOID_TAGS
13
+ (which the parser/SSR still use for self-closing); this set is only the
14
+ wrapper-safety check.
15
+ */
16
+ export const HTML_TAGS: ReadonlySet<string> = new Set([
17
+ 'a',
18
+ 'abbr',
19
+ 'address',
20
+ 'area',
21
+ 'article',
22
+ 'aside',
23
+ 'audio',
24
+ 'b',
25
+ 'base',
26
+ 'bdi',
27
+ 'bdo',
28
+ 'blockquote',
29
+ 'body',
30
+ 'br',
31
+ 'button',
32
+ 'canvas',
33
+ 'caption',
34
+ 'cite',
35
+ 'code',
36
+ 'col',
37
+ 'colgroup',
38
+ 'data',
39
+ 'datalist',
40
+ 'dd',
41
+ 'del',
42
+ 'details',
43
+ 'dfn',
44
+ 'dialog',
45
+ 'div',
46
+ 'dl',
47
+ 'dt',
48
+ 'em',
49
+ 'embed',
50
+ 'fieldset',
51
+ 'figcaption',
52
+ 'figure',
53
+ 'footer',
54
+ 'form',
55
+ 'h1',
56
+ 'h2',
57
+ 'h3',
58
+ 'h4',
59
+ 'h5',
60
+ 'h6',
61
+ 'head',
62
+ 'header',
63
+ 'hgroup',
64
+ 'hr',
65
+ 'html',
66
+ 'i',
67
+ 'iframe',
68
+ 'img',
69
+ 'input',
70
+ 'ins',
71
+ 'kbd',
72
+ 'label',
73
+ 'legend',
74
+ 'li',
75
+ 'link',
76
+ 'main',
77
+ 'map',
78
+ 'mark',
79
+ 'math',
80
+ 'menu',
81
+ 'meta',
82
+ 'meter',
83
+ 'nav',
84
+ 'noscript',
85
+ 'object',
86
+ 'ol',
87
+ 'optgroup',
88
+ 'option',
89
+ 'output',
90
+ 'p',
91
+ 'param',
92
+ 'picture',
93
+ 'pre',
94
+ 'progress',
95
+ 'q',
96
+ 'rp',
97
+ 'rt',
98
+ 'ruby',
99
+ 's',
100
+ 'samp',
101
+ 'script',
102
+ 'search',
103
+ 'section',
104
+ 'select',
105
+ 'slot',
106
+ 'small',
107
+ 'source',
108
+ 'span',
109
+ 'strong',
110
+ 'style',
111
+ 'sub',
112
+ 'summary',
113
+ 'sup',
114
+ 'svg',
115
+ 'table',
116
+ 'tbody',
117
+ 'td',
118
+ 'template',
119
+ 'textarea',
120
+ 'tfoot',
121
+ 'th',
122
+ 'thead',
123
+ 'time',
124
+ 'title',
125
+ 'tr',
126
+ 'track',
127
+ 'u',
128
+ 'ul',
129
+ 'var',
130
+ 'video',
131
+ 'wbr',
132
+ ])
@@ -14,11 +14,13 @@ export const UI_RUNTIME_IMPORTS: { name: string; specifier: string }[] = [
14
14
  { name: 'derived', specifier: 'ui/derived' },
15
15
  { name: 'effect', specifier: 'ui/effect' },
16
16
  { name: 'mount', specifier: 'ui/dom/mount' },
17
- { name: 'openChild', specifier: 'ui/dom/openChild' },
18
17
  { name: 'appendText', specifier: 'ui/dom/appendText' },
18
+ { name: 'appendTextAt', specifier: 'ui/dom/appendTextAt' },
19
19
  { name: 'appendSnippet', specifier: 'ui/dom/appendSnippet' },
20
20
  { name: 'appendStatic', specifier: 'ui/dom/appendStatic' },
21
21
  { name: 'cloneStatic', specifier: 'ui/dom/cloneStatic' },
22
+ { name: 'skeleton', specifier: 'ui/dom/skeleton' },
23
+ { name: 'anchorCursor', specifier: 'ui/dom/anchorCursor' },
22
24
  { name: 'attr', specifier: 'ui/dom/attr' },
23
25
  { name: 'on', specifier: 'ui/dom/on' },
24
26
  { name: 'attach', specifier: 'ui/dom/attach' },
@@ -28,6 +30,7 @@ export const UI_RUNTIME_IMPORTS: { name: string; specifier: string }[] = [
28
30
  { name: 'awaitBlock', specifier: 'ui/dom/awaitBlock' },
29
31
  { name: 'tryBlock', specifier: 'ui/dom/tryBlock' },
30
32
  { name: 'switchBlock', specifier: 'ui/dom/switchBlock' },
33
+ { name: 'mountSlot', specifier: 'ui/dom/mountSlot' },
31
34
  { name: 'mountChild', specifier: 'ui/dom/mountChild' },
32
35
  { name: 'hydrate', specifier: 'ui/dom/hydrate' },
33
36
  { name: 'nextBlockId', specifier: 'ui/runtime/nextBlockId' },
@@ -1,20 +1,23 @@
1
- import { VOID_TAGS } from './VOID_TAGS.ts'
1
+ import { HTML_TAGS } from './HTML_TAGS.ts'
2
2
 
3
3
  /*
4
4
  The element tag a component instance mounts into. Normally the component name
5
5
  lowercased — readable in devtools, a real box like any abide wrapper. But a name
6
- that lowercases to a VOID element (`Input`→`input`, `Img`→`img`) would yield a
7
- wrapper the HTML parser self-closes, reparenting the component's own markup as the
8
- wrapper's siblings so on hydration `openChild` finds the wrapper empty, claims
9
- `null`, and `attr` throws on it. Those names map to a hyphenated custom-element tag
10
- (a custom element is never void) made layout-transparent with `display:contents`,
11
- so the component's real root still lays out as a direct child of the parent the way
12
- the (parse-broken) void wrapper effectively did. Both back-ends call this so the SSR
13
- string and the client build agree on the wrapper.
6
+ that lowercases to a real HTML element (`Button`→`button`, `Input`→`input`) yields a
7
+ wrapper with a content model the parser enforces: void elements self-close, and
8
+ `<button>`/`<a>`/table/list/select families reject or foster the component's own
9
+ markup as the wrapper's siblings so on hydration the skeleton locates the wrapper
10
+ empty, claims `null`, and `attr` throws on it. Those names map to a hyphenated
11
+ custom-element tag (a custom element is never void and has no content model) made
12
+ layout-transparent with `display:contents`, so the component's real root still lays
13
+ out as a direct child of the parent the way the (parse-broken) wrapper would have.
14
+ A name that is NOT a known HTML element (the common case — `Card`, `Dropdown`) is an
15
+ inert unknown tag that holds any content untouched, so it stays as-is. Both back-ends
16
+ call this so the SSR string and the client build agree on the wrapper.
14
17
  */
15
18
  export function componentWrapperTag(name: string): { tag: string; transparent: boolean } {
16
19
  const lower = name.toLowerCase()
17
- return VOID_TAGS.has(lower)
20
+ return HTML_TAGS.has(lower)
18
21
  ? { tag: `abide-${lower}`, transparent: true }
19
22
  : { tag: lower, transparent: false }
20
23
  }