Files
2026-09-15 13:06:45 -05:00

181 lines
8.0 KiB
Markdown

---
name: ignition-exchange-convert
description: Convert an existing Ignition project into a publishable Exchange resource — re-namespacing views/styles/scripts, migrating Perspective session properties to view properties, renaming Python to the guide's conventions — and prove the conversion did not break anything. Use when asked to make a project Exchange-ready, de-brand a project for publication, or refactor one out of session-scoped state. Bundles verify_refs.py and parity.py, which catch the two failure modes a conformance checker cannot see: dangling references and silently dropped features. Not for merely checking conformance (use ignition-exchange-conformance) or uploading (use ignition-exchange-publish).
---
# Converting a project into an Exchange resource
A conversion is a large mechanical rename plus one genuine refactor. It is
**very easy to produce a project that is perfectly named and completely broken**
so the verification gates below are not optional garnish, they are the point.
Check the target shape first with
[`ignition-exchange-conformance`](../ignition-exchange-conformance/SKILL.md);
this skill is what to do about the findings.
## Work on a copy
Never convert in place. Copy the project to its new name, leave the original
untouched, and diff against it at the end. The original is your parity baseline —
without it you cannot prove you did not drop a feature.
Ignition project folder name **is** the project name, so the copy's directory
must already be the new lowercase-dashed name.
## Order matters
Do it in this order. Step 2 invalidates bindings that step 3 would otherwise
have to rewrite twice.
1. **Delete what must not ship.** Vision `client-tags/`, scratch views, dev-only
resources.
2. **Session properties → view properties**, while paths are still the familiar
old ones. Verify the app still works before touching namespaces.
3. **Re-namespace** views, style classes, scripts, page config — one mechanical
sweep.
4. **Python pass** — tabs, camelCase, docstrings, loggers.
5. **Content sweep** — branding, internal names, placeholder text.
6. **Verify** (gates below), then package.
## Step 2 is the real refactor
Everything else is search-and-replace. This one changes how the app works.
The guide forbids session custom properties in an Exchange resource because
importing merges them into the host project's session props. Replace with:
- **State lives on the root view** as `custom` properties.
- **Flows down** to child views as `params` (the guide is explicit: `params` =
public/configuration, `custom` = internal).
- **Flows up** as page-scoped messages handled on the root view, named
`exchange.resourceName.handlerName`.
**Bindings must stay reactive.** Do not park binding-read state in
`system.util.getGlobals()` — globals do not trigger Perspective binding updates.
Globals are only for non-reactive cross-session data, and most resources need
none.
Before rewriting a binding, check **what it actually depended on**. A bidirectional
binding that writes a session prop does not necessarily re-trigger the data fetch;
if the fetch keyed off a `refreshToken`, then an explicit Apply button was already
the commit point and your message-based version preserves behaviour exactly. Read
the dependency graph rather than assuming.
## Jython 2.7 constraints
Gateway-scoped code is Jython 2.7. Violations are silent until they reach the gateway.
- **No f-strings.** `%` formatting only.
- No `typing`, `statistics`, `zoneinfo`.
- Java exceptions bypass `except Exception:` — use bare `except:` with
`sys.exc_info()` in gateway-facing defensive code.
- **Scripts embedded in `view.json`** are JSON strings that must be tab-indented:
the first character of every line in the block is a tab.
## Python renames
The guide wants camelCase functions and variables (deliberately not PEP-8) and
tab indentation. Two traps:
- **Rename through the tokenizer, not regex.** A blind replace will corrupt
string literals and attribute names that happen to match (`lt.tm_wday`).
- **Do not rename data-dictionary keys.** Keys inside dicts returned to view
bindings are a wire format between the scripts and every binding that reads
them. They are not covered by any rule and renaming them is a large, risky
diff for no conformance gain.
Tab conversion applies to **indentation** only — bracket continuation lines
starting with spaces are fine and normal. Verify with the tokenizer's INDENT
tokens, not by grepping for leading spaces.
## Verification gates
Run all four. Each catches something the others cannot.
### 1 · Conformance
```bash
python3 skills/ignition-exchange-conformance/exchange_lint.py <new-project>
```
Target: **0 failures**. Warnings are a judgement call.
### 2 · Referential integrity
```bash
python3 skills/ignition-exchange-convert/verify_refs.py <new-project> --old <OldNamespace>
```
Resolves every embedded view path, style-class reference, `view.params.X` /
`view.custom.X`, and every call into the project library against the functions
that actually exist. Also flags leftover old-namespace strings, surviving
`session.custom` reads, untabbed script transforms, f-strings, and `resource.json`
drift. Target: **0 breakages**.
With ~40 function renames, a missed call site is the single most likely defect,
and nothing else finds it.
### 3 · Feature parity
```bash
python3 skills/ignition-exchange-convert/parity.py <original-project> <new-project>
```
Fingerprints every view — component types and names, bindings by target property
and type, script transforms, event and message handlers, embedded views, declared
params — with names normalised for case, punctuation and snake/camel. **A pure
rename cancels out; a dropped feature does not.**
Target: **zero `LOST` or `MISSING` lines.** `ADDED` lines are expected — the
session-props refactor legitimately adds params and handlers. Read the diff, do
not just count it.
### 4 · Behaviour
If the project has a test suite over its pure modules, run it against the
converted library. Point the package name at the new location and alias
camelCase functions **and their keyword arguments** back to the original names —
a renamed kwarg (`higher_is_worse``higherIsWorse`) breaks callers that pass it
by name, which no static check catches.
Compare against the **original project's** result, not against zero failures. A
pre-existing failure staying pre-existing is a pass.
### Then: load it on a gateway
Static analysis cannot prove a screen renders. Load the project and open it.
A clean project load proves resources parse; only a browser proves components
render.
## De-branding
Conformance checkers read names, never label text. Sweep user-visible strings
explicitly — `props.text`, `title`, `placeholder`, `tooltip` — for company names,
project codenames, contest or demo references, internal hostnames, ticket numbers.
Strip the text rather than deleting the component, so layout and parity are
unchanged.
## Testing a minimum-version claim
To claim a floor lower than the gateway you built on, load the project on that
version — **against a copy of the files**. An older gateway rewrites every
`resource.json` with `lastModification` metadata and leaves root-owned
`.resources/` artifacts behind that your user account cannot delete (clean up
with a throwaway container rather than sudo).
A clean load proves resources parse. It does **not** prove screens render — a
component property introduced in a later version fails at render time, not load
time. Open the client.
## Reporting
Report what the gates actually said, with numbers. A conversion reported
accurately with two known gaps is worth far more than one claimed clean. If a
gate is a false positive, say which and why — the tooling is not infallible and
both bundled scripts have known blind spots documented in their docstrings.
## Provenance
Procedure and both scripts derived from a full conversion of a 20-view / 32-style
/ 3-module Perspective project (2026-09-15), verified on Ignition 8.1.20 and
8.3.7. The gate thresholds are the ones that actually caught defects during it.