Skip to content

v1.0.0

Latest

Choose a tag to compare

@eugeniodepalo eugeniodepalo released this 10 Aug 20:16
a8cfa7c

Read the announcement: GTKX 1.0: The React framework for Linux.

This release makes GObject subclassing a first-class part of the API and rebuilds @gtkx/testing on GTK's own accessibility tree and input handling. Virtual functions are reachable by name and chainable through super, a class can adopt interfaces its parent never implemented, and property writes are checked against the ParamSpec. An application parses its command line the way a C application does, and the widgets GTK4 forbids from being parented portal themselves. Every accessible read a test makes goes through gtk_test_accessible_check_* instead of the props the React layer recorded, and userEvent.click reproduces GTK's own targeting rather than forcing widget state. The generated bindings carry full documentation from the GIR data, and api.json declares which entrypoints are public.

Breaking changes

The generated store has to be rebuilt

gtkx dev, gtkx build and gtkx codegen regenerate node_modules/.gtkx on the first run after upgrading. An rc.4 store will not run against 1.0: the generated modules import runtime symbols that do not exist at rc.4, and the vtable and callback descriptors changed. A project with codegen: false needs a store built by 1.0.

Virtual method overrides are named with a vfunc prefix

A subclass no longer fills a C vtable slot by overriding the plain method name. Every slot is keyed vfunc plus the PascalCased vtable field, and the generated wrapper classes declare those members, so super.vfuncMeasure(...) chains up. Code keeping the old spelling silently stops overriding anything.

// Before
class ReturningModel extends Gtk.StringList {
    override getNItems(): number {
        return 1;
    }
}

// After
class ReturningModel extends Gtk.StringList {
    override vfuncGetNItems(): number {
        return 1;
    }
}

Since the override no longer shadows the generated binding, model.getItem(0) reaches it through the vtable and the rc.4 prototype.getItem.call(...) workaround is gone. A class field holding a function never reaches the vtable, because it is assigned per instance after registration. vfuncDispose, vfuncFinalize, vfuncGetProperty and vfuncSetProperty get no typed member, but declaring them still works.

runApplication takes an argv, and applications come from createApplication

runApplication(application, argv) returns { isPrimary, exitStatus }. Rather than registering and activating itself, it hands the argv to GLib's local command line handling, so the application's own options are parsed, --help prints, and a second instance forwards its command line to the process owning the application ID. It throws when handed an application GTKX did not derive, because GLib parses a given application's command line at most once; createApplication(base, props) builds one.

// Before
const application = new Gtk.Application({ applicationId: "org.example.App" });
runApplication(application);

// After
const application = createApplication(Gtk.Application, { applicationId: "org.example.App" });
const { exitStatus } = runApplication(application, ["App", ...process.argv.slice(2)]);

process.exitCode = exitStatus;

Applications rendered as <GtkApplication> or <AdwApplication> are carried over. gtkx dev forwards everything after -- to the application. An unrecognized option is now rejected by GApplication instead of ignored, and post-activate props such as menubar apply only when activation happens.

Windows, applications, dialogs and size groups portal themselves

GtkWindow and its subclasses, GtkApplication, GtkSizeGroup and the Adwaita dialogs mount at the top level wherever they sit in JSX, so the rc.4 pattern of writing createPortal(..., rootElement) by hand is gone. Two behavior changes come with it: transientFor defaults to the nearest enclosing window when the prop is undefined (pass null to keep a window free-standing), and GtkApplicationWindow portals into the application useApplication returns, throwing when there is no <GtkApplication> ancestor.

A child no behavior claims throws instead of being dropped

The reconciler used to ignore a child left in the default slot that no attach behavior claimed. It now throws, naming the child type, the parent type and the three remedies: pass it to the prop that takes it, portal it to rootElement, or register an attach behavior with defineElements.

// Before: the label was silently dropped
<GtkPaned>
    <GtkLabel>Start</GtkLabel>
</GtkPaned>

// After
<GtkPaned startChild={<GtkLabel>Start</GtkLabel>} />

This is a runtime error, not a compile error: every widget's generated props declare children, so the old tree still type-checks.

Deprecated containers lost their child behaviors

AdwLeaflet, AdwSqueezer, AdwPreferencesWindow and GtkComboBox no longer take children, and AdwFlap no longer takes content, so children written under any of them hit the unclaimed-child error. Migrate to AdwNavigationSplitView or AdwNavigationView, AdwBreakpointBin or AdwMultiLayoutView, AdwPreferencesDialog, and AdwOverlaySplitView. AdwFlapProps.content and GtkComboBoxProps.child are settable instead. AdwClampScrollable's child must implement Gtk.Scrollable, and AdwExpanderRow dropped its deprecated actions slot in favor of suffix.

AdwAlertDialog children fill the extra child

Children now go through adw_alert_dialog_set_extra_child and land below the heading and above the response buttons, where they used to replace the dialog's whole content. The extraChild prop is gone.

// Before
<AdwAlertDialog responses={responses} extraChild={<InteractiveFields />} />

// After
<AdwAlertDialog responses={responses}>
    <InteractiveFields />
</AdwAlertDialog>

ElementBehavior loses mount and unmount, and gains constructOnly

The two node-level hooks are gone with no direct replacement; the nearest substitute is flush(object, context), which runs after every commit touching the node. In their place, constructOnly?: string[] names props a behavior can apply only while the element is being built. The list helper sets it for any list prop declared with neither a remove nor a clear hook, which covers GtkAboutDialog.creditSections and GtkApplication.mainOptions: changing either after a non-empty value has been applied now throws.

@gtkx/testing reads accessibility from GTK

Every accessible read goes through gtk_test_accessible_check_state, check_property and check_relation instead of the props the React layer recorded. A widget GTK publishes no attribute for makes the matcher throw rather than falling back to a getter; a widget GTK does publish one for answers even when nothing declared it in JSX. What this changes in existing suites:

  • Accessible names follow WAI-ARIA naming, so a name-prohibited role computes no name and a GtkFrame's label no longer names it for getByRole. Use getByLabelText.
  • Mnemonic markers are stripped from any widget whose getUseUnderline() is true, so { name: "_OK" } becomes { name: "OK" }.
  • Numeric values are compared within 0.001, and GTK reports the reachable maximum, so a scrollbar's max is its upper bound less one page.
  • A MIXED pressed tristate matches neither pressed: true nor pressed: false. Assert it with toBePartiallyPressed.
  • An AdwSwitchRow publishes SWITCH on the row as well as its inner Gtk.Switch, so a role query needs { as: Gtk.Switch } to disambiguate.

Three matchers were removed and one renamed:

// Before
expect(expander).toBeExpanded();
expect(entry).toHavePlaceholderText("Search");
expect(box).toBeEmpty();

// After
expect(expander).toHaveAccessibleState(Gtk.AccessibleState.EXPANDED, true);
expect(entry).toHaveAccessibleProperty(Gtk.AccessibleProperty.PLACEHOLDER, "Search");
expect(box).toBeEmptyWidget();

toBeSelected moved to toHaveAccessibleState the same way. Matchers are registered by name, so a call to a removed one fails at assertion time in a loosely typed suite. On the reader side, getWidgetNodeText is now getWidgetText, and getWidgetTextContent, getWidgetInvalidState and getWidgetErrorMessage left the entrypoint in favor of toHaveTextContent, toBeInvalid and toHaveAccessibleErrorMessage.

Queries only see mapped widgets

The tree walk backing every query, getRoles, logRoles and the tree dump now filters on widget.getMapped(). A widget rendered with visible={false} is not findable and { hidden: true } does not bring it back, a widget on a non-visible Gtk.Stack page is invisible until that page shows, and a closed popover's contents (including a Gtk.DropDown's popup list) are not findable until it pops up. Capture such widgets through a ref, or make them visible first.

userEvent.click follows GTK's own targeting

click used to special-case Gtk.Button, force setActive on a Gtk.Switch, then fall back to a synthesized gesture at the nearest clickable ancestor's center. It now walks outwards from the clicked widget, keeps every widget carrying a primary-button click gesture with a tracked listener, and stops at the first widget that claims the press. Presses are delivered at coordinates derived from the clicked widget's bounds, so a container's gesture reads the child's position.

So clicking a label inside a row targets the row, onPressed on ancestor boxes fires for clicks on descendants, and no gesture is ever synthesized on a widget that has none. Where GTK4 implements a click in C on its own gesture, the outcome is applied through the public action GTK's handler invokes:

  • A Gtk.ListView, Gtk.GridView or Gtk.ColumnView row grabs focus, replaces the selection through listitem.select, and activates only on the second press or when the view sets single-click-activate. A single click no longer fires the view's activate signal, where rc.4 fired it on every click and never moved the selection. Set singleClickActivate or use dblClick.
  • A Gtk.TreeExpander toggles its row through listitem.toggle-expand; a click on its child label falls through to the enclosing row.
  • A GtkColumnViewTitle sorts through Gtk.ColumnView.sortByColumn, ascending first and inverted while it is already the primary sort column.
  • Column-view cells and the header row are click-transparent: the press reaches the row or view behind them.

Two neighbors changed with it. clear now focuses the widget and deletes through deleteText/deleteInteractive, so delete-text and delete-range handlers run, and it throws for a non-editable widget or a refused deletion. selectOptions and deselectOptions are duck-typed against the container's index and select methods instead of branching on concrete classes, so Gtk.FlowBox and a non-DropDown Adw.ComboRow work, selecting a row no longer calls row.activate(), and Gtk.ComboBox is no longer supported.

The screenshot surface collapsed to screenshot

captureAndSaveScreenshot, logScreenshotPath and WindowSelector are gone. screen.screenshot went from (selector?, options?) to (options?) and always captures the active mapped toplevel. Writing to disk is opt-in through the new path option; nothing is written to a temporary file.

// Before
await screen.screenshot(0, { scale: 2 });

// After
await screen.screenshot({ scale: 2, path: "out/window.png" });

Collection selection and expansion are fully controlled

selectedIds and expandedIds used to be applied only when present. Both are now always applied, so a view without the prop is driven back to nothing selected and every row collapsed, and the app must feed the reported ids back. They are also re-asserted when the widget drifts, so a click on a row absent from selectedIds is snapped back.

const [selectedIds, setSelectedIds] = useState<string[]>([]);

<ListView items={items} renderItem={renderItem} selectedIds={selectedIds} onSelectionChanged={setSelectedIds} />

ListItem.hideExpander, indentForDepth and indentForIcon are now shouldHideExpander, shouldIndentForDepth and shouldIndentForIcon; an item literal using the old names loses the setting. ColumnViewProps omits rowFactory, because ColumnView installs its own to drive rowProps. DropDown and ComboRow build a flat collection, so ListItem.children is ignored. The objects inside a collection model no longer carry the item id either, so code reading ids off listView.getModel() reads empty strings.

registerClass validates property writes

A property declared through registerClass({ properties }) now gets a checked setter on all three of its spellings, unless the class defines the camelCase member itself. A property without WRITABLE always throws a TypeError, and a CONSTRUCT_ONLY one throws unless it is set through the constructor. A writable property is checked by a per-property type guard and then by g_param_value_validate, and a value GObject would have corrected throws a RangeError unless the ParamSpec is marked LAX_VALIDATION. A refused write leaves the stored value untouched and emits no notify.

probe.red = 9999;
// Before: stored 9999, and probe.red served 9999 back
// After:  RangeError: Probe.red: cannot set property 'red' to 9999; the value is
//         invalid or out of range for type 'gint'

At rc.4 the accessor stored whatever it was handed, so a read-only property behaved as writable and an out-of-range value survived until something tried to marshal it. Define the camelCase member on the class to take ownership of the property and turn the checks off. A JSX prop landing on such a property goes through the same setter, so a value the ParamSpec refuses aborts the render instead of being stored.

Registration also rejects a ParamSpec whose name is not its key's canonical spelling, since the ParamSpec's own name is what GObject emits notify with: write paramSpecInt("dew-point", ...) under the key dewPoint, not paramSpecInt("dewPoint", ...).

@gtkx/runtime removals and signature changes

tryGetHandle was deleted and the setWrapper re-export dropped, so a wrapper carrying no handle throws instead of marshalling as null. newObjectWithProperties(gtype, props) is now newObjectWithProperties(gtype, props, wrapper) with plain values rather than [descriptor, value] pairs; descriptors come from the new registerConstructProperties(cls, bindings). A constructor key with no declared binding is resolved against the type's ParamSpec and validated before GObject sees it, so new Gtk.Label({ "accessible-role": 9999 }) now throws rather than emitting a GLib critical. registerInterface takes an InterfaceLayout ({ vfuncs, properties }), and callback descriptors need hasUserData and gained destroyKind.

getInstanceType now reads the GType off the instance's native handle rather than off its wrapper class, so a row inside a Gtk.ListView reports GtkListItemWidget rather than GtkWidget. The old answer is getClassType(cls), newly exported alongside the AnyClass type.

Generated class constructors no longer destructure their props and the root constructor takes object rather than Record<string, unknown>, so a subclass declaring constructor(props: Record<string, unknown>) must widen it. A callable is dropped from the bindings when it cannot be called correctly, which covers Gio.Cancellable.prototype.cancellableConnect, Gtk.CClosureExpression.new and GObject.SignalGroup.prototype.connectData among others. Codegen emits all of this, so only a hand-written descriptor needs editing.

Smaller breaking changes

  • DEFAULT_USER_EVENT_SIGNALS no longer covers GtkAppChooserButton, GtkAppChooserWidget, GtkComboBox, GtkIconView, GtkTreeSelection or GtkTreeView, so handlers on those signals also fire for changes GTKX writes during a commit. Restore them through userEventSignals in gtkx.config.ts.
  • The MCP reference tools gained an optional projectRoot, and now try the project containing the working directory before a connected app's root, inverting the rc.4 order.

New features

registerClass adopts interfaces, and vtable slots are callable

RegisterClassOptions.implements lists interfaces the new type implements on top of the ones it inherits. Their slots are filled from the class's vfunc-prefixed members and checked against the interface's generated Impl type, an interface listed before its prerequisite still registers, adopted properties answer g_object_get and g_object_set, and a slot the class does not fill keeps the interface's own default. rc.4 rejected any interface the parent did not already conform to.

class Sectioned extends GObject implements Gtk.SectionModelImpl {
    vfuncGetNItems(): number {
        return items.length;
    }
    // ...
}

registerClass(Sectioned, { typeName: "GtkxSectionStore", implements: [Gio.ListModel, Gtk.SectionModel] });

callVfunc(owner, key, instance, inputs) and callParent(klass, methodName, instance, ...inputs) are public. callVfunc is what the generated members call; callParent chains up exactly one level and reaches class slots, construct-time slots and interface slots alike, including the four with no generated member. The rc.4 rejection of constructed, setProperty and getProperty is gone: vfuncConstructed runs with the wrapper already bound, so getHandle(this) works inside it, and it runs for instances GTK or Gtk.Builder creates, where the subclass constructor never does.

registerClass returns the registered class as RegisteredClass<T, TProperties>, whose instances carry a type-level map of the installed property names in camelCase. useProperty reads its allowed names off that map, so a hand-written subclass's own properties are addressable for the first time. Bind the call to a name rather than calling it as a bare statement, and leave the properties object to inference.

const Swatch = registerClass(SwatchBase, {
    typeName: "ExampleSwatch",
    properties: { red: paramSpecInt("red", null, null, 0, 255, 0, ParamFlags.READWRITE) },
});

const red = useProperty(swatch, "red"); // number | undefined

createElementComponent is part of @gtkx/react/config

The factory the generated @gtkx/jsx store builds its elements with is now public, so a class registerClass created, which codegen cannot emit an element for, can still be rendered. The element inherits the props, signal handlers, defaults and behaviors registered for each of the class's ancestors, and the component routes a prop whose value is an element into that prop's slot. Name the props as the type argument, since the GType name says nothing about them.

const SwatchElement = createElementComponent<SwatchProps>("ExampleSwatch");

<SwatchElement red={200} widthRequest={48} />;

A JavaScript function is accepted wherever a GObject.Closure is expected

toClosure, tryToClosure, ClosureCallback and ClosureMarshalError are new exports, and the seventeen registry functions that build a closure on the caller's behalf take GObject.Closure | ClosureCallback.

source.bindPropertyFull("value", target, "value", flags, (value) => value * 2, (value) => value / 2);

disconnectSignal and connection tracking

connectSignal records every handler id it creates, and the new disconnectSignal(instance, handlerId) disconnects through that record. GObject.prototype.disconnect is built on it, and onceSignal and offSignal untrack as well, so a disconnected handler is never reported as still listening. Detailed names such as notify::label match their base name.

Generated bindings are fully documented

Generated declarations now render @param, @returns, @throws, @deprecated and @since from the GIR data: the Gtk store alone carries 4407 @param and 1558 @deprecated tags. DocBook markup becomes Markdown and #Type::signal references render as links. The JSX store is documented too, down to each onNotify<Prop> handler. A project with @typescript-eslint/no-deprecated enabled should expect new lint failures after regenerating.

Worker chunks in gtkx build

gtkx build now recognizes new URL("./relative", import.meta.url) written directly inside a new Worker(...) call, emits the target as its own chunk under dist/workers/, and rewrites the URL. Repeated references to the same module share one chunk. Hoisting the URL into a variable fails the build with a message naming the binding.

New Adwaita elements

AdwSidebar takes AdwSidebarSection children, which take AdwSidebarItem children. AdwMultiLayoutView takes its layouts through a layouts prop holding AdwLayout elements, and fills each Adw.LayoutSlot through a prop named after its id plus a Slot suffix.

<AdwMultiLayoutView
    layoutName="wide"
    layouts={<AdwLayout name="wide">{/* ... <AdwLayoutSlot id="sidebar" /> ... */}</AdwLayout>}
    sidebarSlot={<GtkLabel>Side</GtkLabel>}
/>

New testing matchers and extension points

toHaveAccessibleState(state, expected?) and toHaveAccessibleProperty(property, expected?) assert any attribute GTK publishes, with overloads pairing each one with the value type it carries. toAppearBefore and toAppearAfter compare two widgets by position in a depth-first walk, and the toContainAnyBy* and toContainOneBy* families run a built-in query against a widget's own subtree.

expect(toggle).toHaveAccessibleState(Gtk.AccessibleState.PRESSED, Gtk.AccessibleTristate.MIXED);
expect(form).toContainOneByLabelText("Email");

Testing Library's extension points came with them: buildQueries, getElementError, queryAllByObjectProperty, queryHelpers, computeHeadingLevel, prettyFormat, getQueriesForElement (an alias of within), and createEvent, which records a WidgetEvent without emitting it.

userEvent.setup and application accelerators

userEvent.setup(options?) returns an instance whose helpers share one keyboard and pointer state, so held modifiers carry across that instance's calls but not across instances, and honors a delay after each helper resolves. The module-level userEvent keeps its own shared state. userEvent.keyboard now tries application accelerators first, so a combination bound with setAccelsForAction fires its action in tests.

api.json declares the public API surface

A new api.json lists the fourteen entrypoints GTKX supports importing: @gtkx/cli/env, @gtkx/cli/vitest-plugin, @gtkx/codegen, @gtkx/components, @gtkx/components/adw, @gtkx/config, @gtkx/config/vite-plugin, @gtkx/css, @gtkx/gl, @gtkx/react, @gtkx/react/config, @gtkx/runtime, @gtkx/testing and @gtkx/vitest. Anything else a package happens to export is internal by declaration and can change without a major version. The published API reference is generated from the same list, so it now covers @gtkx/cli, @gtkx/codegen, @gtkx/components/adw, @gtkx/config/vite-plugin and @gtkx/react/config. ConfigLoader and ModuleExport moved onto the public surface alongside it.

Smaller additions

  • rowProps on ColumnView resolves ListRowProps per row, which is the only way to reach a Gtk.ColumnViewRow since it is not a widget. ListView and ColumnView both accept expanderDescriptions.
  • mainOptions on GtkApplication registers MainOption records through addMainOption before the command line is parsed, reachable from onHandleLocalOptions.
  • gtkx_get_widget_props returns named properties as { type, value }. The subtree the MCP tools return is now bounded, filled breadth first under a maxDepth (default 8) and 30 widgets in all, with hiddenChildren reporting what was cut so an agent can drill in with a second call. gtkx_query_widgets widened by: "name" to name, accessible label and rendered text.
  • Opening a render root connects it to React DevTools when the browser extension's hook is present. An update triggered from a GTK signal handler now commits once the emission unwinds rather than in the middle of it.
  • GValue marshalling covers the gchar, guchar, glong and gulong fundamentals, which used to fall through to an unsupported-type error.
  • Accessible props apply to anything implementing Gtk.Accessible rather than only to widgets, and the relation props widened to match.
  • The generated OpenGL bindings carry the C prototype, the providing feature, aliases and the GLX opcode; core-profile selection applies require and remove blocks in registry order.
  • create-gtkx writes a build allowance for the chosen package manager, dev-depends on @gtkx/mcp, and explains a failed dependency install with the exact commands that recover it.
  • New guides cover subclassing GObject, worker threads, and the reworked testing surface.

Performance

  • Controlled expansion and selection stopped walking every row. Visible order and positions come from the JavaScript index, so expanding one parent in a 5001-row tree costs one getRow and one setExpanded, and moving the selection costs none. Rows are materialized lazily. Because they are addressed positionally, a pure reorder of items emits no items-changed at all, which anything connected to listView.getModel() will see.
  • A validated property write costs one GValue fill and one g_param_value_validate, roughly half a microsecond per changed write. Reads and writes of the value already stored are unaffected, since the equality short-circuit runs first.
  • A faster call path for plain function bindings. A binding whose arguments are all pass-throughs skips building per-argument values, the out-parameter scan and tuple packing on every call, and out-parameter plans are precomputed once instead of filtered per call. Every generated getter, setter and simple method benefits.

Bug fixes

  • Fixed rows in a collection being identified by ListItem.id, so two items sharing an id rendered the same value and expanded together. Rows are addressed per occurrence now.
  • Fixed DropDown and ComboRow echoing a programmatic selectedId back through onSelectionChanged; both report user choices only.
  • Fixed accessibleExpanded, accessibleSelected and accessibleVisited failing with an FFI error, and accessible props being dropped when GTK overwrote them itself.
  • Fixed notify carrying the key a property was registered under rather than the ParamSpec's canonical name, so no notify:: connection could match it.
  • Fixed a property set through g_object_set_property emitting notify twice, which ran every notify:: handler and React onNotifyX prop twice for writes reaching a custom property from the GObject side.
  • Fixed a GValue typed as an interface failing to resolve a setter or getter, so a property or signal argument typed Gio.ListModel round-trips.
  • Fixed a property whose stored value does not fit its type surfacing an opaque marshalling error from bindings, expressions and sorters. Reads check the value against the ParamSpec first and throw a TypeError naming the property and the type it holds.
  • Fixed the dashed and underscored spellings of a property getting their own storage when the class defined the camelCase member itself.
  • Fixed the wrapper for a registerClass instance keeping its GObject alive forever. Reference counts now match those of unregistered parents.
  • Fixed chaining up from a vfuncMeasure override handing the parent implementation zeroed out baselines instead of GTK's -1, so a forwarding override reports what the parent reports.
  • Fixed arrays of inline structs whose length arrives in a separate out parameter being read at the wrong stride, so Gdk.Display.mapKeyval and Pango.Layout.getLogAttrs return usable records. Out and inout descriptors no longer ignore the extra pointer indirection.
  • Fixed Root.unmount leaving a Gtk.Application registered, so the same application id can be registered again.
  • Fixed userEvent.scroll jumping straight to the target value, which left virtualized views anchored on rows that were no longer shown.
  • Fixed screenshot leaving out mapped popovers, which render on their own surfaces.
  • Fixed placeholder, display value and selection reads being gated on concrete GTK classes, so a Gtk.TextView, a Gtk.PasswordEntry, a selectable Gtk.Label and an Adw.ComboRow now answer their queries.
  • Fixed assertions made immediately after await render(...) missing accessibility state the just-committed tree published.
  • Fixed gtkx_click acting on the enclosing list row instead of the Gtk.TreeExpander under the pointer; every MCP target goes through userEvent.click now.
  • Fixed the MCP server deleting a socket another server still owns, so two servers starting at once end with one owner and one refusal. A leftover socket is removed only after a conclusive probe, where any connect error used to be read as proof of a dead server.
  • Fixed a scaffolded project's test script sitting in watch mode, emitting to out-tsc, and never type-checking its tests.
  • Fixed gtkx dev idling forever when the application refused its command line, and leaking a custom --conditions into the application process.
  • Fixed gtkx build --asset-base emitting require("path") calls an ESM bundle cannot evaluate.
  • Fixed glib-compile-schemas and glib-compile-resources resolution, where a non-executable file on PATH used to win.
  • Fixed interrupted Vitest runs leaving orphaned weston, sway and dbus-daemon processes behind. @gtkx/vitest also no longer injects a setup file, so a project's own test.setupFiles are left alone.
  • Fixed codegen emitting a store that would not type-check when two configured namespaces produce the same generated type name.