Orbit documentation: docs, wiki and FAQ pages

This commit is contained in:
reverse 2026-06-20 06:34:12 +00:00
commit bcbe5fff22
No known key found for this signature in database
18 changed files with 714 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.DS_Store
*.swp
__pycache__/

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# Orbit — documentation
Source for the Orbit IRC web client documentation, rendered at
**[orbit.tchatou.fr/docs](https://orbit.tchatou.fr/docs/)**.
- **`docs/`** — documentation pages (overview, quick start, configuration,
branding, plugins, self-hosting, IRCv3, architecture)
- **`wiki/`** — user wiki (slash commands, formatting, themes, the `#orbit`
channel, troubleshooting)
- **`faq.md`** — frequently asked questions
Every page is Markdown. Edit a page, commit, and the site serves it.
The Orbit client itself lives at
**[codeberg.org/reversefr/orbit](https://codeberg.org/reversefr/orbit)**.

40
docs/architecture.md Normal file
View file

@ -0,0 +1,40 @@
# Architecture
Orbit is a small, modular React app. No framework magic — a thin IRC layer feeds a zustand store,
which renders components.
## Project tree
```
src/
config.ts App.tsx main.tsx index.css
lib/ format.tsx editor.ts ← presentation + editor helpers
ui/ theme.ts prefs.ts viewport.ts
services/ notify.ts push.ts avatars.ts
irc/ parser client modes numerics casemap types caps ctcp
store.ts store/text.ts store/persistence.ts
components/
Chat.tsx ← thin assembler
chat/ MessageList Composer Sidebar Topbar MemberList Banners
settings/ SettingsModal
profile/ ProfileModal
modals/ Modals
Avatar ConnectScreen Turnstile
```
## Layers
- **`irc/`** — the protocol. `client.ts` is the WebSocket transport + CAP negotiation; `parser.ts`,
`modes.ts`, `casemap.ts`, `numerics.ts` are pure primitives. `caps.ts` lists the requested
capabilities.
- **`store.ts`** — turns IRC events into buffers, messages and members (zustand). Pure helpers split
into `store/text.ts` (masking, formatting) and `store/persistence.ts`.
- **`lib/`** — `format.tsx` renders mIRC formatting → React; `editor.ts` is the rich-composer
serialization.
- **`components/`** — the UI, grouped by area (`chat/`, `settings/`, `profile/`, `modals/`).
`Chat.tsx` is a ~30-line assembler.
## Quality
Vitest unit tests cover the pure modules (parser, casemap, masking). Lint + test + build run in CI,
and the self-hosted [push-to-deploy](/docs/push-to-deploy/) gates on the same.

51
docs/branding.md Normal file
View file

@ -0,0 +1,51 @@
# Branding & themes
Everything that ties Orbit to a particular network or brand lives in
[`config.json`](/docs/config/) under `branding` and `defaults` — no code changes, no rebuild.
## Rebrand
```json
{
"branding": {
"name": "ExampleChat",
"icon": "https://example.org/logo.svg",
"url": "https://example.org",
"tagline": "Chat with",
"taglineEm": "everyone.",
"subtitle": "Public rooms, private messages, no signup required."
}
}
```
This changes the connect screen, the console title, the network icon, and the CTCP VERSION/SOURCE
replies the client sends.
## Themes
Orbit ships four themes:
- **light** — clean, default.
- **dark** — easy on the eyes.
- **yomirc** — a retro mIRC / Windows-95 skin (silver chrome, fixed-width log lines).
- **yomirc-dark** — the retro skin in a dark palette.
Set the default for new users:
```json
{ "defaults": { "theme": "dark" } }
```
Users can switch any time in **Settings → Appearance**. See the [Themes wiki page](/wiki/themes/).
## Feature flags
Hide whole features per deployment:
```json
{ "features": { "push": true, "imageUpload": true, "register": false } }
```
- `push` — the Web Push notifications row.
- `imageUpload` — the composer image button + paste/drag upload.
- `register` — the "create an account" tab.

100
docs/compiled-plugins.md Normal file
View file

@ -0,0 +1,100 @@
# Compiled plugins
A quick `.js` plugin (see [Plugin system](/docs/plugins/)) is great for small
things. For anything substantial — real components with state and hooks — build
the plugin like a normal project and compile it down to **one droppable file**: a
compiled, externalized-React plugin model.
## The trick: externalize React
The output bundle must **not** carry its own copy of React. Two React instances
on one page break hooks ("invalid hook call"). Instead, mark `react`, `react-dom`
and `react/jsx-runtime` **external** and map them to Orbit's globals, so your
plugin shares the host's single React instance:
| Module | Maps to |
|---|---|
| `react` | `Orbit.React` |
| `react-dom` | `Orbit.ReactDOM` |
| `react/jsx-runtime` | `Orbit.jsxRuntime` |
Because your component is created with Orbit's React and rendered through a slot
into Orbit's tree, **hooks, state, context and effects all work**.
## Vite config
```ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
lib: { entry: 'src/index.tsx', formats: ['iife'], name: 'MyOrbitPlugin',
fileName: () => 'my-plugin.js' },
rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'],
output: { globals: {
react: 'Orbit.React',
'react-dom': 'Orbit.ReactDOM',
'react/jsx-runtime': 'Orbit.jsxRuntime',
} },
},
},
});
```
## Write normal TSX
```tsx
import { useState } from 'react';
Orbit.plugin('my-plugin', (orbit) => {
orbit.addUi('composer_button', () => <ShrugButton orbit={orbit} />);
orbit.addSettingsSection({
label: 'My plugin', icon: '🧩',
render: () => <Panel orbit={orbit} />,
});
});
function Panel({ orbit }) {
const [count, setCount] = useState(() => orbit.storage.get('count', 0));
const bump = () => { const n = count + 1; setCount(n); orbit.storage.set('count', n); };
return (
<div className="scard"><div className="scard__body">
<div className="sfield"><div className="sfield__intro">
Compiled plugin — real React with hooks. You are <b>{orbit.state.nick()}</b>.
</div></div>
<div className="modal__actions">
<button className="upbtn upbtn--primary" onClick={bump}>Clicked {count}×</button>
</div>
</div></div>
);
}
```
The compiled bundle is tiny — it contains only your code, calling Orbit's React.
## Starter template
A ready-to-copy starter (Vite config with the externals set up, `tsconfig`,
ambient types and a working example) lives in the repo at
[`plugin-template/`](https://codeberg.org/reversefr/orbit/src/branch/main/plugin-template).
```bash
git clone https://codeberg.org/reversefr/orbit.git
cd orbit/plugin-template
npm install
npm run build # → dist/orbit-plugin-template.js
```
## Deploy
1. Copy the built `.js` to where Orbit can fetch it (e.g. `/app/plugins/`).
2. List it in the deployed `config.json` — no rebuild of Orbit needed:
```json
{ "plugins": ["/app/plugins/my-plugin.js"] }
```
3. Reload. Your composer button and Settings section appear.

44
docs/config.md Normal file
View file

@ -0,0 +1,44 @@
# config.json reference
Orbit is configured at **runtime** by `config.json`. It is fetched on startup, deep-merged over the
built-in defaults, then the app renders — so you can re-point or rebrand **without rebuilding**.
- **Source of truth:** `public/config.json` (copied into the build).
- **Served at:** `/app/config.json`.
- **Edit live:** the service worker serves it *network-first*, so editing the deployed file takes
effect on the next reload — no rebuild.
Merge rules: objects merge key-by-key; **arrays and scalars replace** wholesale.
## Options
| Key | Type | What it does |
|-----|------|--------------|
| `server.url` | string | WebSocket URL of the IRCv3 server. |
| `startup.channels` | string[] | Channels auto-joined (first = active). A `?channel=` URL param overrides. |
| `branding.name` | string | App/network name shown in the UI + CTCP VERSION. |
| `branding.icon` | string | Logo / favicon URL. |
| `branding.url` | string | Homepage (used in CTCP VERSION/SOURCE). |
| `branding.tagline` / `taglineEm` / `subtitle` | string | Connect-screen copy. |
| `branding.projectUrl` | string | "Powered by Orbit" link in Settings. |
| `turnstile.enabled` / `sitekey` | bool / string | Cloudflare Turnstile on registration. |
| `report.target` | string | Channel that user reports are sent to. |
| `defaults.theme` | string | `light` · `dark` · `yomirc` · `yomirc-dark`. |
| `defaults.compact` / `sound` / `hideJoinQuit` / `clock24` | bool | Preset new-user prefs. |
| `features.push` / `imageUpload` / `register` | bool | Turn whole features on/off. |
> `defaults.*` only seed a user's preferences the **first** time — once someone changes a setting,
> it's stored in their browser and the config no longer overrides it.
## Example
```json
{
"server": { "url": "wss://irc.example.org/ws/" },
"startup": { "channels": ["#lobby", "#help"] },
"branding": { "name": "ExampleChat", "tagline": "Chat with", "taglineEm": "everyone." },
"turnstile":{ "enabled": false, "sitekey": "" },
"defaults": { "theme": "dark", "compact": true },
"features": { "register": false }
}
```

35
docs/deploy.md Normal file
View file

@ -0,0 +1,35 @@
# Build & deploy
Orbit builds to a **static** `dist/` you can serve from any web server.
```sh
npm run build # → dist/ (index.html + hashed assets + config.json + sw.js)
```
## Serving
Serve `dist/` under a path — conventionally `/app/`. The build's `vite.config.ts` sets
`base: '/app/'`. Example nginx:
```nginx
location /app/assets/ { root /var/www/site; expires 7d; add_header Cache-Control "immutable"; }
location = /app/index.html { root /var/www/site; add_header Cache-Control "no-cache"; }
location /app/ { root /var/www/site; try_files $uri $uri/ /app/index.html; }
```
- **Hashed assets** are immutable → cache forever.
- **`index.html`** is `no-cache` so new builds load immediately.
- **`config.json`** is served *network-first* by the service worker, so edits apply without a rebuild.
## Origin allow-list
The IRC WebSocket server must allow the **Origin** Orbit is served from (e.g. InspIRCd
`<wsorigin allow="https://chat.example.org">`). Otherwise the socket is rejected on connect.
## Service worker
On each deploy, bump the cache version in `public/sw.js` (`const CACHE = 'app-vN'`). The worker
calls `skipWaiting()` + `clients.claim()` and fires `controllerchange`, so existing PWA installs
auto-reload to the new build.
For an automated pipeline, see [Push-to-deploy](/docs/push-to-deploy/).

37
docs/ircv3.md Normal file
View file

@ -0,0 +1,37 @@
# IRCv3 capabilities
Orbit negotiates the following capabilities when the server advertises them. Most features degrade
gracefully if a cap is missing, so it works on any compliant IRCv3 server.
## Core
| Capability | Purpose |
|------------|---------|
| `sasl` | Authenticate to your account during connection. |
| `message-tags` | The basis for msgids, reactions, replies, labels. |
| `server-time` | Accurate timestamps on every message. |
| `echo-message` | The server echoes your own messages back (consistent state). |
| `batch` | Groups related messages (history, netsplits). |
| `labeled-response` | Correlate a command with its replies. |
| `account-tag` | Each message carries the sender's account (avatars, badges). |
## Presence & membership
`away-notify` · `account-notify` · `extended-join` · `chghost` · `multi-prefix` ·
`userhost-in-names` · `setname` · `invite-notify`
## Drafts (modern features)
| Capability | Feature in Orbit |
|------------|------------------|
| `draft/chathistory` + `draft/event-playback` | Server-side history replayed on join. |
| `draft/message-redaction` | Delete your own messages. |
| `draft/read-marker` | "New messages" divider, synced across devices. |
| `draft/multiline` | Send multi-line messages as one. |
| `draft/metadata-2` | Profile metadata. |
| `standard-replies` | Structured `FAIL`/`WARN`/`NOTE` handling. |
| `draft/account-registration` | Create an account from inside the client. |
| `draft/pre-away` | Set away during registration. |
| `draft/webpush` | Push notifications while the tab is closed (VAPID). |
You can list the live set any time by typing `!caps` to the bot in [#orbit](/wiki/orbit-channel/).

29
docs/overview.md Normal file
View file

@ -0,0 +1,29 @@
# Overview
**Orbit** is a modern web client for **IRC** — the open chat protocol that has quietly run
communities for decades. It runs entirely in the browser: pick a nick and you're talking, with
**no install and no signup**.
It powers [tchatou.fr](https://tchatou.fr/app/), and is built to be **re-pointed at any IRCv3
network** and **rebranded** from a single `config.json` — without recompiling.
## What makes it different
- **Full IRCv3.** Orbit negotiates 24 capabilities — SASL, message-tags, reactions, typing
indicators, read-markers, multi-line messages, server-side history, account registration, and
Web Push. See [IRCv3 capabilities](/docs/ircv3/).
- **Pluggable at runtime.** A static `config.json` is fetched on startup and sets the server,
channels, branding, themes and feature flags. One build can serve any community. See
[config.json reference](/docs/config/).
- **Installable PWA.** Mobile-first layout, offline app shell, and notifications when the tab is
closed.
- **Self-hostable.** A static build behind any web server. The project even runs its own git
server with [push-to-deploy](/docs/push-to-deploy/).
- **Free software.** AGPL-3.0 — use it, modify it, self-host it.
## Tech
React 19 · TypeScript · Vite · zustand. Tested with Vitest, linted, and built in CI. The codebase
is a clean modular tree — see [Architecture](/docs/architecture/).
> New here? Jump to the [Quick start](/docs/quick-start/) to run your own in a minute.

99
docs/plugins.md Normal file
View file

@ -0,0 +1,99 @@
# Plugin system
Orbit has a small, **operator-controlled** plugin system. A deployment lists
plugin scripts in `config.json`; each is loaded at startup and registers against
a global `Orbit` object to hook events, read state, send IRC, theme the UI, and
add UI in predefined slots.
> **Experimental.** Orbit is a work in progress and this API may change between
> releases. Plugins are deployment-controlled — same trust level as the app
> itself. There is no user-uploaded plugin marketplace, by design.
## Enabling plugins
Add script URLs to `plugins` in [`config.json`](/docs/config/):
```json
{ "plugins": ["/app/plugins/orbit-demo.js"] }
```
They load in order, after the app boots. Host them anywhere the page can reach
(same-origin recommended).
## A quick plugin
A plugin is a plain `.js` file that calls `Orbit.plugin()`. UI is authored with
the `html` tagged template (runtime markup, no build step), or with
`orbit.h(...)` (`React.createElement`).
```js
Orbit.plugin('my-plugin', (orbit, log) => {
log('loaded, Orbit v' + orbit.version);
orbit.on('message', (m) => log(m.from, 'said', m.text, 'in', m.target));
orbit.addUi('composer_button', () =>
orbit.html`<button class="composer__emoji" title="Shrug"
onClick=${() => orbit.irc.say('¯\\_(ツ)_/¯')}>🤷</button>`);
});
```
For anything substantial — real components with state — build a compiled plugin
instead. See **[Compiled plugins](/docs/compiled-plugins/)**.
## The `Orbit` API
### Global
| Member | Description |
|---|---|
| `Orbit.version` / `Orbit.commit` | app version + git commit (build-time) |
| `Orbit.plugin(name, fn)` | register a plugin; `fn(orbit, log)` |
| `Orbit.on/once/off/emit(event, …)` | the app event bus |
| `Orbit.config()` | the resolved runtime config |
| `Orbit.React` / `Orbit.ReactDOM` / `Orbit.jsxRuntime` / `Orbit.Fragment` / `Orbit.h` / `Orbit.html` | render primitives (externalization targets for compiled plugins) |
### Inside `Orbit.plugin(name, (orbit) => …)`
| Member | Description |
|---|---|
| `orbit.on/once/off/emit` | event bus (see events below) |
| `orbit.state.active()` | active buffer name |
| `orbit.state.nick()` / `account()` | your nick / logged-in account |
| `orbit.state.buffers()` | open buffer names |
| `orbit.state.get()` | full store snapshot (read-only) |
| `orbit.irc.send(line)` | send a raw IRC line |
| `orbit.irc.msg(target, text)` | PRIVMSG a target |
| `orbit.irc.say(text)` | send to the active buffer |
| `orbit.irc.join(chan)` / `part(chan)` | join / part |
| `orbit.irc.list()` | request the channel list |
| `orbit.themes.current()/list()/set(id)` | read/set the theme |
| `orbit.storage.get(key, def)/set(key, val)` | namespaced persistence |
| `orbit.addUi(slot, render)` | add UI to a slot (returns a remover) |
| `orbit.addSettingsSection({label, icon?, render})` | add a whole Settings section |
| `orbit.h / orbit.html` | render helpers |
| `log(…)` | namespaced console logger |
### Events
`ready`, `connected` (`{nick}`), `status` (connection status string),
`buffer.active` (buffer name), `message` (`{from, target, text, self}`),
`raw` (the parsed IRC message).
### UI slots
| Slot | Where |
|---|---|
| `composer_button` | a button in the message composer toolbar |
| `settings_section` | a whole section in Settings (own nav entry + pane) — use `orbit.addSettingsSection()` |
More slots (message decorators, side panels) will be added as the core grows
stable homes for them.
## Trust & security
Plugins are **operator-controlled**: a deployment lists them in `config.json`, so
they run with the same trust as the app itself. There is no user-uploaded plugin
mechanism. Orbit deliberately does **not** expose internal modules or runtime
component replacement — that would couple plugins to internals that are still
moving. The API above is the stable surface.

38
docs/push-to-deploy.md Normal file
View file

@ -0,0 +1,38 @@
# Push-to-deploy
Orbit ships its own self-hosted git workflow: a `git push` runs the tests, builds, and publishes —
no external CI required.
## How it works
A **bare git repo** on the server has a `post-receive` hook. On a push to `main` it:
1. Checks out the new commit.
2. Runs `npm ci && npm run test && npm run build`**gated**, so a broken push never ships.
3. Publishes `dist/` to the web root with `rsync --delete` (keeping a backup for rollback).
The whole loop is one command:
```sh
git commit -m "…"
git push # test → build → deploy (+ mirror)
```
## The hook (sketch)
```bash
#!/bin/bash
set -e; unset GIT_DIR
WORK=/path/to/checkout; WEBROOT=/var/www/site/app
while read -r _old new ref; do
[ "$ref" = "refs/heads/main" ] || continue
cd "$WORK"; git fetch -q self; git reset --hard -q self/main
npm ci --silent && npm run test && npm run build
rsync -a --delete dist/ "$WEBROOT/"
done
```
## Bonus: IRC announcements
The same hook hands the commit list to a small bot that posts a GitHub-style summary to the
project's IRC channel. See [The #orbit channel & bot](/wiki/orbit-channel/).

46
docs/quick-start.md Normal file
View file

@ -0,0 +1,46 @@
# Quick start
Run your own Orbit in about a minute.
## 1. Clone & install
```sh
git clone https://codeberg.org/reversefr/orbit.git
cd orbit
npm install
```
## 2. Point it at your server
Edit **`public/config.json`** — at minimum set the WebSocket URL of your IRCv3 server:
```json
{
"server": { "url": "wss://irc.example.org/ws/" },
"startup": { "channels": ["#lobby"] }
}
```
Your IRC server needs a **WebSocket listener** speaking the `text.ircv3.net` / `binary.ircv3.net`
sub-protocols (InspIRCd, Ergo, etc.), and its Origin allow-list must include where you serve Orbit.
## 3. Develop
```sh
npm run dev # http://localhost:5173
```
## 4. Build & serve
```sh
npm run build # → dist/
```
Serve the static `dist/` behind any web server (commonly under `/app/`). See
[Build & deploy](/docs/deploy/) for nginx and service-worker details.
## Next steps
- [config.json reference](/docs/config/) — every option.
- [Branding & themes](/docs/branding/) — make it yours.
- [IRCv3 capabilities](/docs/ircv3/) — what works on your server.

51
faq.md Normal file
View file

@ -0,0 +1,51 @@
## Is Orbit free?
Yes — it's **free software under AGPL-3.0**. You can use,
modify, and self-host it. If you run a **modified** version as a network service, you must publish
your source.
## Do I need to create an account?
No. Pick a nick and start chatting. Accounts (SASL / registration) are optional and only needed to
reserve a nick, host images, or get push notifications while closed.
## What servers does it work with?
Any **IRCv3** server with a WebSocket listener — InspIRCd, Ergo, and others. The more capabilities
the server supports, the more features light up. See [IRCv3 capabilities](/docs/ircv3/).
## Can I use it for my own network?
Absolutely — that's the point. Re-point it at your server and rebrand it entirely from one
[`config.json`](/docs/config/), no rebuild. See [Branding & themes](/docs/branding/).
## Does it work on mobile?
Yes. Orbit is an installable **PWA** with a mobile-first layout, offline shell, and push
notifications.
## Are my passwords safe?
Orbit connects over TLS, and it **masks** services passwords in the message log. If you accidentally
type `IDENTIFY nick pass` without a leading slash, it intercepts it, sends it privately, and warns
you — it's never broadcast to the channel.
## How do notifications work?
In-tab alerts and sounds work everywhere. **Push** notifications while the tab is closed use the
`draft/webpush` capability (VAPID) and require being logged into an account.
## Can I self-host it?
Yes — it's a static build behind any web server. The project even runs its own git server with
[push-to-deploy](/docs/push-to-deploy/). Start with the [Quick start](/docs/quick-start/).
## How do I report a bug or contribute?
Issues and code live on [Codeberg](https://codeberg.org/reversefr/orbit). You can also clone it
straight from `https://orbit.tchatou.fr/orbit.git`.
## Why IRC in 2026?
Because it's **open, federated, and yours** — no platform lock-in, no ads, no data harvesting. Orbit
gives that protocol a modern, friendly face.

29
wiki/commands.md Normal file
View file

@ -0,0 +1,29 @@
# Slash commands
Type these in the composer. Anything not starting with `/` is sent as a message.
| Command | What it does |
|---------|--------------|
| `/join #channel` | Join a channel (and switch to it). |
| `/part` | Leave the current channel. |
| `/msg <nick> <text>` | Open a private message. |
| `/me <action>` | Send a `/me` action. |
| `/nick <newnick>` | Change your nick. |
| `/whois <nick>` | Look up a user (opens their profile). |
| `/topic <text>` | Set the channel topic. |
| `/kick <nick> [reason]` | Kick a user (ops). |
| `/ban <nick>` | Ban a user (ops). |
| `/op` / `/deop` / `/voice <nick>` | Change a user's mode (ops). |
| `/ignore` / `/unignore <nick>` | Hide / show a user's messages. |
| `/list` | Browse channels. |
| `/clear` | Clear the current buffer. |
## Editor shortcuts
- **↑ / ↓** — recall your previously sent messages (like a shell).
- **Tab** — complete nicks, `/commands`, and `:emoji:`.
- **Shift+Enter** — newline (multi-line message).
> Heads-up: if you type a services command like `IDENTIFY nick pass` **without** the leading slash,
> Orbit catches it, sends it to NickServ privately, and warns you — your password is never
> broadcast.

22
wiki/formatting.md Normal file
View file

@ -0,0 +1,22 @@
# mIRC formatting
Orbit renders and lets you compose classic IRC formatting. Use the composer's toolbar (**B** / *I* /
underline / colour), or the keyboard:
| Style | Key | Code |
|-------|-----|------|
| Bold | Ctrl/Cmd+B | `\x02` |
| Italic | Ctrl/Cmd+I | `\x1D` |
| Underline | Ctrl/Cmd+U | `\x1F` |
| Strikethrough | — | `\x1E` |
| Monospace | — | `\x11` |
| Colour | toolbar | `\x03FF,BB` |
| Hex colour | toolbar | `\x04RRGGBB` |
| Reset | — | `\x0F` |
Colours use the 99-entry **mIRC palette** (`\x03` followed by a 12 digit foreground, optionally
`,background`). Orbit's composer writes these codes for you; incoming messages from any client are
rendered with full colour and styling.
Formatting is "sticky" — turn on bold and it stays on for the next message, exactly like classic
clients.

31
wiki/orbit-channel.md Normal file
View file

@ -0,0 +1,31 @@
# The #orbit channel & bot
**#orbit** is the project's live IRC channel. A bot named **`Pulsar`** stays connected there and does
two things.
## Commit announcements
Every push to the project's git server, Pulsar posts a GitHub-style summary to #orbit, with mIRC colours:
```
[orbit] alice pushed 2 commits to main [+0/-0/±2] https://…/compare/abc…def
[orbit] alice 1a2b3c4 - Fix a thing
[orbit] alice 5d6e7f8 - Add another thing
```
## Interactive commands
Talk to the bot in-channel or by PM (the `!` prefix):
| Command | Reply |
|---------|-------|
| `!help` | List of commands. |
| `!about` | What Orbit is + link. |
| `!commits [n]` | The latest commits, live from the repo. |
| `!stats` / `!version` | Repo stats / current version. |
| `!source` / `!app` | Repo and live-client links. |
| `!account <nick>` | Whether a nick is a registered account. |
| `!uptime` / `!ping` | Bot status. |
The bot is a Django management command, so `!account` reads the real account database. Join it from
the client: [open #orbit](https://tchatou.fr/app/?channel=%23orbit).

17
wiki/themes.md Normal file
View file

@ -0,0 +1,17 @@
# Themes
Switch in **Settings → Appearance**. Four themes ship with Orbit:
- **Light** — the clean default.
- **Dark** — low-light palette.
- **yomIRC** — a faithful retro mIRC / Windows-95 skin: silver 3D-bevelled chrome, fixed-width log
lines (`[HH:MM] <nick> text`), a flat nick list. For the nostalgic.
- **yomIRC dark** — the retro skin in a deep dark palette with a teal accent.
Deployers can set the **default theme** for new users in
[`config.json`](/docs/config/) → `defaults.theme` (`light` · `dark` · `yomirc` · `yomirc-dark`).
Other appearance options in Settings:
- **Compact mode** — denser message rows.
- **Time format** — 12h / 24h timestamps.

27
wiki/troubleshooting.md Normal file
View file

@ -0,0 +1,27 @@
# Troubleshooting
## "Connexion fermée" / the socket closes immediately
The IRC server is rejecting the **Origin**. Add the host Orbit is served from to the server's
WebSocket origin allow-list (e.g. InspIRCd `<wsorigin allow="https://chat.example.org">`).
## I don't see the latest version after a deploy
A service worker is serving the cached build. **Bump the cache version** in `public/sw.js` on every
deploy (`const CACHE = 'app-vN'`) — the worker then auto-reloads installed clients. Or hard-refresh.
## Notifications don't work
- The browser must grant permission (Settings → Notifications).
- Web Push when the tab is closed needs the server's `draft/webpush` support (VAPID) **and** you to
be logged into an account (subscriptions are stored per account).
## The keyboard covers the input on mobile
Orbit tracks the visual viewport to keep the composer above the keyboard. This works best on
Chromium-based browsers; some other mobile browsers report the viewport late.
## Channels start empty
Server-side history needs `draft/chathistory` + a storage backend on the server, and the channel
must have history enabled (e.g. InspIRCd `+H`). Without it, channels begin empty.