Files
cproof-context/playbooks/add-event-handler.md
jabber.developer2 22977846a3 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.
2026-04-30 20:52:06 +03:00

3.0 KiB

Playbook: add an event handler

src/event/ mediates between src/xmpp/ (and locally-triggered actions) and the rest of the codebase. Two directions:

  • Server events (sv_ev_*) — triggered by inbound stanzas.
  • Client events (cl_ev_*) — triggered by local actions (UI, command).

See patterns/events.md for the conceptual split.

Decide direction

Trigger Direction File
Inbound stanza parsed in src/xmpp/. Server. src/event/server_events.c/h
User typed a command, UI key, or internal timer. Client. src/event/client_events.c/h

Add the handler — server example

src/event/server_events.h:

void sv_ev_foo_received(const char* const from, const char* const payload);

src/event/server_events.c:

void
sv_ev_foo_received(const char* const from, const char* const payload)
{
    // 1. Update persistent state.
    chatlog_msg_in(from, payload);

    // 2. Push UI update.
    ProfChatWin* win = wins_get_chat(from);
    if (win) {
        chatwin_incoming_msg(win, payload, NULL, FALSE);
    } else {
        cons_show_incoming_message(from, payload);
    }

    // 3. Plugin callbacks last — state must be consistent.
    plugins_post_chat_message_received(from, payload);
}

Wire the trigger site

For a server event, the matching parser in src/xmpp/ calls the new handler. For instance, if <foo> stanzas are parsed in src/xmpp/message.c:

// ... after parsing the stanza ...
sv_ev_foo_received(from, payload);

For a client event, call from cmd_funcs.c (or wherever the user-facing trigger lives).

Stub the handler in tests

tests/unittests/event/stub_*.c — add a stub for the new function so units that exercise the trigger site link cleanly:

void
sv_ev_foo_received(const char* const from, const char* const payload)
{
    // pass-through (or check_expected, depending on test needs)
}

If a test needs to verify the handler is called with specific args, switch the stub to check_expected and the test queues expect_string / expect_value.

See testing/stubs.md.

Plugin hook (if applicable)

If the event should fire a plugin callback, add a dispatcher in src/plugins/plugins.c/h (e.g. plugins_post_foo_received) and call it last in the handler — after state and UI are consistent. Keep C and Python plugin APIs in sync (c_api.h / python_api.h).

Unit test the handler

tests/unittests/test_server_events.c (or test_client_events.c) holds event-side tests. Pattern:

void
test_sv_ev_foo_received_writes_chatlog(void** state)
{
    expect_string(chatlog_msg_in, jid, "alice@example.com");
    expect_string(chatlog_msg_in, msg, "hello");
    sv_ev_foo_received("alice@example.com", "hello");
}

Conventions

  • One handler, one event. Don't pile multiple unrelated events into the same function.
  • Order inside the handler: state → UI → plugins. Never call plugins before state is committed.
  • sv_ev_* and cl_ev_* should be straight-line — no XMPP protocol parsing, no UI rendering. They orchestrate; they don't implement.