Naming rules

How LYNX names modules, parts and signals across firmware, electronics, Blender sources, tools and documentation. The names live in the files listed under one authoritative mapping. The generated module lookup maps every module's full name and code to its files.

Full names for discovery; compact names for repeated work

Someone encountering LYNX for the first time should be able to identify a module, find its PCB project and open its Blender source without learning abbreviations. Use full names in navigation, headings, module directories, source filenames, standalone CAD parts and distributed exports. Introduce the compact code beside the full name; use the code in commands, internal references and constrained labels.

Consistency means the same concepts, word order and relationships across domains. Formatting may follow the language or application. Do not expand every local variable into a complete hardware description when its enclosing type or module already supplies that context.

Put the side first

For a side-specific item, use this order:

Side → module family → variant → part or signal.

Full display nameCompact codeDescriptive filename stem
Left finger module, 21 keysleft-F21left-finger-module-21-keys
Right finger module, 20 keys + scroll wheelright-F20Wright-finger-module-20-keys-scroll-wheel

Use left and right from the user's hand perspective, regardless of the current CAD view or PCB face. Keep these words spelled out in module codes and filenames. Within a parent explicitly named Left hand unit, a child may display simply “Finger module, 21 keys.” A standalone file or exported part needs the side.

Every module is made for both hands, so a user can build each hand from any available module per slot. A variant is a hand-independent design (F21: “Finger module, 21 keys”); finger and thumb modules exist as mirrored left and right builds, and the side names the build: left-F21, its mirror right-F21. The Assembly catalogues the modules that exist and records which of them one device is fitted with, not which variants a hand can take.

Shared definitions and sources that genuinely serve both sides have no side prefix: bottom modules fit either hand as they are, so “Bottom module, mouse sensor PMW3389” is code BM-PMW3389, stem bottom-module-mouse-sensor-pmw3389. A part number keeps its maker's hyphen in the name and drops it from the stem: “…mouse sensor ADNS-5050” has stem bottom-module-mouse-sensor-adns5050. If a bottom part becomes side-specific, prefix that part with its side.

Compact module nomenclature

  • F means finger module; T means thumb module; B means bottom module.
  • A number following F or T counts ordinary keys.
  • W means a scroll wheel. F20W has 20 ordinary keys plus a scroll wheel; its wheel click is additional to those 20 keys.
  • Use uppercase feature letters consistently: F20W, never F20w.
  • BM means bottom mouse module, followed by the sensor model. Define additional codes when actual variants need them; do not invent a complete vocabulary for hypothetical hardware. The gyro bottom module is BG (“Bottom module, gyro”, stem bottom-module-gyro): one gyro, so no sensor model in the code until a second one exists.
  • Side, module variant and hardware revision are separate attributes. A key count is not a revision. Preserve revision and manufacturing provenance when renaming files or preparing releases.

Tables distinguish ordinary keys, wheel clicks and total switch inputs: the right finger module's matrix carries 21 switch inputs, its wheel click included.

Filenames, PCB projects and Blender parts

Use lowercase hyphen-separated descriptive stems for module paths, filenames and standalone Blender module objects/collections. Keep normal application extensions. Use the same module stem across PCB files, Blender parts and exported artifacts.

electronics/boards/left-thumb-module-11-keys/
    left-thumb-module-11-keys.kicad_pro
    left-thumb-module-11-keys.kicad_sch
    left-thumb-module-11-keys.kicad_pcb

mechanical/blender/thumb-module.blend
    left-thumb-module-11-keys-cover
    right-thumb-module-11-keys-cover

thumb-module.blend and finger-module.blend each contain both sides: one shared source per slot. Do not duplicate Blender sources just to obtain one file per variant. A module's navigation entry identifies both the source file and the object/collection to open inside it.

Use explicit part suffixes such as -cover, -board, and -board-placement; a standalone export would be left-thumb-module-11-keys-cover.stl. The printed parts a cover takes are placed per module as -keycaps-placement (one -keycap-<key> per board key, a1), -led-plugs-placement (one -led-plug-<designator> per LED, LED1) and, over a joystick, -stick-placement (-stick-dome, -stick-cap); each instances the module file's <source>-print stand-in of the part (Key-Cap-print). The screws a part takes are placed per joint as -screws-placement (one -screw-<n> per hole, numbered as its cutter or Lid-Screw-N-Pos; -washers-placement, -washer-<n> under the gyro's), each instancing references.blend's drawing (References-Enclosure-Screw). These placements and the -board-placement sit in the part set's imports set, <set>-imports (left-finger-module-imports; a module fitted to no slot has its own set, named by its stem: left-thumb-module-4-keys-joystick-imports). A bottom module is placed in the imports of the lid that holds it, its placement side first (left-lid-mouse-imports: left-bottom-module-mouse-sensor-pmw3389-refs-placement). Other side-specific parts and containers follow the same order (left-casing-body, right-finger-module). Shared helpers keep contextual names such as Finger-Key-Grid or Lid-Screw-1-Pos where they remain unambiguous. Blender datablocks may be shared or globally named, so a collection parent alone is not proof that a short name is safe; check every lookup before shortening it.

Electrical signals describe their function

A signal is named <SLOT>_<FUNCTION>[_<INDEX>] from the hand unit microcontroller's perspective: FINGER_MATRIX_ROW_1, FINGER_MATRIX_COLUMN_6, FINGER_SCROLL_ENCODER_A, THUMB_LED_DATA. Prefer row/column names because “input” or “output” alone does not explain the function or its viewpoint. The compact alias (FI1, FO6, WA, LED_DATA) stays on the space-constrained silkscreen and in the frozen sources, mapped to the canonical name in wiring-harness.toml and the generated pinout.

The shared signal definition has no side prefix. Both hand units implement the same interface vocabulary on their own circuits. A wiring reference supplies the instance context, for example:

Left finger module, 21 keys → connector J1, pin 7 → matrix row 1 → GPIO38.

If a future assembly schematic includes both hands in one net namespace, scope or qualify the instances so identically named local signals are not accidentally joined. Reusing an interface name does not mean the two hands share an electrical net.

Slot, matrix axis/index, electrical direction and GPIO are explicit metadata. Direction is relative to the hand unit microcontroller: matrix rows are inputs with pull-ups; columns are driven outputs. Do not infer these facts by parsing letters in a name.

Keep these addresses distinct:

  • Connector pin number, such as J1 pin 7.
  • Human-facing matrix row/column number, starting at 1.
  • Firmware matrix index, starting at 0.
  • GPIO number, such as GPIO38.
  • Physical key identity and key binding.

An electrical row is not necessarily a visually straight row of keys.

Keep standard electrical names such as GND and 3V3, and standard component designators such as J1, SW1, D1 and LED1. Add descriptive fields where useful.

Code and control identities

Use the same domain vocabulary in Rust, Python, shell and structured data, with their normal formatting conventions. For example, FINGER_MATRIX_ROW_1 is a net name; finger_matrix_rows is a descriptive Rust binding; rows is sufficient inside FingerPins. A signal group does not need a variable for each named net when an ordered array already expresses its meaning correctly.

Physical control paths such as right.fingers.index.button_2 and compact labels such as Ri2 already provide side-first identity and have consumers in the keymap and Vial. Do not replace control identity with a module filename, matrix address or assigned key.

Read finger, fingers, F and Finger by meaning and scope; do not globally replace substrings. The physical slot, installed variant, PCB, Blender part and Rust module are related concepts, not interchangeable names.

Firmware feature names such as fk, fkw and tk select capabilities and are not module identifiers; the command help explains them. Renaming a feature or a CLI word is its own decision, with its callers and compatibility.

Setup roles

A setup is one or two cats, with or without a tower. Its devices are the Left Cat, the Right Cat and the Tower (physical devices; left and right are the user's hands). What a device does in the setup is its role, a firmware concept, chosen at build time:

  • The Coordinator owns the setup's Input Engine (keymap, layers, pointer, settings) and its USB connection to the computer. Exactly one per setup: the tower when there is one, otherwise a cat (the cat feature coordinator).
  • A Satellite captures input and sends it to the Coordinator over ESP-NOW. Zero or more per setup. Plugging a Satellite into USB powers it; it does not make it the Coordinator.

DeviceRole (LeftCat, RightCat, Tower) stays the wire identity of a device; Coordinator and Satellite are not wire roles. Battery presence is independent of the role: a cat without a battery runs from its USB cable (to the computer, or a USB power supply for a Satellite) and so fits no power switch, and its lid has no holder for either. The two ends of the ESP-NOW link are the Satellite's (cat::link) and the Coordinator's (coordinator_link). On the wire and in the provisioned records the Coordinator end keeps its historical name, tower (target_tower, CatLinkRecord::tower_mac, TowerPairingRecord): a Coordinator cat answers as the tower of its pair.

A setup manifest writes one setup down: its cats, its tower, each cat's modules and battery in build.toml's choice words, and which device is the Coordinator. ./lx setup plan resolves it into each device's build (examples).

The firmware is described by responsibilities, used as names in code where they own something: capture (scan controls and sensors), link (exchange input and state between devices; ESP-NOW), input engine (input_engine: keymaps, layers, keyboard and pointer processing), usb (HID and Vial), feedback (layer, lock, battery and connection state on LEDs or a screen), power, configuration (topology, boards, modules, roles) and storage (keymaps, settings, provisioning records). They are responsibilities, not one crate each; existing names (drivers, slots, protocol, supervisor) stay.

One authoritative mapping

  • LYNXware.toml owns physical identity and module metadata. A variant (the compact variant code, F21) is described once in [variant.*]; each module built of it is one [[module]] entry (its side, or none for a shared variant, and its legacy code), which keeps the module's identity whichever slot it is fitted to, or none. A slot names only the variant it fits; assembly.py derives the code, full name and stem. Derive display names and stems; do not author competing copies.
  • electronics/wiring-harness.toml owns the connector contract and signal vocabulary, checked against the board netlists and firmware GPIO assignments.
  • mechanical/associations.toml owns links from Blender objects to their source files, modules and firmware. It names a module by module (the module code) and purchased hardware by part.

Keep those ownership boundaries; do not duplicate the whole mapping in each file. Resolve a new schema ambiguity together with its readers.

Legacy names

The renames ran as one-shot scripts, removed after use; the commits keep them:

  • 2026-09-24, Blender: node groups localized, names normalized, the part files split (commit 5105493, with its rename maps).
  • 2026-09-25, side-first names: board directories and files renamed to the stem, project, title, sheet and connector-signal tokens rewritten, every KiCad UUID kept; the Blender datablocks renamed by rename_map_sides.json (commit 341456c holds scripts and map).
  • 2026-09-26, Blender flattening: each file's entry collection dissolved into its top level, left-lid-body split into left-lid-plain-body and left-lid-bowl-body; every object's evaluated geometry compared before and after with tools/blender/inventory_ids.py, and the milestones hold the files before it.
  • 2026-10-06, the bottom module in its lid: the bowl gave way to a lid per kind of bottom module, and bottom-module.blend dissolved into lid.blend. Lid-Board-Bowl-Origin became Lid-Board-Pos; left-lid-bowl-body / -bowl-skates-body (and their sets and right twins) left-lid-gyro-body / left-lid-mouse-body; the bases (bottom-module-mouse-sensor-pmw3389-base, Bottom-Gyro-Base) gave way to the lids' Lid-Bottom-Module-Holder and their modules' cuts; bottom-module-mouse-sensor-pmw3389-refs-placement is left- / right-… in the mouse lid's imports. The renames were compared through their map with inventory_ids.py (commit 341456c) before anything was reshaped.

The names from before remain only where they must:

WhereWhy
Each Assembly [[module]] entry's legacy (F21-left)Tools still accept it. It names the frozen electronics/baseline/<legacy>/ source and seeds every KiCad UUID of the board, so it never changes.
wiring-harness.toml alias, the boards' silkscreen, nets.json keysThe compact names on the boards and in the frozen sources.
electronics/baseline/** and the private EasyEDA archivesThe EasyEDA sources as archived.

Compatibility aliases resolve to one canonical record, never to duplicate boards or source files.