रिलीज़ नोट्स अंग्रेज़ी में लिखे जाते हैं; हर रिलीज़ के नोट्स GitHub पर भी हैं, उसके डाउनलोड के साथ: https://github.com/ProAnima/SignalLab/releases.
Changelog
Notable changes to Signal Lab. Versions follow semantic versioning; dates are release dates.
Write changes under Unreleased as they land. npm run release -- <version> turns that section into the version's section, and the release workflow uses it as the release notes — so what is written here is what users read. See Checks, builds and releases.
Unreleased
Added
- Documentation, in all eleven languages. A guide to every screen, protocol and tool; experiments node by node, with data, flow, load and faults; the command line, CI and the assistant's tools (MCP); the server, its security and its HTTP API — every command and event; and a reference of shortcuts, files and every error message. Searchable in every language. It is inside the app and the server, so it works offline: F1, or the book in the header, opens the page of the screen in view; and it is published at https://proanima.github.io/SignalLab/.
Changed
- The firewall's message on a system without per-program rules no longer names a file of the repository; what to do is in the documentation's troubleshooting page.
Fixed
A broken signal library is no longer overwritten. While
signals.jsoncould not be read, any change on the Signals screen wrote the screen's empty list over the file you were fixing. Now nothing writes the library until it reads again: the screens' edits, Save, Save as and Save as signal are off and say what is wrong, a save that meets a file you broke meanwhile is refused and leaves it as it is, and Reload file lifts the block once the file parses. The library is also written through a temporary file, so a write cut short cannot leave half a file.Save as signal makes the right signal. A datagram received on a socket listening on every address (
0.0.0.0:9000,[::]:9000) is now replayed to this computer on that port instead of to0.0.0.0; an MQTT publish becomes an MQTT signal with its broker, topic, QoS, retain flag and exact payload instead of a raw UDP one; a datagram that went through a relay is aimed at the address it was going to; and a TCP chunk, an HTTP exchange, a WebSocket message or an MQTT control packet — which cannot be sent again as they were — is no longer offered: the button is off and its tip says why. A relayed frame now shows the relay's address and where the packet was going; the leg ends its verdict (forwarded +40ms · client→target).An MQTT signal goes to its own broker. While the MQTT screen was connected, every MQTT signal was published on that connection even when it named another broker. It now rides the connection only when it is to the signal's broker (same host, ignoring case, and port, 1883 when none is given); otherwise it publishes to its own broker.
The Inspector's description lists everything it captures — HTTP, MQTT and WebSocket, emulators, Storm and the Scanner, experiments' steps and waits — not only OSC and relay frames.
An impairment relay's target may be a host name. The Impairment screen and the experiment's Impairment node took only an
IP:port; the target now also takeshost:port, looked up once when the relay starts. The listen address stays an IP.A body the HTTP screen hides is not sent. After typing a body under POST and switching to GET or HEAD, which hide the field, the request still carried the old text — and so did a signal saved from it and Add to experiment. A method that hides the body now sends none, everywhere; the text stays in the field for when you switch back.
The MQTT screen refuses to publish to a wildcard. A topic with
+or#was sent to the broker on the screen's connection (which many brokers answer by closing it), while a signal, a step andsignallab send mqttrefused it first. The connection's publish now refuses it with the same message, and the connection stays. Subscribing to filters is unchanged.The OSC sender and generator refuse an address without the leading
/. They sent it, while a step and Broadcast refused it; the message now says the address must start with/.The WebSocket screen enforces the 16 MiB message limit. A longer message was written to the wire; it is now refused (
Too long: at most 16777216) before anything is sent, as a step's was, and the connection stays open.Host names work wherever a destination is
host:port.localhost:9000often failed (Sending to [::1]:9000 failed) because the name's IPv6 address came first while Signal Lab sends from an IPv4 socket. A name with an IPv4 address now uses it, and a name with only IPv6 addresses is sent to from an IPv6 socket. The OSC sender and generator, Storm,signallab send oscand an OSC step also accepthost:portnow — they took only an IP address — and their tips say so.Storm sends the rate it is given. It sent a tenth of the rate in 10 ms steps, rounded down to a multiple of 100, so 50 a second sent about 100, 250 sent 200, and the system's timer made it worse. The rate is now a schedule — packet n is due n ÷ rate seconds after the start — so 50 is 50 and 250 is 250, a duration ends on time, and a computer that falls behind drops the old packets instead of bursting.
What the Inspector samples is counted. The signal generator, a beacon, the discovery listener, Storm, the HTTP burst and load, the MQTT connection, the impairment relays and the emulators draw only some of their traffic, and the rest was counted nowhere. The next frame drawn now says how many were left out, in its verdict (
sampled · +5 not shown).Broadcast shows its targets while a beacon runs. Targets read 0 until you had pressed Send once; the beacon now reports how many destinations each round goes to.
The Broadcast target list says what separates targets. Commas, semicolons or new lines — not spaces, as the tip implied.
The generator's saw and ramp are different waves. Both rose from Min to Max. Saw now falls from Max to Min and jumps back; ramp rises from Min to Max.
A TCP message shows in the Inspector. The TCP message node of an experiment wrote and read without leaving a trace there. Its step now appears as two
tcpframes, sourceexperiment: the payload it wrote and the answer it read (at most 1 024 bytes), with secrets masked like in any other frame.A TCP message has Send now. The button was missing on a TCP message node although the engine could send it; it now connects, writes the payload once and says how many bytes were sent and came back.
Texts that said what Signal Lab does not do. The experiment import's tip said "Version 1" (it opens this version's files and every older one, upgraded, up to 4 MiB); the Impairment node said it degrades UDP only (it relays UDP or TCP); Wait for WebSocket counted messages "since the connection opened" (it counts from the branch's latest action of any kind); the MQTT publish node promised "a local broker" (any broker works); Join branches said only a Parallel branch feeds it (any wire into it must be followed); Corruption said "one byte flipped" (one bit of one byte); the starter OSC device's note said
/pongcarries "the same number" (it carries a count); and the message about secrets named the macOS Keychain, which Signal Lab has no build for. On the Linux desktop app, where there is no credential store at all, the Secrets tip now says so instead of naming the Windows one.The command line told the wrong thing in a few places.
--secrets systemsaid it reads the macOS Keychain (it reads the Windows Credential Manager; on Linux it fails with exit code 3);signallab emulatorswithout a library blamed "the server's data folder";signallab doctorandsignallab firewall allowprinted English whatever--langwas, and now speak the chosen language; and the summary of an Impairment node over TCP counted "datagrams", where it now counts chunks, connections, resets, half-open connections and held-back streams.signal-lab-server --helptold half of the host-name rule. It said that without a token only loopback names are allowed; names given with--allowed-hostare allowed with a token or without. The behaviour is unchanged, the help now says it.The GitHub Action's
langinput lists all eleven languages, not "en or ru".The node catalogue that
signallab nodesand the assistants read is truer. It asks for exactly one End (not "at least one"), lists every method the HTTP node accepts (OPTIONS, and any other token the engine can send), marks the fields that take templates (a header check's name and text, a WebSocket wait's pattern and more), and says what a Wait for WebSocket counts from.
1.0.0 - 2026-10-04
Added
Nine more languages. The interface, its tooltips and errors, the command line, the server's sign-in page and the installer now speak Spanish, French, German, Portuguese (Brazilian), Chinese (Simplified), Japanese, Korean, Hindi and Arabic besides English and Russian. Arabic mirrors the page right to left; addresses, hex dumps, code and the experiment canvas stay left to right.
A language switch with flags. The header shows the current language's flag and letters and opens the list of every language, each by its flag and its own name; the arrows, a letter, Enter and Escape work in it.
TCP impairment. The Impairment screen and the experiment's Impairment node take a protocol: over TCP each connection to the relay is joined to one of its own to the target, and both streams are delayed (in order, whatever the jitter), held to a bandwidth limit (the sender slows down; nothing is dropped), reset (both sides get a reset) or left half-open (nothing more goes through, and nobody is told), or paused while offline — every decision drawn from the seed. The presets have TCP versions; the screen counts connections, resets, half-open connections and how often a stream was held back; a run's report counts them per phase, and Change impairment switches a TCP relay too.
Load on an HTTP node. An experiment's HTTP request can run under a profile — constant, ramp, steps, spike or random (Poisson) arrivals, up to 100 000 requests a second and 512 at once — drawn in the properties before it runs. The step measures p50/p90/p95/p99, mean and slowest, the error rate, the rate achieved, answers by status or cause, each second and a latency histogram, and passes only when its thresholds hold (
p95 < 300 ms,errors < 1 %): the first that does not fails it and says by how much. The timeline counts up every second; the properties show the result. Experiment files are version 9.Compare two runs. Compare in the timeline sets this run beside the one before it, or any earlier run of the experiment: each load step's metrics before and after, the change, a regression in red, and how each threshold went.
signallab runprints each threshold's verdict and fails on one that does not hold (JUnittype="load.threshold"); MCP haslist_runsandcompare_runs; the APIexperiment_runsandexperiment_compare. Run reports are version 5.The Inspector keeps whole frames. A frame keeps its bytes up to 256 KiB instead of the first KiB: the list still shows a preview, Show all brings every row, Save as signal replays a datagram of any size byte for byte (it refused anything over 1 KiB before), and both exports write every byte kept (
.jsonlas base64data). The capture holds up to 8192 frames and 64 MiB; a frame larger than it keeps says so.Several nodes at once in the experiment editor.
ShiftorCtrland a click adds a node to the selection,Shiftand a drag on the empty canvas selects with a frame,Ctrl+Aselects everything. The selection moves, duplicates (Ctrl+D) and deletes together, each one step to undo, andCtrl+C,Ctrl+X,Ctrl+Vcopy, cut and paste it with the wires between its nodes — into another experiment or window too. A pasted node that names another copied node names the copy; a copied Emulator or Impairment listens on the next free port.Updates. The desktop app finds a newer version among the releases published on GitHub — once a day, or Check for updates in About — says what is new in it and installs it when asked: what is pending is saved, running jobs stop, the download's signature is checked against the key built into the app, and Signal Lab starts again as the new version (Windows: the setup or the MSI; Linux: the AppImage,
.debor.rpm). It asks the studio's hub, which offers a release to a share of installs first and can hold it back, and GitHub when the hub cannot be reached; the check tells the hub the version, the system and a random number of this install. Drafts, pre-releases and commits are never offered. A server updates with its image.About. Who makes Signal Lab — ProAnimaStudio, Ian Panaev — and how to reach them (info@proanima.net), the version, the source and the license; the ? in the header.
Write to the developers. A form (the ✉ in the header, and in About): a message, an address for the answer, screenshots pasted with Ctrl+V, picked or dropped, and the console log and the version and system attached on their own — shown before sending, each removable, with this computer's name, its address and your folders left out. It goes to the studio's hub, which mails it from signal-labs@proanima.net to info@proanima.net; the mailbox's password is on the hub and never in the app — see
docs/develop/hub.md.Emulators: Signal Lab as the other side. A new Emulators screen plays the API, device or service your system talks to. An HTTP API answers by routes — method, a path with
:namesegments, conditions on headers, query, body or JSON — with responses in sequence (500, 500, then 200, for retries), in turn, or a seeded mix by weight, with delays, jitter and faults (no answer, a closed connection). An OSC, UDP or TCP device answers by rules: on this address, payload or line, reply that, to the sender or elsewhere, after a delay; a TCP device greets and can hang up. Replies are templates read with what arrived ({{request.params.id}},{{request.args[0]}},{{request.json.name}}). Every exchange is counted per rule, listed as it happens and goes to the Inspector. A starter set (an API, an OSC, a UDP and a TCP device) is in the library, on loopback. Mock this on the HTTP screen turns a response into a route that answers exactly so.Emulators in experiments. The Emulator node serves for the whole run — open before the first step, closed with the run — and the report counts what it received. Wait for HTTP request waits for a request to it (or to a listener of the run that answers 204) by method, path and conditions, so an experiment proves that the system under test called it, and with what. An OSC or UDP emulator shares its port with the run's waits. The template Retry a flaky API shows it end to end (document version 6).
Emulators everywhere.
signallab emulate <file|name>runs emulators from a file or the app's library until Ctrl+C or--for, printing every request and the counts at the end (--jsonfor one object per line,--serverto run them on a lab server);signallab emulatorslists the library. An assistant getsstart_emulator,emulator_exchangesandlist_emulatorsover MCP. The server's API has the same commands (emulator_start,emulator_exchanges,emulators_load, …).An MQTT broker of its own. An emulator can be an MQTT 3.1.1 broker: clients connect (with a user name and password, when it asks for one), subscribe with
+and#, publish at QoS 0, 1 and 2; retained messages, last wills and a client id taking over its old connection work as on any broker. Rules make it a device too: on a message tolab/+/set, publishlab/{{request.levels[1]}}/state. MQTT gear and experiments with Wait for MQTT need no broker installed any more; a demo broker is in the library.Dependencies that flap and break. Any emulator can go down now and then — up for so long, down for so long, from the start: HTTP answers 503 with
Retry-After(or closes, or holds the request), TCP devices and the broker drop and refuse connections, OSC and UDP devices go silent; what met an outage is counted apart. A response can be malformed: complete HTTP whose JSON stops halfway.Faults on a schedule. Three new nodes break things on cue. Impairment puts a UDP impairment relay in front of a device for the whole run (Route through impairment on an OSC or UDP node inserts one and points the node at it); Change impairment switches it to another profile mid-run; Emulator down/up takes one of the run's emulators down and brings it back. The relay's decisions come from the run's seed — the same seed drops the same packets — each phase is counted in the report, and a run that ends in any way closes its relays. Templates Fault phases and Dependency outage show both (document version 7; run reports version 4).
More ways a network fails. The impairment relay adds bursts of loss, reordering, a bandwidth limit and offline, has presets (LAN, Busy Wi-Fi, 4G, Satellite, Intermittent, Offline), takes edits while it runs without dropping its port, and counts what it received, throttled and reordered. A running emulator can be taken down and brought up by hand on the Emulators screen, over the API (
emulator_down) and by an assistant (set_emulator_down).WebSocket. A new WebSocket screen connects to
ws://andwss://services with the headers and subprotocols they expect, sends text or bytes (Ctrl+Enter) and lists every message as it comes, JSON formatted, with the close handshake and who closed. Experiments get WebSocket connect, WebSocket send, Wait for WebSocket (any, contains, regex, hex; the message as{{reply.text}},{{reply.json.field}}) and WebSocket close; a token extracted earlier can be in the URL or a header, and a run that ends in any way closes its connections with a proper close frame. Send now on a send or a wait opens the connection its connect node describes. The template WebSocket echo;signallab send ws <url> --text … --expect …and the assistant'ssend_ws; the API'sws_connect,ws_send,ws_close,ws_exchange. Experiment files are now version 8 — this release's last; files of every earlier version open as before.A parameter matrix for the command line.
signallab runandvalidatetake--matrix NAME=V1,V2(repeat for more names: every combination runs) and--matrix-file(axes, or a list of combinations), and--fail-faststops at the first run that does not pass. Each combination is a run of its own — named with its values, a JUnit suite withparam.NAMEproperties, a report of its own, amatrixobject next to thefileit came from in--json— all checked before the first sends anything, here or on a server. A value given twice runs once, at most 256 runs start from one command, and a run--fail-fastnever started is a skipped suite of the JUnit report. The GitHub Action hasmatrix,matrix-fileandfail-fastinputs.HTTP authentication and cookies. A request — on the HTTP screen, in an experiment's HTTP node, in a burst, from
signallab send http(-u name:password,--digest,--bearer) and the assistant'ssend_http— authenticates with Basic, Bearer or Digest: the server's 401 challenge is answered (RFC 7616: MD5 and SHA-256, their-sessvariants,qop=authandauth-int) and the request sent again; a burst answers one challenge for all its requests — in parallel too, never sending a count twice — and a stale nonce again, a few times at most, as under load a new nonce can run out before a request that met it is answered. Behind a redirect the URL that asks is the one answered; a challenge from another origin is not. The credentials go only into the request: nothing in the Inspector, a step or a report shows them, a node's password can be{{secret.NAME}}, and a secret is masked in Basic's base64 too. A request saved as a signal keeps its credentials — the Signals screen shows and edits them, and the HTTP screen takes them back from it after a restart. A cookie jar sends back what servers set with Set-Cookie, as a browser does (domain, path, Secure, expiry): the HTTP screen keeps one, listed and cleared there (Keep cookies; the library's HTTP signals use it too, and on a server every page shares it), and every run keeps its own (Keep cookies between requests in the Parameters panel). Files from before version 8 open with the run's jar off, so they run as they did.Load at a rate, read in percentiles. The HTTP screen's burst can start requests on a schedule of its own — Rate, req/s, 0.1 to 100 000 — instead of each worker sending again on its answer. A request that finds every worker busy is skipped and counted as Missed rather than sent late, so a server that cannot keep up shows it instead of being given a breather. Every burst reports p50, p90, p95 and p99 next to the average and min/max (read to within half a percent, in constant memory however long it runs), in a row of their own; a short latency reads with two decimals instead of "0 ms", one from a second on in seconds, and the last report rates the whole burst.
http_burst_starttakesrate.Every HTTP route has Copy the URL, and Mock this is offered on a Send now response in the experiment editor as well, a URL template becoming a path pattern (
{{api}}/orders/{{id}}→/orders/:id). A problem in an emulator says which rule and which response it is in.For an assistant (LLM).
signallab mcpserves Signal Lab over the Model Context Protocol: an assistant in Claude Code, Claude Desktop, Cursor or VS Code can learn what an experiment is made of, write one, validate it, run it and read step by step why it failed, send one OSC message, datagram, HTTP request or MQTT publish, listen on a port, and fire library signals — here, or on a lab server with--server.signallab mcp --print-config <client>prints the configuration to paste.signallabcomes with the app. The Windows setup and the MSI install it next to the app and put it onPATH; the.deband.rpmput it in/usr/bin.signallab nodeslists every kind of node with its fields and an example.The firewall, handled. The Windows setup for everyone allows Signal Lab and
signallabthrough Windows Firewall on private and domain networks (and removes the rules on uninstall;/NOFIREWALLskips it). Otherwise the app, the first time it listens, says when the firewall is in the way — a Cancel at the system's prompt, or a public network — and offers Allow with the system's administrator prompt.signallab doctorshows the firewall, the network, the data folder and a server's token;signallab firewall allowfixes the firewall from a terminal. The server's install script opens its port in ufw or firewalld when asked (--open-udpfor monitor ports) and closes it again on--uninstall.Signal Lab in CI/CD.
signallab, a command line next to the installers and in the image:signallab run tests/*.json --junit junit.xmlruns experiments headless and exits 0 when all pass, 1 when one fails, 2 for invalid input, 3 when it could not run; steps print as they happen, in English or Russian, and--jsongives one JSON object per line.--serversends the runs to a lab server that can reach the gear.signallab send osc|udp|http|mqttandsignallab firesend one message the way the app does. Secrets come fromSIGNALLAB_SECRET_*and are never printed.An API for automation.
POST /api/runon the server runs an experiment to its end and answers with the result, or streams its steps as NDJSON; the whole API is described at/api/openapi.json.A GitHub Action (
uses: ProAnima/SignalLab@vX.Y.Z) runs experiments in a workflow and leaves a JUnit report; GitLab CI and plain shell recipes are indocs/automation/cli.md.The server in one command. On a Linux machine,
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | shinstalls Docker if needed (it asks), starts the server with host networking, waits until it answers and prints the address and the access token. Running it again updates;--uninstallremoves it and keeps the data,--purgedeletes that too.The server image makes its own access token on the first start (
/data/token, shown once in the log and kept across updates), so a baredocker runordocker compose up -dis enough;--generate-tokendoes the same outside Docker. On loopback, where no token is needed, nothing is made.Save from where you send. The HTTP, OSC and MQTT screens have Save…: name it, pick a folder, and what you just sent is in the library. The screen stays tied to it — Save (
Ctrl+S) updates it, Save as… makes a copy, and a chip says where it lives and whether it has changed; a click on the chip shows it in Signals.Folders in the signal library. Nested folders that open and close (and stay that way), made, renamed (
F2), moved and removed — what was in a removed folder moves up a level; drag a signal or a folder onto another folder. Open in HTTP / OSC / MQTT loads a signal back into its screen. Library files are now version 2 and keep empty folders; older ones open as before.Ready for more languages. Counts agree with their number in every language (“1 signal”, “5 сигналов”), numbers and sizes are written the language's way, the language is picked from the system's list of preferred ones, and the starter signals are created in it. Adding a language is a dictionary and one line — see
docs/develop/localization.md; the checks name every missing text, placeholder or plural form.Every error in your language. The tool screens — OSC, Broadcast, MQTT, impairment, Storm, Scanner, HTTP, the library and the Inspector — now report failures the way experiments do: what happened and what to do, in English or Russian, with the system's own text folded underneath. A refusing MQTT broker says why (protocol, client id, unavailable, credentials, not authorized), and jobs in the console strip are named in your language.
Repeat. Any step that sends can send again and again — a number of times or for a time, every so many milliseconds with an optional seeded jitter — for heartbeats and polling.
{{counter}}numbers the sends, Retry applies to each, a send that waits for a reply waits for its own, and the timeline shows the progress once a second.Loop. A bounded loop node: the steps on Body run and come back, up to a maximum, until an exit condition holds (checked after each iteration, so the body can set what it tests); Done follows, or Limit when the iterations ran out first. The wire back is drawn over the body. New template Poll until ready (document version 5).
Every screen checked end to end.
npm run e2ewalks every tab of the real app — the desktop app and the server in a browser, on Windows (WebView2, Edge) and Linux (WebKitGTK) — sending real traffic to loopback stand-ins and checking what arrived; CI does it on every change, also for the published image.Wait for MQTT. Wait for a message on a topic filter (
+,#) whose payload matches; the run subscribes before its first step, so a quick answer is not missed, and retained values replayed on subscribing are ignored.Wait for this on a message in the OSC monitor or a topic in the MQTT tree builds the wait for it in the experiment; and a wait that matched links from the timeline to its frame in the Inspector.
A reply in the same node, and Retry. An OSC message or a UDP datagram can wait for its answer — sent from the port it listens on, so a device that answers the sender is heard too — and the reply is a variable for later steps. Every step that sends or listens can retry, with the same or a doubling pause; each failed attempt and its reason are in the timeline, and Stop ends a pause. (Document version 4.)
Parallel work from any output. Drag several wires out of one output — Start included — and their nodes run at the same time, each branch with its own copy of the variables. A Join waits for every wire into it; End completes the run once, after the last branch, and never if a branch failed.
Experiments that use data. Parameters with defaults and target profiles, a
{{template}}language with seeded generators, Extract from a response, Check value and Branch on value, a resolved preview of every field, and Run with… for one-run overrides and a seed.Secrets from the Windows Credential Manager (
{{secret.NAME}}), masked in the timeline, previews, responses, reports and the Inspector.Wait for OSC (OSC 1.0 address patterns, argument rules) and Wait for UDP (any, contains, regex, hex) with Matched and an optional Timeout output. Listeners open when the run starts, so a fast reply is not missed; the reply is a variable (
{{reply.args[0]}}). Listen now runs one wait on its own. New template OSC ping → reply.Errors in your language. The experiment engine reports a code, values, the node and field, and the system's own text; the interface shows Node · Field — message with the technical detail folded underneath, in English or Russian. Network failures are told apart: refused, timed out, name not found, unreachable, port in use, TLS.
Server mode.
signal-lab-serverruns the engine without a window and serves the full interface to a browser, with the same commands over HTTP and live updates over a WebSocket. It listens on this machine only unless given an access token (sign-in page in English or Russian, session cookie); experiment secrets come from read-only files; reports and exports download from the browser.Docker image
ghcr.io/proanima/signallabfor x64 and arm64: unprivileged, with a health check and a/datavolume, plusdeploy/compose.yaml. Broadcast, multicast and discovery reach the network with--network hoston a Linux host.Linux builds (
.deb,.rpm, AppImage) next to the Windows installers.CI on every push and pull request (Windows and Linux), a release workflow that builds both platforms from a tag into a draft release with checksums, and the local commands
npm run check,npm run build:linuxandnpm run release.
Changed
- Installers that look like Signal Lab. The Windows setup and the MSI carry the brand on their pages, the setup has its own icon, and the license page is gone (MIT asks for no acceptance) — one click less. Unattended installs are documented (
/S,/ALLUSERS,/P,msiexec /qn). - The Inspector lives in the bottom panel, a tab next to the console, so it is at hand on every screen; the tab shows when capture is on and how many frames it holds, and the panel can be maximised. Open frame links go there.
- Help where it was missing. Tooltips, in your language, on the fields whose meaning is not obvious — bind addresses, TTL, keepalive, client id, QoS, concurrency, timeouts, banners, every impairment slider — on every icon button, and on the Inspector's buttons and filters. Keyboard focus on a field shows its label's help.
- The server's sign-in page picks the most preferred of the browser's languages that it has, not only the first.
- Wires can be picked and removed. A click on a wire selects it (the properties say what it connects) and
Deleteremoves it; hovering a wire shows a × above its + that removes it at once. Undo brings it back. - Panes you can size. Drag the top edge of the console, the left edge of the properties pane or the top of the run timeline — or focus the handle and use the arrow keys; a double click restores the default. Sizes and whether the console is open are remembered.
- Ports are easier to hit: the area that takes the pointer is several times the dot and grows when the canvas is zoomed out, and a wire released next to a node connects to it. Dragging a wire always adds one; A, Add next and the + on a wire still insert into the flow.
- A lighter interface. Explanatory captions are gone from every screen and the sign-in page; what a control does, its shortcut and what
0means are tooltips in your language, on hover and on keyboard focus, also over dialogs. Labels say only a name and a unit, empty states a few words. - Switching screens keeps everything: typed values, the last response, running monitors and the scroll position.
- The version is written once, in
package.json; the app, the installers and the engine read it from there. - The engine is its own crate, independent of the desktop shell; the desktop app and the server share one command table, so they behave the same.
Fixed
- The GitHub Action with
fail-on-error: "false"still failed its step on a failed run and handed on noexit-codeto branch on. - The Impairment sliders' values sat against their labels (“Latency40 ms”) instead of at the far end of the row.
- The Inspector had no MQTT filter chip, although it captures MQTT.
- A job that ended before it was registered — a burst of one against a refused port — stayed in the console strip's list for good.
- A check of the last HTTP response right after a Loop (HTTP status on Done, an Extract) was refused as having no request before it, although the Loop's body, which always runs, made one.
- A failed UDP signal reported its payload instead of the reason; a failed HTTP signal showed the HTTP client's raw English; an MQTT broker given as an IPv6 address was dialled without brackets. The starter signals' notes carried long runs of spaces.
- Counts read wrongly for one (“1 signals”, “Joined 1 branches”) and for most numbers in Russian (“2 шагов”); units (ms, Mbps, KB) and the run timeline's clock were not in the interface language; captured signals went into an English “Captured” folder.
- On Windows, a discovery listener that answers probes stopped as soon as a prober closed its port, and the impairment relay stopped forwarding — while still looking alive — after its target or client went away for a moment. Both carry on now, and a relay that really cannot receive any more ends with the reason.
- The Inspector could list the same frames many times over when several sources captured at once (a port scan), leaving rows that no filter removed.
- Every field is tied to its label: screen readers name it, and a click on the label puts the cursor in it. Argument rows and icon buttons have names too.
- A stray "0" next to the Inspector's filters, an accent bar on every cell of the selected frame instead of one, a stretched QoS badge in MQTT subscriptions, and a white square where two scrollbars meet.
- The HTTP screen's default URL pointed at an outside service; it is loopback now, like every default target.
- Downloading an export from the server never navigates away from the interface, even when the server answers with an error.
package-lock.jsoncarried version 0.1.0 instead of the app's version.
0.3.1 - 2026-08-27
Fixed
- The 0.3.0 installers were built from the commit before the cancellation fixes; 0.3.1 ships them. A scan no longer reports an empty table, and Stop stops the work it started (storm, impairment relay, broadcast, HTTP burst, scanner).
0.3.0 - 2026-08-26
Added
- Signal library: named OSC, UDP, HTTP and MQTT packets, grouped and searchable, fired from any screen with
Ctrl+K; any Inspector frame can be saved as a signal. - MQTT 3.1.1 client: one live connection as a job, a topic tree, QoS 0/1/2, retained values and last-will.
Fixed
- Job telemetry was dropped before the job id was known, so fast jobs looked empty; Stop aborted only the supervising task and left the work running.
0.2.0 - 2026-08-26
Added
- The app's own icon, and NSIS and MSI installers in English and Russian.
- Broadcast, multicast and CIDR sweep with a discovery responder, the packet Inspector, a bilingual interface, and the MIT license.
Fixed
- Storm and the broadcast beacon reported a count up to 250 ms old at the end of a run.
- Checkbox, row and slider alignment.