Orbit documentation: docs, wiki and FAQ pages
This commit is contained in:
commit
bcbe5fff22
18 changed files with 714 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
__pycache__/
|
||||||
15
README.md
Normal file
15
README.md
Normal 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
40
docs/architecture.md
Normal 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
51
docs/branding.md
Normal 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
100
docs/compiled-plugins.md
Normal 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
44
docs/config.md
Normal 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
35
docs/deploy.md
Normal 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
37
docs/ircv3.md
Normal 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
29
docs/overview.md
Normal 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
99
docs/plugins.md
Normal 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
38
docs/push-to-deploy.md
Normal 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
46
docs/quick-start.md
Normal 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
51
faq.md
Normal 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
29
wiki/commands.md
Normal 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
22
wiki/formatting.md
Normal 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 1–2 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
31
wiki/orbit-channel.md
Normal 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
17
wiki/themes.md
Normal 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
27
wiki/troubleshooting.md
Normal 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue