docs: split context into layered, agent-oriented files

Replace the single file-structure.md with a stratified layout designed
for AI/agent skill consumption: tables and concrete identifiers over
prose, files loaded on demand, content separated by churn rate.

Layers:
- architecture/  stable structural reference (overview, source-map,
                 test-map, data-flow)
- patterns/      memory, commands, autocomplete, events, xmpp,
                 encryption, ui, plugins
- testing/       unit-tests, stubs, functional-tests, bench
- build/         local, docker, ci
- playbooks/     add-command, add-test, add-autocomplete,
                 add-event-handler, add-encryption
- gotchas.md     append-only dated entries (seven seed entries)
- wip/           branch-specific notes; deleted on merge to master

Stable layers describe cproof on master only. In-flight feature
branches (currently feat/ai) get a single file under wip/.

INDEX.md is the entry map with churn labels; SKILL.md is the
always-loaded skill hint pointing to it.
This commit is contained in:
2026-04-30 20:52:06 +03:00
parent bea6ab6df5
commit 22977846a3
30 changed files with 2351 additions and 112 deletions

97
patterns/autocomplete.md Normal file
View File

@@ -0,0 +1,97 @@
# Autocomplete
## The `Autocomplete` type
`src/tools/autocomplete.h` defines an opaque `Autocomplete` (GList-backed
sorted set with a stateful "current iterator" for cycling through matches).
| Function | Purpose |
|---|---|
| `autocomplete_new()` | Create empty AC. |
| `autocomplete_clear(ac)` | Drop all entries (keep object). |
| `autocomplete_free(ac)` | Destroy. |
| `autocomplete_add(ac, item)` | Insert sorted. |
| `autocomplete_add_unsorted(ac, item, reversed)` | Append, no sort. |
| `autocomplete_add_all(ac, items)` | Bulk add from `char**`. |
| `autocomplete_update(ac, items)` | Clear + add_all. |
| `autocomplete_remove(ac, item)` | Drop one. |
| `autocomplete_remove_all(ac, items)` | Drop many. |
| `autocomplete_complete(ac, search, quote, previous)` | Cycle to next/previous match. |
| `autocomplete_reset(ac)` | Reset cycle position. |
| `autocomplete_contains(ac, value)` | Membership. |
## Two completion flavours used in commands
In `src/command/cmd_ac.c`:
```c
autocomplete_param_with_ac(input, "/cmd sub", static_ac, quote, previous);
autocomplete_param_with_func(input, "/cmd sub", callback, previous, ctx);
```
| When to use | Choose |
|---|---|
| Fixed token set known at startup. | `autocomplete_param_with_ac` + a static `Autocomplete` (allocated in `cmd_ac_init`, freed in `cmd_ac_uninit`). |
| Token set is dynamic / queried from state. | `autocomplete_param_with_func` + a callback `char* fn(const char* search, gboolean previous, void* ctx)`. |
## Static Autocompletes
Pattern (from `cmd_ac.c`):
```c
// File-scope:
static Autocomplete account_ac;
// In cmd_ac_init():
account_ac = autocomplete_new();
autocomplete_add(account_ac, "list");
autocomplete_add(account_ac, "show");
autocomplete_add(account_ac, "add");
// ...
// Registered with:
g_hash_table_insert(ac_funcs, "/account", _account_autocomplete);
```
Static `Autocomplete` instances are added to a file-scope free-list array
near the top of `cmd_ac.c` so `cmd_ac_uninit` can call `autocomplete_free`
on each at shutdown. **Add new static ACs to that free-list.**
## Function-callback completers
```c
char* roster_contact_autocomplete(const char* const search_str,
gboolean previous, void* context);
```
- Stateless across invocations — no module-level `_last_match` globals.
- Returns a freshly-allocated `char*` (caller frees), or `NULL` when no more
matches.
- `previous` cycles backward (Shift+Tab).
- `context` is whatever was passed in `autocomplete_param_with_func`'s last
arg (often `NULL`).
`roster_contact_autocomplete` (`src/xmpp/roster_list.c`) is a clean
canonical example: it delegates straight to `autocomplete_complete` against
a roster-owned `Autocomplete` object — no callback-local state.
**Avoid the legacy "store previous match in a static" pattern.** Some older
completers do it; do not copy them. Keeping callbacks stateless makes
shift-tab cycling and concurrent completers safe.
## Per-command dispatcher
Every command's autocomplete is a single static function in `cmd_ac.c`:
```c
static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous);
```
Registered in `cmd_ac_init`:
```c
g_hash_table_insert(ac_funcs, "/account", _account_autocomplete);
```
The dispatcher inspects the input prefix and picks the right
`autocomplete_param_with_*` call.

109
patterns/commands.md Normal file
View File

@@ -0,0 +1,109 @@
# Commands
## Three files, three concerns
| File | Holds |
|---|---|
| `src/command/cmd_defs.c` (`cmd_defs.h`) | The big static array of `Command` records — every `/foo` is one entry. |
| `src/command/cmd_funcs.c` (`cmd_funcs.h`) | Handler implementations: `gboolean cmd_<name>(ProfWin*, const char* const, gchar**)`. |
| `src/command/cmd_ac.c` (`cmd_ac.h`) | Per-command autocomplete logic and registration. |
## Anatomy of a command record
Defined inline in `cmd_defs.c` using uppercase macros (declared in `cmd_defs.c`):
```c
{ CMD_PREAMBLE("/caps",
parse_args, 0, 1, NULL)
CMD_MAINFUNC(cmd_caps)
CMD_TAGS(
CMD_TAG_DISCOVERY,
CMD_TAG_CHAT,
CMD_TAG_GROUPCHAT)
CMD_SYN(
"/caps",
"/caps <fulljid>|<nick>")
CMD_DESC("...")
CMD_ARGS(
{ "<fulljid>", "..." },
{ "<nick>", "..." })
CMD_EXAMPLES(
"/caps user@host/res")
}
```
| Macro | Role |
|---|---|
| `CMD_PREAMBLE(name, parse_fn, min, max, pre_hook)` | Command name, parser, arg count bounds, optional pre-hook. |
| `CMD_MAINFUNC(fn)` | Single handler for the whole command. |
| `CMD_SUBFUNCS({"sub", fn}, ...)` | Dispatch by first argument. Use **instead of** `CMD_MAINFUNC` when the command has subcommands (e.g. `/status get`, `/status set`). |
| `CMD_TAGS(...)` | One or more `CMD_TAG_*` (UI grouping, search). |
| `CMD_SYN(...)` | Synopsis lines for `/help <cmd>`. |
| `CMD_DESC(...)` | Long description. |
| `CMD_ARGS({"name", "desc"}, ...)` | Argument table for help. |
| `CMD_EXAMPLES(...)` | Examples shown in help. |
The full set of `CMD_*` macro definitions is at the top of `cmd_defs.c`
read them when authoring a new command.
## Argument parsing
`parse_args()` lives in `src/tools/parser.c`:
```c
gchar** parse_args(const char* const inp, int min, int max, gboolean* result);
```
- Splits on whitespace, honours quoted segments.
- Returns `NULL` on parse failure (sets `*result = FALSE`).
- The handler receives the result as `gchar** args` — free is owned by the
caller in `cmd_defs.c`, the handler does **not** free.
Parser variants used in `CMD_PREAMBLE`:
- `parse_args` — standard quoted-aware split.
- `parse_args_with_freetext` — last argument is the rest of the line, no
splitting (used by `/msg`, `/me`, etc.).
- `parse_args_as_one` — concatenates remaining tokens into a single string.
## Handler signature
```c
gboolean cmd_foo(ProfWin* window, const char* const command, gchar** args);
```
Return `TRUE` on success, `FALSE` to print bad-usage. `command` is the literal
`/foo` typed (useful when the same handler is registered under multiple names).
## Connection-state gates
Most commands that touch XMPP must verify connection state first:
```c
if (connection_get_status() != JABBER_CONNECTED) {
cons_show("You are not currently connected.");
return TRUE;
}
```
`cons_bad_cmd_usage(cmd)` is the canonical "wrong arguments" path; pass the
literal command name.
## Adding a new command
See `playbooks/add-command.md` for the full walkthrough. Short version:
1. New entry in `cmd_defs.c` with `CMD_PREAMBLE`/`CMD_MAINFUNC`/...
2. Implement `cmd_<name>()` in `cmd_funcs.c` (declare in `cmd_funcs.h`).
3. (If autocompletion needed) implement `_<name>_autocomplete()` in
`cmd_ac.c` and register it via `g_hash_table_insert(ac_funcs, "/name", ...)`.
4. Add a unit test pair `tests/unittests/test_cmd_<name>.{c,h}` and register
tests in `tests/unittests/unittests.c`.
## Conventions
- `cmd_<name>` for handlers; `_<name>_autocomplete` (file-static) for the
autocomplete callback.
- Subcommand dispatch via `CMD_SUBFUNCS` is preferred over `if/else` chains
inside `cmd_<name>` when possible.
- Keep handlers in `cmd_funcs.c` thin: parse & validate args, then delegate
to a domain module (`xmpp/`, `config/`, `ui/`).

71
patterns/encryption.md Normal file
View File

@@ -0,0 +1,71 @@
# Encryption
Three independent stacks, all optional at build time (`./configure` flags):
| Module | Backend | XMPP bridge |
|---|---|---|
| `src/omemo/` | libsignal-protocol-c | `src/xmpp/omemo.c` |
| `src/otr/` | libotr v4 | (in-stream, no separate xmpp bridge) |
| `src/pgp/` | gpgme | `src/xmpp/ox.c` (for OX/XEP-0373) |
## Module layouts
### OMEMO (`src/omemo/`)
| File | Role |
|---|---|
| `omemo.c/h` | Public surface: session setup, encrypt/decrypt, key publish/fetch. |
| `crypto.c/h` | AES-GCM payload encryption; libsignal-protocol crypto provider. |
| `store.c/h` | Persistent identity / pre-key / signed pre-key / session storage. |
`src/xmpp/omemo.c` is the bridge — it talks to `src/omemo/omemo.c` and to
libstrophe stanza building.
### OTR (`src/otr/`)
| File | Role |
|---|---|
| `otr.c/h` | Public surface: start/end/secure messaging, fingerprint mgmt. |
| `otrlib.h` | Compatibility shim across libotr versions. |
| `otrlibv4.c` | libotr v4-specific implementation. |
OTR runs in-band on the message stream, no separate XMPP bridge file.
### PGP / OX (`src/pgp/`)
| File | Role |
|---|---|
| `gpg.c/h` | Classic PGP signing/encrypting via gpgme. |
| `ox.c/h` | XEP-0373 / XEP-0374 (OX): OpenPGP for XMPP. |
`src/xmpp/ox.c` is the OX bridge; `gpg.c` is invoked directly from message
handlers.
## Conditional compilation
Each module is gated by `HAVE_OMEMO`, `HAVE_LIBOTR`, `HAVE_LIBGPGME` (set by
`configure.ac`). Code that calls into a module guards with `#ifdef
HAVE_<X>` and supplies a graceful fallback. When editing:
- Search for the existing `#ifdef` use of the symbol you are touching to
confirm the gating macro.
- Stubs in `tests/unittests/{omemo,otr,pgp}/stub_*.c` exist regardless — they
satisfy the test linker even when the feature is "off" at runtime.
## Encryption-aware tests
`tests/unittests/test_forced_encryption.{c,h}` exercises the policy where
sending plain text is refused if a session is OMEMO/OTR/PGP-locked. Touching
encryption-policy code → check this test does not regress.
## Adding or modifying calls into encryption
1. Locate the bridge: outgoing call → typically `src/xmpp/omemo.c` /
`src/xmpp/ox.c` / `src/otr/otr.c`.
2. Confirm the `#ifdef HAVE_*` guard at the call site.
3. If the module's public surface (header) changes, update the corresponding
stub in `tests/unittests/{omemo,otr,pgp}/stub_*.c`.
4. Run the relevant unit tests (`test_forced_encryption`, plus anything in
`test_cmd_otr.c`, `test_cmd_pgp.c`).
See `playbooks/add-encryption.md`.

59
patterns/events.md Normal file
View File

@@ -0,0 +1,59 @@
# Events
`src/event/` mediates between the XMPP protocol layer and consumers (UI,
config, chatlog). Two main directions plus one common module.
## Files
| File | Purpose |
|---|---|
| `server_events.c/h` | `sv_ev_*` — events triggered by inbound stanzas. |
| `client_events.c/h` | `cl_ev_*` — events triggered locally (UI / commands). |
| `common.c/h` | Shared helpers used by both. |
## Server events (`sv_ev_*`)
Called from `src/xmpp/` parsers (`message.c`, `presence.c`, `iq.c`, `muc.c`,
`roster.c`) once a stanza has been validated and decomposed. Each `sv_ev_*`:
1. Updates persistent state (chatlog, roster, accounts).
2. Pushes UI updates (`chatwin_*`, `mucwin_*`, `cons_*`, `console_*`).
3. May invoke plugin callbacks (`src/plugins/callbacks.c`).
Examples (current symbols, grep `sv_ev_` in `server_events.c`):
- `sv_ev_incoming_message` — chat message arrived.
- `sv_ev_room_message` — MUC message.
- `sv_ev_presence_update` — presence change.
- `sv_ev_roster_received` — roster pushed by server.
## Client events (`cl_ev_*`)
Called from `cmd_funcs.c` and UI code when the user does something locally
that needs the same downstream fan-out as a server event.
Examples:
- `cl_ev_send_msg` — outgoing chat message.
- `cl_ev_send_presence` — presence update from `/status` or autoaway.
- `cl_ev_disconnect``/disconnect` initiated locally.
## Adding an event handler
1. Decide direction: inbound (server) or outbound (client).
2. Add `sv_ev_<name>` or `cl_ev_<name>` in the matching `.c` and declare in
the matching `.h`.
3. Call it from the trigger site (a parser in `src/xmpp/` for server events;
a command handler or UI input handler for client events).
4. Inside, perform the fan-out: state update → UI → plugin callback.
5. If unit-tested, add a stub for it in `tests/unittests/event/stub_*.c`
(or extend the existing stub).
See `playbooks/add-event-handler.md`.
## Conventions
- Keep XMPP-protocol concerns in `src/xmpp/`. The event module is a
*dispatcher*, not a parser.
- Keep UI rendering decisions in `src/ui/`. The event module *invokes* UI
functions but does not draw.
- Plugin callbacks are the **last** step — state must already be consistent
before they run, since plugins may re-enter the codebase.

65
patterns/memory.md Normal file
View File

@@ -0,0 +1,65 @@
# Memory management
## `auto_*` macros
GCC/Clang `__cleanup__` attribute, declared in `src/common.h` (one exception:
`auto_jid` lives in `src/xmpp/jid.h` because it needs the `Jid` type).
| Macro | Type | Cleanup | Use for |
|---|---|---|---|
| `auto_gchar` | `gchar*` | `auto_free_gchar()` | GLib strings (`g_strdup`, `g_strdup_printf`, etc.) |
| `auto_gcharv` | `gchar**` | `auto_free_gcharv()` | GLib string arrays (`g_strsplit`, `g_strjoinv` arg) |
| `auto_char` | `char*` | `auto_free_char()` | C strings (`strdup`, `malloc`'d) |
| `auto_guchar` | `guchar*` | `auto_free_guchar()` | Unsigned char buffers (e.g. `g_base64_decode`) |
| `auto_gfd` | `gint` | `auto_close_gfd()` | File descriptors held in a `gint` |
| `auto_FILE` | `FILE*` | `auto_close_FILE()` | `FILE*` from `fopen` |
| `auto_jid` | `Jid*` | `jid_auto_destroy()` | `Jid` structs (XMPP) |
Place the macro **before** the type, like `gboolean`-style attribute:
```c
auto_gchar gchar* msg = g_strdup_printf("hello %s", name);
auto_gcharv gchar** parts = g_strsplit(line, " ", -1);
```
The cleanup runs at scope exit, including early returns. Do **not** call the
matching `g_free`/`g_strfreev`/`fclose` manually — that double-frees.
## Manual cleanup helpers
In `common.h`:
| Macro | Behaviour |
|---|---|
| `FREE_SET_NULL(ptr)` | `free(ptr); ptr = NULL;` (use for `malloc`'d) |
| `GFREE_SET_NULL(ptr)` | `g_free(ptr); ptr = NULL;` (use for GLib-allocated) |
Use these when the pointer is a struct field that must remain accessible
after the free (so a later `if (x->p)` is safe).
## GLib free-function reference
| Allocator | Free with |
|---|---|
| `g_strdup`, `g_strdup_printf`, `g_strconcat`, ... | `g_free` (or `auto_gchar`) |
| `g_strsplit`, `g_strdupv` | `g_strfreev` (or `auto_gcharv`) |
| `g_list_*` of allocated items | `g_list_free_full(list, free_fn)` |
| `g_hash_table_new[_full]` | `g_hash_table_destroy` (uses key/value destroy fns if supplied) |
| `g_base64_decode` | `g_free` (or `auto_guchar`) |
| `g_key_file_*` | `g_key_file_free` |
**Common pitfall:** mixing `g_free` and `free`. GLib uses its own allocator
shim; never cross the boundary. If you got the buffer from a GLib function,
free it with the matching GLib function.
**Common pitfall:** `g_strsplit` returns `gchar**` — free with `g_strfreev`,
not `g_free`. (See `gotchas.md`.)
## Adding a new `auto_*`
1. Declare cleanup function: `void auto_close_foo(Foo** p);`
2. Define the macro: `#define auto_foo __attribute__((__cleanup__(auto_close_foo)))`
3. Place both in the header that owns the type — `common.h` for project-wide,
the type's own header otherwise.
4. The cleanup must tolerate `NULL` and idempotent re-entry; assign `*p = NULL`
inside if you keep the variable accessible after free.

67
patterns/plugins.md Normal file
View File

@@ -0,0 +1,67 @@
# Plugins
`src/plugins/` exposes both a C plugin ABI and a Python plugin runtime. Both
funnel through a shared core that fires callbacks on cproof events.
## Files
| File | Role |
|---|---|
| `plugins.c/h` | Public surface: load/unload, list, dispatch hooks. |
| `c_plugins.c/h` | C plugin loader (dlopen). |
| `python_plugins.c/h` | Python plugin loader (embedded interpreter). |
| `c_api.c/h` | C-side API exposed **to** plugins. |
| `python_api.c/h` | Python-side API exposed **to** plugins. |
| `api.c/h` | Shared API helpers. |
| `profapi.c/h` | The `prof` Python module surface. |
| `callbacks.c/h` | Hooks: pre/post message, pre/post presence, etc. |
| `autocompleters.c/h` | Plugin-registered autocompleters. |
| `disco.c/h` | Service-discovery feature contributions from plugins. |
| `settings.c/h` | Per-plugin settings storage. |
| `themes.c/h` | Plugin-supplied themes. |
## Hook points
Plugins fire on events around (cf. `src/plugins/callbacks.c`):
- Pre/post message send, pre/post message receive.
- Pre/post chat-message-display.
- Pre/post connect, disconnect.
- Pre/post quit.
- Window-switch, room-join, room-leave.
The callback names follow `prof_pre_*` / `prof_post_*` in the plugin API
surface; in cproof internals they are dispatched via
`plugins_pre_chat_message_send()` and similar.
## API surface for plugins
C API (`c_api.h`) and Python API (`python_api.h`) are kept in sync — any
new function in one should land in the other unless explicitly C-only or
Python-only.
The Python wrapper is in `profapi.c/h` and is exposed as the `prof` module
inside plugins.
## Adding a plugin hook point
1. Define dispatcher in `plugins.c/h`:
`void plugins_<verb>_<event>(<args>);`
2. Wire callback storage in `callbacks.c/h`.
3. Expose to plugins via `c_api.c/h` and `python_api.c/h` (matching names).
4. Call the dispatcher at the appropriate site (usually in `src/event/` or
`src/xmpp/`).
5. Document the hook in cproof's plugin docs (out of scope for this repo).
## Testing
`tests/unittests/test_plugins_disco.{c,h}` covers plugin-disco feature
contribution; broader plugin-system testing is light. Stubs:
`tests/unittests/plugins/stub_*.c`.
## Pitfalls
- Plugin callbacks may re-enter cproof via the C/Python API. Ensure state is
consistent **before** firing a callback — see `patterns/events.md`.
- The Python interpreter is global; plugins share state. Don't assume
isolation.
- Plugin loading is not sandboxed; treat plugins as trusted code.

63
patterns/ui.md Normal file
View File

@@ -0,0 +1,63 @@
# UI
`src/ui/` is built on ncurses (wide-char). It owns windows, the input line,
status/title bars, and tray notifications.
## Window types
| File | Window | Notes |
|---|---|---|
| `chatwin.c` | One-on-one chat | One per peer JID. |
| `mucwin.c` | MUC room | Tracks occupants list, subject, role. |
| `privwin.c` | MUC private chat | Per-occupant private window inside a room. |
| `console.c` | Console | Always present, index 1; system messages. |
| `confwin.c` | Form / config | Used for ad-hoc commands and data forms. |
| `xmlwin.c` | XML console | Raw stanza tracer. |
| `vcardwin.c` | vCard view | Read-only viewer. |
`win_types.h` declares the window-type enum used to discriminate at runtime.
`window.c/h` is the base type; `window_list.c/h` manages the open-windows
collection.
## Buffers
`buffer.c/h` — append-only ring of rendered lines per window. UI rendering
reads from the buffer; chatlog persists separately.
## Bars and chrome
| File | Role |
|---|---|
| `statusbar.c/h` | Bottom status line (window list, unread counts). |
| `titlebar.c/h` | Top bar (current window title, encryption state). |
| `tray.c/h` | System-tray notifications via libnotify. |
| `screen.c/h` | Screen-level helpers (resize, refresh). |
| `notifier.c` | Sound and visual notifications dispatch. |
| `inputwin.c/h` | Input line: line editor, history, completion trigger. |
| `core.c` | UI lifecycle (init / shutdown / refresh loop). |
## Roster panel
`rosterwin.c` and `occupantswin.c` render the side panels (roster and MUC
occupants). They subscribe to roster / muc state changes via the event layer.
## Input → completion
`inputwin.c` calls into `cmd_ac.c` on Tab. See `patterns/autocomplete.md` for
how that flow continues.
## Conventions
- Public UI calls used from outside `src/ui/`:
- `cons_show("...")` — formatted line into the console.
- `cons_bad_cmd_usage(cmd)` — print canonical "wrong usage" line.
- `chatwin_*`, `mucwin_*`, `privwin_*` — write into a specific window type.
- UI code does not block on XMPP. State queries go through getters in
`src/xmpp/` and `src/config/`.
- Color / theme lookups go through `src/config/color.c` and
`src/config/theme.c` — do not hardcode ncurses attribute pairs.
## Testing
UI is heavily stubbed in unit tests — see `tests/unittests/ui/stub_ui.c` and
sibling stubs. Real ncurses calls do not run under `make check`.

95
patterns/xmpp.md Normal file
View File

@@ -0,0 +1,95 @@
# XMPP layer
`src/xmpp/` wraps libstrophe and exposes higher-level operations to the
command, event, and UI layers.
## Connection lifecycle
`src/xmpp/connection.c` owns the connection state machine. Public surface in
`connection.h`:
- `jabber_conn_status_t connection_get_status(void);`
- `jabber_conn_status_t connection_connect(const char* fulljid, const char* passwd, ...);`
- `jabber_conn_status_t connection_register(...);`
- `void connection_disconnect(void);`
`jabber_conn_status_t` values:
| Value | Meaning |
|---|---|
| `JABBER_DISCONNECTED` | Not connected. |
| `JABBER_CONNECTING` | Connect in progress. |
| `JABBER_CONNECTED` | Live session. |
| `JABBER_DISCONNECTING` | Tearing down. |
**Most code paths gate on `JABBER_CONNECTED`.** A canonical command-handler
guard:
```c
if (connection_get_status() != JABBER_CONNECTED) {
cons_show("You are not currently connected.");
return TRUE;
}
```
In tests, `will_return(connection_get_status, JABBER_CONNECTED)` (or
`JABBER_DISCONNECTED`) before the call. See `gotchas.md`.
## Stanza building
`src/xmpp/stanza.c` is the assembler. Helpers like
`stanza_create_message(...)`, `stanza_create_presence(...)`, plus low-level
`xmpp_stanza_*` from libstrophe.
Outgoing flow:
- `cmd_funcs.c` or `client_events.c``xmpp/message.c` (or `presence.c`,
`iq.c`) → `stanza.c` to build → libstrophe to send.
Incoming flow:
- libstrophe handler → `xmpp/message.c` (or sibling) parses → `event/server_events.c`
fans out → `ui/`, `chatlog.c`, plugin callbacks.
## JIDs
`src/xmpp/jid.h` defines `Jid` and the `auto_jid` cleanup macro. Use
`auto_jid` rather than manual `jid_destroy()` whenever practical:
```c
auto_jid Jid* parsed = jid_create(input);
if (!parsed || !parsed->barejid) {
return FALSE;
}
```
`Jid` exposes `barejid`, `fulljid`, `localpart`, `domainpart`, `resourcepart`.
## Sessions, MUC, presence, roster
| File | Responsibility |
|---|---|
| `session.c` | Logical XMPP session: account, fulljid, presence, settings. |
| `muc.c` | Multi-user chat rooms (joins, occupants, configuration). |
| `presence.c` | Presence stanzas, subscription requests. |
| `roster.c` / `roster_list.c` | Roster fetch, push, contact lookup. |
| `chat_session.c` | Per-conversation state (carbons, OTR channel, etc.). |
| `chat_state.c` | Composing / paused / inactive notifications (XEP-0085). |
| `bookmark.c` | Bookmarked rooms (XEP-0048). |
| `blocking.c` | Block list (XEP-0191). |
| `vcard.c` / `vcard_funcs.h` | vCard fetch / publish. |
| `avatar.c` | Avatar publish / fetch (XEP-0084). |
| `iq.c` | Generic IQ handling and pending-IQ map. |
| `capabilities.c` | Entity capabilities cache (XEP-0115). |
## Encryption bridges
`src/xmpp/omemo.c` and `src/xmpp/ox.c` are *bridges* into the encryption
modules — they live alongside same-named files in `src/omemo/` and
`src/pgp/`. Be precise about which path you mean.
## Testing
When unit-testing code that calls into `xmpp/`, expect to:
- Mock `connection_get_status` with `will_return(...)`.
- Stub session getters (`session_get_account_name`) and roster lookups.
- Use stubs in `tests/unittests/xmpp/stub_*.c`. See `testing/stubs.md`.