Overview — what this site is and does
This file describes Swiftpoint Hub as a product: who it is for, the four browser tools it ships, the content areas it holds, and the handful of design decisions
This file describes Swiftpoint Hub as a product: who it is for, the four browser tools it ships, the content areas it holds, and the handful of design decisions that are visible in the code and worth knowing before you change anything. It is the shortest path through the documentation — 02 Project structure picks up where this ends.
What this is
Swiftpoint Hub is an unofficial, non-commercial community hub for the Swiftpoint Z3 gaming mouse. It runs on its own server and has no cloud dependency on Swiftpoint: nothing about the site's operation requires an API, a licence or an account with the manufacturer.
The thing people come here for is the .spcf file — the mouse configuration document the Swiftpoint Control Panel exports. The site lets people upload one, and stores it byte-for-byte, so that the file you download is the same file somebody uploaded. Around that, it provides:
- a searchable, filterable gallery of community profiles, each parsed and rendered as bindings rather than as an opaque download;
- a forum whose support categories turn every thread into a ticket with a status and a trail;
- an updates list split into "what changed on this site" and "what changed in the mouse";
- a guides site served from markdown files in the repository;
- four tools that run in the browser.
The whole thing is one Node.js process, one Express app and one SQLite file. There is no build step, no bundler and no front-end framework; the front-end is plain classic scripts in public/.
The footer of every page carries the sentence that is the site's actual product promise:
Unofficial community hub · not affiliated with Swiftpoint Ltd. · .spcf files are stored 1:1 and download identical to the original.
Who it is for
| Audience | What they use it for |
|---|---|
| Players who already own a Z3 | Browse and download profiles made by other people; read the guides; ask for help in the forum. |
| Players who want to build one | The Profile Builder, the OLED editor and the walkthrough recorder, all in the browser, with a file to download at the end. |
| People who want their profile to be findable | Sign in with Discord, upload, and get a page at /profiles/<id> with votes, comments and a social card. |
| People with a problem | Open a thread in a support category; it becomes a ticket with a status, and a moderator picks it up. |
| Whoever runs the site | The admin panel: statistics, staff, the upload limit, the guides editor, the report queue and the recordings. |
Nobody needs an account to read, download, build, draw or check anything. An account is needed to contribute: upload a profile, write in the forum, comment or vote, or watch a thread.
The four tools at a glance
| Tool | Route | What it does | Needs an account? | Uploads anything? |
|---|---|---|---|---|
| Profile Builder | /editor |
Creates and edits a full .spcf document: groups and sub-profiles, button bindings, deep-click pressure zones, tilt and pivot triggers, nested bindings (a tree of gated triggers — a binding under another runs while it is held, not after it), DPI levels, device settings, OLED actions. Downloads the file, or publishes it to the gallery. |
No to use it; yes to publish | Only on publish (POST /api/profiles) or save-as-own (PUT /api/profiles/:id) |
| OLED editor | /oled |
A 64×32 monochrome pixel editor for the Z3's display: frames, layers, keyframes, onion skin, text and a symbol set, exports to PNG, a frame sheet, a ZIP of PNGs and a copy-to-clipboard image. Can also inject the animation into your own .spcf as a chain of oledFlash actions and download that. |
No | No — everything is local; the only outbound call is the one it makes to fetch a file you chose |
.spcf checker |
/check |
Opens a .spcf and renders every group, button mapping, force stage, nested-binding tree, tilt trigger, device setting and OLED frame. Parses in the browser using the same parser the server uses. |
No | No — "the file never leaves the device" |
| Walkthrough recorder | /editor (admin only), managed at /records |
Hooks into the Builder, watches the document change, and produces a .rec.json of steps with screenshots and semantic change descriptions that can be embedded in a guide or a support answer. |
Yes, admin only | Yes — POST /api/records |
The first three are local-first: the file lives in the browser tab until you do something with it. The recorder is the exception, because a recording is authored content for the guides, and the Builder's route sets <body data-can-record="1"> only for an admin session.
The content areas
| Area | Route | What lives there |
|---|---|---|
| Front page | / |
What the Z3 is and what this site has for it; the tool cards; the latest update. |
| Profile gallery | /profiles/hub |
Every stored profile as a card, with search, sort, game/tag filter, and an upload button. |
| Profile detail | /profiles/:id |
One profile: author, votes, comments, parsed bindings per group and sub-profile, deep-click depth bars, tilt triggers, device settings, a share image and a download of the original file. |
| Member page | /users/:id |
One account's public activity: profiles, comments, join date. |
| Forum index | /forum |
Categories with their threads, the open-ticket queue, and recent activity. |
| Thread / ticket | /forum/t/:id |
Posts, replies, and — in a support category — the status bar, the ticket trail and the moderation controls. |
| Updates | /updates |
One list, two kinds (website, mouse), filterable and pinnable. |
| Guides | /docs, /docs/<slug> |
The markdown guides from docs/, with a tree, tags, an outline rail, a notes map and per-guide social cards. |
| Legal | /privacy, /imprint |
Generated from the code by lib/legal.js, so they cannot claim anything the implementation does not do. |
| Admin | /admin |
Statistics, staff management, settings, the guides editor. |
| Recordings | /records |
Admin-only management of authored walkthroughs: edit, delete, publish to the repository. |
How the pieces fit together
A visitor's journey, end to end:
- They land on
/. The server readspublic/index.html, replaces the<title>, the description and the canonical, and returns it. There is no inline script;js/chrome.jsattaches the theme switch and the mobile drawer to markup that is already in the file. - They follow Browse. That is
/profiles/hub, served fromgallery.htmlby a route registered before/profiles/:idso that the word "hub" is never read as an id.js/app.jscallsGET /api/profiles, which queries SQLite, and paints the cards. - They open a profile. The route reads the row, renders the stored parsed summary, and serves the same
profile-beta.htmlshell with the title and canonical rewritten. Votes and comments need a session; the download does not, and streams${format_header}\n${raw_json}back as anapplication/octet-streamattachment. - They hit a problem in-game. They open a thread in a support category, which is stored as a normal thread plus a status and a first ticket event. A moderator sets the status, which writes a row into the ticket trail and notifies the watchers.
- They want to build something instead of downloading it. They open
/editor, load or create a profile, and everything they do is in memory plus alocalStoragedraft. When they publish, the BuilderPOSTs the serialised.spcf; the server validates it withlib/spcfParser.js, stores the original bytes and a parsed summary, and hands back a row.
The request path, in one picture:
browser
│ GET /profiles/hub (or POST /api/profiles/:id/vote, …)
▼
nginx ── /swiftpoint/ ──────────▶ 127.0.0.1:3000
│ │
│ ┌─────────────────┴──────────────────┐
│ │ server.js (one Express app) │
│ │ 1. express.json limit 64 MB │
│ │ 2. session cookie + SQLite │
│ │ 3. security headers (CSP, …) │
│ │ 4. cache-control │
│ │ 5. /api/auth /api/profiles │
│ │ /api/notifications │
│ │ /api/reports /api/admin │
│ │ /api/users /api/forum │
│ │ /api/records │
│ │ 6. routes/docs.js → /docs/… │
│ │ 7. routes/seo.js → every page │
│ │ 8. express.static(public) │
│ │ 9. error handler │
│ └─────────────────┬──────────────────┘
│ │
│ ┌───────────────────────┼───────────────────────┐
│ ▼ ▼ ▼
│ routes/*.js lib/*.js lib/docs/index.js
│ HTTP + permissions plain data in/out read docs/**/*.md
│ │ │ │
│ └───────────┬───────────┴───────────┬───────────┘
│ ▼ ▼
│ lib/db.js (better-sqlite3) renderDoc()
│ │ │
│ ▼ ▼
│ data/swiftpoint-hub.db HTML string
│ │ │
└──────────────────────────┴───────────────────────┘
▼
HTML back to the browser
Two consequences of that diagram are worth stating outright. First, a running process only loads code at start, so a route you just added 404s with Cannot GET … until the service restarts. Second, backing up the site means copying one file: data/swiftpoint-hub.db.
Design principles that are visible in the code
These are not aspirations in a readme; each one is enforced somewhere you can point at.
CSP-safe, no inline JavaScript. script-src 'self' means an inline script or an event-handler attribute does not run. Every behaviour is a file in public/js/ loaded with src=. js/theme.js is deliberately a separate file loaded without defer, so data-theme is on <html> before first paint instead of flashing dark. test/dom-contract.test.js asserts that every element a page script reaches for actually exists in that page's markup, which is the price of building a page in HTML and enhancing it afterwards.
One source of truth per concern. The header is defined once in lib/header.js and written into the twelve static pages by npm run sync:header; npm test runs check:header first and fails if a page has drifted. The footer is lib/footer.js, rendered into the markup rather than built by JavaScript, so the disclaimer survives a blocked script. The allowlists are read in lib/admin.js and nowhere else, and test/admin-access.test.js fails if a second module parses them. The share paths that several files must agree on are constants in lib/paths.js.
Byte-exact file storage. An upload stores the header line in profiles.format_header and everything after the first newline in profiles.raw_json; the download route writes them back joined by a single \n and serves it as an attachment. The file that comes out is the file that went in, which is what the footer promises.
Local-first tools. The checker parses in the browser. The OLED editor never talks to the server. The Builder keeps its draft in localStorage and only posts when the user asks it to. The shared parser, nesting core and writer are UMD files served to the browser at /js/spcfParser.js, /js/nesting.js, /js/spcfWriter.js and /js/step-diff.js, so the server and the browser cannot disagree about what a .spcf means — including what a PredecessorId means, which is input nesting and not the sequence the old code and the old docs claimed.
Accessibility, to the extent it does not cost anything. Spoilers are <details>/<summary> with no script involved. Icons carry aria-hidden, controls carry aria-label, interactive controls are real <button> and <a> elements, and the guides, the OLED editor and the docs viewer all document keyboard shortcuts. The profile detail page's group navigation is a list of links, so it works before any script runs.
Every page is a real URL with a canonical. /profiles/hub and /profiles/:id, /forum/t/:id, /docs/<slug>, /og-image/<variant>.png, /sitemap.xml, /robots.txt — each one is a URL you can send to somebody. Canonicals are rewritten per route, case-insensitive guide slugs redirect permanently to the one address the sitemap uses, and the two wordmark variants redirect to the clean paths.
What it is not
- Not affiliated with Swiftpoint. No part of this site is operated by, endorsed by, or connected to Swiftpoint Ltd. The word appears once, in the disclaimer.
- Not dependent on a Swiftpoint service. There is no API call to the vendor at runtime, no licence check and no telemetry leaving the machine. The server needs Node, a disk and nginx.
- Not a password-based account system. Sign-in is Discord OAuth2 with the
identifyscope, and the site stores the Discord user id, username and avatar — never a password, never a Discord token. WithoutDISCORD_CLIENT_ID/DISCORD_CLIENT_SECRETthe site stays fully readable and only the write actions are disabled. - Not running anyone's analytics vendor. The only traffic measurement is a first-party counter in
lib/analytics.jsthat HMAC-hashes the visitor IP withSESSION_SECRETand stores the hash and the path in SQLite. No third-party script is loaded at all, with exactly one exception:https://www.youtube-nocookie.comis allowed inframe-srcso that a::: videoblock in a guide can embed a video. - Not storing anything about you beyond what a contribution requires. No profile file is read or executed server-side beyond parsing, no forum post is rendered as HTML, and the legal pages are generated from the implementation so they cannot overstate any of it.
Next: 02 Project structure.