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