electrobun-preact-devtools 0.0.0 → 0.1.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.
@@ -0,0 +1,92 @@
1
+ # Overview
2
+
3
+ `electrobun-preact-devtools` lets an Electrobun app run the Preact devtools UI in a separate desktop window.
4
+ It keeps Bun in the middle, installs `window.__PREACT_DEVTOOLS__` in the inspected app window, and mounts the vendored Preact devtools UI in a standalone window.
5
+
6
+ # When to Use
7
+
8
+ - Your application is built with Electrobun and uses Preact.
9
+ - You want a standalone devtools window instead of a browser extension.
10
+ - You already use Electrobun RPC and need a composition-friendly integration.
11
+ - You want to keep using normal app-side imports like `preact/debug` or `preact/devtools`.
12
+
13
+ # When Not to Use
14
+
15
+ - Your app runs in a browser and can use the normal Preact devtools extension.
16
+ - Your windows are sandboxed and cannot use Electrobun RPC.
17
+ - You need one devtools window to retarget across many app windows.
18
+ - You need framework support beyond Preact.
19
+
20
+ # Core Abstractions
21
+
22
+ - `PreactDevtoolsHost`: the Bun-side router that tracks targets and opens devtools windows.
23
+ - App bridge: the preload-installed `window.__PREACT_DEVTOOLS__` object in the inspected app window.
24
+ - Devtools window: the standalone Electrobun window that renders the vendored Preact devtools UI.
25
+ - Target: one inspected app window identified by a `targetId`.
26
+ - Reserved message channel: `preactDevtoolsMessage`, the package-owned Electrobun RPC message name.
27
+
28
+ # Data Flow / Lifecycle
29
+
30
+ 1. Bun creates a `PreactDevtoolsHost`.
31
+ 2. The app window is created with host-owned or host-composed RPC and is registered with a `targetId`.
32
+ 3. The app preload installs the bridge before the app imports `preact/debug` or `preact/devtools`.
33
+ 4. When the user opens devtools, the host creates a standalone devtools window and wires its RPC.
34
+ 5. The app bridge sends renderer events to Bun.
35
+ 6. Bun forwards those events to the devtools window.
36
+ 7. The devtools window updates its local store and sends UI commands back through Bun to the app bridge.
37
+
38
+ # Common Tasks -> Recommended APIs
39
+
40
+ - New integration with package-owned RPC:
41
+ Use `createPreactDevtoolsHost`, `host.createAppWindowRPC`, `composeAppWindowViewRPC`, `installPreactDevtoolsBridge`, `composeDevtoolsWindowViewRPC`, and `mountPreactDevtoolsWindow`.
42
+ - Existing app-window RPC:
43
+ Use `composeAppWindowRPC` in Bun and `composeAppWindowViewRPC` in the preload.
44
+ - Existing devtools-window RPC:
45
+ Use `composeDevtoolsWindowRPC` in Bun and `composeDevtoolsWindowViewRPC` in the devtools window.
46
+ - Logging package lifecycle issues:
47
+ Pass `logger` to `createPreactDevtoolsHost`, `installPreactDevtoolsBridge`, or `mountPreactDevtoolsWindow`.
48
+
49
+ # Recommended Patterns
50
+
51
+ - Create one `PreactDevtoolsHost` for the app process.
52
+ - Install the app bridge in preload before any Preact devtools import runs.
53
+ - Use the composition helpers when a window already has its own Electrobun RPC handlers.
54
+ - Treat `preactDevtoolsMessage` as reserved package transport, not application RPC.
55
+ - Keep one devtools window per target in v1.
56
+
57
+ # Patterns to Avoid
58
+
59
+ - Installing the bridge after the application has already imported `preact/debug`.
60
+ - Reusing `preactDevtoolsMessage` for unrelated application messages.
61
+ - Expecting direct browser-to-browser communication between the app window and the devtools window.
62
+ - Opening devtools for a target that has not been registered with the host.
63
+
64
+ # Invariants and Constraints
65
+
66
+ - Both the app window and the devtools window must have Electrobun RPC enabled.
67
+ - The package owns exactly one reserved message channel per window schema.
68
+ - Bun is the only supported relay between the inspected app window and the devtools window.
69
+ - The vendored `preact-devtools` snapshot defines the supported upstream bridge and UI behavior.
70
+
71
+ # Error Model
72
+
73
+ - `installPreactDevtoolsBridge` throws if the chosen global name is already occupied by a different value.
74
+ - `host.registerAppWindow(...)` throws if the same `targetId` is registered to a different window.
75
+ - `host.open(targetId)` throws if the target was never registered or the host has already been disposed.
76
+ - Composition helpers throw if consumer handlers try to claim the reserved `preactDevtoolsMessage` name.
77
+ - Missing RPC transports do not throw immediately in browser code; the package logs a warning through the optional logger.
78
+
79
+ # Terminology
80
+
81
+ - Target: one inspected application window.
82
+ - App bridge: the preload-side hook installation in the inspected window.
83
+ - Devtools window: the standalone UI window.
84
+ - Host: the Bun-side controller.
85
+ - Session: the pairing between one target and one devtools window.
86
+
87
+ # Non-Goals
88
+
89
+ - Emulating Chrome DevTools or browser-extension panels.
90
+ - Inspecting non-Preact applications.
91
+ - Supporting sandboxed windows in v1.
92
+ - Providing multi-target retargeting from one devtools window in v1.
@@ -0,0 +1,145 @@
1
+ import type { BrowserWindow, RPCSchema } from 'electrobun/bun'
2
+ import { Electroview } from 'electrobun/view'
3
+ import {
4
+ composeAppWindowRPC,
5
+ composeDevtoolsWindowRPC,
6
+ createPreactDevtoolsHost,
7
+ type PreactDevtoolsHost,
8
+ } from 'electrobun-preact-devtools/bun'
9
+ import {
10
+ composeAppWindowViewRPC,
11
+ installPreactDevtoolsBridge,
12
+ } from 'electrobun-preact-devtools/app-preload'
13
+ import {
14
+ composeDevtoolsWindowViewRPC,
15
+ mountPreactDevtoolsWindow,
16
+ } from 'electrobun-preact-devtools/devtools-view'
17
+ import type {
18
+ WithPreactDevtoolsAppWindowRPC,
19
+ WithPreactDevtoolsDevtoolsWindowRPC,
20
+ } from 'electrobun-preact-devtools/shared'
21
+
22
+ export type AppWindowRPC = WithPreactDevtoolsAppWindowRPC<{
23
+ bun: RPCSchema<{
24
+ requests: {
25
+ openFile: {
26
+ params: { path: string }
27
+ response: string
28
+ }
29
+ }
30
+ messages: {
31
+ logAnalytics: { event: string }
32
+ }
33
+ }>
34
+ webview: RPCSchema<{
35
+ requests: {
36
+ getTheme: {
37
+ params: {}
38
+ response: 'light' | 'dark'
39
+ }
40
+ }
41
+ messages: {
42
+ themeChanged: { theme: 'light' | 'dark' }
43
+ }
44
+ }>
45
+ }>
46
+
47
+ export type DevtoolsWindowRPC = WithPreactDevtoolsDevtoolsWindowRPC<{
48
+ bun: RPCSchema<{
49
+ requests: {}
50
+ messages: {
51
+ trackWindowMetric: { metric: string; value: number }
52
+ }
53
+ }>
54
+ webview: RPCSchema<{
55
+ requests: {}
56
+ messages: {
57
+ setWindowChrome: { title: string }
58
+ }
59
+ }>
60
+ }>
61
+
62
+ declare const appWindow: BrowserWindow
63
+ declare const devtoolsWindow: BrowserWindow
64
+ declare const container: HTMLElement
65
+
66
+ export const host = createPreactDevtoolsHost({
67
+ openWindow() {
68
+ return devtoolsWindow
69
+ },
70
+ })
71
+
72
+ export function registerExistingAppWindow(currentHost: PreactDevtoolsHost) {
73
+ currentHost.registerAppWindow({
74
+ targetId: 'main',
75
+ window: appWindow,
76
+ })
77
+ }
78
+
79
+ export function createAppWindowRPC(currentHost: PreactDevtoolsHost) {
80
+ return composeAppWindowRPC<AppWindowRPC>(currentHost, {
81
+ targetId: 'main',
82
+ handlers: {
83
+ requests: {
84
+ openFile: async ({ path }) => path,
85
+ },
86
+ messages: {
87
+ logAnalytics: ({ event }) => {
88
+ console.log('analytics', event)
89
+ },
90
+ },
91
+ },
92
+ })
93
+ }
94
+
95
+ export const appWindowViewRPC = composeAppWindowViewRPC<AppWindowRPC>({
96
+ handlers: {
97
+ requests: {
98
+ getTheme: () => 'dark',
99
+ },
100
+ messages: {
101
+ themeChanged: ({ theme }) => {
102
+ document.documentElement.dataset.theme = theme
103
+ },
104
+ },
105
+ },
106
+ })
107
+
108
+ export function installComposedBridge() {
109
+ const electroview = new Electroview({
110
+ rpc: appWindowViewRPC,
111
+ })
112
+
113
+ return installPreactDevtoolsBridge({ electroview })
114
+ }
115
+
116
+ export function createStandaloneDevtoolsRPC(currentHost: PreactDevtoolsHost) {
117
+ return composeDevtoolsWindowRPC<DevtoolsWindowRPC>(currentHost, {
118
+ targetId: 'main',
119
+ handlers: {
120
+ messages: {
121
+ trackWindowMetric: ({ metric, value }) => {
122
+ console.log(metric, value)
123
+ },
124
+ },
125
+ },
126
+ })
127
+ }
128
+
129
+ export const devtoolsWindowViewRPC = composeDevtoolsWindowViewRPC<DevtoolsWindowRPC>({
130
+ handlers: {
131
+ messages: {
132
+ setWindowChrome: ({ title }) => {
133
+ document.title = title
134
+ },
135
+ },
136
+ },
137
+ })
138
+
139
+ export function mountComposedDevtools() {
140
+ const electroview = new Electroview({
141
+ rpc: devtoolsWindowViewRPC,
142
+ })
143
+
144
+ return mountPreactDevtoolsWindow({ container, electroview })
145
+ }
@@ -0,0 +1,52 @@
1
+ import { BrowserWindow } from 'electrobun/bun'
2
+ import { Electroview } from 'electrobun/view'
3
+ import { createPreactDevtoolsHost } from 'electrobun-preact-devtools/bun'
4
+ import {
5
+ composeAppWindowViewRPC,
6
+ installPreactDevtoolsBridge,
7
+ } from 'electrobun-preact-devtools/app-preload'
8
+ import {
9
+ composeDevtoolsWindowViewRPC,
10
+ mountPreactDevtoolsWindow,
11
+ } from 'electrobun-preact-devtools/devtools-view'
12
+
13
+ export const host = createPreactDevtoolsHost({
14
+ openWindow({ rpc }) {
15
+ return new BrowserWindow({
16
+ title: 'Preact Devtools',
17
+ url: 'views://preact-devtools/index.html',
18
+ rpc,
19
+ })
20
+ },
21
+ })
22
+
23
+ export const appWindow = new BrowserWindow({
24
+ title: 'My App',
25
+ url: 'views://app/index.html',
26
+ preload: 'views://app/preload.js',
27
+ rpc: host.createAppWindowRPC({ targetId: 'main' }),
28
+ })
29
+
30
+ host.registerAppWindow({
31
+ targetId: 'main',
32
+ window: appWindow,
33
+ })
34
+
35
+ export function installBridgeInAppPreload() {
36
+ const electroview = new Electroview({
37
+ rpc: composeAppWindowViewRPC({}),
38
+ })
39
+
40
+ return installPreactDevtoolsBridge({ electroview })
41
+ }
42
+
43
+ export function mountStandaloneDevtools(container: HTMLElement) {
44
+ const electroview = new Electroview({
45
+ rpc: composeDevtoolsWindowViewRPC({}),
46
+ })
47
+
48
+ return mountPreactDevtoolsWindow({
49
+ container,
50
+ electroview,
51
+ })
52
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "include": ["./"],
4
+ "exclude": ["../src/vendor/preact-devtools"],
5
+ "compilerOptions": {
6
+ "baseUrl": "..",
7
+ "ignoreDeprecations": "6.0",
8
+ "paths": {
9
+ "electrobun-preact-devtools": ["./dist/index.d.mts"],
10
+ "electrobun-preact-devtools/shared": ["./dist/index.d.mts"],
11
+ "electrobun-preact-devtools/bun": ["./dist/bun.d.mts"],
12
+ "electrobun-preact-devtools/app-preload": ["./dist/app-preload.d.mts"],
13
+ "electrobun-preact-devtools/devtools-view": ["./dist/devtools-view.d.mts"]
14
+ }
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,13 +1,78 @@
1
1
  {
2
2
  "name": "electrobun-preact-devtools",
3
- "version": "0.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
3
+ "version": "0.1.0",
11
4
  "license": "MIT",
12
- "type": "commonjs"
13
- }
5
+ "author": "Alec Larson",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/aleclarson/electrobun-preact-devtools.git"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "docs",
13
+ "examples"
14
+ ],
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.mts",
19
+ "default": "./dist/index.mjs"
20
+ },
21
+ "./shared": {
22
+ "types": "./dist/index.d.mts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "./bun": {
26
+ "types": "./dist/bun.d.mts",
27
+ "default": "./dist/bun.mjs"
28
+ },
29
+ "./app-preload": {
30
+ "types": "./dist/app-preload.d.mts",
31
+ "default": "./dist/app-preload.mjs"
32
+ },
33
+ "./devtools-view": {
34
+ "types": "./dist/devtools-view.d.mts",
35
+ "default": "./dist/devtools-view.mjs"
36
+ }
37
+ },
38
+ "dependencies": {
39
+ "@preact/signals": "2.9.0",
40
+ "@preact/signals-core": "^1.14.1",
41
+ "errorstacks": "2.4.1"
42
+ },
43
+ "devDependencies": {
44
+ "@tsdown/css": "^0.21.7",
45
+ "@types/node": "^25.6.0",
46
+ "@types/three": "^0.183.1",
47
+ "degit": "^2.8.4",
48
+ "electrobun": "1.16.0",
49
+ "oxfmt": "^0.44.0",
50
+ "oxlint": "^1.59.0",
51
+ "preact": "10.29.1",
52
+ "tsdown": "^0.21.7",
53
+ "tsnapi": "^0.1.1",
54
+ "typescript": "^6.0.2",
55
+ "vitest": "^4.1.4"
56
+ },
57
+ "peerDependencies": {
58
+ "electrobun": "*",
59
+ "preact": "*"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "electrobun": {
63
+ "optional": false
64
+ },
65
+ "preact": {
66
+ "optional": false
67
+ }
68
+ },
69
+ "scripts": {
70
+ "dev": "tsdown --sourcemap --watch",
71
+ "build": "tsdown",
72
+ "format": "oxfmt .",
73
+ "lint": "oxlint src",
74
+ "typecheck": "tsc --noEmit && tsc -p test --noEmit",
75
+ "check:examples": "tsc -p examples --noEmit",
76
+ "test": "vitest"
77
+ }
78
+ }
package/readme.md DELETED
@@ -1 +0,0 @@
1
- Coming soon...