diff --git a/docs/guide/build-your-own-hub-ui.md b/docs/guide/build-your-own-hub-ui.md
index 83258683..f1b582ed 100644
--- a/docs/guide/build-your-own-hub-ui.md
+++ b/docs/guide/build-your-own-hub-ui.md
@@ -16,6 +16,7 @@ interface DevframeHubUi {
viewer?: { distDir: string } // a standalone SPA served at the hub base
embedded?: { entry: string } // a self-contained bootstrap at embedded.js
assets?: Record string | Uint8Array> // extra UI-owned files
+ setup?: (ctx) => void | Promise // publish static config via ctx.staticConfig
}
```
@@ -24,6 +25,14 @@ prebuilt assets: the viewer SPA is built with relative asset paths, and the
embedded entry is one self-contained ES module that mounts your dock into any
host page.
+`setup(ctx)` runs once during hub init — write your static, boot-time config
+to `ctx.staticConfig`, which is serialized into `ConnectionMeta.configs` and
+read by the client from the one connection handshake it already performs. The
+reference UI's `createUi({ branding })` uses it to set
+`ctx.staticConfig.ui = { branding, … }`; the hub never interprets what you
+write. It's the structured, read-only counterpart to `assets` (arbitrary
+served files).
+
## The client contracts
A viewer renders from the hub's shared state and drives it through
diff --git a/docs/guide/devframe-definition.md b/docs/guide/devframe-definition.md
index e389d7e4..f9500deb 100644
--- a/docs/guide/devframe-definition.md
+++ b/docs/guide/devframe-definition.md
@@ -113,6 +113,7 @@ interface DevframeNodeContext {
diagnostics: DevframeDiagnosticsHost
agent: DevframeAgentHost // experimental
services: DevframeServicesHost // typed cross-plugin service registry
+ staticConfig: Partial // this context's own ConnectionMeta.configs
scope: (id) => DevframeScopedNodeContext // namespaced view (preferred)
}
@@ -130,6 +131,22 @@ ctx.services.whenAvailable('my-plugin:sources', (sources) => {
})
```
+### Static connection configs
+
+`ctx.staticConfig` is this context's own `ConnectionMeta.configs` — static, boot-time data delivered once through the connection handshake every client already performs, and read-only from the browser. It's a plain, **non-reactive** object: write it during `setup(ctx)`, never during the session (it's serialized once, after setup). Contrast it with `ctx.scope(id).settings`, which is mutable and synced bidirectionally over shared-state RPC for the life of the session.
+
+```ts
+declare module 'devframe/types' {
+ interface DevframeConnectionConfigsRegistry {
+ 'my-plugin': { featureFlag: boolean }
+ }
+}
+
+ctx.staticConfig['my-plugin'] = { featureFlag: true }
+```
+
+`updater` receives whatever's been contributed to that key so far (or `undefined` on the first contribution), so multiple contributors sharing a key — a hub aggregating each installed devframe's own preference, for example — own their own merge semantics (overwrite, shallow-merge a record, …) rather than the host imposing one.
+
### Storage scopes
`ctx.host.getStorageDir(scope)` places persisted state in one of three classes:
@@ -152,6 +169,7 @@ Each devframe-level host has a dedicated page:
- [Shared State](./shared-state) — `ctx.rpc.sharedState`
- [Diagnostics](./diagnostics) — `ctx.diagnostics`
- [Agent-Native](./agent-native) — `ctx.agent`
+- [Cross-Plugin Services](./services) — `ctx.services`
## Browser setup
diff --git a/docs/guide/hub-initiate.md b/docs/guide/hub-initiate.md
index 2ae4ab67..9499a080 100644
--- a/docs/guide/hub-initiate.md
+++ b/docs/guide/hub-initiate.md
@@ -56,10 +56,27 @@ The hub is headless — `DevframeHubUi` is pure data, and whoever fills it decid
interface DevframeHubUi {
viewer?: { distDir: string } // a standalone SPA served at the namespace root
embedded?: { entry: string } // a prebuilt bootstrap served at embedded.js
+ assets?: Record string | Uint8Array> // extra UI-owned files
+ setup?: (ctx) => void | Promise // publish static config via ctx.staticConfig
}
```
-`@devframes/hub-ui`'s `createUi()` is the reference implementation: a standalone viewer plus the floating dock — one `