electrobun 1.18.4-beta.19 → 1.18.4-beta.21

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 (53) hide show
  1. package/dist/api/browser/ui/__tests__/dom.test.ts +473 -0
  2. package/dist/api/browser/ui/__tests__/domStub.ts +218 -0
  3. package/dist/api/browser/ui/dom.ts +490 -0
  4. package/dist/api/browser/ui/index.ts +44 -0
  5. package/dist/api/browser/ui/jsx-dev-runtime.ts +16 -0
  6. package/dist/api/browser/ui/jsx-runtime.ts +56 -0
  7. package/dist/api/config/ElectrobunConfig.ts +33 -0
  8. package/dist/api/preload/.generated/compiled.ts +1 -1
  9. package/dist/api/preload/index.ts +2 -0
  10. package/dist/api/preload/uiTag.ts +45 -0
  11. package/dist/api/sdks/main/__tests__/utils-quit-exit-code.test.ts +44 -0
  12. package/dist/api/sdks/main/core/GpuWindow.ts +19 -0
  13. package/dist/api/sdks/main/core/Utils.ts +52 -4
  14. package/dist/api/sdks/main/core/WGPUView.ts +9 -0
  15. package/dist/api/sdks/main/entries/ui.ts +1 -0
  16. package/dist/api/sdks/main/proc/native.ts +187 -0
  17. package/dist/api/sdks/main/ui/__tests__/font.test.ts +49 -0
  18. package/dist/api/sdks/main/ui/__tests__/hit.test.ts +64 -0
  19. package/dist/api/sdks/main/ui/__tests__/jsx.test.ts +252 -0
  20. package/dist/api/sdks/main/ui/__tests__/layout.test.ts +159 -0
  21. package/dist/api/sdks/main/ui/__tests__/paint.test.ts +115 -0
  22. package/dist/api/sdks/main/ui/__tests__/reactive.test.ts +456 -0
  23. package/dist/api/sdks/main/ui/__tests__/scroll-focus-input.test.ts +298 -0
  24. package/dist/api/sdks/main/ui/__tests__/tree.test.ts +96 -0
  25. package/dist/api/sdks/main/ui/__tests__/ui.test.ts +170 -0
  26. package/dist/api/sdks/main/ui/elements.ts +135 -0
  27. package/dist/api/sdks/main/ui/font.ts +168 -0
  28. package/dist/api/sdks/main/ui/hit.ts +46 -0
  29. package/dist/api/sdks/main/ui/index.ts +71 -0
  30. package/dist/api/sdks/main/ui/input.ts +268 -0
  31. package/dist/api/sdks/main/ui/jsx-dev-runtime.ts +16 -0
  32. package/dist/api/sdks/main/ui/jsx-runtime.ts +136 -0
  33. package/dist/api/sdks/main/ui/keymap.ts +147 -0
  34. package/dist/api/sdks/main/ui/layout.ts +178 -0
  35. package/dist/api/sdks/main/ui/paint.ts +196 -0
  36. package/dist/api/sdks/main/ui/reactive.ts +4 -0
  37. package/dist/api/sdks/main/ui/renderer.ts +278 -0
  38. package/dist/api/sdks/main/ui/text.ts +175 -0
  39. package/dist/api/sdks/main/ui/textInput.ts +121 -0
  40. package/dist/api/sdks/main/ui/tree.ts +276 -0
  41. package/dist/api/sdks/main/ui/ui.ts +457 -0
  42. package/dist/api/sdks/main/ui/uiTagHost.ts +56 -0
  43. package/dist/api/sdks/main/ui/uiwindow.ts +330 -0
  44. package/dist/api/shared/build-dependencies.test.ts +1 -1
  45. package/dist/api/shared/build-dependencies.ts +4 -4
  46. package/dist/api/shared/linux-webkit-automation.test.ts +1 -1
  47. package/dist/api/shared/warren/jsx.ts +279 -0
  48. package/dist/api/shared/warren/reactive.ts +638 -0
  49. package/dist/api/shared/windows-unicode-ui.test.ts +4 -4
  50. package/dist/preload-full.js +35 -0
  51. package/dist/zig-sdk/electrobun.zig +197 -162
  52. package/{dash.config.ts → hutch.config.ts} +3 -2
  53. package/package.json +11 -2
@@ -0,0 +1,490 @@
1
+ // Warren's DOM renderer — the browser/webview twin of the GPU retained-tree
2
+ // renderer. Same reactivity core, same JSX semantics (components run once,
3
+ // reactivity is explicit via live()), rendered into real DOM nodes.
4
+ //
5
+ // The renderer never enumerates HTML tags: any tag name becomes
6
+ // document.createElement(tag) (createElementNS inside <svg>), Solid-style.
7
+ // The only per-key knowledge is the small property-vs-attribute table below.
8
+ //
9
+ // Regions (dynamic, each) are delimited by comment anchors so conditional
10
+ // and keyed content can live inside any parent without wrapper elements.
11
+
12
+ import {
13
+ claimLive,
14
+ cleanup,
15
+ createChildScope,
16
+ createRoot,
17
+ disposeScope,
18
+ getOwner,
19
+ isLive,
20
+ liveScope,
21
+ runWithOwner,
22
+ signal,
23
+ type Accessor,
24
+ type LiveBinding,
25
+ } from "../../shared/warren/reactive";
26
+ import {
27
+ createJsxRuntime,
28
+ element,
29
+ type UIChild,
30
+ type UIElement,
31
+ type WarrenRenderer,
32
+ } from "../../shared/warren/jsx";
33
+
34
+ function bareFunctionError(key: string): never {
35
+ throw new Error(
36
+ `Warren: prop "${key}" received a bare function. Wrap reactive expressions with live(() => ...) from "electrobun/browser/ui", or pass a plain value.`,
37
+ );
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Insertion point — where created nodes attach. Building is synchronous, so
42
+ // a single ambient {parent, before} pair (insertBefore semantics) is enough.
43
+ // ---------------------------------------------------------------------------
44
+
45
+ interface InsertPoint {
46
+ parent: Node;
47
+ /** Insert before this node; null appends. */
48
+ before: Node | null;
49
+ /** Namespace for created elements ("" = HTML, else SVG etc.). */
50
+ ns: string;
51
+ }
52
+
53
+ let insert: InsertPoint | null = null;
54
+
55
+ function requireInsert(): InsertPoint {
56
+ if (!insert) {
57
+ throw new Error(
58
+ "Warren: no active mount — build DOM elements inside render(...)",
59
+ );
60
+ }
61
+ return insert;
62
+ }
63
+
64
+ function withInsert<T>(point: InsertPoint, fn: () => T): T {
65
+ const prev = insert;
66
+ insert = point;
67
+ try {
68
+ return fn();
69
+ } finally {
70
+ insert = prev;
71
+ }
72
+ }
73
+
74
+ function docOf(node: Node): Document {
75
+ return (node.ownerDocument ?? (node as unknown as Document)) as Document;
76
+ }
77
+
78
+ function insertNode(node: Node): void {
79
+ const point = requireInsert();
80
+ point.parent.insertBefore(node, point.before);
81
+ }
82
+
83
+ /**
84
+ * The element (or container) nodes are currently attaching into — escape
85
+ * hatch for imperative DOM code inside a bare-function child.
86
+ */
87
+ export function currentParent(): Node {
88
+ return requireInsert().parent;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Props: property-vs-attribute, class/style shapes, events, refs.
93
+ // ---------------------------------------------------------------------------
94
+
95
+ const SVG_NS = "http://www.w3.org/2000/svg";
96
+
97
+ /** Keys that must be set as element properties, not attributes. */
98
+ const PROPERTY_KEYS = new Set([
99
+ "value",
100
+ "checked",
101
+ "selected",
102
+ "muted",
103
+ "volume",
104
+ "srcObject",
105
+ "currentTime",
106
+ "indeterminate",
107
+ "innerHTML",
108
+ "innerText",
109
+ "textContent",
110
+ "scrollTop",
111
+ "scrollLeft",
112
+ ]);
113
+
114
+ const ATTR_ALIASES: Record<string, string> = {
115
+ className: "class",
116
+ htmlFor: "for",
117
+ };
118
+
119
+ function applyStyle(el: any, value: unknown): void {
120
+ if (value == null) {
121
+ el.removeAttribute?.("style");
122
+ return;
123
+ }
124
+ if (typeof value === "string") {
125
+ el.style.cssText = value;
126
+ return;
127
+ }
128
+ const style = el.style;
129
+ for (const [name, v] of Object.entries(value as Record<string, unknown>)) {
130
+ if (v == null || v === false) {
131
+ if (name.startsWith("--")) style.removeProperty(name);
132
+ else style[name] = "";
133
+ } else if (name.startsWith("--")) {
134
+ style.setProperty(name, String(v));
135
+ } else {
136
+ style[name] = String(v);
137
+ }
138
+ }
139
+ }
140
+
141
+ function applyClassList(el: any, value: unknown): void {
142
+ if (value == null) return;
143
+ for (const [name, on] of Object.entries(value as Record<string, unknown>)) {
144
+ // Multi-class keys ("a b") toggle each class, matching Solid.
145
+ for (const cls of name.split(/\s+/)) {
146
+ if (cls) el.classList.toggle(cls, !!on);
147
+ }
148
+ }
149
+ }
150
+
151
+ function applyProp(el: any, key: string, value: unknown): void {
152
+ if (key === "class" || key === "className") {
153
+ if (el.namespaceURI === SVG_NS) {
154
+ if (value == null) el.removeAttribute("class");
155
+ else el.setAttribute("class", String(value));
156
+ } else {
157
+ el.className = value == null ? "" : String(value);
158
+ }
159
+ return;
160
+ }
161
+ if (key === "classList") {
162
+ applyClassList(el, value);
163
+ return;
164
+ }
165
+ if (key === "style") {
166
+ applyStyle(el, value);
167
+ return;
168
+ }
169
+ if (PROPERTY_KEYS.has(key)) {
170
+ el[key] = value;
171
+ return;
172
+ }
173
+ const attr = ATTR_ALIASES[key] ?? key;
174
+ if (value == null || value === false) {
175
+ el.removeAttribute(attr);
176
+ } else if (value === true) {
177
+ el.setAttribute(attr, "");
178
+ } else {
179
+ el.setAttribute(attr, String(value));
180
+ }
181
+ }
182
+
183
+ function applyProps(el: any, props: Record<string, unknown>): void {
184
+ for (const [key, raw] of Object.entries(props)) {
185
+ if (raw === undefined) continue;
186
+ if (key === "ref") {
187
+ if (typeof raw !== "function") {
188
+ throw new Error("Warren: ref takes a function: ref={(el) => ...}");
189
+ }
190
+ raw(el);
191
+ continue;
192
+ }
193
+ if (key.startsWith("on") && key.length > 2) {
194
+ if (typeof raw !== "function") {
195
+ throw new Error(`Warren: handler "${key}" must be a function`);
196
+ }
197
+ el.addEventListener(key.slice(2).toLowerCase(), raw);
198
+ continue;
199
+ }
200
+ if (isLive(raw)) {
201
+ claimLive(raw as LiveBinding<unknown>, (v) => applyProp(el, key, v));
202
+ continue;
203
+ }
204
+ if (typeof raw === "function") bareFunctionError(key);
205
+ applyProp(el, key, raw);
206
+ }
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Renderer primitives
211
+ // ---------------------------------------------------------------------------
212
+
213
+ function createText(value: string | number | LiveBinding<string | number>): void {
214
+ const point = requireInsert();
215
+ const node = docOf(point.parent).createTextNode("");
216
+ if (isLive(value)) {
217
+ claimLive(value as LiveBinding<string | number>, (v) => {
218
+ node.data = v == null ? "" : String(v);
219
+ });
220
+ } else {
221
+ node.data = String(value);
222
+ }
223
+ insertNode(node);
224
+ }
225
+
226
+ /** Remove every node strictly between `start` and `end` (exclusive). */
227
+ function clearRange(parent: Node, start: Node, end: Node): void {
228
+ let node = start.nextSibling;
229
+ while (node && node !== end) {
230
+ const next = node.nextSibling;
231
+ parent.removeChild(node);
232
+ node = next;
233
+ }
234
+ }
235
+
236
+ function createDynamic(build: () => void): void {
237
+ const point = requireInsert();
238
+ const doc = docOf(point.parent);
239
+ const start = doc.createComment("warren");
240
+ const end = doc.createComment("/warren");
241
+ insertNode(start);
242
+ insertNode(end);
243
+ const parent = point.parent;
244
+ const ns = point.ns;
245
+ liveScope(() => {
246
+ clearRange(parent, start, end);
247
+ withInsert({ parent, before: end, ns }, build);
248
+ });
249
+ }
250
+
251
+ interface RowEntry {
252
+ scope: unknown;
253
+ start: Comment;
254
+ end: Comment;
255
+ setIndex: (i: number) => void;
256
+ }
257
+
258
+ /** Move the inclusive node range [entry.start, entry.end] before `before`. */
259
+ function moveRange(parent: Node, entry: RowEntry, before: Node | null): void {
260
+ let node: Node = entry.start;
261
+ for (;;) {
262
+ const next = node.nextSibling;
263
+ parent.insertBefore(node, before);
264
+ if (node === entry.end) break;
265
+ node = next!;
266
+ }
267
+ }
268
+
269
+ function removeRange(parent: Node, entry: RowEntry): void {
270
+ clearRange(parent, entry.start, entry.end);
271
+ parent.removeChild(entry.start);
272
+ parent.removeChild(entry.end);
273
+ }
274
+
275
+ function createEach<T>(
276
+ items: () => readonly T[],
277
+ key: (item: T, index: number) => string | number,
278
+ render: (item: T, index: Accessor<number>) => void,
279
+ ): void {
280
+ const point = requireInsert();
281
+ const doc = docOf(point.parent);
282
+ const start = doc.createComment("warren:each");
283
+ const end = doc.createComment("/warren:each");
284
+ insertNode(start);
285
+ insertNode(end);
286
+ const parent = point.parent;
287
+ const ns = point.ns;
288
+ const hostOwner = getOwner();
289
+ if (!hostOwner) {
290
+ throw new Error("Warren: each must be created inside a reactive root");
291
+ }
292
+ // Row scopes parent under the host scope (not the diff scope), so rows
293
+ // survive re-runs and are torn down with the region.
294
+ const entries = new Map<string | number, RowEntry>();
295
+ cleanup(() => entries.clear());
296
+
297
+ liveScope(() => {
298
+ const list = items();
299
+ const seen = new Set<string | number>();
300
+ let prevEnd: Node = start;
301
+
302
+ for (let i = 0; i < list.length; i++) {
303
+ const item = list[i]!;
304
+ const k = key(item, i);
305
+ if (seen.has(k)) {
306
+ throw new Error(`Warren: <For> duplicate key "${String(k)}"`);
307
+ }
308
+ seen.add(k);
309
+
310
+ let entry = entries.get(k);
311
+ if (!entry) {
312
+ const scope = createChildScope(hostOwner);
313
+ const [index, setIndex] = signal(i);
314
+ const rowStart = doc.createComment("row");
315
+ const rowEnd = doc.createComment("/row");
316
+ parent.insertBefore(rowStart, end);
317
+ parent.insertBefore(rowEnd, end);
318
+ // runWithOwner suspends tracking, so the diff scope never tracks
319
+ // reads made while building a row.
320
+ runWithOwner(scope, () =>
321
+ withInsert({ parent, before: rowEnd, ns }, () =>
322
+ render(item, index),
323
+ ),
324
+ );
325
+ entry = { scope, start: rowStart, end: rowEnd, setIndex };
326
+ entries.set(k, entry);
327
+ } else {
328
+ entry.setIndex(i);
329
+ }
330
+
331
+ // Keep sibling order in sync with item order.
332
+ if (prevEnd.nextSibling !== entry.start) {
333
+ moveRange(parent, entry, prevEnd.nextSibling);
334
+ }
335
+ prevEnd = entry.end;
336
+ }
337
+
338
+ // Remove rows whose keys are gone.
339
+ for (const [k, entry] of entries) {
340
+ if (!seen.has(k)) {
341
+ disposeScope(entry.scope);
342
+ removeRange(parent, entry);
343
+ entries.delete(k);
344
+ }
345
+ }
346
+ });
347
+ }
348
+
349
+ function createIntrinsic(type: string, props: Record<string, unknown>): void {
350
+ const point = requireInsert();
351
+ const doc = docOf(point.parent);
352
+ const ns = type === "svg" ? SVG_NS : point.ns;
353
+ const el =
354
+ ns !== ""
355
+ ? doc.createElementNS(ns, type)
356
+ : doc.createElement(type);
357
+ const { children, ...rest } = props;
358
+ applyProps(el, rest);
359
+ insertNode(el);
360
+ if (children !== undefined) {
361
+ // foreignObject re-enters HTML namespace.
362
+ const childNs = type === "foreignObject" ? "" : ns;
363
+ withInsert({ parent: el, before: null, ns: childNs }, () =>
364
+ mountChild(children as UIChild),
365
+ );
366
+ }
367
+ }
368
+
369
+ function createLiveChild(binding: LiveBinding<unknown>): void {
370
+ const point = requireInsert();
371
+ const doc = docOf(point.parent);
372
+ const start = doc.createComment("warren");
373
+ const end = doc.createComment("/warren");
374
+ insertNode(start);
375
+ insertNode(end);
376
+ const parent = point.parent;
377
+ const ns = point.ns;
378
+ liveScope(() => {
379
+ const value = binding.fn();
380
+ if (typeof value === "string" || typeof value === "number") {
381
+ // Primitive result: update a single text node in place instead of
382
+ // rebuilding the region.
383
+ const existing = start.nextSibling;
384
+ if (
385
+ existing !== null &&
386
+ existing !== end &&
387
+ existing.nodeType === 3 &&
388
+ existing.nextSibling === end
389
+ ) {
390
+ (existing as Text).data = String(value);
391
+ return;
392
+ }
393
+ clearRange(parent, start, end);
394
+ parent.insertBefore(doc.createTextNode(String(value)), end);
395
+ return;
396
+ }
397
+ clearRange(parent, start, end);
398
+ if (value == null || typeof value === "boolean") return;
399
+ withInsert({ parent, before: end, ns }, () =>
400
+ mountChild(value as UIChild),
401
+ );
402
+ });
403
+ }
404
+
405
+ const renderer: WarrenRenderer = {
406
+ text: createText,
407
+ liveChild: createLiveChild,
408
+ dynamic: createDynamic,
409
+ each: createEach,
410
+ intrinsic: createIntrinsic,
411
+ escape(fn) {
412
+ // A bare function child runs imperatively against the current parent;
413
+ // if it returns something mountable, mount it.
414
+ const result = fn();
415
+ if (result != null) mountChild(result as UIChild);
416
+ },
417
+ };
418
+
419
+ const runtime = createJsxRuntime(renderer);
420
+ export const jsx = runtime.jsx;
421
+ export const jsxs = runtime.jsxs;
422
+ export const Fragment = runtime.Fragment;
423
+ export const mountChild = runtime.mountChild;
424
+ export const Show = runtime.Show;
425
+ export const For = runtime.For;
426
+ export const Switch = runtime.Switch;
427
+ export { Match } from "../../shared/warren/jsx";
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // Mounting
431
+ // ---------------------------------------------------------------------------
432
+
433
+ /**
434
+ * Mount a Warren app into a DOM container. The container's existing content
435
+ * is left alone; everything Warren created is removed on dispose.
436
+ */
437
+ export function render(
438
+ app: () => UIElement | UIChild | void,
439
+ container: Element,
440
+ ): () => void {
441
+ const doc = docOf(container);
442
+ const start = doc.createComment("warren:root");
443
+ const end = doc.createComment("/warren:root");
444
+ container.appendChild(start);
445
+ container.appendChild(end);
446
+ let disposeRoot = () => {};
447
+ createRoot((dispose) => {
448
+ disposeRoot = dispose;
449
+ withInsert({ parent: container, before: end, ns: "" }, () => {
450
+ const result = app();
451
+ if (result !== undefined) mountChild(result as UIChild);
452
+ });
453
+ });
454
+ return () => {
455
+ disposeRoot();
456
+ clearRange(container, start, end);
457
+ container.removeChild(start);
458
+ container.removeChild(end);
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Render children into a different parent (dialogs, context menus) while
464
+ * keeping ownership — cleanup removes them when the owning scope goes away.
465
+ */
466
+ export function Portal(props: {
467
+ mount?: Element;
468
+ children?: UIChild;
469
+ }): UIElement {
470
+ return element(() => {
471
+ const point = requireInsert();
472
+ const target = props.mount ?? docOf(point.parent).body;
473
+ if (!target) {
474
+ throw new Error("Warren: <Portal> needs a mount element (no body)");
475
+ }
476
+ const doc = docOf(target);
477
+ const start = doc.createComment("warren:portal");
478
+ const end = doc.createComment("/warren:portal");
479
+ target.appendChild(start);
480
+ target.appendChild(end);
481
+ cleanup(() => {
482
+ clearRange(target, start, end);
483
+ target.removeChild(start);
484
+ target.removeChild(end);
485
+ });
486
+ withInsert({ parent: target, before: end, ns: "" }, () =>
487
+ mountChild(props.children),
488
+ );
489
+ });
490
+ }
@@ -0,0 +1,44 @@
1
+ // Warren for the browser — the DOM renderer. Same reactivity core and JSX
2
+ // semantics as electrobun/main/ui, rendered into real DOM nodes inside a
3
+ // webview (or any web page).
4
+
5
+ export {
6
+ batch,
7
+ cleanup,
8
+ createRoot,
9
+ inert,
10
+ isLive,
11
+ live,
12
+ memo,
13
+ setDevMode,
14
+ signal,
15
+ store,
16
+ type Accessor,
17
+ type LiveBinding,
18
+ type Reactive,
19
+ type Setter,
20
+ type StoreSetter,
21
+ } from "../../shared/warren/reactive";
22
+ export { isUIElement } from "../../shared/warren/jsx";
23
+ export type {
24
+ ForProps,
25
+ MatchProps,
26
+ ShowProps,
27
+ SwitchProps,
28
+ UIChild,
29
+ UIElement,
30
+ } from "../../shared/warren/jsx";
31
+ export {
32
+ For,
33
+ Fragment,
34
+ Match,
35
+ Portal,
36
+ Show,
37
+ Switch,
38
+ currentParent,
39
+ jsx,
40
+ jsxs,
41
+ mountChild,
42
+ render,
43
+ } from "./dom";
44
+ export type { DomProps, JSX } from "./jsx-runtime";
@@ -0,0 +1,16 @@
1
+ // Dev-transform entrypoint: same runtime, jsxDEV signature.
2
+ import { Fragment, jsx, type UIElement } from "./jsx-runtime";
3
+
4
+ export { Fragment };
5
+ export type { DomProps, JSX, UIChild, UIElement } from "./jsx-runtime";
6
+
7
+ export function jsxDEV(
8
+ type: Parameters<typeof jsx>[0],
9
+ props: Parameters<typeof jsx>[1],
10
+ key?: unknown,
11
+ _isStaticChildren?: boolean,
12
+ _source?: unknown,
13
+ _self?: unknown,
14
+ ): UIElement {
15
+ return jsx(type, props, key);
16
+ }
@@ -0,0 +1,56 @@
1
+ // JSX runtime for Warren's DOM renderer. Point tsconfig at it:
2
+ //
3
+ // { "jsx": "react-jsx", "jsxImportSource": "electrobun/browser/ui" }
4
+ //
5
+ // (Type-checking configuration only — Cottontail/Bun transpile .tsx natively.)
6
+ //
7
+ // Any HTML/SVG tag works: the runtime never enumerates elements, it calls
8
+ // document.createElement(tag). The IntrinsicElements typing below is
9
+ // deliberately open (Solid-style, types only).
10
+
11
+ import type { Reactive } from "../../shared/warren/reactive";
12
+ import type { UIChild, UIElement } from "../../shared/warren/jsx";
13
+ import { Fragment, jsx, jsxs } from "./dom";
14
+
15
+ export { Fragment, jsx, jsxs };
16
+ export type { UIChild, UIElement };
17
+
18
+ export interface DomProps {
19
+ children?: UIChild;
20
+ class?: Reactive<string>;
21
+ className?: Reactive<string>;
22
+ classList?: Reactive<Record<string, boolean | undefined>>;
23
+ style?: Reactive<string | Record<string, string | number | undefined>>;
24
+ id?: Reactive<string>;
25
+ ref?: (el: any) => void;
26
+ onClick?: (e: any) => void;
27
+ onInput?: (e: any) => void;
28
+ onChange?: (e: any) => void;
29
+ onKeyDown?: (e: any) => void;
30
+ onKeyUp?: (e: any) => void;
31
+ onPointerDown?: (e: any) => void;
32
+ onPointerUp?: (e: any) => void;
33
+ onPointerMove?: (e: any) => void;
34
+ onMouseDown?: (e: any) => void;
35
+ onMouseUp?: (e: any) => void;
36
+ onMouseEnter?: (e: any) => void;
37
+ onMouseLeave?: (e: any) => void;
38
+ onFocus?: (e: any) => void;
39
+ onBlur?: (e: any) => void;
40
+ onScroll?: (e: any) => void;
41
+ onWheel?: (e: any) => void;
42
+ onSubmit?: (e: any) => void;
43
+ onDblClick?: (e: any) => void;
44
+ onContextMenu?: (e: any) => void;
45
+ [key: string]: unknown;
46
+ }
47
+
48
+ export declare namespace JSX {
49
+ type Element = UIElement;
50
+ interface ElementChildrenAttribute {
51
+ children: {};
52
+ }
53
+ interface IntrinsicElements {
54
+ [tag: string]: DomProps;
55
+ }
56
+ }
@@ -173,6 +173,39 @@ export interface ElectrobunConfig {
173
173
  * @default "src/bun/index.ts"
174
174
  */
175
175
  entrypoint?: string;
176
+
177
+ /**
178
+ * Ship the main process as precompiled JavaScriptCore bytecode
179
+ * instead of (or alongside) source. Cottontail-only.
180
+ *
181
+ * > **Coming soon.** This option is declared but not yet wired —
182
+ * > setting it is currently a no-op (the app ships normal source).
183
+ * > Build-time bytecode/obfuscation lands as a fast-follow after
184
+ * > Electrobun 2.0; the underlying Cottontail/JSC support is already
185
+ * > in place.
186
+ *
187
+ * - `false` (default): ship JavaScript source. Normal builds.
188
+ * - `true`: compile the main process to bytecode for faster startup
189
+ * (skips parsing at launch). Source is still shipped as a fallback,
190
+ * so the app stays fully debuggable and `Function.prototype.toString`
191
+ * returns real source. Low risk — a version mismatch falls back to
192
+ * parsing the source.
193
+ * - `"obfuscate"`: bytecode **and** strip the source. Ships only
194
+ * bytecode, so the app's source is not distributed. Tradeoffs:
195
+ * `Function.prototype.toString` returns `[native code]` (can break
196
+ * libraries that introspect function source), error stacks lose
197
+ * source frames, no sourcemaps, and there is no source fallback
198
+ * (a runtime/version mismatch is a hard error rather than a reparse).
199
+ * This is source obfuscation, not encryption.
200
+ *
201
+ * Dev builds (`--env=dev`) ignore this and always ship source so
202
+ * sourcemaps, the debugger, and hot reload keep working. The
203
+ * bytecode is generated against the exact Cottontail bundled into
204
+ * this app and regenerated on every build.
205
+ *
206
+ * @default false
207
+ */
208
+ bytecode?: boolean | "obfuscate";
176
209
  } & BundlerOptions;
177
210
 
178
211
  /**