QG flow programs
QG expresses ordered automation behavior: call Objects, evaluate conditions, wait, record state, and handle faults and recovery. The only current source format is .qg. Source and Flow are two views of the same program.
If this is your first QG task, complete QG flow, Object calls, and Debug before using this page as a reference.
When to use QG
QG is a good fit for:
- ordering equipment or business actions;
- conditions, branches, bounded waits, and timeouts;
- coordinating several Objects;
- behavior that runs when an App starts, updates, or stops;
- control logic that you need to observe with Debug breakpoints.
Do not use QG for:
- Page layout and button appearance;
- protocol handling, register parsing, or driver implementation;
- emergency stops, safety circuits, or certified safety functions;
- unmanaged third-party code, dynamic scripts, or unbounded background work.
Create and open a QG program
- Right-click Scripts in Resources.
- Select Create QG Program and use a clear, stable filename such as
packaging-station.qg. - The editor creates the source under
graphs/and establishes its corresponding Object instance. Confirm its Object ID and name under Resources → Objects. - Write or review source in Source and switch to Flow when a structured view is useful.
- Save, resolve live document diagnostics, and use Build when you need a complete result.
The filename stem becomes the WIT resource name unchanged, so it must use lowercase kebab-case such as packaging-station. Uppercase letters, underscores, dots, and implicit renaming are rejected.
Start each QG file with a short JSDoc paragraph that explains the capability it provides to the App. Describe engineering purpose, such as “orchestrates packaging cycles and timeout recovery,” rather than implementation history or volatile parameters.
Source and Flow
- Source is the only source of truth and can be edited by people or AI.
- Flow is generated from current Source and presents data structures, functions, conditions, loops, and Object calls as a vertical flow from top to bottom. Control enters the top of each node and exits the bottom, with branch outputs side by side at the bottom; parameter and return-value data ports stay on the sides. QG previews in App Hub use the same node cards, parameter fields, branches, edges, and vertical layout as Editor. App Hub currently supports read-only viewing, including function switching, node selection, pan, and zoom; open Editor to change the flow or set breakpoints. In narrow areas, the outline collapses automatically and the function selector stays above the canvas. Use the outline button to open it temporarily; selecting a function closes it again.
- An accepted Flow edit updates the same Source. There is no second wiring file, coordinate file, or draft to synchronize.
- Source and Flow use the same breakpoints. After source locations change, run Build Debug again before starting a new debug session.
If Flow cannot render, inspect Source and Problems first. Do not create an empty flow by deleting types or guessing method names while upstream QG or Object errors remain.
Program structure
A QG ObjectType normally contains four kinds of content:
| Content | Purpose |
|---|---|
declare const value property | A read-only value property bound when the Object is created and shown in that QG Object's property settings |
Top-level let state | Short-lived state owned by one resource instance; reset when that instance is reloaded |
| Ordinary functions | Local calculations and process steps used only by this QG program |
export function | Methods that this QG Object exposes to Pages, other QG programs, or approved online tools |
Every QG resource has the same three lifecycle methods. Declare one in source only when it needs behavior; the compiler generates an empty implementation when it is omitted:
Init(): void: runs once when the App starts;Update(): void: called periodically by the Runtime while running;Dispose(): void: runs during an explicit Stop; an execution fault closes the current activation without running more QG code.
Do not create a second scan timer or permanent loop inside QG. Keep each Update bounded and give every external wait an explicit timeout and recovery path.
Data structures and record values
People who do not write code can create App-local fixed records from Outline → Data structures in Flow. Every structure shown there is defined by the current QG. Select one to rename it, add or remove fields, reorder fields, and choose exact scalar types in the Inspector. Every accepted change updates the current .qg directly; Flow does not create a separate type file or graph document. Renaming a structure or field also updates source references that the editor can resolve safely.
ObjectType record types come from the available Object catalog and do not depend on whether the App already has a concrete Object instance of that ObjectType. When Source explicitly names an ObjectType record, use a standard TypeScript type-only import:
import type { DropSettings } from "drop.workflow";
The module string is the exact ObjectType ID. If two ObjectTypes both export Settings, use a TypeScript alias: import type { Settings as MotionSettings } from "motion.controller";. Do not copy a same-shaped local interface; QG has no name-based schema guessing or mapping to maintain. Flow type pickers list available ObjectType records and automatically maintain these imports when a type is selected or no longer used.
Object instances need no default type import and are not constructor properties. QG accesses them through the global objects surface generated exactly from the current App Object configuration; records within an ObjectType continue to use named imports. For example:
import type { MoveSettings } from "motion.controller";
declare const speed: f64;
let cycles: u64 = u64(0);
export function Run(settings: MoveSettings): void {
objects.motor.MoveTo(speed, settings);
cycles = cycles + u64(1);
}
Here motor is the real Object ID shown under Resources → Objects, speed is a value property each QG Object instance must bind, and cycles is isolated state owned by each resource instance. If an ID is not a TypeScript identifier, use objects["line.motor"].MoveTo(...). objects is an exact compile-time read-only call surface; dynamic indexing, enumeration, destructuring, assignment, and retained Object handles are unsupported.
Use Insert → Records for Create record, Read field, and Set field nodes. The Inspector expands record literals into one input per field, so users do not need to write { field: value } manually. When a local field is added, removed, reordered, or given a different scalar type, the editor migrates record literals it can identify unambiguously. If another expression cannot be migrated safely, the change is rejected with diagnostics and invalid source is not written.
Flow also blocks deletion of a structure while state, functions, or nodes still use it. Remove or migrate those uses first.
Use Object capabilities
QG calls real App Object instances directly through global objects, such as objects.motor.MoveTo(...). Use this workflow:
- Create and configure the target Object instance under Objects in Resources.
- Select a capability from Flow's method list grouped by real Object ID.
- Pass values in the displayed order, type, and unit.
- Save and resolve Object or QG diagnostics.
- After Build, verify real behavior in a simulator or approved equipment state.
Object methods must come from capabilities installed in the current App. Never infer method names, units, polarity, or permissives from an old project, screenshot, or similar device. AI should also inspect the current Object catalog first and open only the ObjectType interfaces it actually needs.
Common language rules
QG uses syntax that TypeScript tools can understand, but it is not a general JavaScript runtime.
| What you need | Current syntax |
|---|---|
| Boolean | bool |
| Signed integers | i8, i16, i32, i64 |
| Unsigned integers | u8, u16, u32, u64 |
| Floating point | f32, f64 |
| Text and no return value | string, void |
| Fixed record | A top-level TypeScript interface in this QG, or an ObjectType record introduced through import type |
| One-dimensional list | T[], with length, indexing, assignment, push, and for...of |
Important limits:
- Do not use
number,boolean, ornull.anyandunknownare allowed only as optional catch-variable annotations. - Make numeric conversions explicit, such as
u32(value). - QG functions cannot declare type parameters. Generics exist only on imported library calls such as
saved; TypeScript infers the concrete type from exact arguments or the receiving context, and an explicit type argument remains available when needed. - Do not use
async,await,Promise, classes, or dynamic modules. Platform capabilities use the ordinaryimport { ... } from "theseus"; Object instances are accessed through globalobjects, while ObjectType records use named type-only imports. - Object calls complete in source order. Do not add asynchronous markers even when an underlying operation waits.
- Every loop needs a clear exit condition. QG does not add an automatic iteration limit to an infinite loop.
Treat editor diagnostics as authoritative. Do not bypass them with casts, suppression comments, or generated code.
Handle call exceptions
Wrap Object calls, including both reads and writes, in try/catch. Ordinary C# exceptions and errors returned by Rust Objects can both be caught; error.message is read-only error text. Method names and arguments must still match the current Object catalog.
import { log } from "theseus";
export function Move(): void {
try {
objects.motor.MoveTo(100);
} catch (error) {
log.error(error.message);
}
}
An exception skips the remaining try statements, executes catch, and then continues after it. The Flow try / catch node has two corresponding statement areas. A handler can update existing state, log the error, or use throw error; to let an outer handler decide.
Without a catch, or when a rethrown error has no outer handler, execution still stops. Stop requests and unrecoverable low-level faults bypass catch. finally and constructing or throwing arbitrary error values are currently unsupported. Use catch { ... } when no error variable is needed.
Time, logging, and durable state
Platform capabilities are an ordinary theseus library, not special QG syntax:
clock.monotonicMilliseconds()reads monotonic time.clock.delay(milliseconds)performs a cancellable wait.log.debug/info/warn/error(...)writes runtime logs.saved.get/setstores scalars or fixed records that must survive runtime-instance replacement.
Math, bit operations, and arrays use the standard TypeScript surface: Math.min/max/abs, & | ^ ~ << >>, [], indexing, length, and push. Compose clamping from Math.min and Math.max; generate computed sequences with an explicitly bounded loop and push.
Durable state belongs to an App-global namespace on the current runtime target. It is not written back to the App workspace or moved automatically with Git. saved.get and saved.set infer their concrete type from an exact default value, written value, or receiving context; saved.get<T> / saved.set<T> may still state it explicitly. An ambiguous value is rejected instead of receiving a default QG type. The name is an ordinary string expression, and names of the same concrete type share one generated WIT function pair. A successful saved.set is not rolled back by a later fault. Plan an explicit migration or use a new name when changing a stored shape.
Example:
import { saved } from "theseus";
export function IncrementTotal(): u64 {
let total: u64 = saved.get("totalCycles", u64(0));
total = total + u64(1);
saved.set("totalCycles", total);
return total;
}
When a Page needs a durable value, call an exported QG Object method instead of owning another persistence implementation.
Debug a QG program
- Save QG and related Object changes.
- Add breakpoints to the intended statements in Source or Flow.
- Confirm Stopped, then select Build Debug.
- Select Debug. If no suitable Build exists, the editor asks whether to Build and continue; it proceeds only after confirmation.
- Trigger the target method from a Page, another QG program, or an approved online entry.
- At a breakpoint, inspect the current statement, arguments, state, Console, and equipment feedback, then select Continue.
- Select Stop to end Debug and verify the equipment's cleaned-up state.
The current debugger provides breakpoints and Continue, but not source stepping or conditional breakpoints. Debug does not simulate devices, skip real outputs, or prove process safety.
Save, Build, and runtime boundaries
- Save updates project files only. Flow and live diagnostics also describe source only.
- Build uses files saved to disk and deploys the result to the selected runtime target.
- Design never executes Object or Query calls. Use a runtime Page after Start / Debug to validate real methods and device behavior.
- Start and Debug execute the deployed version. When the editor offers Build, it waits for explicit user confirmation before building and continuing.
- QG and Objects are never replaced in a running instance. Use Stop → Save → Build → Start/Debug after a change.
Common problems
Flow is empty or a method is missing
Resolve upstream Source and Object errors first. Confirm that each objects.id in source names a real Object ID shown under Resources → Objects, then reopen Flow. Do not handwrite an interface or fill the gap with an old method name.
A Page cannot find a QG method
Confirm export function, save the QG file, verify the corresponding Object binding, and run Build again. Pages receive only methods in the current resolved Object model.
A breakpoint does not hit
Confirm that the current source has a matching Build Debug, the breakpoint is inside the executed method, and Debug uses the target bound to this editor.
Equipment still shows old behavior
Save does not replace the runtime instance. Stop, Build, and Start/Debug again, and verify the title bar's runtime target.
The App returns to Stopped after a fault
This is the unified cleanup behavior. Record the fault phase, message, triggering action, and Console output before repairing the cause. Do not hide an unexplained fault by repeatedly selecting Start.
Engineering and safety checks
Before connecting real equipment, confirm:
- Object identity, address, unit, polarity, and range;
- start permissives and the safe state after Stop;
- timeout, cancellation, and recovery for every wait;
- equipment behavior in
Init, explicit-StopDispose, and a fresh Run after a fault; - whether retries can repeat an irreversible side effect;
- that emergency stops, guards, and hard limits are enforced by a separate, suitable safety system.
QG owns flow orchestration. It does not replace device drivers, backend authorization, or certified safety control.