Releases: Gaskony-Ignition/module-script-ide
Release list
Script IDE v1.25.0
Full Changelog: v1.24.0...v1.25.0
Script IDE 1.24.0
The last three items of the Ectobox borrowings brief. All four groups are now done.
Nothing is lost if the tab dies
Every unsaved buffer is kept in your browser as you type, and offered back when the page comes up again — after a crash, an accidental close, or a laptop that slept and reloaded. The IDE's save history could never help here: it only ever sees text that was successfully written, which is exactly the text that was not at risk.
Restore loads the buffer; it never writes. The gateway's current copy is opened first and your recovered text applied over it, so the tab is dirty against what the gateway holds now — not against a signature from a session that has ended. You can see what you are about to save before you save it.
The notice says the work was kept in this browser only, because that is the limit of the promise: nothing was sent to the gateway, and a draft is invisible from any other machine.
Start from a template
Six starting points for a new library script — a documented module, a parameterised database read, a transaction, tag read/write, a test module, and a module with a logger. Library scripts only: an event script's signature is dictated by its type and already seeded from a measured stub, so a picker there would offer to replace a correct answer with a guess.
Each one obeys the rules it would otherwise teach people to break: tabs never spaces, except Throwable never except Exception around a platform call, and parameterised SQL rather than a formatted string.
They are compiled by the gateway before they ship. The live suite runs every template through the running Jython, and the calls they make were checked against the live 8.3.8 system.*. That caught one before it went out: the transaction template used beginNamedQueryTransaction, which is for runNamedQuery — runPrepUpdate takes its id from beginTransaction.
Take the console output with you
Export saves the output as a text file, stamped, with colour codes stripped — the point is something you can paste into a ticket or grep. The header says the times are the browser's clock, not the gateway's, which matters the moment anyone lines them up against a gateway log.
Times stamps each block on screen. Off by default, remembered per viewer, and rendered outside the text so copying the output copies the output.
Signed module, Ignition 8.3.0+. 815 unit tests, 18 live suites (446 checks) against a running 8.3.8 gateway.
Script IDE 1.23.0
See who else has the file open
A bar above the editor and a badge on the tab when somebody else is in the same script — from another browser, or from the Ignition Designer. It names them and says where they are working from.
Two feeds, one list. Browser clients report their open paths over the socket they already hold. Designers come from Ignition's own concurrent-editing feed: the gateway posts DesignerResourceSessionEvent on the Guava EventBus that CommonContext.getEventBus() returns — a public SDK accessor — carrying session id, username, hostname, IP, start time and the exact ResourcePaths open. This module registers on the same bus the platform's own manager uses; nothing is injected, wrapped or replaced. Everything is pushed, so a badge appears as fast as a keystroke travels.
Verified against a real Designer on 8.3.8, not off the bytecode. Opening MiningDemo.config in the Designer put ignition/script-python/MiningDemo/config in the list within seconds, and the browser showed "admin in the Designer on 172.31.0.2 also has this open." Closing the script cleared the path; exiting the Designer cleared the peer.
It is a warning, not a lock
Nothing here refuses a save — If-Match on the write path is what prevents a lost update. Presence exists so two people find out about each other before the conflict rather than after it, and the bar says so: "Saving is not blocked — talk to them first."
The wording is "has it open", never "is editing". That is what both feeds actually report, and a tab left open over lunch counts.
The caveat, stated plainly
DesignerResourceSessionEvent lives in gateway.jar, not in the SDK's gateway-api — checked on both 8.3.6 and 8.3.8. It is an internal type and may change in any patch release. Everything it carries is public SDK, so the reflection is confined to reaching the accessor.
If a future Ignition renames it, the listener stops matching and says so once, and presence falls back to session level from GatewaySessionManager, which is fully supported. The module never fails to start over it. GET /api/presence reports designerFeed (attached) and designerEventSeen (a real event actually read), so the failure is visible rather than silent.
Also
Run history is searchable, and records how long a run took. Fifty runs are kept and finding one was scrolling. The search reads the source and the output, because "which run printed that error" is not answerable from the code. Duration is wall time; a record kept before this release shows none rather than claiming 0 ms.
Signed module, Ignition 8.3.0+. 763 unit tests, 17 live suites (429 checks) against a running 8.3.8 gateway, plus a real Designer.
Script IDE 1.22.0
Authoring features, and one fix that predates them.
Snippets
18 tab-stop templates, offered through ordinary completion. The tag-change one binds tagPath, initialChange, currentValue and previousValue — the names an Ignition tag-change script actually gets, not an event object that does not exist there.
Imports
Organise imports groups, sorts and drops the unused ones. Suggest an import offers one for an unresolved name. The organiser skips the module docstring — counting it as code made the feature a silent no-op on nearly every well-written file.
Completions from the running gateway
Tag paths inside a string literal, browsed live from the tag provider. DB schema — table, then column — read from the connection's own DatabaseMetaData. Both behind a short TTL cache, so typing does not hammer the gateway.
Style lints
Seven checks, over the AST rather than over the text: bare except:, a mutable default argument, == None, is with a literal, an asserted tuple, a duplicate dict key, and mixed indentation. Structural checking is why == None inside a docstring that explains why not to write == None is not a finding. All warnings; none of them stops anything running.
Console
cprint(text, colour) and jsonPrint(value), with an ANSI parser in the web console so colour from any library renders instead of printing as escape codes.
Fixed: a coding declaration disabled every static feature in the file
A file starting # -*- coding: utf-8 -*- was a syntax error to this module. Python 2 refuses a coding declaration in a unicode source, and everything here parses from a StringReader. The header had already done its job before the bytes arrived as text, so it meant nothing — but the parse failed, and with it the outline, go-to-definition, the lints, and test discovery, which skipped such a module entirely.
It had two halves in different places: the parser, and the test runner's own compile() of source seeded through Py.java2py. The console path takes a byte str and was never affected. The live suite asserts all three states, so the half that always worked does not get "fixed" later.
Signed module, Ignition 8.3.0+. 625 gateway tests, 731 web tests, 16 live suites (409 checks) against a running 8.3.8 gateway.
Script IDE 1.21.0
A test framework: decorators, assertions, mocks, and a namespace per run.
The first of four groups of ideas taken from Ectobox's Script IDE. What is
portable from their runner is the shape of a test framework, not its mechanism —
and their mocking mechanism is not safe on a gateway.
The measurement it rests on
__import__ of a project-library module hands back the manager's own module
object. Measured on 8.3.8: the same id() from two separate runs, a module
global set in one run read back by the next, and system living in each module's
own globals. So swapping globals()['system'] — how their mocks work — would
change what every other user's scripts see for as long as the block is open.
The runner is therefore handed each selected module's source and executes it
into a namespace private to the run. A mock is then safe, module state no
longer carries between runs, and where the source cannot be read the runner
imports instead and a mock refuses rather than quietly writing into the shared
copy.
Added
@test,@skip,@timeout,@cases,@beforeAll/@afterAll/
@beforeEach/@afterEach.setUpandtearDownstill work.- Twelve assertions, including
assertRaises,assertAlmostEquals,
assertTagValueandassertDbRowCount. mockTagsandmockQuery— answer tag reads from a dict and queries by SQL
fragment, record every write and call, and raise for anything they were not
given.- A fourth outcome,
skip, and re-run failed only.
See docs/TEST-FRAMEWORK.md.
Two limits, stated rather than left to be discovered
A mock reaches the test module only — production code the test calls still
reaches the real gateway. And scriptide exists only during a run, so a test
module that imports it at the top is not importable outside one. Both are
asserted.
@timeout is a budget, not an interrupt: the test finishes and then fails if
it took too long.
Gate PASS. Java 530, Vitest 689, fifteen live suites 371/371.
Internal module — not on the public portal.
Script IDE 1.20.0
Export and import code, in the Designer's own format.
Export
Right-click a script or a package in the tree → Export…. You get a .zip in exactly the format the Designer writes — so the file opens in the Designer, and a file the Designer wrote opens here. A package exports every script under it, at any depth, whether the branch is open or shut.
Import
Import… is on the same menu. Choose a resource zip and you get a list of what is in it, ticked and ready, with two things the Designer's own import does not show you up front:
- which scripts already exist, so replacing one is a choice rather than something you find out afterwards; and
- which resources will not be imported at all — a Designer export can carry Perspective views and images, and a script editor that quietly dropped them would look like it had lost them.
Nothing is written until you press Import, and every path is reported afterwards, including the ones that did nothing.
Proved against the real Designer
The format was measured by driving the Designer, and then the round trip was finished: a zip written by this module was opened in the Designer, imported, and the script came back byte-identical. That is what caught the one real defect — a missing path prefix that would have made every export import as nothing, in both tools, while looking perfectly reasonable in a listing.
Also
The script tree has a right-click menu for the first time.
Upgrading
Nothing to do.
Script IDE 1.19.0
A script could run, succeed, and print nothing. Fixed — and the console now splits where you want it.
Output that survives an import
If your script imported a project library module, everything it printed after that import was lost — it went to the gateway's own console instead of the output pane. The run succeeded and took the time the work really took, so the only symptom was an empty Output panel.
Two things made it look random rather than broken, and both are why it lasted this long:
- Only the first import of a module does it.
import jsonwas never affected, and neither is a module the gateway has already loaded — so the same script prints on its second run. sys.stdout.writewas lost too, not justprint.
Nothing you need to change: import wherever you like, and print wherever you like.
The console splits where you want it
- Drag the divider between the editor and the output. They were fixed at 45/55.
- Rows or columns — output below the editor, or beside it — from the toggle in the console toolbar. A wide monitor running a stacked console wastes most of its width.
- Both are remembered, as a proportion rather than a pixel size, so they survive resizing the window or popping the console out into its own tab. Each orientation keeps its own split.
Saving no longer looks like a conflict
Clicking Save briefly showed the stale marker, the "this has changed" bar and the Pull n changes button — over your own save. It now stays quiet for the length of the write, and speaks up if the save actually fails or conflicts.
And the popped-out console in the glass themes
Under Glass Aurora — Teal the popped-out console showed a sliver of teal at the top left and violet everywhere else. Both aurora themes share the same base colour; all the teal is in the theme's glow, which the console was painting over. It now carries the theme's own ground, and fills its tab.
Upgrading
Nothing to do.
Script IDE 1.18.0
The compare-two-gateways feature is removed, and the split button is one you can see.
Removed: comparing two gateways
Nigel, 07/09/2026: "The comparison to other gateways is not required. You can remove that. I didn't realise that was something you were trying to do."
It shipped in 1.17.0 and it is gone — the Compare view, the peer configuration, the outbound client, the /api/remote/* routes, the digest route and everything behind them. Removed rather than hidden behind a flag, because two properties come back with it:
- The module has no non-session authentication again. The peer read token gate went with the feature, so every route requires a browser session. The four reads it covered — projects, the script tree, a script body, the digest — take the ordinary authenticated gate.
- The module makes no outbound HTTP calls. The client that talked to another gateway was the only one.
policy.properties is back to the two switches it had before: execution and the terminal.
The Jython test runner is untouched. It was the other half of 1.17.0 and it stays.
A split button you can find
Nigel: "the button is so small I didn't even notice it until I went deliberately searching."
It was a bare glyph in muted grey at the end of the tab strip — the quietest thing on the strip while being the only action on it. It now carries an icon, a border and the word: Split, then Move right and Move left once there are two panes. The README has a picture of the split editor for the first time.
Upgrading
Nothing to do. If you had peers configured in policy.properties, those keys are now ignored and can be deleted; the execution and terminal switches are unaffected.
Script IDE 1.17.0
Compare two gateways, and run your Jython tests. The last two items from the product review, plus its own backlog closed out.
Two gateways side by side
Point the Compare view at another gateway running this module and every script, gateway event handler, Web Dev handler and named query is listed with whether it matches over there. Open a differing one and the peer's copy sits beside your own, read-only.
It reads and it cannot write — the client that talks to the other gateway has a single verb and it is GET, so there is no code path from a comparison to a write on production. Peers are named in the gateway's own policy.properties and chosen by that name, never by a URL from the browser: a route taking ?url= would be a server-side request forgery primitive mounted inside a gateway. Redirects are not followed for the same reason.
The one new authentication surface is a peer read token. It is off unless configured, mounted on reads only — never a write, an execution, an attribute save or the terminal — compared in constant time, and a token under 24 characters is treated as absent rather than accepted.
A Jython test runner
Nothing in Ignition offers one. A def test_* in a module named for tests is discovered, run on the gateway in an isolated interpreter, and reported as passed, failed or errored — with how long it took, the traceback, and whatever it printed before it stopped. setUp and tearDown work the way unittest means them.
Discovery is deliberately narrow: finding def test_* anywhere would put plc.diagnostics.test_connection under a Run All button. Running a test is running arbitrary project code, so it takes the same gate, the same execution service and the same audit line as the console.
Also
- The README's screenshots are current again, and re-taking them is now a script rather than a chore.
- The audience decision is on the record: this module stays internal.
Requires Ignition 8.3+. Signed with the Gaskony keystore.
Script IDE 1.16.1
Signed module, md5-verified identical to the file ignition-module-testing is running (b6ecf18a…). Ignition 8.3, signed with the Gaskony keystore.
feat: search that covers what the IDE actually edits, guards on the way out, and most of the product review's own backlog.
Nigel, 06/09/2026, after five more ideas: "do all 5 they are good" — and then
"also fix up everything from the past review. that should have all been done
already."
Added — the five
- Search covers everything the IDE edits.
ProjectIndexfiltered to
library scripts while the module had grown to edit gateway event scripts, Web
Dev handlers and pages, and named-query SQL. So search, references and (since
1.15.0) replace covered about a quarter of what a user can open, and looking
for a string in your own timer script returned nothing — which reads as "it
isn't there". The NAME index stays library-only, deliberately: definition and
quick-open symbols are about IMPORTABLE modules, and a timer script is not one. - A save that does not parse asks once. Nothing stopped writing broken
Python into a running gateway; on a timer script that starts failing on the
next tick. Only an ERROR stops — a warning never does. - Rename a script, with the call sites offered as a separate, confirmed
project-wide replace. One resource, never a folder: the server refuses a
folder rather than half-moving it, and the dialog says so before you type. - Compare an override with its parent. Discard Overrides has always been
one click and there was no way to see what it would discard — in this IDE or
the Designer. Read-only, and it walks the parent CHAIN rather than stopping at
the immediate parent. - An unused report. Top-level functions and classes nothing else names.
Name-based, and the panel says so at length: anything called from a
Perspective binding, a Vision window or outside the project appears here and
is not unused.
Added — the product review's backlog
- R1, impact before save. A save that removes or re-declares a top-level
function names its call sites first. A line scan, not the AST: the question is
about two versions of a buffer and the server only knows one of them. - R3, run history that survives a restart. The console kept nothing;
ExecAuditstores a SHA-256 of the source by design, so it could say a run
happened and never what was run. Source and output now, per user, bounded.
"Load into console" does not re-run it. - R4, tag event scripts — the blocker was never the Designer. This type has
been body-only since 1.1.0 waiting for someone to measure the tag-path list in
the Designer's workspace. A real tag-change resource on the rig had the shape
written on it all along:pathsandchangeTypesas JSON ARRAYS and
enabled. Both arrays are now editable, withchangeTypesas an allowlist —
the platform ignores an unrecognised one silently, so the script would sit
there configured and never fire. - F4, the
Administratorliteral is a policy key now
(com.gaskony.scriptide.admin.role), resolved file >-D> default. A
gateway whose admin role is called anything else fell back to a check that
could only say no. - R2, half of it, and the half that is real. The review asked for last fire,
duration and next fire per event script. The 8.3.0 SDK exposes none of
them — there is no timer-task registry to ask, and a log line appears only
on failure, so inventing "last run" from one would make a healthy script look
like one that never runs. What ships is which gateway event scripts are
FAILING, how often and when, in the Problems panel.
Not done, and why
R5 (two gateways side by side) and R6 (a Jython test runner) are
untouched. R5 is a cross-gateway authentication surface rather than a feature
flag; R6 is a product in its own right. F1 (internal or public) and F2
(prove it on a second gateway) are decisions and environment, not code.
Fixed
- Two over-broad test assertions that counted every
buttonin the Search panel
and broke the moment it grew a control — the same shape as 1.15.0's class-name
collisions. They assert the result rows now, which is what they meant.
Verified
deploy_gate.py PASS (6 checks, 14 routes, none unmounted). v13 27/27,
v15_tree 18/18, v16_nav 25/25, v17_nq 45/45, v23_insight 27/27.
Java 450, Vitest 619.
The Tag Change labels came from driving the real Designer, and they had to:
the resource stores ValueChange and the Designer's Change Triggers row says
Value. Reading the JSON alone — which is how the shape was found — would have
shipped a vocabulary this IDE invented for a control that already has names
people know. Screenshot at docs/images/designer-tag-change.png.