Gum gum-project-versioning
Reference guide for Gum's .gumx schema versioning and migration strategy. Load this when changing the shape of GumProjectSave, ElementSave, or any other serialized save class — particularly when deciding whether a change needs a version bump, when adding a backward-compat shim, or when writing XmlSerializer-aware properties that must round-trip across tool versions.
git clone https://github.com/vchelaru/Gum
T=$(mktemp -d) && git clone --depth=1 https://github.com/vchelaru/Gum "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/gum-project-versioning" ~/.claude/skills/vchelaru-gum-gum-project-versioning && rm -rf "$T"
.claude/skills/gum-project-versioning/SKILL.mdGum Project Versioning Reference
The GumxVersions enum
Located in
GumDataTypes/GumProjectSave.cs. Currently two entries:
— verbose XML (names as child elements)InitialVersion = 1
— compact XML (names as attributes onAttributeVersion = 2
/ElementReference
)BehaviorReference
NativeVersion is the version that the current tool writes by default. A new GumProjectSave is constructed at NativeVersion.
The three load-time behaviors
ProjectManager.LoadProject (~line 201 and ~288 per recent research) compares the file's Version against NativeVersion:
- File version > NativeVersion → dialog: "saved with a newer version of Gum... Please update Gum." File is rejected.
- File version < AttributeVersion → Output-tab warning with an upgrade docs link (
). File still opens and works.https://docs.flatredball.com/gum/gum-tool/upgrading/upgrading-file-gumx-version - File version == NativeVersion → normal load.
Save doesn't auto-bump
This is unusual. Most authoring tools silently upgrade on save. Gum does not — a file loaded as v1 stays v1 after the user re-saves it. Upgrading is a user-initiated action following the docs link. Don't "fix" this without discussion.
XmlSerializer tolerance as the migration mechanism
There is no explicit migration pass for v1 → v2. Instead:
(inGumFileSerializer
) builds separate serializers (GumDataTypesNet6/
,GetCompactSerializer
,GetLegacyInstancesCompactSerializer
) usingGetGumProjectCompactSerializer
to handle the attribute-vs-element shape differences.XmlAttributeOverrides- Unknown XML elements are silently ignored by
, so adding a new element to a serialized class is inherently forward-compatible: old tools just skip it.XmlSerializer
This means most schema changes don't require a version bump.
Bump-vs-don't-bump decision framework
- Adding a new optional field → don't bump. Old tools ignore it.
- Adding a new field while replacing/renaming an old one → don't bump, if you keep the old field as a backward-compat shim (an always-serialized view over the new one so old tools still read something sensible).
- Removing a field with no shim, or making a breaking semantic change → bump. Accept the forward-compat barrier (old tools refuse the file).
- Bundle multiple schema changes into one bump when possible.
The
LocalizationFile → LocalizationFiles change in issue #2512 used the no-bump route: new List<string> property plus the legacy string property kept as a shim reading/writing index 0.
Writing backward-compat shims — XmlSerializer gotchas
If you add a new property and keep the old one as a shim, know these:
-
alone silently drops the property from XML output. XmlSerializer skips[Obsolete]
members. If you need a shim to keep serializing, use[Obsolete]
plus an explicit[EditorBrowsable(EditorBrowsableState.Never)]
attribute instead of[XmlElement("OldName")]
.[Obsolete] -
appends;List<T>
replaces. On deserialize, XmlSerializer callsT[]
on aAdd
property once per child element — so if the old element appears before the new one, both contribute and you end up with duplicates. Arrays are assigned via the setter, which replaces. If your deserialized shape might contain both old and new elements, serialize asList<T>
(with anT[]
attribute) and expose a[XmlArray]
that syncs.[XmlIgnore] List<T> -
Declaration order affects which setter wins when both old and new elements are present in XML. XmlSerializer applies properties in document order. If your shim and new property both set the same underlying state, putting the legacy property first (so the new property's setter fires last) is the intended round-trip shape.
-
Always emit the legacy element when the new one is populated. That way, an older tool opening a new file still reads something — typically the first entry of a list.
Related skills
- gum-tool-save-classes — broader overview of the save/load data model,
,ShouldSerialize*
, two-phase loading.[XmlIgnore] - gum-localization — localization-specific file loading, the place where
is consumed.LocalizationFile(s) - gum-tool-codegen —
files have their own.codsj
field (separate fromVersion
versioning), migrated in.gumx
on load. Don't auto-save — let the next user save persist the new version. Each version bump needs a comment explaining what it does and why.CodeOutputProjectSettingsManager.MigrateIfNeeded