Mark overrides in gold instead of bold, and up the hierarchy

An inspector label whose value deviates from its base was drawn bold, which is
easy to miss next to the regular labels and says nothing in the hierarchy, where
a folded subtree could hide a change entirely.

The colour now lives as a theme token (`override`, #f2c84b) used by both the
inspector labels and the hierarchy rows. Whether a row deviates is decided by
`src/core/properties/deviation.ts`: `objectDeviates` compares an object against
what it follows -- the container template for content, the asset otherwise --
and `deviationMarks` carries the mark up to every ancestor, so a folded subtree
still shows that something inside was changed.

`deviationMarks` takes the stored holder, not the resolved one: in the resolved
view container content looks like an ordinary object and `instanceTargetOf`
would compare against the asset, marking every instance.

Covered by 3 unit tests and 5 e2e checks, including one that strips an object's
own overrides to prove the row stays gold for a deviating child and restores
them byte for byte. Full suite remote on the RTX 4070: 275 checks, all passed.
This commit is contained in:
root committed 2026-09-09 10:54:25 +02:00
1 parent 37e1450fca
commit b87fcdfbad
13 files changed
+197 -14

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
import { ancestorIdsOf, type ObjectTree } from '../model/hierarchy';
import { instanceTargetOf, isEmptyEdit, resolveObjects } from '../model/instance';
import { findAsset, type Project } from '../model/Project';
import type { SceneObject } from '../model/SceneObject';
import { schemaForAsset } from './PropertySchema';
import { deviatesFromAsset } from './resolve';
/**
* Whether a placed object deviates from whatever it follows: the container template it was
* resolved from, or its asset. `holder` must be the stored holder – what an object follows is
* decided by where it lives, and in a resolved view everything looks stored.
*/
export function objectDeviates(project: Project, holder: ObjectTree, object: SceneObject): boolean {
const target = instanceTargetOf(project, holder, object.id);
if (target) {
// Instance edits are pruned to what really deviates, so their presence is the answer.
const edit = target.instance.instanceEdits[target.path];
return edit !== undefined && !isEmptyEdit(edit);
}
if (object.editorOffset.x !== 0 || object.editorOffset.y !== 0) return true;
const asset = findAsset(project, object.assetId);
return (
asset !== undefined && schemaForAsset(asset).some((def) => deviatesFromAsset(object, asset, def.key))
);
}
/**
* Ids the hierarchy marks: everything deviating itself, plus every ancestor of one. The ancestors
* matter because subtrees fold – without them a collapsed row would hide that something below it
* was changed.
*/
export function deviationMarks(project: Project, holder: ObjectTree): Set<string> {
const view: ObjectTree = { objects: resolveObjects(project, holder) };
const marks = new Set<string>();
for (const object of view.objects) {
if (!objectDeviates(project, holder, object)) continue;
marks.add(object.id);
for (const ancestorId of ancestorIdsOf(view, object.id)) marks.add(ancestorId);
}
return marks;
}