feat: propagate feature suppression in recompute

This commit is contained in:
2026-08-02 22:37:41 -04:00
parent fca408784d
commit bc7adb500b
6 changed files with 102 additions and 26 deletions

View File

@@ -45,6 +45,7 @@ const commonProperties = (item: ModelTreeItem): ObjectPropertySnapshot[] => [
{ name: 'Label', label: 'Label', group: 'Identity', scope: 'data', type: 'App::PropertyString', value: item.label },
{ name: 'TypeId', label: 'Type', group: 'Identity', scope: 'data', type: 'App::PropertyString', value: typeIdForItem(item), readOnly: true },
{ name: 'Status', label: 'Status', group: 'Identity', scope: 'data', type: 'App::PropertyString', value: item.state === 'warning' ? 'Warning' : 'Valid', readOnly: true },
...(item.type === 'feature' ? [{ name: 'Suppressed', label: 'Suppressed', group: 'Feature state', scope: 'data' as const, type: 'App::PropertyBool' as const, value: false, recompute: true }] : []),
]
const viewProperties = (): ObjectPropertySnapshot[] => [
@@ -286,6 +287,11 @@ export function createMockFacade(): BitBybitWebCadFacade {
createFacadeGeometryRecomputeExecutor(geometryRuntime, featureShapes),
(documentId) => state.document.id === documentId ? state.document.version : null,
)
const releaseFeatureShape = (objectId: string) => {
const shape = featureShapes.get(objectId)
featureShapes.delete(objectId)
if (shape) void geometryRuntime.release(shape)
}
const clearFeatureShapes = () => {
const retained = [...featureShapes.values()]
featureShapes.clear()
@@ -414,20 +420,21 @@ export function createMockFacade(): BitBybitWebCadFacade {
const message = `Dependency cycle: ${cycle.join(' -> ')}`
for (const objectId of cycle) { objectStates[objectId] = 'error'; errors.push({ objectId, code: 'DEPENDENCY_CYCLE', message }) }
}
if (errors.length === 0) {
for (const objectId of plan.order) {
objectStates[objectId] = 'up-to-date'
const item = document.tree.find((candidate) => candidate.id === objectId)
if (item?.state === 'dirty') item.state = item.type === 'body' ? 'active' : 'valid'
const status = document.objects.find((candidate) => candidate.id === objectId)?.properties.find((property) => property.name === 'Status')
if (status?.value === 'Touched') status.value = 'Valid'
}
} else {
for (const objectId of plan.affected) if (objectStates[objectId] !== 'error') objectStates[objectId] = 'upstream-failed'
for (const objectId of plan.affected) {
const item = document.tree.find((candidate) => candidate.id === objectId)
if (item && item.state !== 'active') item.state = 'warning'
for (const objectId of plan.order) {
const dependencies = graph.dependenciesOf(objectId)
if (dependencies.some((dependencyId) => objectStates[dependencyId] === 'error' || objectStates[dependencyId] === 'upstream-failed')) {
objectStates[objectId] = 'upstream-failed'
} else if (dependencies.some((dependencyId) => objectStates[dependencyId] === 'suppressed' || objectStates[dependencyId] === 'upstream-suppressed')) {
objectStates[objectId] = 'upstream-suppressed'
} else {
const object = document.objects.find((candidate) => candidate.id === objectId)
objectStates[objectId] = object?.properties.some((property) => property.name === 'Suppressed' && property.value === true) ? 'suppressed' : 'up-to-date'
}
if (objectStates[objectId] === 'suppressed' || objectStates[objectId] === 'upstream-suppressed') releaseFeatureShape(objectId)
const item = document.tree.find((candidate) => candidate.id === objectId)
if (item && item.state !== 'readonly') item.state = objectStates[objectId] === 'upstream-failed' ? 'warning' : item.type === 'body' ? 'active' : 'valid'
const status = document.objects.find((candidate) => candidate.id === objectId)?.properties.find((property) => property.name === 'Status')
if (status) status.value = objectStates[objectId] === 'suppressed' ? 'Suppressed' : objectStates[objectId] === 'upstream-suppressed' ? 'Upstream suppressed' : objectStates[objectId] === 'up-to-date' ? 'Valid' : 'Warning'
}
const nextRecompute = { generation, status: errors.length === 0 ? 'completed' as const : 'failed' as const, objectStates, dirtyObjects: errors.length === 0 ? [] : plan.affected, order: plan.order, errors }
document.recompute = nextRecompute
@@ -445,12 +452,13 @@ export function createMockFacade(): BitBybitWebCadFacade {
const updates = new Map(result.objectUpdates.map((object) => [object.id, object]))
document.objects = document.objects.map((object) => updates.has(object.id) ? updates.get(object.id) as DocumentObjectSnapshot : object)
for (const objectId of result.affected) {
if (result.objectStates[objectId] === 'suppressed' || result.objectStates[objectId] === 'upstream-suppressed') releaseFeatureShape(objectId)
const item = document.tree.find((candidate) => candidate.id === objectId)
if (!item || item.state === 'readonly') continue
if (result.objectStates[objectId] === 'up-to-date') item.state = item.type === 'body' ? 'active' : 'valid'
if (result.objectStates[objectId] === 'up-to-date' || result.objectStates[objectId] === 'suppressed' || result.objectStates[objectId] === 'upstream-suppressed') item.state = item.type === 'body' ? 'active' : 'valid'
else if (result.objectStates[objectId] === 'error' || result.objectStates[objectId] === 'upstream-failed') item.state = 'warning'
const status = document.objects.find((candidate) => candidate.id === objectId)?.properties.find((property) => property.name === 'Status')
if (status) status.value = result.objectStates[objectId] === 'up-to-date' ? 'Valid' : 'Warning'
if (status) status.value = result.objectStates[objectId] === 'suppressed' ? 'Suppressed' : result.objectStates[objectId] === 'upstream-suppressed' ? 'Upstream suppressed' : result.objectStates[objectId] === 'up-to-date' ? 'Valid' : 'Warning'
}
document.recompute = {
generation: result.generation,