From f84892d84e144e599d99754c6fad5767c49ebc12 Mon Sep 17 00:00:00 2001 From: Brandon Corfman Date: Sat, 18 Jul 2026 00:38:40 -0400 Subject: [PATCH] Phase 6f complete --- ...rs-demo-editor-gaps-implementation-plan.md | 10 ++-- src/editor/Inspector.tsx | 47 +++++++++++++--- src/model/validation.ts | 6 +++ ...attachment-inspector-set-property.test.tsx | 53 +++++++++++++++++++ tests/model/validation.test.ts | 5 ++ 5 files changed, 108 insertions(+), 13 deletions(-) diff --git a/.plans/stars-demo-editor-gaps-implementation-plan.md b/.plans/stars-demo-editor-gaps-implementation-plan.md index 0f8d060..d772e9a 100644 --- a/.plans/stars-demo-editor-gaps-implementation-plan.md +++ b/.plans/stars-demo-editor-gaps-implementation-plan.md @@ -79,12 +79,12 @@ Mockups: `.plans/mockups/stars-bounds-event-no-code-actions.svg` and `.plans/moc ### 6F — Enforce the no-code boundary -- [ ] Do not add a Script, Expression, Formula, Callback Body, TypeScript, or JavaScript field anywhere in this workflow. -- [ ] Do not introduce implicit identifiers such as `bounds`, `exitSide`, or `self`; every available target, property, filter, and value source must be discoverable through labeled controls. -- [ ] Do not introduce callable functions such as `rand(...)`; Random Range is a typed value-source option with visible Min, Max, and Seed fields. +- [x] Do not add a Script, Expression, Formula, Callback Body, TypeScript, or JavaScript field anywhere in this workflow. +- [x] Do not introduce implicit identifiers such as `bounds`, `exitSide`, or `self`; every available target, property, filter, and value source must be discoverable through labeled controls. +- [x] Do not introduce callable functions such as `rand(...)`; Random Range is a typed value-source option with visible Min, Max, and Seed fields. - [x] Keep the Set Property property list allowlisted and registry-described. Users cannot type arbitrary object paths such as `bounds.minX` or mutate unknown runtime state. -- [ ] Keep event fields available only through labeled selectors and compatible controls; do not expose an untyped event object or JSON expression context. -- [ ] Require a separate user-approved product proposal before any future in-editor scripting initiative. That proposal would need to define language choice, API/reference discovery, types, editor tooling, sandbox/security model, determinism, debugging, persistence/versioning, publishing, and migration independently of this demo. +- [x] Keep event fields available only through labeled selectors and compatible controls; do not expose an untyped event object or JSON expression context. +- [x] Require a separate user-approved product proposal before any future in-editor scripting initiative. That proposal would need to define language choice, API/reference discovery, types, editor tooling, sandbox/security model, determinism, debugging, persistence/versioning, publishing, and migration independently of this demo. ## Phase 7 — Assemble and Prove the Faithful Stars Workflow diff --git a/src/editor/Inspector.tsx b/src/editor/Inspector.tsx index 436fefc..04122f4 100644 --- a/src/editor/Inspector.tsx +++ b/src/editor/Inspector.tsx @@ -2112,6 +2112,13 @@ function AttachmentInspector({ : (scene.groups[attachment.target.groupId]?.name ?? attachment.target.groupId); const eventBlockTrigger = attachment.eventId ? scene.eventBlocks?.[attachment.eventId]?.trigger : attachment.trigger; const canTargetEventSource = eventBlockTrigger?.type === 'bounds'; + const canUseEventFields = eventBlockTrigger?.type === 'bounds'; + const eventFieldOptions = [ + ['positionX', 'Position X'], + ['positionY', 'Position Y'], + ['priorPositionX', 'Prior Position X'], + ['priorPositionY', 'Prior Position Y'], + ] as const; const supportedPresetIds = new Set([ 'MoveUntil', 'MoveTo', @@ -2139,6 +2146,15 @@ function AttachmentInspector({ 'RemoveSelfFromCollection', ]); const supportedPresets = registry.actions.filter((entry) => entry.implemented && supportedPresetIds.has(entry.type)); + const setPropertyEntry = registry.actions.find((entry) => entry.type === 'SetProperty'); + const registryPropertyKeys = (setPropertyEntry?.propertyTargets ?? []) + .map((target) => String(target.key)) + .filter((key): key is 'x' | 'y' | 'tint' | 'alpha' | 'visible' | 'vx' | 'vy' => + key === 'x' || key === 'y' || key === 'tint' || key === 'alpha' || key === 'visible' || key === 'vx' || key === 'vy' + ); + const allowedSetPropertyKeys = registryPropertyKeys.length > 0 + ? registryPropertyKeys + : (['x', 'y', 'tint', 'alpha', 'visible', 'vx', 'vy'] as const); const params = attachment.params ?? {}; const world = getSceneWorld(scene); const foldouts = useInspectorFoldouts(); @@ -2951,13 +2967,11 @@ function AttachmentInspector({ onUpdate({ ...attachment, params: { ...params, property, valueSource: { kind: 'constant', value: defaultValue } } }); }} > - - - - - - - + {allowedSetPropertyKeys.map((property) => ( + + ))} @@ -2973,15 +2987,32 @@ function AttachmentInspector({ onUpdate({ ...attachment, params: { ...params, valueSource: { kind: 'randomRange', min: 0, max: 720, seed: 'set-property' } } }); return; } + if (kind === 'eventField' && canUseEventFields) { + onUpdate({ ...attachment, params: { ...params, valueSource: { kind: 'eventField', field: 'positionX' } } }); + return; + } onUpdate({ ...attachment, params: { ...params, valueSource: { kind: 'constant', value: params.property === 'visible' ? true : 0 } } }); }} > {String(params.property ?? 'x') !== 'visible' && } + {canUseEventFields && String(params.property ?? 'x') !== 'visible' && } - {String((params.valueSource as any)?.kind ?? 'constant') === 'randomRange' ? ( + {String((params.valueSource as any)?.kind ?? 'constant') === 'eventField' ? ( + + ) : String((params.valueSource as any)?.kind ?? 'constant') === 'randomRange' ? ( <>