PRD: Hotwire IRC Client (from Ibis / mIRC reference scripts)
Version: 1.2 Status: Ready for implementation Source analysis: reference/stuff Β· 39 script files Output path: docs/pdf.md Stack: Ruby on Rails 8, Hotwire (Turbo + Stimulus), ActionCable, SQLite/PostgreSQL
1. Executive summary
Build a fully featured web IRC client that recreates the ergonomics of the legacy Ibis mIRC script suite (reference/stuff, formerly reference/newmirc) using modern Rails tooling.
Real-time chat flows through ActionCable WebSockets; UI updates use Turbo Streams and Stimulus controllers for nick lists, topic bars, and context menus. The goal is desktop-class IRC in the browser without requiring mIRC.
Analysis scanned 104 event handlers, 241 popup menu items, and 20 dialog definitions from the reference scripts.
The bundled mirc.pdf scripting reference is available under reference/mirc.pdf for alias/syntax parity during implementation.
Phased delivery
| Phase | Goal | Outcome |
|---|---|---|
| Phase 1 | Core client | Connect, join/part channels, styled message log, nick list, PMs |
| Phase 2 | Power user | Context menus, CTCP, modes, notify/ignore, logging, dialogs |
| Phase 3 | Advanced | DCC/XDCC bridge, services auth, auto-voice/op, flood limits |
2. Problem statement
The Ibis script collection (style.ibis, popups_*.ibis, dialogs.ibis, events.ibis, etc.) implements a rich IRC workflow:
-
Coloured, indented channel logs with custom join/part/kick/topic formatting
-
Deep right-click menus on nicks, channels, and the status window
-
DCC chat/send, CTCP tooling, and XDCC leech helpers
-
Settings dialogs for flood protection, auto-join, auto-connect, and away behaviour
No browser client in the Rabbot ecosystem reproduces this surface area. A Rails Hotwire app provides a maintainable path to parity while keeping server-side IRC connection logic testable.
3. Goals and non-goals
Goals
-
ActionCable bridge between IRC server and browser sessions (one server-side IRC connection per user or shared bouncer model)
-
Hotwire UI: Turbo Frames for channel tabs, Turbo Streams for incremental log lines
-
Stimulus controllers for context menus, mode toggles, and dialog panels
-
Feature parity with detected reference capabilities (see section 5)
-
mIRC colour code rendering (0β98) matching Rabbot
IrcFormatconventions -
Responsive layout: channel list, chat pane, nick list, status bar
-
Companion bot: Opt-in per-user Rabbot with fandom.com persona; persists IRC events while browser tab is closed; replays on reconnect
Non-goals (v1)
-
Binary-compatible mIRC script execution in the browser
-
Full Windows-only integrations (Notepad, Explorer paths from popups)
-
IRCnet/EFnet-specific exploits or channel flooding tools from legacy scripts
4. Architecture overview
app/ channels/irc_channel.rb # ActionCable β stream log lines to browser controllers/irc/ sessions_controller.rb # Connect/disconnect IRC channels_controller.rb # Join/part, topic, modes messages_controller.rb # Send channel/PM text context_menus_controller.rb # Popup menu actions (Turbo responses) javascript/controllers/ irc_log_controller.js # Append Turbo Stream fragments context_menu_controller.js # popups_* parity dialog_controller.js # settings/flood/lock dialogs models/ irc_connection.rb # Server-side IRC client (Cinch / ircinch) irc_message.rb # Parsed PRIVMSG/NOTICE/JOIN/PART irc_companion_profile.rb # user β fandom URL β character YAML path irc_event.rb # persisted IRC events for replay services/irc/ event_formatter.rb # style.ibis coloured output ctcp_handler.rb # ping/time/version dcc_coordinator.rb # Phase 3 β WebRTC or relay fallback network_profile.rb # per-network services + startup config capability_probe.rb # parse 004/005/MOTD into supported features connection_notes.rb # @stuff parity β structured connect log companion_bot_provisioner.rb # spawn/stop Rabbot from profile event_store.rb # append + cursor query sync_catchup.rb # build Turbo Stream replay payload jobs/ companion_bot_lifecycle_job.rb # enable/disable, channel mirror lib/irc_format.rb # Shared with Rabbot β mIRC colours lib/character_profile.rb # existing β persona source lib/character_profile_generator.rb # existing β URL β YAML
Real-time flow
-
Browser subscribes to
IrcChannelwith session token -
Rails IRC worker receives
PRIVMSGβ parses βIrcMessagerecord (optional) β broadcasts Turbo Stream -
Stimulus updates scroll position, nick highlights, and unread badges
5. Feature mapping (reference β web)
Detected from mirc.pdf, style.ibis, popups_chat.ibis, popups_channel.ibis, popups_nicklist.ibis, popups_status.ibis, popups_menubar.ibis:
| Feature | Reference | Web implementation |
|---|---|---|
| auto_op | events/aliases | Auto-op trusted users on join |
| auto_voice | events/aliases | Auto-voice trusted users on join |
| away | events/aliases | Away/AFK state with nick suffix and mass-message suppression |
| channel_modes | events/aliases | Channel mode toggles (+m, +s, +i, voice/op batching) |
| context_menus | popups_channel.ibis, popups_chat.ibis, popups_menubar.ibis, popups_nicklist.ibis, popups_status.ibis | Right-click menus for chat, channel, nick list, status bar, and menubar |
| ctcp | events/aliases | CTCP ping, time, version, and finger requests/replies |
| dcc_chat | events/aliases | Direct Client-to-Client private chat sessions |
| dcc_send | events/aliases | Direct file transfer offers and progress tracking |
| dialogs | asdf, chan, diuqil, draw, first, ircspy, lock, mp3, pc, settings, setup_autojoin, setup_ban, setup_flood, startup, tdccchangesettings, topic, who, whois, whoischan, xdccleech | Modal setup dialogs (settings, flood protection, lock screen) |
| dns | events/aliases | Reverse DNS lookup tooling |
| flood_protection | events/aliases | Rate limits for CTCP and outbound messages |
| ignore | events/aliases | Per-user ignore list management |
| logging | events/aliases | Per-channel and per-query log files with open-in-editor actions |
| notify | events/aliases | Online/offline notify list with status window feed |
| server_management | events/aliases | Auto-connect servers and auto-join channel lists |
| services | events/aliases | NickServ/ChanServ/MemoServ integration (per-network; see Β§12) |
| style_reference | style.ibis | Centralised colour and echo-indent styling from style.ibis |
| styled_events | events/aliases | Custom join/part/kick/topic/notice rendering with mIRC colour codes |
| whois | events/aliases | WHOIS lookup from nick context menus |
| xdcc | events/aliases | XDCC pack lists, queues, and leech helpers |
| companion_bot | settings.ibis (new) | Opt-in Fandom persona bot; cursor-based missed-content sync on reconnect |
Detailed script inventory, alias notes, and popup mapping: docs/ibis-script-reference.md.
Context menus (popups_*.ibis)
Implement Stimulus-driven menus mirroring:
-
popups_channel.ibisβ channel window -
popups_chat.ibisβ query/private chat window -
popups_menubar.ibisβ main menu bar -
popups_nicklist.ibisβ nick list selection -
popups_status.ibisβ status window
Dialogs
Priority matrix: see docs/irc-client-foundations.md Β§20.
-
asdfβ drop v1 β not in scope -
chanβ drop v1 β not in scope -
diuqilβ drop v1 β not in scope -
drawβ drop v1 β not in scope -
firstβ drop v1 β not in scope -
ircspyβ drop v1 β not in scope -
lockβ Turbo Frame modal β Phase 3 β screen lock / away -
mp3β drop v1 β not in scope -
pcβ drop v1 β not in scope -
settingsβ Turbo Frame modal β Phase 2 β auto-join, colours, companion bot -
setup_autojoinβ Turbo Frame modal β Phase 2 β merged into settings -
setup_banβ Turbo Frame modal β Phase 3 β ban list viewer -
setup_floodβ Turbo Frame modal β Phase 3 β flood limits -
startupβ Turbo Frame modal β Phase 2 β connection notes panel -
tdccchangesettingsβ drop v1 β not in scope -
topicβ Turbo Frame modal β Phase 2 β channel topic editor -
whoβ Turbo Frame modal β Phase 2 β context menu driven -
whoisβ Turbo Frame modal β Phase 2 β context menu driven -
whoischanβ drop v1 β not in scope -
xdccleechβ drop v1 β not in scope
6. UI / UX requirements
-
Messagelog: Plain body text; colour only on nicks, joins/parts, topics, and timestamps (match lib/CONVENTIONS.md Β§ IRC presentation) -
Nick list: Group by op/voice; colour from
style.ibisnick colouring rules -
Status bar: Network, lag, uptime, unread PM count (from
popups_status.ibis/ titlebar toggles) -
Channel tab bar: Auto-join list persistence (localStorage + server profile)
-
Mobile: Collapsible nick list; long-press for context menu
7. Technical requirements
-
Rails 8, import maps, Turbo 8, Stimulus 3
-
solid_cableor Redis for ActionCable adapter in production -
Background job for IRC read loop (
IrcReadJob) with graceful reconnect -
Encrypted credentials per
IrcNetworkProfile; never log rawPASS/IDENTIFY -
Never emit services auth commands unless the network profile and connection notes (MOTD + startup numerics) confirm support
-
Rate limiting mirroring flood protection dialog (
allow N CTCP events every M seconds) -
System tests with Capybara + ActionCable test helper
8. Phase 1 deliverables (MVP)
-
[ ] IRC connect form (server, port, nick, SSL toggle)
-
[ ] Join/part channel; live message stream via ActionCable
-
[ ] Styled JOIN/PART/QUIT/TOPIC lines from
style.ibisrules -
[ ] Private message pane
-
[ ] Basic nick list with op/voice prefixes
9. Phase 2 deliverables
-
[ ] Context menus (
popups_chat,popups_channel,popups_nicklist) -
[ ] CTCP ping/time/version
-
[ ] Channel mode toggles from
popups_channel.ibis -
[ ] Notify and ignore lists
-
[ ] Settings dialog (auto-join, colours, echo indent)
-
[ ] Channel/query logging (downloadable)
-
[ ] Settings: companion bot toggle, fandom URL field, visibility option
-
[ ] Fandom URL validation + profile preview/generation
-
[ ]
IrcEventStorewith cursor-based replay API -
[ ] ActionCable / Turbo catch-up on reconnect
10. Phase 3 deliverables
-
[ ] DCC chat relay (WebSocket tunnel or external helper)
-
[ ] File upload/send with progress bar
-
[ ] XDCC pack browser (read-only; no leech automation)
-
[ ] Per-network services auth (profile + MOTD/numeric discovery)
-
[ ] Auto-voice/op rules engine
-
[ ] Flood protection enforcement
-
[ ] Visible-mode channel mirroring (bot JOIN/PART sync with user)
-
[ ] Retention policy + storage compaction
-
[ ] Companion status in status bar (e.g. βDr. Weird is watching troutβ)
11. Success metrics
-
Connect and chat on Undernet test channel within 30 seconds of signup
-
Sub-200ms Turbo Stream delivery for 95th percentile local messages
-
Context menu covers β₯80% of
popups_chat.ibisactions (excluding OS-specific ones) -
mIRC colour rendering matches Rabbot golden fixtures
-
Services commands (identify, ghost, ChanServ recovery) only fire when network profile and connection notes confirm capability
-
Reopen tab after 5+ minutes away β 100% of missed messages appear within 2s without manual refresh
-
Fandom URL β usable companion nick + profile in <30s (cached profile <2s)
-
Disabling companion bot quits IRC within 10s; no ghost nicks after 60s
12. Legacy reference caveats
The Ibis mIRC scripts in reference/stuff were written for 2000s-era DALnet/Undernet networks. NickServ, ChanServ, and MemoServ menu items, startup aliases, and raw numeric handlers are historical defaults β not a portable spec for a modern web client.
12.1 Outdated services assumptions
Do not port these patterns literally:
-
nickserv IDENTIFY %IbisWordβ global identify on connect/away-return (aliases1.ibis,input.ibis,popups_menubar.ibis) -
nickserv ghost/RECOVERβ auto-ghost on nick-in-use (raw 433inraw.ibis) -
chanserv set $chan mlock -i/chanserv unbanβ join-failure recovery (raw 473/474inraw.ibis) -
Register prompts baked into
raw 477
Modern networks differ: SASL PLAIN, CertFP, different service nicks (e.g. nickserv@services.dal.net on DALnet via pns in aliases1.ibis), or no services at all (EFnet). Auth and recovery commands must be configured per network, never assumed globally.
12.2 Per-network connection profiles
Mirror mIRCβs servers.ini groups with a saved IrcNetworkProfile per network:
| Field | Purpose |
|---|---|
services_available |
Whether services exist on this network |
nickserv_target |
Nick or nick@host (e.g. legacy nickserv@services.dal.net) |
auth_strategy |
:none, :msg_identify, :sasl_plain, etc. |
supported_commands |
Subset of identify / ghost / recover / register actually used |
chanserv_join_recovery |
Whether to attempt invite/ban recovery on 473/474 |
startup_sequence |
Ordered hooks (ghost β nick β identify β mode +i β autojoin delay) |
Encrypted credentials stay per profile, not one global %ibisword.
12.3 Connection notes β capability discovery
The legacy @stuff status window (events.ibis opens it on start; raw.ibis feeds it) is the model for the web clientβs connection log. On each connect, parse and surface startup IRC numerics, then gate services commands on what the server actually supports.
Startup IRC numerics (from reference/stuff/raw.ibis):
| Numeric | Legacy use | Web client use |
|---|---|---|
| 001β003 | Connection banner | Status bar + connection notes |
| 004β005 | %stUserModes, %stChanModes, PREFIX, CHANTYPES |
Mode UI + dynamic menus (setDynamicMenu) |
| 251β255, 265β266 | echo_startup_stats / startup dialog |
Optional stats panel (parity blah.ibis startup dialog) |
| 375β377 | @MOTD window + MOTD footer |
MOTD pane; scan for register/identify instructions |
| 378 | βconnectingβ server messages | Connection progress log |
| 433 | Auto-ghost | Only if profile enables ghost |
| 473/474/477 | ChanServ recovery prompts | Only if profile enables chanserv recovery |
MOTD text often documents the networkβs real registration flow β treat it as authoritative over hardcoded menu commands. Read connection notes (MOTD + startup numerics) to determine what is supported before enabling NickServ/ChanServ actions.
12.4 Implementation mapping
See Β§4 architecture: network_profile.rb, capability_probe.rb, and connection_notes.rb under app/services/irc/.
13. Fandom companion bot (offline sync)
Closing the browser tab drops the ActionCable subscription. Without a persistent agent, missed messages, nick-list changes, and topics are lost when the user returns.
Each web IRC user may enable a companion bot in settings. While enabled, a Rabbot instance (persona from a fandom.com wiki page) maintains channel presence and writes structured events to server storage. On reconnect, the client fetches and renders everything since its last cursor β no manual scroll-back or /names refresh.
13.1 Problem and solution
Problem: The web client is ephemeral; IRC traffic continues whether or not a tab is open.
Solution: An opt-in companion bot acts as a per-user bouncer (see Β§3 shared bouncer model). It logs IRC events server-side and replays them when the browser reconnects.
13.2 Settings (options dialog)
Add to the settings dialog (Β§5 dialogs / Phase 2 deliverables):
| Setting | Type | Notes |
|---|---|---|
companion_bot_enabled |
boolean | Off by default; master toggle |
companion_fandom_url |
URL | Any *.fandom.com/wiki/... page (character, episode, item, location, etc.) |
companion_visibility |
enum | :visible (separate nick in channel) or :invisible (logger-only; no channel nick) |
companion_stay_in_channels |
boolean | When tab closed, bot remains in channels user had open (default: true) |
Activation flow:
-
User pastes fandom URL β server validates via
CharacterTranscripts.fandom_url? -
Server generates or loads
CharacterProfile(reuseCharacterProfileGeneratorfor first-time URLs; cache YAML per user) -
Preview shows character name + summary before save
-
On enable, provision companion bot config (nick derived from profile
name, IRC-sanitized) -
On disable, graceful quit; optional event retention policy (keep N days for re-enable)
13.3 Fandom persona source
Any fandom wiki page is valid β not limited to characters:
-
Character pages (primary example; matches existing Dr. Weird flow)
-
Episode/ transcript pages -
Locations, items, factions β generator extracts title + summary + traits from MediaWiki API (
CharacterProfileGenerator.fetch_api_source)
Reuse existing Rabbot libs (lib/character_profile.rb, lib/character_profile_generator.rb, lib/character_transcripts.rb); no new fandom parser required for v1.
Bot behaviour (non-sync): Companion is a lightweight Rabbot instance β CharacterResponder + minimal plugins only (no AI spam). Persona is flavour; sync is the primary job.
13.4 Sync and catch-up
Event store (new service; do not reuse AiChannelLog, which caps at 10 entries):
-
IrcEventStoreβ append-only, keyed by(user_id, network, channel, monotonic_id) -
Event types:
PRIVMSG,NOTICE,JOIN,PART,QUIT,TOPIC,NICK,MODE,KICK -
Store plain + formatted text (formatted via
event_formatter.rb/IrcFormat)
Cursor model:
-
Browser persists
last_event_idper(network, channel)in session + localStorage -
On ActionCable subscribe / Turbo reconnect:
GET /irc/sync?since=<cursor>β Turbo Stream batch append -
Nick list and topic bar updated from latest snapshot events in the batch
13.5 Visibility modes
-
Visible mode:
Botjoins when user joins; mirrors channel set; separate nick visible to others -
Invisible mode:
Botdoes not JOIN channels; piggybacks on the userβs server-side IRC connection (bouncer worker) and logs from that read loop β preferred when channel etiquette matters
User selects mode via companion_visibility in settings (see Β§13.2).
13.6 Architecture
See Β§4 architecture for irc_companion_profile.rb, irc_event.rb, companion_bot_provisioner.rb, event_store.rb, sync_catchup.rb, and companion_bot_lifecycle_job.rb.
Real-time catch-up flow:
-
Companion bot (or bouncer worker) appends IRC events to
IrcEventStore -
Browser tracks
last_event_idper channel -
On reconnect,
sync_catchup.rbbuilds Turbo Stream payload for events since cursor -
Stimulus appends missed lines and refreshes nick list / topic from snapshot events
13.7 Phased deliverables
Phase 2 (core companion bot):
-
[ ] Settings: companion bot toggle, fandom URL field, visibility option
-
[ ] Fandom URL validation + profile preview/generation
-
[ ]
IrcEventStorewith cursor-based replay API -
[ ] ActionCable / Turbo catch-up on reconnect
Phase 3 (hardening):
-
[ ] Visible-mode channel mirroring (bot JOIN/PART sync with user)
-
[ ] Retention policy + storage compaction
-
[ ] Companion status in status bar
13.8 Success metrics
See Β§11 success metrics for companion-bot-specific targets.
13.9 Non-goals
-
Companion bot is not a full AI chat agent in channels (persona flavour only)
-
No automatic fandom page picking β user supplies URL
-
No cross-user shared companions
14. Foundations supplement
Build-from-scratch decisions (gem packaging, optional auth, schema, workers, embed API, style.ibis mapping, dialog triage, deployment, tests) are documented in docs/irc-client-foundations.md.
Key decisions:
-
rabbot-ircgem β Rails engine embeddable in any host app; mount full-page or attach to any DOM element via Stimulusirc-chat -
Optional accounts β anonymous IRC connect; accounts unlock persistence, saved networks, and companion bot (Β§13)
-
Cinch workers β long-lived
bin/irc_workerholds sockets; Solid Queue for short jobs only -
Scale path β Postgres + Redis + Vinyl HTTP edge; see foundations Β§23
-
Federated donate β optional volunteer worker mesh; see foundations Β§24
End of PRD. Generated by !directchat prd from Rabbot directchat plugin. Foundations supplement: docs/irc-client-foundations.md (Β§14βΒ§24). Script reference: docs/ibis-script-reference.md.