@llui/dom 0.0.30 → 0.0.33
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/README.md +1 -0
- package/dist/binding-descriptors.d.ts +126 -0
- package/dist/binding-descriptors.d.ts.map +1 -0
- package/dist/binding-descriptors.js +144 -0
- package/dist/binding-descriptors.js.map +1 -0
- package/dist/devtools.d.ts +7 -3
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +52 -19
- package/dist/devtools.js.map +1 -1
- package/dist/el-split.d.ts.map +1 -1
- package/dist/el-split.js +12 -0
- package/dist/el-split.js.map +1 -1
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +14 -0
- package/dist/elements.js.map +1 -1
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +194 -0
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/mount.d.ts +22 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +156 -173
- package/dist/mount.js.map +1 -1
- package/dist/primitives/sample.d.ts +16 -4
- package/dist/primitives/sample.d.ts.map +1 -1
- package/dist/primitives/sample.js +16 -4
- package/dist/primitives/sample.js.map +1 -1
- package/dist/primitives/unsafe-html.d.ts +1 -1
- package/dist/primitives/unsafe-html.d.ts.map +1 -1
- package/dist/primitives/unsafe-html.js.map +1 -1
- package/dist/types.d.ts +99 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-loop.d.ts.map +1 -1
- package/dist/update-loop.js.map +1 -1
- package/dist/view-helpers.d.ts +19 -3
- package/dist/view-helpers.d.ts.map +1 -1
- package/dist/view-helpers.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ mountApp(document.getElementById('app')!, Counter)
|
|
|
44
44
|
`view` receives a single `View<S, M>` bag. Destructure what you need — `send` plus any state-bound helpers. TypeScript infers `S` from the component definition, so no per-call generics:
|
|
45
45
|
|
|
46
46
|
```typescript
|
|
47
|
+
// @doc-skip — illustrative shape; uses `[...]` placeholders for render results
|
|
47
48
|
view: ({ send, text, show, each, branch, memo }) => [
|
|
48
49
|
text(s => s.label), // s is State — inferred
|
|
49
50
|
...show({ when: s => s.visible, render: () => [...] }),
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { ComponentInstance } from './update-loop.js';
|
|
2
|
+
import type { Lifetime } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A single agent-dispatchable variant tied to currently-rendered UI.
|
|
5
|
+
*
|
|
6
|
+
* The agent layer's `list_actions` reads `getBindingDescriptors()` to
|
|
7
|
+
* surface which Msg variants the LLM can usefully send right now —
|
|
8
|
+
* not just which the app *could* accept in principle, but which have
|
|
9
|
+
* a live UI binding the human user could also click. Each entry maps
|
|
10
|
+
* to one variant string the compiler discovered as a literal `send({
|
|
11
|
+
* type: '<variant>' })` call inside an event-handler arrow.
|
|
12
|
+
*/
|
|
13
|
+
export interface BindingDescriptor {
|
|
14
|
+
variant: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Per-instance live registry. Keyed by variant; the value is a
|
|
18
|
+
* refcount because multiple bindings can dispatch the same variant
|
|
19
|
+
* (e.g. one button per item in an `each`), and the variant must
|
|
20
|
+
* remain "live" as long as ANY of those bindings is mounted.
|
|
21
|
+
*
|
|
22
|
+
* The compiler tags every event-handler arrow function containing a
|
|
23
|
+
* literal `send({type: 'X'})` call with a `__lluiVariants` array of
|
|
24
|
+
* the discovered variants. The runtime (in `elements.ts`) reads that
|
|
25
|
+
* tag at bind time and calls `registerBindingVariants(inst, lifetime,
|
|
26
|
+
* variants)`. Each registration increments the variant's refcount;
|
|
27
|
+
* an `onDispose` hook on the lifetime decrements when the binding's
|
|
28
|
+
* scope is torn down. `getBindingDescriptors(inst)` then returns the
|
|
29
|
+
* variants whose refcount is currently > 0.
|
|
30
|
+
*
|
|
31
|
+
* Refcount semantics (rather than a Set) matter for `each` loops.
|
|
32
|
+
* 100 rows that all bind `'Item/Remove'` produce 100 increments; the
|
|
33
|
+
* variant stays live until every row is unmounted, which mirrors what
|
|
34
|
+
* the LLM should observe — the action remains affordable as long as
|
|
35
|
+
* any row offers it.
|
|
36
|
+
*/
|
|
37
|
+
export interface BindingDescriptorRegistry {
|
|
38
|
+
/**
|
|
39
|
+
* Variant → live refcount. Entries are deleted when the count
|
|
40
|
+
* reaches zero so iteration stays cheap regardless of churn over
|
|
41
|
+
* the lifetime of the app.
|
|
42
|
+
*/
|
|
43
|
+
counts: Map<string, number>;
|
|
44
|
+
}
|
|
45
|
+
export declare function createBindingDescriptorRegistry(): BindingDescriptorRegistry;
|
|
46
|
+
/**
|
|
47
|
+
* Increment the live refcount for each variant in `variants`, and
|
|
48
|
+
* register a lifetime disposer that decrements them on unmount.
|
|
49
|
+
*
|
|
50
|
+
* The registry is lazily attached to the instance the first time a
|
|
51
|
+
* binding registers. Apps that don't bind any tagged event handlers
|
|
52
|
+
* never allocate the registry — `getBindingDescriptors` returns an
|
|
53
|
+
* empty array in that case.
|
|
54
|
+
*/
|
|
55
|
+
export declare function registerBindingVariants(inst: ComponentInstance, lifetime: Lifetime, variants: readonly string[]): void;
|
|
56
|
+
/**
|
|
57
|
+
* Read the current set of live binding descriptors from the
|
|
58
|
+
* instance. Order is iteration order over the registry map (insertion
|
|
59
|
+
* order with deletions); callers that need a deterministic ordering
|
|
60
|
+
* should sort by `variant` themselves.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getBindingDescriptors(inst: ComponentInstance): BindingDescriptor[];
|
|
63
|
+
/**
|
|
64
|
+
* Library helper for `*.connect` implementations: tags an event
|
|
65
|
+
* handler with the variants it dispatches at runtime, so the binding
|
|
66
|
+
* registers them when the user spreads the bag onto an element.
|
|
67
|
+
*
|
|
68
|
+
* Resolution rules — choose whichever is defined and non-empty:
|
|
69
|
+
*
|
|
70
|
+
* 1. **`send.__lluiVariants`** (translator pattern). When the user
|
|
71
|
+
* passed a compiler-tagged dispatch translator like
|
|
72
|
+
* `(m) => dispatch({type: 'Auth/UserMenu'})`, `send` itself
|
|
73
|
+
* carries the user-side variants the translator forwards. We
|
|
74
|
+
* surface those — the agent should see what `update()` actually
|
|
75
|
+
* receives, not the library's internal Msg shape.
|
|
76
|
+
*
|
|
77
|
+
* 2. **`libraryVariants`** fallback. When `send` is the user's raw
|
|
78
|
+
* component send (no translator), the library's internal Msgs flow
|
|
79
|
+
* directly into `update()`, so the library's own variants ARE the
|
|
80
|
+
* user variants. Library author hand-lists them once per handler.
|
|
81
|
+
*
|
|
82
|
+
* Returns `fn` mutated (via `Object.assign`) so the same reference
|
|
83
|
+
* remains identity-equal — important for downstream code that diffs
|
|
84
|
+
* handlers across re-bindings.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { tagSend } from '@llui/dom'
|
|
89
|
+
*
|
|
90
|
+
* export function connect<S>(get, send, opts) {
|
|
91
|
+
* return {
|
|
92
|
+
* trigger: {
|
|
93
|
+
* onClick: tagSend(send, ['Open'], () => send({ type: 'open' })),
|
|
94
|
+
* },
|
|
95
|
+
* }
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function tagSend<F extends (...args: never[]) => unknown>(send: unknown, libraryVariants: readonly string[], fn: F): F;
|
|
100
|
+
/**
|
|
101
|
+
* Compiler-emitted runtime helper. The vite-plugin's `*.connect(get,
|
|
102
|
+
* sendFn, ...)` pattern matcher emits a call to this function
|
|
103
|
+
* immediately before each connect call, with the variants statically
|
|
104
|
+
* discovered in `sendFn`'s body — covering the dispatch-translation
|
|
105
|
+
* layers that the event-handler tagger can't follow (a library
|
|
106
|
+
* onClick calls the user's `sendFn`, which in turn calls
|
|
107
|
+
* `dispatch(translatedMsg)`; static analysis of the library's onClick
|
|
108
|
+
* can't see across that hop).
|
|
109
|
+
*
|
|
110
|
+
* Reads the active render context's `instance` and `rootLifetime` —
|
|
111
|
+
* which is the right scope automatically: when invoked from the
|
|
112
|
+
* top-level view body, registers on the component's root scope; when
|
|
113
|
+
* invoked from inside an `each(...)` render callback, the active
|
|
114
|
+
* `rootLifetime` is the per-item scope, so the registration ties to
|
|
115
|
+
* that item's lifetime and unregisters on item removal.
|
|
116
|
+
*
|
|
117
|
+
* **No-op when called outside a render context.** The compiler tries
|
|
118
|
+
* to skip emission at module top-level, but tooling never has full
|
|
119
|
+
* scope visibility (re-exports, transformations, generated code), so
|
|
120
|
+
* the helper itself defensively short-circuits rather than throwing.
|
|
121
|
+
* The translator's variants simply don't surface — the app still
|
|
122
|
+
* functions; agents can fall back to declared `agentAffordances` or
|
|
123
|
+
* the message schema.
|
|
124
|
+
*/
|
|
125
|
+
export declare function __registerScopeVariants(variants: readonly string[]): void;
|
|
126
|
+
//# sourceMappingURL=binding-descriptors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-descriptors.d.ts","sourceRoot":"","sources":["../src/binding-descriptors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAI1C;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAED,wBAAgB,+BAA+B,IAAI,yBAAyB,CAE3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,IAAI,CAaN;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAMlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,EAC7D,IAAI,EAAE,OAAO,EACb,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,EAAE,EAAE,CAAC,GACJ,CAAC,CAQH;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAUzE"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { addDisposer } from './lifetime.js';
|
|
2
|
+
import { getRenderContext } from './render-context.js';
|
|
3
|
+
export function createBindingDescriptorRegistry() {
|
|
4
|
+
return { counts: new Map() };
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Increment the live refcount for each variant in `variants`, and
|
|
8
|
+
* register a lifetime disposer that decrements them on unmount.
|
|
9
|
+
*
|
|
10
|
+
* The registry is lazily attached to the instance the first time a
|
|
11
|
+
* binding registers. Apps that don't bind any tagged event handlers
|
|
12
|
+
* never allocate the registry — `getBindingDescriptors` returns an
|
|
13
|
+
* empty array in that case.
|
|
14
|
+
*/
|
|
15
|
+
export function registerBindingVariants(inst, lifetime, variants) {
|
|
16
|
+
if (variants.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
const registry = (inst._bindingDescriptors ??= createBindingDescriptorRegistry());
|
|
19
|
+
for (const v of variants) {
|
|
20
|
+
registry.counts.set(v, (registry.counts.get(v) ?? 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
addDisposer(lifetime, () => {
|
|
23
|
+
for (const v of variants) {
|
|
24
|
+
const next = (registry.counts.get(v) ?? 0) - 1;
|
|
25
|
+
if (next <= 0)
|
|
26
|
+
registry.counts.delete(v);
|
|
27
|
+
else
|
|
28
|
+
registry.counts.set(v, next);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read the current set of live binding descriptors from the
|
|
34
|
+
* instance. Order is iteration order over the registry map (insertion
|
|
35
|
+
* order with deletions); callers that need a deterministic ordering
|
|
36
|
+
* should sort by `variant` themselves.
|
|
37
|
+
*/
|
|
38
|
+
export function getBindingDescriptors(inst) {
|
|
39
|
+
const reg = inst._bindingDescriptors;
|
|
40
|
+
if (!reg)
|
|
41
|
+
return [];
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const variant of reg.counts.keys())
|
|
44
|
+
out.push({ variant });
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Library helper for `*.connect` implementations: tags an event
|
|
49
|
+
* handler with the variants it dispatches at runtime, so the binding
|
|
50
|
+
* registers them when the user spreads the bag onto an element.
|
|
51
|
+
*
|
|
52
|
+
* Resolution rules — choose whichever is defined and non-empty:
|
|
53
|
+
*
|
|
54
|
+
* 1. **`send.__lluiVariants`** (translator pattern). When the user
|
|
55
|
+
* passed a compiler-tagged dispatch translator like
|
|
56
|
+
* `(m) => dispatch({type: 'Auth/UserMenu'})`, `send` itself
|
|
57
|
+
* carries the user-side variants the translator forwards. We
|
|
58
|
+
* surface those — the agent should see what `update()` actually
|
|
59
|
+
* receives, not the library's internal Msg shape.
|
|
60
|
+
*
|
|
61
|
+
* 2. **`libraryVariants`** fallback. When `send` is the user's raw
|
|
62
|
+
* component send (no translator), the library's internal Msgs flow
|
|
63
|
+
* directly into `update()`, so the library's own variants ARE the
|
|
64
|
+
* user variants. Library author hand-lists them once per handler.
|
|
65
|
+
*
|
|
66
|
+
* Returns `fn` mutated (via `Object.assign`) so the same reference
|
|
67
|
+
* remains identity-equal — important for downstream code that diffs
|
|
68
|
+
* handlers across re-bindings.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* import { tagSend } from '@llui/dom'
|
|
73
|
+
*
|
|
74
|
+
* export function connect<S>(get, send, opts) {
|
|
75
|
+
* return {
|
|
76
|
+
* trigger: {
|
|
77
|
+
* onClick: tagSend(send, ['Open'], () => send({ type: 'open' })),
|
|
78
|
+
* },
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function tagSend(send, libraryVariants, fn) {
|
|
84
|
+
const sendVariants = send
|
|
85
|
+
?.__lluiVariants;
|
|
86
|
+
const variants = sendVariants && sendVariants.length > 0 ? sendVariants : libraryVariants;
|
|
87
|
+
if (variants.length > 0) {
|
|
88
|
+
Object.assign(fn, { __lluiVariants: variants });
|
|
89
|
+
}
|
|
90
|
+
return fn;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compiler-emitted runtime helper. The vite-plugin's `*.connect(get,
|
|
94
|
+
* sendFn, ...)` pattern matcher emits a call to this function
|
|
95
|
+
* immediately before each connect call, with the variants statically
|
|
96
|
+
* discovered in `sendFn`'s body — covering the dispatch-translation
|
|
97
|
+
* layers that the event-handler tagger can't follow (a library
|
|
98
|
+
* onClick calls the user's `sendFn`, which in turn calls
|
|
99
|
+
* `dispatch(translatedMsg)`; static analysis of the library's onClick
|
|
100
|
+
* can't see across that hop).
|
|
101
|
+
*
|
|
102
|
+
* Reads the active render context's `instance` and `rootLifetime` —
|
|
103
|
+
* which is the right scope automatically: when invoked from the
|
|
104
|
+
* top-level view body, registers on the component's root scope; when
|
|
105
|
+
* invoked from inside an `each(...)` render callback, the active
|
|
106
|
+
* `rootLifetime` is the per-item scope, so the registration ties to
|
|
107
|
+
* that item's lifetime and unregisters on item removal.
|
|
108
|
+
*
|
|
109
|
+
* **No-op when called outside a render context.** The compiler tries
|
|
110
|
+
* to skip emission at module top-level, but tooling never has full
|
|
111
|
+
* scope visibility (re-exports, transformations, generated code), so
|
|
112
|
+
* the helper itself defensively short-circuits rather than throwing.
|
|
113
|
+
* The translator's variants simply don't surface — the app still
|
|
114
|
+
* functions; agents can fall back to declared `agentAffordances` or
|
|
115
|
+
* the message schema.
|
|
116
|
+
*/
|
|
117
|
+
export function __registerScopeVariants(variants) {
|
|
118
|
+
if (variants.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
// Probe the render context without throwing — the helper is allowed
|
|
121
|
+
// outside a view (no-op rather than fatal). `getRenderContext`
|
|
122
|
+
// throws when there's no context, so we pre-check with the module-
|
|
123
|
+
// private accessor pattern: import the same module and read the
|
|
124
|
+
// current context manually.
|
|
125
|
+
const ctx = getCurrentRenderContext();
|
|
126
|
+
if (!ctx || !ctx.instance)
|
|
127
|
+
return;
|
|
128
|
+
registerBindingVariants(ctx.instance, ctx.rootLifetime, variants);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Internal helper: read the current render context without throwing.
|
|
132
|
+
* Returns null when no context is active (module top-level, async
|
|
133
|
+
* callbacks, etc.) so callers can degrade gracefully instead of
|
|
134
|
+
* crashing.
|
|
135
|
+
*/
|
|
136
|
+
function getCurrentRenderContext() {
|
|
137
|
+
try {
|
|
138
|
+
return getRenderContext('__registerScopeVariants');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=binding-descriptors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding-descriptors.js","sourceRoot":"","sources":["../src/binding-descriptors.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ComponentInstance } from './update-loop.js'\nimport type { Lifetime } from './types.js'\nimport { addDisposer } from './lifetime.js'\nimport { getRenderContext } from './render-context.js'\n\n/**\n * A single agent-dispatchable variant tied to currently-rendered UI.\n *\n * The agent layer's `list_actions` reads `getBindingDescriptors()` to\n * surface which Msg variants the LLM can usefully send right now —\n * not just which the app *could* accept in principle, but which have\n * a live UI binding the human user could also click. Each entry maps\n * to one variant string the compiler discovered as a literal `send({\n * type: '<variant>' })` call inside an event-handler arrow.\n */\nexport interface BindingDescriptor {\n variant: string\n}\n\n/**\n * Per-instance live registry. Keyed by variant; the value is a\n * refcount because multiple bindings can dispatch the same variant\n * (e.g. one button per item in an `each`), and the variant must\n * remain \"live\" as long as ANY of those bindings is mounted.\n *\n * The compiler tags every event-handler arrow function containing a\n * literal `send({type: 'X'})` call with a `__lluiVariants` array of\n * the discovered variants. The runtime (in `elements.ts`) reads that\n * tag at bind time and calls `registerBindingVariants(inst, lifetime,\n * variants)`. Each registration increments the variant's refcount;\n * an `onDispose` hook on the lifetime decrements when the binding's\n * scope is torn down. `getBindingDescriptors(inst)` then returns the\n * variants whose refcount is currently > 0.\n *\n * Refcount semantics (rather than a Set) matter for `each` loops.\n * 100 rows that all bind `'Item/Remove'` produce 100 increments; the\n * variant stays live until every row is unmounted, which mirrors what\n * the LLM should observe — the action remains affordable as long as\n * any row offers it.\n */\nexport interface BindingDescriptorRegistry {\n /**\n * Variant → live refcount. Entries are deleted when the count\n * reaches zero so iteration stays cheap regardless of churn over\n * the lifetime of the app.\n */\n counts: Map<string, number>\n}\n\nexport function createBindingDescriptorRegistry(): BindingDescriptorRegistry {\n return { counts: new Map() }\n}\n\n/**\n * Increment the live refcount for each variant in `variants`, and\n * register a lifetime disposer that decrements them on unmount.\n *\n * The registry is lazily attached to the instance the first time a\n * binding registers. Apps that don't bind any tagged event handlers\n * never allocate the registry — `getBindingDescriptors` returns an\n * empty array in that case.\n */\nexport function registerBindingVariants(\n inst: ComponentInstance,\n lifetime: Lifetime,\n variants: readonly string[],\n): void {\n if (variants.length === 0) return\n const registry = (inst._bindingDescriptors ??= createBindingDescriptorRegistry())\n for (const v of variants) {\n registry.counts.set(v, (registry.counts.get(v) ?? 0) + 1)\n }\n addDisposer(lifetime, () => {\n for (const v of variants) {\n const next = (registry.counts.get(v) ?? 0) - 1\n if (next <= 0) registry.counts.delete(v)\n else registry.counts.set(v, next)\n }\n })\n}\n\n/**\n * Read the current set of live binding descriptors from the\n * instance. Order is iteration order over the registry map (insertion\n * order with deletions); callers that need a deterministic ordering\n * should sort by `variant` themselves.\n */\nexport function getBindingDescriptors(inst: ComponentInstance): BindingDescriptor[] {\n const reg = inst._bindingDescriptors\n if (!reg) return []\n const out: BindingDescriptor[] = []\n for (const variant of reg.counts.keys()) out.push({ variant })\n return out\n}\n\n/**\n * Library helper for `*.connect` implementations: tags an event\n * handler with the variants it dispatches at runtime, so the binding\n * registers them when the user spreads the bag onto an element.\n *\n * Resolution rules — choose whichever is defined and non-empty:\n *\n * 1. **`send.__lluiVariants`** (translator pattern). When the user\n * passed a compiler-tagged dispatch translator like\n * `(m) => dispatch({type: 'Auth/UserMenu'})`, `send` itself\n * carries the user-side variants the translator forwards. We\n * surface those — the agent should see what `update()` actually\n * receives, not the library's internal Msg shape.\n *\n * 2. **`libraryVariants`** fallback. When `send` is the user's raw\n * component send (no translator), the library's internal Msgs flow\n * directly into `update()`, so the library's own variants ARE the\n * user variants. Library author hand-lists them once per handler.\n *\n * Returns `fn` mutated (via `Object.assign`) so the same reference\n * remains identity-equal — important for downstream code that diffs\n * handlers across re-bindings.\n *\n * @example\n * ```ts\n * import { tagSend } from '@llui/dom'\n *\n * export function connect<S>(get, send, opts) {\n * return {\n * trigger: {\n * onClick: tagSend(send, ['Open'], () => send({ type: 'open' })),\n * },\n * }\n * }\n * ```\n */\nexport function tagSend<F extends (...args: never[]) => unknown>(\n send: unknown,\n libraryVariants: readonly string[],\n fn: F,\n): F {\n const sendVariants = (send as { __lluiVariants?: readonly string[] } | null | undefined)\n ?.__lluiVariants\n const variants = sendVariants && sendVariants.length > 0 ? sendVariants : libraryVariants\n if (variants.length > 0) {\n Object.assign(fn, { __lluiVariants: variants })\n }\n return fn\n}\n\n/**\n * Compiler-emitted runtime helper. The vite-plugin's `*.connect(get,\n * sendFn, ...)` pattern matcher emits a call to this function\n * immediately before each connect call, with the variants statically\n * discovered in `sendFn`'s body — covering the dispatch-translation\n * layers that the event-handler tagger can't follow (a library\n * onClick calls the user's `sendFn`, which in turn calls\n * `dispatch(translatedMsg)`; static analysis of the library's onClick\n * can't see across that hop).\n *\n * Reads the active render context's `instance` and `rootLifetime` —\n * which is the right scope automatically: when invoked from the\n * top-level view body, registers on the component's root scope; when\n * invoked from inside an `each(...)` render callback, the active\n * `rootLifetime` is the per-item scope, so the registration ties to\n * that item's lifetime and unregisters on item removal.\n *\n * **No-op when called outside a render context.** The compiler tries\n * to skip emission at module top-level, but tooling never has full\n * scope visibility (re-exports, transformations, generated code), so\n * the helper itself defensively short-circuits rather than throwing.\n * The translator's variants simply don't surface — the app still\n * functions; agents can fall back to declared `agentAffordances` or\n * the message schema.\n */\nexport function __registerScopeVariants(variants: readonly string[]): void {\n if (variants.length === 0) return\n // Probe the render context without throwing — the helper is allowed\n // outside a view (no-op rather than fatal). `getRenderContext`\n // throws when there's no context, so we pre-check with the module-\n // private accessor pattern: import the same module and read the\n // current context manually.\n const ctx = getCurrentRenderContext()\n if (!ctx || !ctx.instance) return\n registerBindingVariants(ctx.instance, ctx.rootLifetime, variants)\n}\n\n/**\n * Internal helper: read the current render context without throwing.\n * Returns null when no context is active (module top-level, async\n * callbacks, etc.) so callers can degrade gracefully instead of\n * crashing.\n */\nfunction getCurrentRenderContext(): import('./render-context.js').RenderContext | null {\n try {\n return getRenderContext('__registerScopeVariants')\n } catch {\n return null\n }\n}\n"]}
|
package/dist/devtools.d.ts
CHANGED
|
@@ -28,9 +28,13 @@ export declare function enableDevTools(): void;
|
|
|
28
28
|
* by `@llui/mcp`. If the MCP server is running, the response gives
|
|
29
29
|
* us the actual port — we connect immediately. This avoids the race
|
|
30
30
|
* where HMR events fire before the listener registers, and handles
|
|
31
|
-
* cases where MCP runs on a non-default port.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* cases where MCP runs on a non-default port. When the canonical
|
|
32
|
+
* path is shadowed (e.g. `@cloudflare/vite-plugin` routes every
|
|
33
|
+
* HTTP request to the worker), the client falls back to
|
|
34
|
+
* `/cdn-cgi/llui_mcp_status` which the Vite plugin also registers
|
|
35
|
+
* — Cloudflare lets `/cdn-cgi/*` paths through to the dev server.
|
|
36
|
+
* 2. **Compile-time fallback**: if both endpoints are unavailable
|
|
37
|
+
* (network error, non-Vite environment), we attempt a single
|
|
34
38
|
* connection to the compiled-in `port` parameter as a best-effort.
|
|
35
39
|
*
|
|
36
40
|
* Either way: no retry loop. If both fail, `window.__lluiConnect(port?)`
|
package/dist/devtools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAqB,YAAY,EAAE,MAAM,YAAY,CAAA;AAEjE,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACrF,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAA;AAEtC,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CACxD;AAyBD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAiGD
|
|
1
|
+
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"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"}
|
package/dist/devtools.js
CHANGED
|
@@ -135,9 +135,13 @@ function connectRelay(port, isInitial) {
|
|
|
135
135
|
* by `@llui/mcp`. If the MCP server is running, the response gives
|
|
136
136
|
* us the actual port — we connect immediately. This avoids the race
|
|
137
137
|
* where HMR events fire before the listener registers, and handles
|
|
138
|
-
* cases where MCP runs on a non-default port.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
138
|
+
* cases where MCP runs on a non-default port. When the canonical
|
|
139
|
+
* path is shadowed (e.g. `@cloudflare/vite-plugin` routes every
|
|
140
|
+
* HTTP request to the worker), the client falls back to
|
|
141
|
+
* `/cdn-cgi/llui_mcp_status` which the Vite plugin also registers
|
|
142
|
+
* — Cloudflare lets `/cdn-cgi/*` paths through to the dev server.
|
|
143
|
+
* 2. **Compile-time fallback**: if both endpoints are unavailable
|
|
144
|
+
* (network error, non-Vite environment), we attempt a single
|
|
141
145
|
* connection to the compiled-in `port` parameter as a best-effort.
|
|
142
146
|
*
|
|
143
147
|
* Either way: no retry loop. If both fail, `window.__lluiConnect(port?)`
|
|
@@ -156,23 +160,22 @@ export function startRelay(port = 5200) {
|
|
|
156
160
|
g.__lluiConnect = (p) => {
|
|
157
161
|
connectRelay(p ?? relayPort, false);
|
|
158
162
|
};
|
|
159
|
-
// Try the Vite middleware first (knows the actual port from the marker file)
|
|
160
163
|
if (typeof fetch !== 'undefined') {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.
|
|
174
|
-
//
|
|
175
|
-
|
|
164
|
+
void resolveMcpStatus().then((result) => {
|
|
165
|
+
if (result.kind === 'found') {
|
|
166
|
+
relayPort = result.port;
|
|
167
|
+
connectRelay(result.port, true);
|
|
168
|
+
}
|
|
169
|
+
else if (result.kind === 'network-error') {
|
|
170
|
+
// No Vite server reachable (e.g., production build, test
|
|
171
|
+
// harness without a server). Fall back to the compile-time
|
|
172
|
+
// port; the WS connect will either succeed or quietly fail.
|
|
173
|
+
connectRelay(port, true);
|
|
174
|
+
}
|
|
175
|
+
// result.kind === 'not-running' → both endpoints 404'd. MCP isn't
|
|
176
|
+
// active. Don't fall back to the compile-time port; the HMR
|
|
177
|
+
// `llui:mcp-ready` event fires if MCP starts later, and manual
|
|
178
|
+
// `window.__lluiConnect()` is the escape hatch.
|
|
176
179
|
});
|
|
177
180
|
}
|
|
178
181
|
else {
|
|
@@ -180,6 +183,36 @@ export function startRelay(port = 5200) {
|
|
|
180
183
|
connectRelay(port, true);
|
|
181
184
|
}
|
|
182
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Try the canonical Vite middleware path; if it 404s (Cloudflare
|
|
188
|
+
* plugin's catch-all routes everything to the worker), fall back to
|
|
189
|
+
* `/cdn-cgi/llui_mcp_status` which the Vite plugin also registers.
|
|
190
|
+
*
|
|
191
|
+
* Distinguishes "MCP not running" (404 from a real Vite server) from
|
|
192
|
+
* "no Vite server" (fetch threw). Callers handle these differently —
|
|
193
|
+
* the former should NOT fall back to the compile-time port (avoids
|
|
194
|
+
* spurious WS connection attempts), the latter SHOULD.
|
|
195
|
+
*/
|
|
196
|
+
async function resolveMcpStatus() {
|
|
197
|
+
let allThrew = true;
|
|
198
|
+
for (const path of ['/__llui_mcp_status', '/cdn-cgi/llui_mcp_status']) {
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch(path);
|
|
201
|
+
// We got a response — even a 404 means a server is live; don't
|
|
202
|
+
// treat this as a network error.
|
|
203
|
+
allThrew = false;
|
|
204
|
+
if (!res.ok)
|
|
205
|
+
continue;
|
|
206
|
+
const data = (await res.json());
|
|
207
|
+
if (typeof data.port === 'number')
|
|
208
|
+
return { kind: 'found', port: data.port };
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Network error on this path — try the next.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return allThrew ? { kind: 'network-error' } : { kind: 'not-running' };
|
|
215
|
+
}
|
|
183
216
|
function diffNodes(client, server, path, out) {
|
|
184
217
|
const clientAttrs = new Map(Array.from(client.attributes).map((a) => [a.name, a.value]));
|
|
185
218
|
const serverAttrs = new Map(Array.from(server.attributes).map((a) => [a.name, a.value]));
|