Glossary — the vocabulary of this codebase
Every term used across this documentation, defined once. Terms are grouped by subject rather than strictly alphabetically inside each group, but the groups them
Every term used across this documentation, defined once. Terms are grouped by subject rather than strictly alphabetically inside each group, but the groups themselves run A to Z: the .spcf format, then the site and its code. Where a word has a codebase-specific meaning that differs from its everyday one, the definition says so.
The .spcf file format
.spcf — The configuration document the Swiftpoint Control Panel exports. A text file whose first line is a format header such as json,2,4, followed by a JSON body. The hub stores both parts separately (profiles.format_header and profiles.raw_json) and rejoins them with a single newline on download, so the file that comes back is byte-identical to the one that was uploaded.
RootProfile — The top-level object inside a .spcf document. It holds the profile name and a Groups array; a file without a RootProfile containing at least one group is rejected by lib/spcfParser.js.
Group — A named section of a profile: one game, one application, or one context. A group owns its own ActionMappings and its own device settings, which is why two groups in the same file can behave completely differently.
Sub-profile — A Group nested inside another Group. The Swiftpoint Control Panel presents these as a sub-profile tree; on the site they render as their own navigation row showing the associated game or application, and they inherit the device settings of their base group.
ActionMapping — One rule inside a group: an input side that says what you do, and one or two output sides that say what happens. The field PredecessorId nests one mapping under another, which gates it rather than sequencing it.
InputActionType — The trigger half of a mapping. The site understands six user triggers plus the hidden settings trigger: buttonToggle, forceRegionToggle, imuAngleRegionToggle, imuAngleRange, scroll and systemEvent.
OutputActionItem — The action half of a mapping. Roughly two dozen kinds exist, from keyboardKey and mouseButton through dpiConfigure, oledSet, oledFlash, wait and gotoOutputAction.
Activation list — The ActivationOutputActionItems of a mapping: the actions that fire when the trigger engages. In the guides this is written "on activation". In this codebase a mapping with an activation list and no deactivation list stays active until something else stops it.
Deactivation list — The DeactivationOutputActionItems: the actions that fire when the trigger releases. A stopLooping item here is what ends a loop that would otherwise be endless.
trigger — What you physically do for a mapping to fire: a button press, a press past a pressure threshold, a tilt, a pivot, a scroll. The guides use "trigger"; the Builder's internal name is the input side of a mapping.
System event — A trigger the player does not perform. The important one is profileChanged, the hidden mapping that fires when the profile activates, and which is where the device settings (DPI levels, report rate, lift-off, LED, OLED mode, auto-release, tilt auto-zeroing) are stored.
On-load outputs — The actions on a profileChanged system event that are not device settings: keys, media keys, haptics, a DPI switch, a jump-to-action, a drawn OLED frame. The parser separates them from the settings so the UI can show exactly what runs the instant a profile activates.
PredecessorId — The field that nests one mapping under another: it is a zero-based index into the group's own raw ActionMappings array, and the mapping it points at is active while that mapping's trigger is held. The value 65535 means "no parent". It is not "runs after" — the format has no ordering and no timing, so it cannot say what runs when.
Predecessor — The other half of the same link, and pure duplication: a byte-identical recursive deep copy of the parent mapping, null for a root. Every chained mapping in the store carries it and nothing in it says anything the index does not. The writer's applyNesting() regenerates it from the array, which is verified byte-identical on all 60 groups of all 48 stored profiles.
Input nesting — The format's only structure inside a group, and what PredecessorId means: a tree of gated triggers. A binding indented under another is live exactly as long as the trigger above it is held. Nesting is arbitrarily deep (the limit is MAX_NEST_DEPTH, 20 edges) and branches freely — 31 of the 196 chained mappings in the store are branching parents. There is no if/else, no priority and no timed order; the array order is the Control Panel's insertion order and carries no meaning. Everything renders under the root key, so the button card of a binding that is only ever a child stays empty.
Nesting tree — How the parser exposes that structure: group.nesting = { count, roots, children, parentOf, depthOf, maxDepth, issues, order, nodes, pressStages }. Every key is a raw ActionMappings index — there is exactly one index space in the parser, so a nested binding appears in buttonMappings too rather than being filtered out of the flat lists.
Gate — What a nested binding waits on, in words. A renderer says while Left Button held, never after Left Button pressed. The one exception is a button's own press stack (its light press and its deep zones), where the link is a force threshold rather than a gate and the deep-click ladder draws it instead.
Press stack — One button's light press plus its own deep zones, linked by PredecessorId. It is a depth axis, not gating: the same link field means two different things depending on whether both sides are on the same control. nesting.singleButtonStackMembers() is the single definition, shared by the parser and the Builder, and a run made entirely of press stages is not drawn as a nesting tree.
Branch — A parent with more than one follower. Normal, not exotic: 31 of the stored profiles' parents branch, and a traversal that follows only the first follower at each hop silently loses mappings — that is how the old reader dropped 5 of profile 69's 9.
Loop — In this codebase, a gotoOutputAction output action with LoopCount 0, which jumps back to an earlier action index and repeats indefinitely. A LoopCount above 0 repeats that many times. It is not a control-flow construct the site evaluates; it is a field it reads and displays, because the mouse evaluates it. A loop is only safe if a stopLooping deactivation item ends it when the button is released.
Gated scroll — A scroll binding with a PredecessorId. That is a modifier combo: hold Top Thumb and scroll to cycle DPI levels. A scroll trigger carries no Button key of its own, so the parser files it under the button that gates it and every surface labels it while <button> held. It used to have its own classification — the parser refused to call such a chain a sequence — and that special case is gone, because no chain is a sequence.
Deep-click zone — Several forceRegionToggle steps on the same button, so that one physical button fires different actions at different press depths. The Z3 has a tactile dash halfway down the force axis; a press that stops before it is a light press, and pushing through it is a deep press. The profile detail page draws this as a depth bar with the exact threshold in percent.
Light press — The 0 % to the first threshold segment of a button's force axis: everything before the first forceRegionToggle entry point. On the site it is rendered as the first segment of the depth bar, labelled Light press.
Force stage — One band of a button's 0–100 % force axis defined by a forceRegionToggle region, with separate entry and exit percentages so the trigger can behave differently entering and leaving. The names come from the device, not from this site: a stage is what the site calls a deep-click step.
forceRegionToggle — The trigger type for a pressure band: a button, a direction, and a region with entry and exit start/end percentages. The percentages are stored in tenths (EntryStartPercentageTenths) and divided by ten by the parser.
buttonToggle — The trigger type for an ordinary press, carrying the physical button and the active state.
Tilt / roll — Rolling the mouse left or right, like banking an aeroplane. The Builder's label is Tilt (roll), and the parser's axis is imuAngleRegionToggle / imuAngleRange with the roll axis.
Pitch — Nosing the mouse up or down. The Builder's label is Tilt forward/back (pitch).
Pivot / yaw — Swivelling the mouse on the spot, pointing left or right without moving it. This is the axis people use for free-look, turning with a thumb instead of flicking the mouse.
IMU — The inertial measurement unit in the Z3 that measures how the mouse is held. It is what makes tilt, pitch and pivot available as triggers and as analog ranges.
Auto-zero / tilt auto-zeroing — The device setting that re-calibrates "level" whenever the IMU idles, so a tilt trigger means the same thing on a desk and on a pad. It is stored as a tiltAutoZeroing output action on the profileChanged event. Turning it off and zeroing manually is the way to rely on a fixed starting angle.
DPI level — One X × Y DPI preset the mouse can cycle through at runtime. Levels are a list in the device settings (dpiConfigure), the first one is the default, and a dpiSelect output action can set an absolute value, pick a level by index, or step up and down.
Report rate / polling rate — How often the mouse reports its position to the computer, stored as a reportInterval output action whose value encodes the rate in hertz.
Lift-off — The height at which the sensor stops registering movement, stored as a liftOffSelect output action in millimetres.
OLED — The Z3's 64 × 32 monochrome status display. It is 1-bit: every pixel is either on or off. oledSet sets a persistent display mode, oledFlash shows a frame for a duration and then proceeds.
Binding — What a person calls a mapping: a trigger, the actions on activation, and optionally the actions on deactivation. The guides use "mapping" for the file format and "binding" for the user-facing idea; they are the same object.
Layer — In the Profile Builder, a group or sub-profile as it appears in the left rail. The rail is searchable, because a profile can have dozens of layers and a tree is bad at finding one of seventy.
Keyframe — In the OLED editor, a frame marked as an anchor in the timeline. Two or more keyframes can be played as an animation at the chosen frame rate, or interpolated between. Keyframes are markers, not a separate frame type.
Onion skin — The OLED editor's ghosting of the previous frame in orange and the next frame in cyan, so a position in an animation can be placed against what comes before and after it. A display toggle, nothing more.
Frame — One 64 × 32 image in the OLED editor's animation. Each frame has its own layers and objects. A single-frame project is still an animation with one frame.
Onion-skin ghost colours — [255,150,38] for the previous frame and [70,205,255] for the next, defined at the top of public/oled/js/oled-editor.js.
Recordings and guides
Recording — A recorded walkthrough: a .rec.json document with a title, a list of steps, and a screenshot per step. Produced by the admin-only recorder inside the Profile Builder and managed at /records.
Walkthrough — The same thing from the reader's side: a ::: steps block embedded in a guide or a support answer, with the player from public/js/steps-player.js. The recorder and the player are the two ends of one feature.
Step — One entry in a recording: a title, an image, and the semantic diff describing what changed in the profile at that moment. lib/step-diff.js caps the description at two changes per step, because a step is a beat, not a diff.
.rec.json — The wire format of a recording. Version 1, a steps array, images as data URLs. The site serves it at /docs/recordings/<slug>.rec.json, from the repository file when one has been published and from the recordings table otherwise.
Recordings directory — docs/recordings/, the one place a .rec.json lives on this site. It is at the root of the guides tree rather than inside a guide's folder because a recording is a shared asset and not something a guide owns: two guides may embed the same walkthrough, and it must not live in either of them. A ::: steps therefore always says recordings/<slug>.rec.json and resolves to the same URL from any folder. The old /docs/profile-builder/recordings/… address is gone and nothing redirects to it.
Guide — A markdown file in docs/ with front matter, served at /docs/<slug>. Twelve are public today; eleven are under profile-builder/, and docs/testing.md is the one at the root. The twenty-five files in docs/about/ are guides too — inside the served tree, but closed to the public by a doc_visibility rule.
Wiki — What the engine is flavoured after, not a directory: Obsidian's wikilinks, callouts and tabs. The guides tree is docs/ itself, with no folder in front of it. It is the repository and not a database: an admin editing a guide in the panel writes a file.
Guide slug — The path of a guide below docs/, without the .md. Resolution is case-insensitive, so /docs/profile-builder/Testing finds the same file — but only the canonical spelling is an address; the other is a 301, not a second page.
order — A front-matter key, an integer, that decides where a guide sits in the reading order of the tree. Absent means 999, so anything unordered sinks to the bottom.
Tag — A front-matter value, free-form, that makes a guide reachable at /docs?tag=<tag>. Comparison ignores case and surrounding spaces, so #Tilt and tilt are one tag. The filter is applied when the page is generated, not in the browser, which is why the URL works with JavaScript off.
Notes map — The link-graph overlay on the guides page: every guide as a node, every resolved cross-reference as an edge. Built by graphData() and drawn by public/js/docs-graph.js.
Backlink — A guide that links here, computed from the resolved link set and listed at the bottom of a guide. The inverse of an outgoing link, and the reason the notes map is readable in both directions.
Changelog block — A ::: added, ::: changed, ::: fixed, ::: removed or ::: security block. Five names, three surfaces (guides, forum, updates), one list: CHANGELOG in lib/markdown.js, imported by the docs engine so a sixth kind is added in one place. In the forum they render a badge only for authors allowed to make that claim.
Callout — An Obsidian-style > [!note] block or its ::: note equivalent, with types note, info, tip, warning, danger, success and example. The docs engine uses d-* classes, the forum fr-*, so neither can restyle the other by writing a heading.
Spoiler — A ::: spoiler block, rendered as <details>/<summary> with no script involved. A guide hides the same thing a post hides, which is why the same construct serves both engines.
Wiki link — An Obsidian-style note, label, #section or label. The docs engine resolves it to a real URL; the forum engine has no wikilinks, because a post may not reference a file tree.
::: steps — A header-only block that embeds a recording:
::: steps "Tilt autozero" recordings/tilt-autozero.rec.json
There is deliberately no closing fence: the block is a header and a data reference, and closing it would put a stray ::: at the bottom of every guide with a walkthrough in it.
::: video — Embeds a YouTube video by link or id, optionally with chapters and a range. The only third party the site's CSP allows anything from is https://www.youtube-nocookie.com, and only in frame-src.
Site and content
Ticket — A thread in a category whose kind is support. There is no second table and no second URL: the ticket is the thread, with a status, an optional note and a trail. A thread moved into a support category acquires its status.
Ticket trail — The forum_ticket_events rows: who moved a ticket, when, from which status to which, and the note. Every status change writes a row before the thread is updated, and notifies the watchers and the asker.
Ticket status — One of open, in_progress, on_hold, closed, declared once in STATUSES in lib/forum.js with its label and hint. closed is not final: a ticket can be reopened, and the trail records it.
Update — One entry on /updates. Two kinds: website (something changed here) and mouse (something changed in the mouse — firmware, the app, a default). The split is the feature; a single undifferentiated changelog is a list half of whose entries are irrelevant.
Accent — The fixed colour vocabulary of an update: lime, blue, teal, violet, orange, critical. A fixed list, not a colour picker, because the colour says "look at this first" and that is a decision about meaning.
Category — A forum category. Its kind is discussion (a conversation, no status, ever) or support (a ticket).
Watch / subscription — Following a thread. A subscriber is notified when somebody replies. The asker of a ticket is subscribed automatically.
Report — A member's flag on a profile, a comment or a forum post, with a reason. The queue is admin-only; filing one needs an account.
Notification — A row in the notifications table, pushed to an open browser over the stream, and counted in the header badge.
Roles, accounts and staff
Staff — Anyone with a moderation or admin role. Two sources, and the union is the answer: the .env allowlists and the staff table. .env is the stronger claim.
Admin — A Discord id in ADMIN_DISCORD_IDS, or a staff row with role admin. Admins are automatically moderators. They can delete profiles, read /records and use the admin panel.
Moderator — A Discord id in MODERATOR_DISCORD_IDS, or a staff row with role moderator. Moderators can set ticket status, pin, lock, move and delete threads, edit and delete other people's posts, manage categories, read the ticket queue and the report queue, and write updates. They cannot use the admin panel, open /records, or delete profiles.
Allowlist — A comma-separated list of Discord user ids in .env. It is the bootstrap and the floor: a person on it cannot be demoted or removed from the panel, because the panel writes to the database and a mistake in the database must not lock everybody out.
Discord OAuth2 — The only sign-in method. The identify scope returns id, username and avatar; the site stores those and never a token or a password. Without a client id and secret the site stays readable and the write actions return 503.
BASE_PATH — The path prefix the app is mounted under, e.g. /swiftpoint. It prefixes every mount; BASE_URL is the public origin without the path, and the two are folded together by siteUrl() in both routes/seo.js and lib/docs/index.js.
Reverse proxy — nginx, in front of the app on 127.0.0.1:3000, terminating TLS and passing /swiftpoint/ through unchanged. app.set('trust proxy', 1) trusts exactly one hop so req.ip is the real client and req.protocol is https; trusting more would let a client spoof its own forwarded headers.
Technical terms
CSP — Content-Security-Policy. This site sets default-src 'self', script-src 'self', object-src 'none', base-uri 'self', frame-ancestors 'none' and form-action 'self', plus a stylesheet allowance for Google Fonts and a single named frame-src host. The practical consequence is that there is not one inline script on the site: an inline <script> or an onclick= does not run.
SSE — Server-Sent Events. One long-lived HTTP response per signed-in browser, to which the server writes data: frames when something happens. lib/events.js is the in-process fan-out; routes/notifications.js owns the endpoint, with a heartbeat and X-Accel-Buffering: no so nginx does not buffer it.
HMAC — Keyed hashing. lib/analytics.js HMAC-SHA-256s the visitor IP with SESSION_SECRET, so the stored visitor_hash is stable enough to count unique visitors and useless for identifying one.
UMD — A module wrapper that works both as CommonJS in Node and as a global in the browser. lib/spcfParser.js, lib/spcfWriter.js, lib/nesting.js, lib/step-diff.js, public/js/export-helpers.js, public/js/export.js and public/js/share-render.js are all UMD, and the first four are served to the browser by routes/seo.js at /js/spcfParser.js, /js/nesting.js, /js/spcfWriter.js and /js/step-diff.js. That is the mechanism behind "the server and the browser cannot disagree about what a .spcf means".
Byte-exact storage — The promise in the footer. An upload keeps the header line and the JSON body apart and rejoins them on download, so the retrieved file is identical to the uploaded one rather than a re-serialisation of it.
Working draft — The Profile Builder's copy of its document in localStorage under sph:editor:draft, written 600 ms after each change and read back on boot. It is a draft, not a save: it never leaves the browser, and publishing is still the only thing that uploads anything.
Cache-control policy — The three-tier rule in server.js. Stylesheets, scripts, images and fonts get a day plus a week of stale-while-revalidate; .spcf and JSON get an hour with must-revalidate; everything else, HTML included, is no-cache. Assets are not fingerprinted, which is exactly why they must never be immutable.
HSTS — Strict-Transport-Security: max-age=31536000, sent on HTTPS responses only, deliberately without includeSubDomains because *.mecke.dev must stay reachable.
DOM contract — The rule, enforced by test/dom-contract.test.js, that every element a page script reaches for must exist in that page's markup. It is the price of building pages in HTML and enhancing them, and it fails loudly instead of at runtime in a browser.
Tool strip — The second row of the site header, authored per page in its own markup with the .tbar-* vocabulary. It is the one part of the header scripts/sync-header.js deliberately leaves alone.
Search Console verification — google6a47ec6ec477047a.html in the repository root, the token file Google uses to prove the site is yours. It is not served by the app; it is placed on the domain for the crawler.
Back to the documentation index, or on to 01 Overview.