Skip to content

NayaFlow and NayaCore

How the vendor's desktop app is built and how it talks to the keyboard: its three programs, the HTTP and ZeroMQ bridge between them, the events that reach the device service, what a "Flash Create" really sends, which features reach the keyboard, and the app's hazards. The one thing to know: NayaFlow never reads your board back into its UI, so a NayaFlow flash overwrites whatever another tool stored; and two of its buttons act on the keyboard without a dialog. All facts are for NayaFlow 1.25.1 (NayaCore 6.11.0) unless a row says otherwise; our static work is on the Windows build, checked against the macOS builds of the same version.

Two NayaFlow shortcuts and buttons act immediately

  • Ctrl/Cmd+D on the Hardware Manager page starts a firmware update of both halves, with no dialog unless a warning is present.
  • The Danger Zone buttons (Test and Format SPI-Flash, Clear all keymap data, Clear BLE Devices) were seen to run without a confirmation (one observation, 2026-09-01). "Clear all keymap data" runs 30/10ca, which wipes the keymaps: back up first (see App data).

At a glance

  • Three programs: the Electron app (renderer and main), the Go server flow-bg-server, and the C++/Qt service NayaCore, which does all device I/O.
  • The renderer talks HTTP and SSE to flow-bg-server on a port chosen at every launch; flow-bg-server talks to NayaCore over ZeroMQ publish/subscribe on runtime ports found through shared memory.
  • NayaCore accepts 16 named events; the renderer sends 9 of them.
  • A flash writes only what changed, to the left half only, then verifies by reading back.
  • No account, cloud or vendor server is needed; the only outbound calls are update checks, which fail against GitHub because of an unsubstituted token placeholder.

The programs

NayaFlow 1.25.1 is four layers in three programs STATIC134:

Layer Program Role
Renderer NayaFlow (Electron, React) the UI; talks HTTP and server-sent events to flow-bg-server
Main NayaFlow (Electron main) window, tray, auto-updater; starts flow-bg-server; no action model, no SQL and no ZMQ code
Background server flow-bg-server (Go 1.25.5) HTTP API and SSE, the SQLite user data, templates, backups, component updates, the ZMQ bridge to NayaCore
Device service NayaCore (C++, Qt 6) owns the USB serial ports (binary protocol and the MCUboot SMP upload); reads the user data itself

The chain, in words: renderer -> (HTTP + SSE on a local port chosen at each launch) -> flow-bg-server -> (ZeroMQ publish/subscribe, ports exchanged through Qt shared memory) -> NayaCore -> (USB CDC, dst 0x50) -> left half -> (split link) -> right half. flow-bg-server creates and edits user-data.db; NayaCore opens the same file for reading when a keymap flash hands it the path.

  • Electron main holds the window, tray and auto-updater and starts flow-bg-server; a byte-level search of its bundle finds no action-catalog name, no SQL and no ZMQ code STATIC1.
  • flow-bg-server: Go 1.25.5 with gin, sqlc, goose, mattn/go-sqlite3 and pebbe/zmq4 (libzmq 4.3.5), about 25.7 MB. It serves the HTTP API and SSE, owns the SQLite user data (schema migrations and queries live in it), templates, backups, component updates, and the ZMQ bridge STATIC3. Its port changes at every launch (below).
  • NayaCore 6.11.0: C++ with Qt 6 (Core, SerialPort, Sql with the qsqlite driver), built with MSVC on Windows, libzmq 4.3.6; 8.5 MB on Windows, 12.6 MB (Intel) and 11.9 MB (arm64) on macOS. It owns the USB serial ports and reads user-data.db itself. It was compiled "Jul 17 2026 14:20:59"; its version string format is v%1.%2.%3; its command-line flags are --version and enableFileLogging STATIC45.
  • NayaCore reaches Bluetooth state only through keyboard commands over USB (the be/10xx family); no use of the host's own Bluetooth stack was found INFERRED (no host Bluetooth API names in its strings).
  • NayaCore opens user-data.db with Qt's QSQLITE driver in a reader thread ("Spawned SQLReaderThread with dbPath: %1") when an update_keymap event hands it db_path, reads the profile whose state is ON_BOARD, and reads settings by correlation id with built-in defaults STATIC4.

Where things live

Platform Layout Evidence
Windows (1.25.1) resources/app.asar (dist/main, dist/renderer); flow/flow-bg-server.exe + libzmq-mt-4_3_5.dll; core/NayaCore/NayaCore.exe + Qt6Core.dll, Qt6SerialPort.dll, Qt6Sql.dll, libzmq-v143-mt-4_3_6.dll, sqldrivers/ (qsqlite, qsqlmimer, qsqlodbc, qsqlpsql) STATIC (installed tree of the public installer)
macOS NayaFlow.app (the dmg installs it in /Applications); Contents/flow/flow-bg-server; Contents/core/NayaCore.app/Contents/MacOS/NayaCore; Contents/Resources/app.asar STATIC (1.25.1 release zips)
Where NayaCore sits, by release Windows: inside the asar up to 1.17.3, core/NayaCore/ from 1.19.1. macOS: under Contents/core since at least 1.11.11 (naya_core_project.app; NayaCore.app from 1.14.3); the macOS asars of 1.14.5 to 1.17.3 also carry a leftover copy of the Windows NayaCore.exe STATIC (release installers and zips); details on History

Build provenance. The Windows NayaCore carries 60 source paths under D:\a\NayaCore\NayaCore\... and the bg-server paths under D:/a/NayaFlow/NayaFlow/apps/desktop/bg-server/...: GitHub Actions Windows runners, with NayaFlow a monorepo containing apps/desktop/bg-server STATIC INFERRED (the CI reading). The macOS builds carry /Users/runner/work/NayaCore/NayaCore/... (62 paths in the arm64 NayaCore) and /Users/runner/work/NayaFlow/NayaFlow/apps/desktop/bg-server STATIC5.

NayaCore's source tree (60 files) STATIC4: Naya_Data (Binding, Key, LED, Layer, ModuleConfig, Profile, Slot); Naya_MainController; Naya_SQLReader; Naya_SerialPort with MCUBootWorker (base, CreateLeft, CreateLeft_Modules, CreateRight), NayaDevice, Naya_DeviceManager (+ ClearAllData, ClearBLEDevices, Enqueue, FWUpdate, ModuleFwUpdate, Operation, Pairing, TestSPIFlash), Naya_SerialWorker, Naya_SerialWorkerBroker, ProtocolCDCWorker (Integration_Worker for keyscan events; Process_Worker groups BLE, Firmware, Flash, LED, META, Module, Remap, SysPower, System; Utility Command, MessageQueue, Message, Process); Naya_Threads; Naya_ZMQHandler; main.cpp.

Electron main

Fact Evidence
At start-up the main process asks the OS for two free ports (it binds port 0): one for its own JSON-RPC server, one passed to flow-bg-server as -port <n>. The bg-server port therefore changes at every launch. STATIC1
Observed on Windows: the bg-server listened on port 61957 on 2026-09-01 (IPv6 loopback). Find the current port through the listening socket of the flow-bg-server process (PowerShell Get-NetTCPConnection -OwningProcess <pid>, state Listen). MEASURED (owner's machine, 2026-09-01)
The renderer learns the port from the preload (window.EXPOSED.bgServerPort, the second-to-last argv entry) and falls back to 3001 when it is absent; the update window also receives a webSocketPort. STATIC12
Environment given to flow-bg-server: ELECTRON_USER_DATA_PATH, ELECTRON_APP_NAME, ELECTRON_APP_VERSION, NAYA_CREATE_FW_VERSION (3.41.0), NAYA_MODULE_FW_VERSION (2.3.3), NAYA_CORE_EXE_DIR (resources/../core), GIN_MODE, ELECTRON_RESTARTED, ELECTRON_JSON_RPC_SERVER_PORT_MAIN, ELECTRON_IS_PACKAGED, PATH, TMPDIR, and a GitHub token variable whose value is an unsubstituted build placeholder (not reproduced here). STATIC1
Main's JSON-RPC server (POST /json-rpc) offers quit, abort-quit, restart-main-process, open-in-browser, open-folder, save-file (diagnostics zip, default name nayaflow-diagnostics.zip), check-for-updates, enable-module-battery-indicator-tray, disable-module-battery-indicator-tray, change-zoom-level. Main calls the bg-server's get-setting-value-by-correlation-id and, every 5 s for the tray battery display, get-real-time-naya-device-info. STATIC1

flow-bg-server: routes, events and data

HTTP routes the 1.25.1 renderer calls STATIC2:

Kind Routes
API /api/actions, /api/actions/${id}, /api/components/${type}, /api/diagnostics/report, /api/i18n/ui, /api/info/system, /api/list-userdata-backups, /api/templates, /api/ui/module-settings, /api/ui/settings, /api/ui/state, /api/userdata
RPC /rpc/check-for-updates, /rpc/export-user-data, /rpc/import-userdata-beta, /rpc/logs, /rpc/open-log-folder, /rpc/open-url-in-browser, /rpc/open-userdata-backup-folder, /rpc/respond-to-quit-event, /rpc/restore-userdata-backup, /rpc/send-flash-keymap-request, /rpc/send-nayacore-zmq-message, /rpc/trigger-userdata-backup, /rpc/use-template
  • Route strings in flow-bg-server 1.25.1: /naya-core-zmq, /naya-core-device-info, /naya-core-naya-devices, /export-user-data, /import-user-data, /import-userdata-beta, /list-userdata-backups, /module-settings, a :symbol route parameter (actions), /open-log-folder, /factory-reset, plus bg-server-only fragments /components/start-naya-component-update, /diagnostics/serial-port-info, /rpc/debug, /rpc/server STATIC3.
  • /factory-reset has no caller in the 1.25.1 renderer (0 hits for factory-reset, factory_reset, factoryReset); its string sits among the route strings STATIC23. How or whether it is registered is OPEN (details).
  • Server-sent events on GET /sse: the renderer listens for sse:ui-state-change, sse:naya-devices-stream, sse:flash-keymap-state, sse:device-operation-options, sse:main-process-quit; the bg-server also emits sse:message-stream and sse:naya-component-update-progress; its strings also contain device_list, message-stream, flash-keymap-state, device-info-stream, naya-devices-stream STATIC23.
  • GET /api/info/system returns the versions bundled with the app (createFWVersion 3.41.0, moduleFWVersion 2.3.3, naya_core_version 6.11.0), not what a keyboard reports STATIC MEASURED (owner's machine, 2026-09-01).
  • The action catalog the UI offers lives in flow-bg-server (data.ActionsJSON): 652 records (698 with 46 empty placeholders), 389 distinct action codes; front-end types key 314, shortcut_alias 211, LED 38, mouse 26, value 19, modifier 16, bluetooth 10, out 4, naya 2, trans 2, none 2, and 2 each of the four layer types; served by GET /api/actions?t=<tab>&g=<group>&d=<context> STATIC3.
  • Palette tabs: Basic (Letters, Numbers, Modifiers, Control, Symbols), Extended (Empty, Mouse, Connection, Configuration Toggles, Lighting, System, Keypad, Navigation, International, Locks, Function Keys), Layers (Hold, Toggle, Force, Sticky Layer), Integrations (MacOS, Windows, VS Code Presets, VS Code Presets (Mac)), and a Modules tab for module editing. The JIS input source switches to Japanese labels: 219 _JIS action ids, 42 of them mapping to a different code STATIC32.
  • Templates: Naya Default Windows, Naya Default MacOS, Naya Japanese Windows, Naya Japanese MacOS, each also as a "(07-18-2026)" variant; module templates Naya Touch Windows, Naya Touch MacOS, Naya Touch MacOS 2.0, Naya Track Left, Naya Track Right, Naya Track, Naya Tune Mac/Win, Naya Tune. The template library supports AES-GCM encrypted payloads (no key material examined) STATIC3.
  • Go packages (naya.tech/server/internal/...): app, data (ActionsJSON, I18nFS, ModuleSettingUIsJSON, UiSettingsJSON), db (+gen, MigrationsFS), diagnostics, features (actions, diagnostics, external-component-manager, settings, template, ui, userdata), naya (ComponentRegistry, NayaComponentManager, NayaCoreProcessManager, NayaComponentUpdater: Idle, Initializing, Downloading, Completed, Failed), naya/nayacoreclient, naya/nayadevicemanager (BLEProfile, BLEStatus, FlashKeymapState, NayaCreateDevice, SplitLinkStatus, WarningCondition, WarningType, normalizeVersion), settings, state (UIState, ZoomLevel), tcp (Server, SSEConfig), templates STATIC3.
  • The diagnostics report on Windows lists present USB devices with VID and PID through PowerShell Get-PnpDevice -PresentOnly; a one-click support report arrived in 1.21.0 STATIC DOC36.
  • The bg-server has a field oldBLELibDeprecatedCreateLeftFWVersion (a version boundary for the old Bluetooth library); its value was not found STATIC (details).

NayaCore's ZMQ link is publish/subscribe on runtime ports: it binds a publisher on tcp://localhost:0, writes the chosen port to Qt shared memory ZMQ_CORE_PUB_PORT_SHARED_MEM, reads the bg-server's publisher port from ZMQ_FLOW_PUB_PORT_SHARED_MEM and subscribes to tcp://localhost:<port>. The Go side has nayacoreclient.writePortToSharedMemory, FreePortFinder and BoundPortFinder. Topics: command (requests and their replies), stream:fw_update_status, stream:operation_status_normal, stream:operation_status_fwupdate, stream:device_list STATIC45. nayactl carries the same shared-memory names and topics, found independently9.

  • Messages are JSON. The renderer's only device-control path is POST /rpc/send-nayacore-zmq-message with the body {"messages": [topic, event, ...frames]}, topic always command STATIC2.
  • A {"status": "message sent"} answer from that route only means the bg-server forwarded the message (the literal is in the binary); the outcome is in NayaCore's log ("ZMQ Command response: Event=%1, ProcessID=%2, Status=..., Duration=...ms") STATIC34.
  • The bridge logs every message it sends as "Sending ZMQ message to NayaCore: %+v" STATIC3.

NayaCore accepts exactly 16 events (enum 1-16; 0 is invalid_command_event) STATIC54:

Event Sent by (1.25.1) Payload keys What it starts
quit not in the renderer (sender not identified) none NayaCore quits
update_keymap bg-server, during a keymap flash db_path a SQL reader caches the "flow keymap"
flash_keymap bg-server, during a keymap flash none the keymap flash (below)
repair_flash renderer (Danger Zone) target_devices, target_partitions Test and Format SPI-Flash
clear_data renderer (Danger Zone) targetDevices the ClearAllData operation, which sends 30/10ca
clear_ble_devices renderer (Danger Zone) a target list ClearBLEDevices
start_device_manager none found none
close_device_manager none found none
force_touch_start, force_tune_start, force_track_start renderer (forced module update) see the key list below ModuleFwUpdate (forced)
create_pairing_start renderer target_device_left, target_device_right Pairing
update_module_fw renderer see the key list below ModuleFwUpdate
update_create_fw renderer two empty frames, or a firmware file path FWUpdate
update_fw_files none found
set_handshake_frequency none found frequencyMs the periodic poll interval
  • Anything else logs "Unknown command event:" (in the arm64 build that log entry carries error code 0; the status of the reply needs a running NayaFlow to check). A strings pass finds only 15 event names: the 16th, quit, is built by the compiler from an immediate STATIC5.
  • clear_all_data is not an event: it is the process name the ClearAllData operation gives to the 30/10ca it queues STATIC5.
  • "Clear all keymap data" runs 30/10ca. The Danger Zone button sends clear_data; NayaCore's dispatcher maps clear_data (enum 5) to its clearAllData request, whose chain ends in doClearAllDataOperations, which queues category 0x30 subcommand 0x10ca. Its step names are Clearing, ReadData, VerifyDataCleared, and it logs "Reconnected to device %1 after clear all data." STATIC54. The bytes the button sends are OPEN until a USB capture (details); NayaCore's own construction suggests params 00 00 (see Disassembly).
  • Payload keys NayaCore parses: db_path, target_devices, target_partitions, target_device, firmware_file_path, target_device_left, target_device_right, targetIds, firmwareFilePath, frequencyMs; the Go side uses a JSON tag targetDevices. Match the exact key per event STATIC43. The renderer sends clear_data with the frames ["", "{\"targetDevices\":[]}"] and repair_flash with {"target_devices":[],"target_partitions":[]} STATIC2.
  • A target list must hold 0 or 2 hardware ids ("Invalid target device list (need 0 or 2 HWIDs)"). A hardware id is the half's USB serial string, which equals the fe/1004 reply; NayaCore checks "HWID mismatch - provided port serial (%1) does not match device HWID (%2)" STATIC4 MEASURED (owner's board, 3.41.0).
  • update_create_fw with two empty frames flashes the bundled image for each half and flash generation; a firmware_file_path / firmwareFilePath flashes a given file instead STATIC24.
  • A keymap flash is two events: update_keymap (with db_path) then flash_keymap; out of order NayaCore answers "Flow profile is missing. Please update the keymap first." The renderer triggers both through POST /rpc/send-flash-keymap-request; the exact bg-server sequence is INFERRED STATIC42.
  • set_handshake_frequency (frequencyMs, example "6000") sets NayaCore's periodic fact poll ("Periodic:Fact:%1:%2") STATIC4.

Traffic while NayaFlow runs

  • Connect sequence to the left half: fe/1001, fe/1002, fa/1001, then five Bluetooth reads (be/1008, be/1002, be/1006, be/100f, be/100c), 30/1001 (params 00 00), 30/1003 per layer with the continue loop, 30/1009, 30/100b for slots 0-4, 30/100d per layer, fe/100b. To the right half: fe/1001, fe/1002, fa/1001, be/1008, be/1002, be/100f MEASURED (owner's board, 3.41.0, NayaFlow 1.25.1, capture of 2026-09-01).
  • Steady poll every 6 s: left be/100c, de/1001, de/1008, de/100b, fe/1006; right the same without be/100c. The keyboard never pushes battery values; the 6 s tick is NayaCore's handshake frequency MEASURED (owner's board, 3.41.0, 2026-09-01).
  • While NayaFlow runs, NayaCore holds every Create port: on Windows a second program gets "Access is denied" MEASURED; on macOS it gets "Resource busy" (EBUSY), because NayaCore opens its ports through Qt's serial-port module, which opens them exclusively STATIC INFERRED (see Troubleshooting). Quit NayaFlow (all three programs) before using another tool.
  • Port sorting. Per half NayaCore keeps a Broker port, a ProtocolCDC port (binary protocol), a SystemCDC port (text protocol) and two MCUboot ports; it sorts ports with a broker that sends aa 00 50 00 fe 03 10 01 00 11 04 (and the 0x51 form) and expects aa 50 00 00 fe 03 10 01 00 11 04 from a left half STATIC4. Details on Disassembly.

What "Flash Create" sends

NayaCore reads the board first, then writes the layer list (changed entries only), each changed layer as one sparse 30/1004 write (00 <layer> + only the changed records), a new layer in full (82 records 0x00-0x51, unbound keys as 0e 00, bays as 78 00), the module config list and data, the LED map, then verification reads, and fe/100a + fe/100b on every flash whether or not the timeouts changed. Payloads over one frame are chunked with the byte-3 countdown MEASURED STATIC (owner's board, 3.41.0: three NayaFlow flashes captured 2026-09-01 and one 2026-09-17; NayaCore's read_profile_before_write_profile and step names4).

Example (two keys on layer 1): params 00 01 24 01 04 04 00 07 00 34 01 04 1e 00 07 00, and the ack echoes layer 01 MEASURED (capture, 2026-09-01).

Fact Evidence
The order of NayaCore's write steps is _remapWriteLayerList, _remapReadLayerData / _remapWriteLayerData, _remapWriteModuleConfigList, _remapReadModuleData / _remapWriteModuleData, _remapReadColorData / _remapWriteColorData; the captured order matches. STATIC4 MEASURED
Every REMAP write NayaFlow sends goes to the left half (dst 0x50); zero REMAP frames went to 0x51 in any captured flash. The left half's LED map has 136 entries per layer and covers both halves (0-73 the keys of both halves, 74-80 and 81-87 the side bars, 88-111 the left bay, 112-135 the right bay), so there is no separate right-half color path. MEASURED (owner's board, 3.41.0, captures 2026-09-01; band painting 2026-09-08)
A single-key color change goes out as one sparse 30/100e write with params 00 <layer> <KK> <hue lo> <hue hi> <sat>. MEASURED (capture, 2026-09-01)
NayaFlow never imports a board's keymap into its UI: it shows its own profile and reads the board only to verify, so "Flash Create" pushes its profile over whatever another tool stored. MEASURED STATIC (owner's machine, 2026-09-01)
Only the profile marked active (profiles.state = 'ON_BOARD') is flashed; the Flash button is disabled with unsaved changes and when the edited profile is not the active one. Labels since 1.19.x: "Active profile (flashes to Naya device)" / "Inactive profile (saves to NayaFlow only)". STATIC43 DOC6

Which features reach the keyboard

Feature What reaches the keyboard Evidence
Scan mode, LED maximum brightness, LED action override A keymap flash sends no ED frame, but the three settings are real device commands: ed/1012 scan-mode PWM, ed/1013 persistent maximum brightness, ed/1014 LED action override (0 until keyboard restart, 1 until the next layer change). NayaCore names them SET SCANMODE PWM, SET LED MAX BRIGHTNESS, SET LED LAYER OVERRIDE and checks scanmode_pwm and led_layer_override in its settings verify; the 1.25.0 notes say NayaCore 6.11.0 "added protocol support" for them. When NayaCore sends them is OPEN. MEASURED (owner's board, 3.41.0, 2026-09-13/16) STATIC4 DOC6
Per-layer LED animations Sent: byte 2 of each 20-byte layer-list entry [idx][id][animation][10][uuid16] holds the animation in ZMK underglow order (0 solid, 1 breathe, 2 spectrum, 3 swirl). Two NayaFlow flashes with known inputs read back 1, 3, 2 and 0, 1, 3. The animation_id column came with migration 20260421070750_add_animation_to_layers; an unset animation reads NULL there. The registry names SOLID, SWIRL, BREATHE, SPECTRUM and the icons LED_SOLID, LED_BREATHE, LED_SWIRL, LED_SPEC exist. MEASURED (owner's board, 3.41.0, 2026-09-08/09) STATIC DOC (1.25.0 notes: "per-layer predefined animations"6)
Macros Host-only. The bg-server has full macro CRUD and macro tables, the renderer ships macros switched off, and NayaCore has no macro read, write, SQL query or serializer; a key bound to a macro is flashed as NONE ([KK] 07 00, captured 2e 07 00). The firmware keeps no macro table: 30/1005 returns one status byte, macro writes are acked and dropped (3.41.0), and 3.28.7 answers the macro opcodes with status 11 (unimplemented). MEASURED (owner's board 3.41.0, 2026-09-07; donor board 3.28.7, 2026-09-19) STATIC
"naya" actions The 1.25.1 catalog has one, MODULE_FORCE_CHARGING ("Activate Module Recovery Mode", group Configuration Toggles), and it reaches the board as record type 06 with id 401: 3e 06 04 91 01 00 00 at position 62 of the stock System layer. The MacOS, Windows and VS Code groups on the Integrations tab are chords flashed as ordinary 01 key presses, so plain HID is the intended result for them. MEASURED (owner's board, 3.41.0, read-back of a NayaFlow flash, 2026-09-08) STATIC
"Factory Reset" No device factory-reset control exists in 1.25.1. The only "Factory Reset" string is an unused locale entry (settings.general_tap.title.factory_reset, described "Reset all changes to Naya Flow", beside Language, Theme and placeholder "example" blocks) that the renderer never references. The Danger Zone offers "Clear all keymap data" instead. STATIC2

The UI

  • Destinations on the icon rail: Keymap (Bindings), Module Configuration, Hardware Manager (Create firmware update, Create pairing, module firmware update, Danger Zone force update), Help, Settings (tabs Behavior, UI, Connection, Software Update, Backup (Adv.), Troubleshooting (Adv.), Logging (Adv.), Software Info). Color mapping is a mode of the key view; there is no macro editor. "Hardware Manager" was called DeviceManager until 1.17.2 STATIC (observed in the running app, 2026-09-01) DOC6.
  • Feature flags (1.25.1): on colorMapping, keyMapping, moduleConfigurations, settings, help, basicActions, extendedActions, layersActions, moduleBindingSlots, basicLayers; off macros, layerManagement, bugReport, modulesActions, integrationsActions, customActions, ledToggle, behaviorToggle, dynamicLayers, viewToggle, signIn, backwardAndForward, undoAndRedo, advancedBehaviors. Only 13 flags are ever read STATIC2.
  • Key behavior slots: Tap (press), Hold (needs a tap), Double Tap (needs tap and hold), Tap + Hold (label "Tap & Hold", needs the other three); value actions are not allowed on keys. Double Tap and Tap and Hold arrived with 1.25.0 / NayaCore 6.11.0; a 2026-09-03 capture shows NayaFlow 1.25.1 writing both banks of a key STATIC DOC MEASURED (owner's board, 3.41.0).
  • Shortcuts (1.25.0 and later): Ctrl/Cmd+S saves; Ctrl/Cmd+D runs "Flash Create", and on the Hardware Manager page runs the hardware operation (a firmware update of both halves) immediately when no warnings are present (the confirmation dialog opens only when there are warnings) STATIC2 DOC6.
  • Update warnings: CREATE_LEFT_BLE_V1_DETECTED, CREATE_RIGHT_BLE_V1_DETECTED, PAIR_MISMATCH, MULTIPLE_CREATE_LEFT_CONNECTED, MULTIPLE_CREATE_RIGHT_CONNECTED, FOUR_OR_MORE_CREATE_HALVES_CONNECTED ("update at most 2 halves at a time"). Updating a BLE v1 left half to v2 erases its saved hosts; the split link fails until both halves run BLE v2 STATIC DOC23.
  • Vendor rules for a keyboard update: no modules on either half, both halves on direct USB-C cables (Y-cables "known to cause issues"), halves switched on, up to 3 minutes, wait for the LEDs to come back. The same cabling applies to half-to-half pairing, which NayaCore refuses when the halves run different firmware ("Devices have different firmware versions") DOC STATIC34. The halves still type while mismatched, so the refusal is a policy of the pairing flow MEASURED8.
  • Vendor rules for a module update: a single up-to-date Create Left with the module docked; the module on; up to 7 s for the module to appear (a minute the first time for a Tune). "Force update" (Danger Zone) is for a module that does not answer after Battery Recovery was tried: pick the module type physically docked; keyboard and module restart several times; up to 4 minutes DOC3.
  • Troubleshooting buttons: "Test and Format SPI-Flash" (repair_flash) tests the halves' flash and reformats only partitions with errors, warning that keymaps may be overwritten; "Clear all keymap data" (clear_data), which the vendor recommends when Flash Create keeps failing; "Clear BLE Devices" makes the keyboard forget every Bluetooth host (the host may need to forget it too) DOC STATIC34.
  • The tray setting "Module battery in tray" shows each docked module's type, side and level, polled every 5 s STATIC1. NayaFlow relaunches itself whenever a module is docked again (docking re-enumerates that half's USB) MEASURED (owner's machine, 3.41.0).
  • NayaFlow and the NayaFlow-Beta app cannot run at the same time; the beta imports user data from the stable app (/rpc/import-userdata-beta, beta 1.17.0 "User Data Import System") DOC STATIC72.

Hazards in stock NayaFlow

Hazard Severity Evidence
Ctrl/Cmd+D on the Hardware Manager page updates both halves' firmware without a dialog when no warning is present. high STATIC2
Danger Zone actions (repair_flash, clear_data, clear_ble_devices) ran without a confirmation dialog when observed. high STATIC (observed once, 2026-09-01; re-check open, details)
A NayaFlow flash overwrites whatever another tool stored. medium MEASURED (2026-09-01)
"Failed to verify written data" after a flash: the board holds second-bank records (position KK+0x52, double tap or tap + hold) that the profile does not describe, for example written by another tool; NayaFlow's sparse diff never clears them and its reader folds a key and its second bank together. The flash itself landed. Writing [KK+0x52] 07 00 once clears it. medium MEASURED (owner's board, 3.41.0, 2026-09-09 and 2026-09-17)
NayaFlow's verify read-back shows every hold-tap record at tapping term 200 and flavor 1 whatever the board holds (for example 180 / 0), so any key with a hold mismatches once the term is not 200. The keyboard is not affected. low MEASURED (owner's board, 3.41.0, 2026-09-17)
NayaCore has no ENTER, PAGE_UP, PAGE_DOWN or CLICK token (it uses RETURN, PG_UP, PG_DN), so catalog chords using them flash wrongly: LALT + ENTER (WIN_DISPLAY_PROPERTIES) went out as 00 00 00 04, the modifier alone. medium MEASURED (NayaFlow flash capture) STATIC
The renderer drops the base layer from Toggle Layer targets because &tog 0 from a higher layer leaves the keyboard stuck there until a power cycle. (guarded) STATIC MEASURED (owner's board, 3.41.0)
NayaFlow cannot write pinch and spread (the category-8 zoom axis on Tune and Touch): its combined control accepts only value actions and its catalog has no zoom; of three pinch bindings in its database the profile NayaCore built carried only spread. low MEASURED (owner's board, 3.41.0, 2026-09-18)
NayaFlow accepts a hold on a Track button that the device cannot store: the hold silently replaces the tap. medium MEASURED (owner's board, 3.41.0, 2026-09-03)
In one flash NayaFlow removes module profiles no layer references (bays set to 0, list entry deleted, slot blanked with 40 empty records), writes its own profile UUIDs back to slots, and once cleared Track Right button 1 during an "enable all modules" flash (params 00 01 0b 07 00: slot 1, field 0x0b set to NONE). medium MEASURED (owner's board, 3.41.0, 2026-09-03)
The stock Tune three-finger swipes labeled "LED Brightness Up/Down" are flashed as an empty key press (01 04 00 00 00 00): no HID output and no LED change was seen. What the keyboard does with it is OPEN. low MEASURED STATIC (owner's board, 3.41.0, module 2.3.3)
The chord recorder writes codes that differ from the catalog (CAPS_LOCK vs CAPSLOCK, SCROLL_LOCK vs SCROLLLOCK, ESCAPE vs ESC, PAGE_UP/PAGE_DOWN vs PG_UP/PG_DN), accepts LSHFT/RSHFT and bare CTRL; PrintScreen cannot be recorded; NumpadEnter and NumpadDecimal are unmapped. low STATIC2
Chord presets carry one name per chord regardless of OS (for example Ctrl+Up named "Mission Control" in the Windows presets). low STATIC3

Local programs can drive the keyboard through the bridge

set_handshake_frequency, repair_flash, clear_data and clear_ble_devices can be sent by any local program through /rpc/send-nayacore-zmq-message; do not script them against a daily-driver board.

Runs without any vendor server

  • No API base URL, account, credential, license check, cloud sync or telemetry was found, nor a kernel driver, background service, autostart entry or per-device calibration file; every route is on localhost, and the keyboard and module firmware ship inside NayaCore, so updates work offline STATIC (searches of all four bundles).
  • The only outbound calls found are update checks: electron-updater against NayaTech/NayaFlow-releases (channel latest, publisher "Naya B.V.", auto-download and install-on-quit on, downgrade off) and the bg-server's component manager (repositories NayaFlow-, NayaCore-, NayaTouch-, NayaTrack-, NayaTune-, NayaFloat-releases). The token they send is an unsubstituted build placeholder, so GitHub answers 401 even for the public repository and every "latest tag" lookup comes back empty STATIC MEASURED (app-update.yml; proxy capture on the owner's machine, 2026-09-01).

Unpacking the app

An app.asar file starts with u32 4 and a u32 header size, then a pickled string: its u32 JSON length sits at file offset 12 and the JSON at offset 16; file data starts at offset 16 + the JSON length rounded up to 4, and each entry's offset counts from there. npx @electron/asar extract does the same STATIC.

Useful trees in the 1.25.1 asar: dist/main/index.js, dist/renderer/assets/ (renderer bundle index-mihUmo_8.js), assets/icons/action/ with 860 action icons. The firmware is not in the asar: it is embedded as Qt resources in the NayaCore binary STATIC. Carving the images is covered on Firmware images.

Open questions

  • OPEN The bytes the "Clear all keymap data" button sends (details).
  • OPEN When NayaCore sends ed/1012, ed/1013, ed/1014 (capture a settings change without flashing).
  • OPEN How /factory-reset is registered and what it answers (details).
  • OPEN The value of oldBLELibDeprecatedCreateLeftFWVersion (details).
  • OPEN What the keyboard does with an empty key press record.
  • OPEN Whether the Danger Zone buttons really act without a dialog (details).

Sources


  1. NayaFlow 1.25.1, resources/app.asar: dist/main/index.js, dist/main/preload.mjs, preload.update-window.mjs, and app-update.yml in the installed tree. ↩↩↩↩↩↩↩

  2. NayaFlow 1.25.1, resources/app.asar: renderer bundle dist/renderer/assets/index-mihUmo_8.js and dist/renderer/locales/. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. NayaFlow 1.25.1, flow/flow-bg-server.exe (Windows) and Contents/flow/flow-bg-server (macOS): strings and embedded data (catalog, templates, settings, i18n). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  4. NayaFlow 1.25.1 for Windows, core/NayaCore/NayaCore.exe (NayaCore 6.11.0): strings (event names, step names, log formats, build paths, the port-detector frames). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. NayaFlow 1.25.1 for macOS, NayaFlow.app/Contents/core/NayaCore.app/Contents/MacOS/NayaCore (NayaCore 6.11.0; the arm64 build's MD5 is 84ded78022b6d312483aae0192584168): symbols and code (the event table, the dispatcher, doClearAllDataOperations). See Disassembly. ↩↩↩↩↩↩↩

  6. Vendor release notes, NayaTech/NayaFlow-releases, mirrored in create-legacy-firmware CHANGELOG.md. ↩↩↩↩↩↩

  7. Vendor release notes of the beta channel, NayaTech/NayaFlow-beta-releases. ↩

  8. create-legacy-firmware, FLASHING-PROCEDURE.md, "Measured on hardware, both halves (2026-09-20)". ↩

  9. nayactl, constants.py (shared-memory names and topics). ↩