# Pubsub `pub/*` is Deft's inter-runtime message bus. Any runtime can publish to a topic; any runtime can subscribe. The broker is shared across the runtime hierarchy, so messages flow between the scripting binary, child runtimes, and TUI app panels transparently. Pubsub is the primary loose-coupling mechanism in Deft. It's always available — in the standalone `deft` binary, the ANSI binary, and the full TUI. ## The Broker A single `PubSub` broker lives on the root runtime and is shared with all children. Subscriptions are registered with the broker; publishes dispatch through it. - **Same-runtime delivery**: zero-copy — the subscriber receives the original Value directly. - **Cross-runtime delivery**: the message is serialised via `deft/stringify`, queued on the target runtime's thread-safe message queue, and parsed back via `deft/parse` on delivery. Either way, the handler sees a consistent event map shape. ## `pub/*` Stdlib functions | Function | Returns | Purpose | |---|---|---| | `[pub/subscribe "topic" "handler-name"]` | bool | Subscribe a named handler | | `[pub/publish "topic" data]` | number | Publish; returns subscriber count delivered to | | `[pub/unsubscribe "topic" "handler-name"]` | bool | Unsubscribe | | `[pub/topics]` | list | List of active topics | | `[pub/subscribers "topic"]` | number | Subscriber count | ## Subscribing The handler is a **named function** looked up at delivery time via `VM.resolveVar`. This indirection lets the broker deliver to handlers in other runtimes (where a closure reference would be invalid) and survive module reloads.
 def on-config {|ev|
    @doc "React to a config change event."
    echo "config: $ev~data~key -> $ev~data~value"
}

pub/subscribe "config" "on-config" 
The handler takes one argument — the event map (see "Event Map Shape" below). ## Publishing
 # Publish a string
pub/publish "file-open" "/path/to/file.dft"

# Publish a structured value
pub/publish "user-loggedIn" %{ :user $user :at [clock/seconds] }

# Publish to a topic no one subscribed to — silently a no-op
pub/publish "orphan" "data" 
The data value can be any Deft value that round-trips through `deft/stringify` (scalars, strings, lists, maps, tagged maps, keywords). Closures and native resources don't serialise. ## Event Map Shape The handler receives a `%{:type :message :topic :data }` map:
 def on-event {|ev|
    echo "topic: $ev~topic"
    echo "data:  $ev~data"
    echo "type:  $ev~type"
}
pub/subscribe "file-open" "on-event" 
| Field | Always present? | Value | |---|---|---| | `:type` | yes | Always `:message` | | `:topic` | yes | Topic string | | `:data` | yes | The published value (parsed back from string for cross-runtime) | ## Topic Naming Topics are arbitrary strings. Conventions: - **`/`-separated namespaces** for hierarchical topics: `"ranger/files-deleted"`, `"diff/load"`, `"debug-trace"`. - **Module-prefixed topics** to avoid collisions: `"editor/file-saved"`, `"shell/eval-code"`. - **Cross-cutting topics** without a prefix for system-wide events: `"file-open"`, `"panel-focus"`, `"config"`. Filter on prefix in the handler when you only care about a subset:
 def on-pubsub {|ev|
    if [starts-with? $ev~topic "ranger/"] {
        echo "ranger event: $ev~topic"
    |
    }
}
pub/subscribe "ranger/selection-changed" "on-pubsub"
pub/subscribe "ranger/cwd-changed"       "on-pubsub"
pub/subscribe "ranger/files-deleted"     "on-pubsub" 
Or subscribe to one topic per handler if you prefer direct dispatch. ## Built-in Topics The platform defines a few topics with conventional payloads: | Topic | Published by | Payload | |---|---|---| | `"config"` | `config/set`, `config/unset` | `%{:key :value :old}` | | `"runtime/eval-result"` | `runtime/eval-async` | `%{:id :value :error}` | | `"dialog-result-"` | `dialog/*` (TUI) | Encoded dialog result string | | `"panel-focus"` | TUI workspace | Focused panel id | | `"file-open"` | Editor / files panel / git / chat | File path string | TUI apps publish dozens of additional topics — see the source of `packages/editor/src/editor.dft`, `packages/files/src/files.dft`, etc. for real-world examples. ## Unsubscribing
 pub/unsubscribe "config" "on-config"
pub/unsubscribe "ranger/files-deleted" "on-pubsub" 
A runtime's subscriptions are automatically removed when the runtime is destroyed — no explicit cleanup needed for short-lived children. ## Introspection
 [pub/topics]                                  # => list of active topic strings
[pub/subscribers "config"]                    # => number 
Useful for debugging "why isn't my handler firing?" scenarios. ## Examples ### Reactive configuration
 def on-config {|ev|
    if [starts-with? $ev~data~key "log/"] {
        echo "log config changed: $ev~data~key -> $ev~data~value"
        [reload-logger]
    |
    }
}
pub/subscribe "config" "on-config"

# Somewhere else in the system:
config/set "log/max-lines" 1000              # triggers on-config 
### Cross-runtime worker dispatch
 # Parent runtime
runtime/create "worker"
runtime/spawn "worker" "src/worker.dft"

def on-work-done {|ev|
    @doc "Handle work-completion events from the worker."
    echo "worker finished: $ev~data~job-id"
}
pub/subscribe "work-done" "on-work-done" 
 # src/worker.dft (runs in "worker" runtime)
def do-job {|job-id|
    # ... process ...
    pub/publish "work-done" %{ :job-id $job-id :result $r }
}

pub/subscribe "work-request" "do-job" 
### Real-world: editor file-open
 def on-pubsub {|ev|
    match $ev~topic {
        "file-open"       => [cmd-open-file $ev~data]
        "panel-focus"     => [on-focus-change $ev~data]
        "debug-attach-ready" => [attach-debugger $ev~data]
        _                 => nil
    }
}

pub/subscribe "file-open"          "on-pubsub"
pub/subscribe "panel-focus"        "on-pubsub"
pub/subscribe "debug-attach-ready" "on-pubsub" 
## Compared To `rpc` | Aspect | `pub/*` | `rpc` | |---|---|---| | Coupling | Loose (broadcast, any subscribers) | Tight (direct call to named fn) | | Direction | One-to-many | One-to-one | | Hosts | All (`deft`, `retra`, `retro`) | All hosts | | Return value | None (fire-and-forget) | Future resolving to return value | | Type safety | None (data is a value) | Some (`@rpc` decorator + spec map) | Use `pub/*` when you want decoupling and fan-out; use `rpc` when you need a typed return value (see [RPC](rpc)). ## Quick Reference | Form | Purpose | |---|---| | `[pub/subscribe "topic" "handler-name"]` | Subscribe a named handler | | `[pub/publish "topic" data]` | Publish a value | | `[pub/unsubscribe "topic" "handler-name"]` | Remove a subscription | | `[pub/topics]` | List active topics | | `[pub/subscribers "topic"]` | Count subscribers | | Handler signature | `{|ev| ... }` where `ev` is `%{:type :message :topic ... :data ...}` |