Manage kits: reconcile the schema on save, and a dialog to repin builds
Placing a palette component no longer edits <schema> straight away. The kit is noted as pending, its manifest is compiled so the box can be painted, and the status bar says the kit will be declared on save. The <schema> block is then reconciled when the file is written, in the one place that can report the whole kit list the file will carry, with each kit's version and checksum. What a kit's manifest depends on counts as used too, transitively, so placing a component can bring in more than one entry: hvac pulls in types, ontrolPion pulls in ontrolPlatform. New Kits > Manage Kits... dialog lists every kit in three groups -- needed by the app, declared but unused, and merely installed -- with the pinned build, the newest installed build, and an Action dropdown of every build a kit ships. The tick means the schema declares the kit: grey and fixed for kits the app needs, unticked to remove, ticked to add at the newest build. "Upgrade all kits to latest version" moves every declared kit; "Update kits" applies the ticks and the Action column, additions and removals included. Every schema change is applied as a batch, then verified: a missing manifest or a component type that stops resolving reverts the whole block and reports why, and a kit the app needs cannot be removed. Changes are one undo step, which recompiles the slot registry in both directions since a repin can change a component's slots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+40
-3
@@ -19,6 +19,7 @@ import app_state
|
||||
_versions_cache = {} # kit name -> {checksum: version string}
|
||||
_manifest_cache = {} # kit name -> (version string, {type name: element})
|
||||
_component_cache = {} # 'kit::Type' -> bool
|
||||
_builds_cache = {} # kit name -> [(version string, checksum)], newest first
|
||||
|
||||
|
||||
def _manifests_dir():
|
||||
@@ -40,7 +41,7 @@ def list_kits():
|
||||
)
|
||||
|
||||
|
||||
def _version_sort_key(version_string):
|
||||
def version_sort_key(version_string):
|
||||
numbers = [int(part) for part in re.findall(r"\d+", version_string)]
|
||||
return numbers or [0]
|
||||
|
||||
@@ -94,7 +95,7 @@ def resolve_manifest_path(kit_name):
|
||||
for entry in manifests:
|
||||
checksum = entry[len(kit_name) + 1:-4]
|
||||
if checksum in versions:
|
||||
available.append((_version_sort_key(versions[checksum]), entry, versions[checksum]))
|
||||
available.append((version_sort_key(versions[checksum]), entry, versions[checksum]))
|
||||
|
||||
if available:
|
||||
available.sort()
|
||||
@@ -106,6 +107,41 @@ def resolve_manifest_path(kit_name):
|
||||
return os.path.join(kit_dir, entry), versions.get(checksum, "")
|
||||
|
||||
|
||||
def kit_builds(kit_name):
|
||||
"""Every build of a kit this Sedona home can describe, newest version first.
|
||||
|
||||
One entry per manifest under manifests/<kit>, as (version string, checksum). A
|
||||
manifest with no matching .kit file has no version to report, so its version is empty
|
||||
and such builds sort last — there is nothing to order them by.
|
||||
"""
|
||||
if kit_name in _builds_cache:
|
||||
return _builds_cache[kit_name]
|
||||
|
||||
kit_dir = os.path.join(_manifests_dir(), kit_name)
|
||||
builds = []
|
||||
if os.path.isdir(kit_dir):
|
||||
versions = kit_versions(kit_name)
|
||||
for entry in sorted(os.listdir(kit_dir)):
|
||||
if not (entry.startswith(f"{kit_name}-") and entry.endswith(".xml")):
|
||||
continue
|
||||
checksum = entry[len(kit_name) + 1:-4]
|
||||
builds.append((versions.get(checksum, ""), checksum))
|
||||
|
||||
builds.sort(
|
||||
key=lambda build: (bool(build[0]), version_sort_key(build[0])),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
_builds_cache[kit_name] = builds
|
||||
return builds
|
||||
|
||||
|
||||
def latest_build(kit_name):
|
||||
"""The newest build of a kit, as (version string, checksum), or None if it has none."""
|
||||
builds = kit_builds(kit_name)
|
||||
return builds[0] if builds else None
|
||||
|
||||
|
||||
def _load_kit(kit_name):
|
||||
"""Parses a kit's chosen manifest into (version label, {type name: element})."""
|
||||
if kit_name in _manifest_cache:
|
||||
@@ -201,7 +237,8 @@ def list_component_types(kit_name):
|
||||
|
||||
|
||||
def invalidate():
|
||||
"""Clears every cache, for when Sedona home changes in Preferences."""
|
||||
"""Clears every cache, for when Sedona home changes or a kit is repinned."""
|
||||
_versions_cache.clear()
|
||||
_manifest_cache.clear()
|
||||
_component_cache.clear()
|
||||
_builds_cache.clear()
|
||||
|
||||
Reference in New Issue
Block a user