qbrsh 0.2.0

A fast, keyboard-driven web browser
# Architecture

qbrsh is built on a hand-rolled Elm-style architecture, often called TEA (The
Elm Architecture). The goal is a browser that stays responsive and is easy to
reason about, without the shared mutable state and re-entrancy that tend to
accumulate in GTK applications.

## The loop

```
sources -> Msg queue -> update(&mut State) -> [Effect] -> runner -> results back as Msg
```

- **State** is one owned value. Every subsystem (modes, tabs, input, completion,
  config, hints) is a plain field on it. There is no `Rc<RefCell>` sharing of
  state across subsystems.
- **Msg** is the single type every source of change produces: key presses,
  WebKit signals, IPC requests, worker results, and so on.
- **update** is the only place state mutates. It is synchronous, does no I/O,
  and returns a list of effects to perform. Because it is pure, it is fully
  unit-tested without GTK.
- **Effect** values are carried out by an effect runner after update returns.
  Effects that produce a value (such as evaluating JavaScript) deliver the
  result back as a new message.

A single consumer drains the message queue on glib's main context. Nothing
mutates state inside a signal callback, so there is no re-entrancy and no need
for defensive borrow guards. There are no polling timers.

## Where the code lives

| Path | Responsibility |
| --- | --- |
| `src/core/` | the engine-agnostic TEA core: state, msg, effect, update, command, key handling, completion. Unit-tested without GTK. |
| `src/engine/` | the `EngineView` trait and the WebKitGTK backend, the only place WebKit types appear. |
| `src/input/`, `src/ui/`, `src/app.rs` | GDK key translation, the window, and the effect runner that drives GTK and WebKit. |
| `src/history.rs` | browsing history on a dedicated SQLite writer thread. |
| `src/plugin.rs` | the Rune plugin runtime. |
| `src/ipc.rs` | the JSON-RPC control socket. |

## Threading

Asynchronous work runs on glib's main context; there is no second runtime.
Blocking work is offloaded to worker threads that report results back as
messages. History writes happen on a single dedicated writer thread, and ad
filter compilation runs off the main thread. The mailbox that carries messages
is thread-safe, so any worker can hand results back to the main loop.

## Hot paths stay native

Decisions that must be fast and synchronous, such as ad blocking in the
navigation handler and permission requests, are made directly in the engine
rather than routed through the message loop. Plugins only attach to cold events
(page load, tab open, command) and never to per-request or per-keystroke paths.