Skip to content

Commit bbea719

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/dock-group-shortcuts
# Conflicts: # docs/content/8.references/6.hub-api.md
2 parents e22596d + f2632b2 commit bbea719

168 files changed

Lines changed: 7683 additions & 2286 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,38 @@ jobs:
1818
lint: pnpm run lint && pnpm run knip
1919
build-for-lint: true
2020

21+
# The RPC transport binds a native WebSocket on Bun/Deno (crossws's
22+
# Bun/Deno adapters over `Bun.serve` / `Deno.serve`) and falls back to SSE
23+
# for a shared foreign `node:http` server. This runs the cross-runtime smoke
24+
# test under each runtime so that binding can't regress (issue #317).
25+
runtime:
26+
runs-on: ubuntu-latest
27+
timeout-minutes: 15
28+
strategy:
29+
fail-fast: false
30+
matrix:
31+
runtime: [bun, deno]
32+
steps:
33+
- uses: actions/checkout@v7
34+
- uses: pnpm/action-setup@v6
35+
- uses: actions/setup-node@v7
36+
with:
37+
node-version: 22
38+
cache: pnpm
39+
- name: Set up Bun
40+
if: matrix.runtime == 'bun'
41+
uses: oven-sh/setup-bun@v2
42+
- name: Set up Deno
43+
if: matrix.runtime == 'deno'
44+
uses: denoland/setup-deno@v2
45+
with:
46+
deno-version: v2.x
47+
- run: pnpm install --frozen-lockfile
48+
- name: Build devframe
49+
run: pnpm --filter devframe run build
50+
- name: Run the ${{ matrix.runtime }} RPC transport smoke test
51+
run: pnpm run test:runtime:${{ matrix.runtime }}
52+
2153
e2e:
2254
runs-on: ubuntu-latest
2355
timeout-minutes: 15

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const alias = {
3434
'devframe/utils/nanoid': r('devframe/src/utils/nanoid.ts'),
3535
'devframe/utils/nostics': r('devframe/src/utils/nostics.ts'),
3636
'devframe/utils/open': r('devframe/src/utils/open.ts'),
37+
'devframe/utils/origin': r('devframe/src/utils/origin.ts'),
3738
'devframe/utils/remote-assets': r('devframe/src/utils/remote-assets.ts'),
3839
'devframe/utils/simple-schema': r('devframe/src/utils/simple-schema.ts'),
3940
'devframe/utils/serve-static': r('devframe/src/utils/serve-static.ts'),

docs/app/app.config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ export default defineAppConfig({
99
title: 'Devframe',
1010
logo: {
1111
alt: 'Devframe',
12-
light: '/logo.svg',
13-
dark: '/logo.svg',
12+
mark: 'devframe',
1413
},
1514
nav: [
1615
{

docs/app/components/LogoMark.vue

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
name?: string
4+
}>()
5+
</script>
6+
7+
<template>
8+
<svg class="h-6 w-auto" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
9+
<g filter="url(#filter0_d_8_208)">
10+
<path d="M238.464 40H66V236.007H104.577C220.688 236.007 232.79 158.437 232.79 121.733C232.79 108.867 243.38 108.867 243.38 121.733C251.318 236.701 356.35 245.457 365.527 246.223L365.542 246.224C374.619 246.981 382.561 251.521 365.542 254.548C253.592 264.765 243.758 364.282 243.38 375.634C243.002 386.986 235.438 399.851 232.79 375.634C222.957 274.603 144.289 257.954 104.577 257.954H66V460.772H238.464C358.356 460.772 452.152 371.472 452.152 251.521C452.152 131.571 356.465 40 238.464 40Z" fill="url(#paint0_radial_8_208)" />
11+
<path d="M238.464 40H66V236.007H104.577C220.688 236.007 232.79 158.437 232.79 121.733C232.79 108.867 243.38 108.867 243.38 121.733C251.318 236.701 356.35 245.457 365.527 246.223L365.542 246.224C374.619 246.981 382.561 251.521 365.542 254.548C253.592 264.765 243.758 364.282 243.38 375.634C243.002 386.986 235.438 399.851 232.79 375.634C222.957 274.603 144.289 257.954 104.577 257.954H66V460.772H238.464C358.356 460.772 452.152 371.472 452.152 251.521C452.152 131.571 356.465 40 238.464 40Z" stroke="#86997F" stroke-opacity="0.5" stroke-width="1.33156" />
12+
</g>
13+
<defs>
14+
<filter id="filter0_d_8_208" x="60.7404" y="39.3342" width="396.671" height="431.358" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
15+
<feFlood flood-opacity="0" result="BackgroundImageFix" />
16+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
17+
<feOffset dy="4.66045" />
18+
<feGaussianBlur stdDeviation="2.29694" />
19+
<feComposite in2="hardAlpha" operator="out" />
20+
<feColorMatrix type="matrix" values="0 0 0 0 0.25464 0 0 0 0 0.285175 0 0 0 0 0.243391 0 0 0 0.1 0" />
21+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_8_208" />
22+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_8_208" result="shape" />
23+
</filter>
24+
<radialGradient id="paint0_radial_8_208" cx="0" cy="0" r="1" gradientTransform="matrix(399.011 555.102 -568.423 408.982 -11.6221 -34.9218)" gradientUnits="userSpaceOnUse">
25+
<stop stop-color="#ADC77F" />
26+
<stop offset="0.71903" stop-color="#517158" />
27+
<stop offset="1" stop-color="#486954" />
28+
</radialGradient>
29+
</defs>
30+
</svg>
31+
</template>

docs/content/1.guide/12.in-page-channel.md

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,29 @@ Channel names are namespaced with the devframe id, like RPC ids. Function names
5454

5555
## The page script endpoint
5656

57-
Functions are defined with `defineChannelFunction`the same authoring shape as `defineRpcFunction` (`name`, `type`, Standard-Schema `args`/`returns`, `jsonSerializable`, `handler`), narrowed to the browser. Define each side's functions in that side's source files; the shared protocol file carries only types.
57+
Functions use the same authoring metadata as `defineRpcFunction` (`type`, Standard-Schema `args`/`returns`, `jsonSerializable`, `handler`), narrowed to the browser. The required `functions` object's keys are the function names, and it implements every function on that endpoint's protocol side. Each handler is contextually typed from its key and the corresponding function in the protocol. `defineChannelFunction` retains the named definition shape for lower-level authoring. Define each side's functions in that side's source files; the shared protocol file carries only types.
5858

5959
```ts
6060
import type { MyChannelProtocol } from '../shared/protocol'
6161
// inject/index.ts — runs in the user app's page
62-
import { createPageScriptChannel, defineChannelFunction } from 'devframe/in-page-channel'
62+
import { createPageScriptChannel } from 'devframe/in-page-channel'
6363
import { MY_CHANNEL } from '../shared/protocol'
6464

6565
const channel = createPageScriptChannel<MyChannelProtocol>({
6666
name: MY_CHANNEL,
67-
functions: [
68-
defineChannelFunction({
69-
name: 'highlight',
67+
functions: {
68+
highlight: {
7069
type: 'event', // fire-and-forget
7170
jsonSerializable: true,
72-
handler: (selector: string) => drawRing(document.querySelector(selector)),
73-
}),
74-
defineChannelFunction({
75-
name: 'measure', // request/response (the default `query` type)
76-
handler: (selector: string) => {
71+
handler: selector => drawRing(document.querySelector(selector)),
72+
},
73+
measure: { // request/response (the default `query` type)
74+
handler: (selector) => {
7775
const rect = document.querySelector(selector)!.getBoundingClientRect()
7876
return { width: rect.width, height: rect.height }
7977
},
80-
}),
81-
],
78+
},
79+
},
8280
})
8381

8482
channel.callEvent('flash', 'scanning…') // fans out to every connected panel
@@ -96,7 +94,14 @@ import type { MyChannelProtocol } from '../shared/protocol'
9694
import { connectPanelChannel } from 'devframe/in-page-channel'
9795
import { MY_CHANNEL } from '../shared/protocol'
9896

99-
const channel = connectPanelChannel<MyChannelProtocol>({ name: MY_CHANNEL })
97+
const channel = connectPanelChannel<MyChannelProtocol>({
98+
name: MY_CHANNEL,
99+
functions: {
100+
flash: {
101+
handler: message => showFlash(message),
102+
},
103+
},
104+
})
100105

101106
channel.callEvent('highlight', '.hero') // buffered until connected
102107
const size = await channel.call('measure', '.hero')

docs/content/1.guide/14.security.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Devtools ready — authenticate this browser: http://localhost:3000/#devframe_ot
6767

6868
The code rides the URL **fragment** (`#devframe_otp=…`), which browsers never send to the server, keeping the single-use code out of access logs and `Referer` headers. `connectDevframe` reads it, exchanges it, and strips it from the URL. Because the link grants trust to whoever opens it within the code's lifetime, print it only to a trusted channel (the terminal).
6969

70+
The link points at the **public origin**. A standalone dev server derives it from its own bound address; an owned listener uses that address regardless of any inbound `Host` header. A handler or middleware without an explicit `origin` derives one from a request only when the request's own origin is loopback or exactly matches an `allowedOrigins` entry — a raw inbound authority and forwarded headers are never trusted. Set `origin` explicitly for non-loopback handler deployments (behind a proxy, on a LAN, or on a public host) so the magic link always resolves to the address you intend.
71+
7072
For your own auth UI, disable built-in handling with `otpParam: false`, then call `authenticateWithUrlOtp(rpc)` or `consumeOtpFromUrl()` from `devframe/client`.
7173

7274
## Practices for tools built on devframe

docs/content/1.guide/17.client-context.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ The context carries the [RPC client](/guide/client) (`rpc`) and the page's `clie
3636

3737
`getDevframeClientContext()` returns the context anywhere; `undefined` before boot.
3838

39+
### Tracking panel state
40+
41+
`ctx.panel.state` is the current dock panel snapshot. It contains `state: 'open' | 'closed' | 'hidden'` and includes `selectedDockId` while a dock is selected. Subscribe to [`ctx.panel.events`](/references/events#client-context-events) for later changes:
42+
43+
```ts
44+
import type { DockClientScriptContext } from '@devframes/hub/client'
45+
import { HUB_EVENTS } from '@devframes/hub/constants'
46+
47+
export default function setup(context: DockClientScriptContext) {
48+
const reportPanelState = (panelState: typeof context.panel.state) => {
49+
void context.rpc.call('my-devframe:panel-state', panelState).catch(error => console.error(error))
50+
}
51+
52+
reportPanelState(context.panel.state)
53+
context.panel.events.on(
54+
HUB_EVENTS.client.docksPanelStateChanged,
55+
reportPanelState,
56+
)
57+
}
58+
```
59+
60+
The custom RPC keeps node-side reporting opt-in.
61+
3962
### Client-only docks
4063

4164
A client runtime can register a dock local to the host page (unlike [node hub context](/guide/hub) docks synced via `devframe:docks`). `ctx.docks.register(entry)` — e.g. `type: 'custom-render'` with `renderer: { importFrom }` — returns a handle whose `update({ badge })` patches in place (id immutable) and `dispose()` removes it. One sharing a server dock's id overrides it locally; re-registering an owned id throws unless you pass `register(entry, true)`.

docs/content/1.guide/18.hub-initiate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface DevframeHubUi {
5353
`@devframes/hub-ui`'s `createUi()` is the reference (standalone `viewer` SPA + floating dock); its `setup(ctx)` publishes config to `ctx.staticConfig.ui` (`ConnectionMeta.configs.ui`):
5454

5555
- **`viewer`** — set to `false` to disable the standalone viewer.
56-
- **`branding`** — rebrand the UI (logo, name, primary color). `background` accepts any CSS `background` value (color, gradient, image, or `transparent`) or `{ light, dark }` variants; omit it to keep the design default.
56+
- **`branding`** — rebrand the UI (logo, name, primary color). `background` accepts any CSS `background` value (color, gradient, image, or `transparent`) or `{ light, dark }` variants. These flat forms apply everywhere. Use `{ standalone, iframe? }` to specialize the framed viewer; an omitted `iframe` value falls back to `standalone`.
5757
- **`dockPreferences`** — dock-rail: `categoryOrder`, floating-dock `maxVisibleItems`, first-run `defaultMode` (`'float'`/`'edge'`) and `defaultPosition`.
5858
- **`embeddedVisibility`** — the floating dock's reveal policy:
5959
- `'normal'` (default) — shows immediately.

docs/content/2.adapters/1.initiate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Fetch handlers only hand over `Request`s, so the host framework binds the RPC so
129129

130130
## Auth
131131

132-
The running devframe **gates by default**. The interactive OTP handler wires automatically, printing its code/magic-link banner once the public origin is known (the first request, or the `origin` option). Pass `auth: false` for single-user localhost, or a `DevframeAuthHandler` for a custom scheme.
132+
The running devframe **gates by default**. The interactive OTP handler wires automatically, printing its code/magic-link banner once the public origin is known — from the `origin` option, or derived from a request whose own origin is loopback or exactly matches an `allowedOrigins` entry. A non-loopback deployment (behind a proxy, on a LAN, on a public host) sets `origin` explicitly so the magic link resolves to the intended address; a raw inbound `Host` header and forwarded headers are never trusted. Pass `auth: false` for single-user localhost, or a `DevframeAuthHandler` for a custom scheme.
133133

134134
## Relation to the other adapters
135135

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
title: Devframes

0 commit comments

Comments
 (0)