Cross-island communication#
Three tools, in order of preference: stores for shared state, events for notifications, the bridge for agent-side observation.
Shared state → stores#
If two islands need the same data, that data is a store — not synchronized copies:
export const Theme = store({
name: 'theme',
state: schema({ mode: enums(['light', 'dark']) }),
intents: { toggle: intent({ run: ({ state }) => (state.mode = state.mode === 'dark' ? 'light' : 'dark') }) },
});
// Any island: use: { theme: Theme } — writes update every reader, fine-grained.Notifications → events#
When islands need to react to something happening, not share data:
// Cart emits (payload is schema-validated):
emits: { 'cart.checkedOut': schema({ orderId: str(), total: money() }) },
run: ({ emit }) => emit('cart.checkedOut', { orderId, total }),
// A Toast island (or any other) listens:
on: {
'cart.checkedOut': ({ state, event }) => {
state.message = `Order ${event.orderId} confirmed ✔`;
},
},One bus per page: component events, store events and (soon) server events all fan out through it. Undeclared events throw at emit time.
Observation → the bridge#
Agents and page-level scripts subscribe without being components:
window.janux.subscribe('cart.checkedOut', (payload) => { ... });
document.addEventListener('janux:tool-call', ({ detail }) => {
// { tool, phase } — drive a glow effect over the island the agent is touching
});Choosing#
| Need | Use |
|---|---|
| Two islands render the same value | Store |
| Island B does something when A acts | Event |
| Chrome/copilot reacts to agent activity | janux:tool-call DOM event |
| Undo/audit/what-changed | You get it free: every mutation is a named intent |