@operato/scene-ops 10.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.js","sourceRoot":"","sources":["../../src/ops.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Scene edit operations — the vocabulary for describing a change to a scene.\n *\n * One vocabulary, two appliers: `apply-model.ts` changes the stored JSON, `apply-scene.ts`\n * changes a Scene that is open on screen. They live together so they cannot drift.\n *\n * ── Targeting ──\n * Existing components are addressed by `refid` only. things-scene issues one to every\n * component; `model.id` is optional metadata and is absent more often than not, so it cannot\n * be a target. There is one channel, not two.\n *\n * ── The root ──\n * The scene root is the top-level parent (things-scene's model-layer) and carries its own\n * properties: fillStyle, width, height, fitMode, translate, scale, sky, skyColor, exposure,\n * the hemi/dir light fields, the camera fields. It has no refid, so `modifyScene` reaches it\n * and `modify` reaches everything else.\n *\n * Note that a board's *name* is a column on the board row in the database, not a field of\n * the scene model. Sending it through `modifyScene` writes a dead key into the JSON and\n * changes nothing on screen.\n */\nimport type { SceneComponentModel, SceneModel } from './model.js'\n\nexport type AlignDirection = 'left' | 'right' | 'center' | 'top' | 'middle' | 'bottom'\n\nexport type DistributeAxis = 'horizontal' | 'vertical'\n\nexport type ZorderDirection = 'front' | 'back' | 'forward' | 'backward'\n\n/**\n * The layout an `arrange` operation asks for.\n *\n * This sits above align/distribute so that \"in a 3x2 grid\", \"in one row\", \"in a column\"\n * is one operation rather than a list of coordinates.\n *\n * Only left/top change — width and height are kept. Changing size is a separate `modify`.\n *\n * Positions are computed by the scene applier, because they need each component's current\n * width and height. The model applier treats `arrange` as a no-op.\n */\nexport type ArrangeLayout =\n | { type: 'grid'; cols: number; gap?: number; anchor?: { left: number; top: number } }\n | {\n type: 'row'\n gap?: number\n anchor?: { left: number; top: number }\n align?: 'start' | 'center' | 'end'\n }\n | {\n type: 'column'\n gap?: number\n anchor?: { left: number; top: number }\n align?: 'start' | 'center' | 'end'\n }\n\n/** An operation that changes the scene. */\nexport type SceneEditOp =\n | { op: 'add'; component: SceneComponentModel }\n | { op: 'remove'; refid: number }\n | { op: 'modify'; refid: number; patch: Partial<SceneComponentModel> }\n | { op: 'modifyScene'; patch: Partial<SceneModel> }\n | { op: 'replace'; model: SceneModel }\n | { op: 'align'; refids: number[]; direction: AlignDirection }\n | { op: 'distribute'; refids: number[]; axis: DistributeAxis }\n | { op: 'group'; refids: number[] }\n | { op: 'ungroup'; refid: number }\n | { op: 'zorder'; refid: number; direction: ZorderDirection }\n | { op: 'arrange'; refids: number[]; layout: ArrangeLayout }\n\n/**\n * An operation that changes what the viewer sees but not what the scene is.\n *\n * Separate from `SceneEditOp` on purpose: these leave the model alone, so they do not enter\n * the undo history and do not make the document dirty. A host that mixes the two ends up\n * asking the user to save because the AI scrolled the view.\n */\nexport type SceneActionOp =\n | { action: 'selectComponents'; refids: number[] }\n | { action: 'centerToComponent'; refid: number; animated?: boolean }\n | { action: 'fitToView'; mode?: 'fit' | 'ratio' | 'width' | 'height' }\n | { action: 'setSceneMode'; mode: 'edit' | 'view' }\n /** Outline several components at once — the \"these are all the matches\" of a search. */\n | { action: 'highlightComponents'; refids: number[] }\n\n/** A batch of edit operations, with whatever the proposer wants to say about them. */\nexport interface SceneEditPatch {\n ops: SceneEditOp[]\n /** One or two sentences, for the person who has to approve it. */\n summary: string\n /** 0..1 */\n confidence: number\n}\n"]}
@@ -0,0 +1 @@
1
+ {"fileNames":["../../../node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/tslib/tslib.d.ts","../src/model.ts","../src/ops.ts","../src/apply-model.ts","../src/apply-scene.ts","../src/index.ts","../../../node_modules/@types/node/compatibility/disposable.d.ts","../../../node_modules/@types/node/compatibility/indexable.d.ts","../../../node_modules/@types/node/compatibility/iterators.d.ts","../../../node_modules/@types/node/compatibility/index.d.ts","../../../node_modules/@types/node/globals.typedarray.d.ts","../../../node_modules/@types/node/buffer.buffer.d.ts","../../../node_modules/@types/node/globals.d.ts","../../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../../node_modules/@types/node/web-globals/domexception.d.ts","../../../node_modules/@types/node/web-globals/events.d.ts","../../../node_modules/buffer/index.d.ts","../../../node_modules/undici-types/header.d.ts","../../../node_modules/undici-types/readable.d.ts","../../../node_modules/undici-types/file.d.ts","../../../node_modules/undici-types/fetch.d.ts","../../../node_modules/undici-types/formdata.d.ts","../../../node_modules/undici-types/connector.d.ts","../../../node_modules/undici-types/client.d.ts","../../../node_modules/undici-types/errors.d.ts","../../../node_modules/undici-types/dispatcher.d.ts","../../../node_modules/undici-types/global-dispatcher.d.ts","../../../node_modules/undici-types/global-origin.d.ts","../../../node_modules/undici-types/pool-stats.d.ts","../../../node_modules/undici-types/pool.d.ts","../../../node_modules/undici-types/handlers.d.ts","../../../node_modules/undici-types/balanced-pool.d.ts","../../../node_modules/undici-types/agent.d.ts","../../../node_modules/undici-types/mock-interceptor.d.ts","../../../node_modules/undici-types/mock-agent.d.ts","../../../node_modules/undici-types/mock-client.d.ts","../../../node_modules/undici-types/mock-pool.d.ts","../../../node_modules/undici-types/mock-errors.d.ts","../../../node_modules/undici-types/proxy-agent.d.ts","../../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../../node_modules/undici-types/retry-handler.d.ts","../../../node_modules/undici-types/retry-agent.d.ts","../../../node_modules/undici-types/api.d.ts","../../../node_modules/undici-types/interceptors.d.ts","../../../node_modules/undici-types/util.d.ts","../../../node_modules/undici-types/cookies.d.ts","../../../node_modules/undici-types/patch.d.ts","../../../node_modules/undici-types/websocket.d.ts","../../../node_modules/undici-types/eventsource.d.ts","../../../node_modules/undici-types/filereader.d.ts","../../../node_modules/undici-types/diagnostics-channel.d.ts","../../../node_modules/undici-types/content-type.d.ts","../../../node_modules/undici-types/cache.d.ts","../../../node_modules/undici-types/index.d.ts","../../../node_modules/@types/node/web-globals/fetch.d.ts","../../../node_modules/@types/node/web-globals/navigator.d.ts","../../../node_modules/@types/node/web-globals/storage.d.ts","../../../node_modules/@types/node/web-globals/streams.d.ts","../../../node_modules/@types/node/assert.d.ts","../../../node_modules/@types/node/assert/strict.d.ts","../../../node_modules/@types/node/async_hooks.d.ts","../../../node_modules/@types/node/buffer.d.ts","../../../node_modules/@types/node/child_process.d.ts","../../../node_modules/@types/node/cluster.d.ts","../../../node_modules/@types/node/console.d.ts","../../../node_modules/@types/node/constants.d.ts","../../../node_modules/@types/node/crypto.d.ts","../../../node_modules/@types/node/dgram.d.ts","../../../node_modules/@types/node/diagnostics_channel.d.ts","../../../node_modules/@types/node/dns.d.ts","../../../node_modules/@types/node/dns/promises.d.ts","../../../node_modules/@types/node/domain.d.ts","../../../node_modules/@types/node/events.d.ts","../../../node_modules/@types/node/fs.d.ts","../../../node_modules/@types/node/fs/promises.d.ts","../../../node_modules/@types/node/http.d.ts","../../../node_modules/@types/node/http2.d.ts","../../../node_modules/@types/node/https.d.ts","../../../node_modules/@types/node/inspector.d.ts","../../../node_modules/@types/node/inspector.generated.d.ts","../../../node_modules/@types/node/module.d.ts","../../../node_modules/@types/node/net.d.ts","../../../node_modules/@types/node/os.d.ts","../../../node_modules/@types/node/path.d.ts","../../../node_modules/@types/node/perf_hooks.d.ts","../../../node_modules/@types/node/process.d.ts","../../../node_modules/@types/node/punycode.d.ts","../../../node_modules/@types/node/querystring.d.ts","../../../node_modules/@types/node/readline.d.ts","../../../node_modules/@types/node/readline/promises.d.ts","../../../node_modules/@types/node/repl.d.ts","../../../node_modules/@types/node/sea.d.ts","../../../node_modules/@types/node/sqlite.d.ts","../../../node_modules/@types/node/stream.d.ts","../../../node_modules/@types/node/stream/promises.d.ts","../../../node_modules/@types/node/stream/consumers.d.ts","../../../node_modules/@types/node/stream/web.d.ts","../../../node_modules/@types/node/string_decoder.d.ts","../../../node_modules/@types/node/test.d.ts","../../../node_modules/@types/node/timers.d.ts","../../../node_modules/@types/node/timers/promises.d.ts","../../../node_modules/@types/node/tls.d.ts","../../../node_modules/@types/node/trace_events.d.ts","../../../node_modules/@types/node/tty.d.ts","../../../node_modules/@types/node/url.d.ts","../../../node_modules/@types/node/util.d.ts","../../../node_modules/@types/node/v8.d.ts","../../../node_modules/@types/node/vm.d.ts","../../../node_modules/@types/node/wasi.d.ts","../../../node_modules/@types/node/worker_threads.d.ts","../../../node_modules/@types/node/zlib.d.ts","../../../node_modules/@types/node/index.d.ts","../../../node_modules/@jest/expect-utils/build/index.d.ts","../../../node_modules/chalk/index.d.ts","../../../node_modules/@sinclair/typebox/typebox.d.ts","../../../node_modules/@jest/schemas/build/index.d.ts","../../../node_modules/pretty-format/build/index.d.ts","../../../node_modules/jest-diff/build/index.d.ts","../../../node_modules/jest-matcher-utils/build/index.d.ts","../../../node_modules/expect/build/index.d.ts","../../../node_modules/@types/jest/index.d.ts"],"fileIdsList":[[58,108,125,126],[58,108,125,126,161],[58,108,125,126,163,166],[58,105,106,108,125,126],[58,107,108,125,126],[108,125,126],[58,108,113,125,126,143],[58,108,109,114,119,125,126,128,140,151],[58,108,109,110,119,125,126,128],[53,54,55,58,108,125,126],[58,108,111,125,126,152],[58,108,112,113,120,125,126,129],[58,108,113,125,126,140,148],[58,108,114,116,119,125,126,128],[58,107,108,115,125,126],[58,108,116,117,125,126],[58,108,118,119,125,126],[58,107,108,119,125,126],[58,108,119,120,121,125,126,140,151],[58,108,119,120,121,125,126,135,140,143],[58,100,108,116,119,122,125,126,128,140,151],[58,108,119,120,122,123,125,126,128,140,148,151],[58,108,122,124,125,126,140,148,151],[56,57,58,59,60,61,62,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157],[58,108,119,125,126],[58,108,125,126,127,151],[58,108,116,119,125,126,128,140],[58,108,125,126,129],[58,108,125,126,130],[58,107,108,125,126,131],[58,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157],[58,108,125,126,133],[58,108,125,126,134],[58,108,119,125,126,135,136],[58,108,125,126,135,137,152,154],[58,108,120,125,126],[58,108,119,125,126,140,141,143],[58,108,125,126,142,143],[58,108,125,126,140,141],[58,108,125,126,143],[58,108,125,126,144],[58,105,108,125,126,140,145,151],[58,108,119,125,126,146,147],[58,108,125,126,146,147],[58,108,113,125,126,128,140,148],[58,108,125,126,149],[58,108,125,126,128,150],[58,108,122,125,126,134,151],[58,108,113,125,126,152],[58,108,125,126,140,153],[58,108,125,126,127,154],[58,108,125,126,155],[58,100,108,125,126],[58,100,108,119,121,125,126,131,140,143,151,153,154,156],[58,108,125,126,140,157],[58,108,125,126,159,165],[58,108,125,126,163],[58,108,125,126,160,164],[58,108,125,126,162],[58,72,76,108,125,126,151],[58,72,108,125,126,140,151],[58,67,108,125,126],[58,69,72,108,125,126,148,151],[58,108,125,126,128,148],[58,108,125,126,158],[58,67,108,125,126,158],[58,69,72,108,125,126,128,151],[58,64,65,68,71,108,119,125,126,140,151],[58,72,79,108,125,126],[58,64,70,108,125,126],[58,72,93,94,108,125,126],[58,68,72,108,125,126,143,151,158],[58,93,108,125,126,158],[58,66,67,108,125,126,158],[58,72,108,125,126],[58,66,67,68,69,70,71,72,73,74,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,94,95,96,97,98,99,108,125,126],[58,72,87,108,125,126],[58,72,79,80,108,125,126],[58,70,72,80,81,108,125,126],[58,71,108,125,126],[58,64,67,72,108,125,126],[58,72,76,80,81,108,125,126],[58,76,108,125,126],[58,70,72,75,108,125,126,151],[58,64,69,72,79,108,125,126],[58,108,125,126,140],[58,67,72,93,108,125,126,156,158],[47,48,49,58,108,125,126],[47,49,50,58,108,125,126],[47,48,49,50,51,58,108,125,126],[47,58,108,125,126],[47,48,58,108,125,126]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6a5253138c5432c68a1510c70fe78a644fe2e632111ba778e1978010d6edfec","impliedFormat":1},{"version":"2bff0eeeb7940e0ffa525dd3ed8a8f139e02b896a130a5ff9094bba7bb492ddd","signature":"411a3210bdc804b7a5fac9e8411bdf55a775a21974daa0ed15bff6bb9efe471b"},{"version":"c71a9cd939f621d4f184238f86126bf86e5fb4309b8c337548a98dc00607f7e9","signature":"b56dd87ffa39030b9094bcbf2d36dbfa5bb273b9d9b059b11c2793c204e9177f"},{"version":"a38e9da950955cfce7f801fff612888302ffe12804be0cd55ac6d8dce1ce3968","signature":"cc238e55e711f6994d9cf7775451e243edb79d1ce2c8254884775fe9450a473e"},{"version":"b1ae624b1550a46d4b8c45385df83d9445d883368b731bef2c8a19fdbfec3233","signature":"0feba10954d2edf22d2e7d4559ab145dae2fb17a1e507b6e75d66f598148a70d"},{"version":"4b54ceb00fe6b4e6f2612cb966e3648ac70b5bc238e87ab8c1c363609a74b429","signature":"8229d30586d3560e5a3c150a0a2988e6d9a9306e7bc0fe0b702c66d6b1d940db"},{"version":"6c7176368037af28cb72f2392010fa1cef295d6d6744bca8cfb54985f3a18c3e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"437e20f2ba32abaeb7985e0afe0002de1917bc74e949ba585e49feba65da6ca1","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"98cffbf06d6bab333473c70a893770dbe990783904002c4f1a960447b4b53dca","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"808069bba06b6768b62fd22429b53362e7af342da4a236ed2d2e1c89fcca3b4a","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f","impliedFormat":1},{"version":"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656","impliedFormat":1},{"version":"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88","impliedFormat":1},{"version":"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"b52476feb4a0cbcb25e5931b930fc73cb6643fb1a5060bf8a3dda0eeae5b4b68","affectsGlobalScope":true,"impliedFormat":1},{"version":"f9501cc13ce624c72b61f12b3963e84fad210fbdf0ffbc4590e08460a3f04eba","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fa06ada475b910e2106c98c68b10483dc8811d0c14a8a8dd36efb2672485b29","impliedFormat":1},{"version":"33e5e9aba62c3193d10d1d33ae1fa75c46a1171cf76fef750777377d53b0303f","impliedFormat":1},{"version":"2b06b93fd01bcd49d1a6bd1f9b65ddcae6480b9a86e9061634d6f8e354c1468f","impliedFormat":1},{"version":"6a0cd27e5dc2cfbe039e731cf879d12b0e2dded06d1b1dedad07f7712de0d7f4","affectsGlobalScope":true,"impliedFormat":1},{"version":"913be43dad2a40e500104700aa26cfb5c51a976114bedf19624de43fdae7474f","impliedFormat":1},{"version":"e60477649d6ad21542bd2dc7e3d9ff6853d0797ba9f689ba2f6653818999c264","impliedFormat":1},{"version":"c2510f124c0293ab80b1777c44d80f812b75612f297b9857406468c0f4dafe29","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"4c829ab315f57c5442c6667b53769975acbf92003a66aef19bce151987675bd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"b2ade7657e2db96d18315694789eff2ddd3d8aea7215b181f8a0b303277cc579","impliedFormat":1},{"version":"78dbea00e90d2df8ea3dbef0cc379d95b8be9b71cd6bde4c28728f306811803b","impliedFormat":1},{"version":"4d631b81fa2f07a0e63a9a143d6a82c25c5f051298651a9b69176ba28930756d","impliedFormat":1},{"version":"836a356aae992ff3c28a0212e3eabcb76dd4b0cc06bcb9607aeef560661b860d","impliedFormat":1},{"version":"1e0d1f8b0adfa0b0330e028c7941b5a98c08b600efe7f14d2d2a00854fb2f393","impliedFormat":1},{"version":"41670ee38943d9cbb4924e436f56fc19ee94232bc96108562de1a734af20dc2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e1e46d0a9837ee058c100501080c920fa98081ea3956af0374308ba6f22a33e","impliedFormat":1},{"version":"272ca407e0c9068bdc5152552d876e68037ceae3de62e529306403e973dec8e1","impliedFormat":1},{"version":"fa7834c715d5357e4540cee40ce96c3250ddb67a7b879a6b7fa0e86d6696f121","impliedFormat":1},{"version":"22dfb07a7ab15b66ac043829056fe70124844636ae719551812ac631ba04985b","impliedFormat":1},{"version":"a10f0e1854f3316d7ee437b79649e5a6ae3ae14ffe6322b02d4987071a95362e","impliedFormat":1},{"version":"e208f73ef6a980104304b0d2ca5f6bf1b85de6009d2c7e404028b875020fa8f2","impliedFormat":1},{"version":"d163b6bc2372b4f07260747cbc6c0a6405ab3fbcea3852305e98ac43ca59f5bc","impliedFormat":1},{"version":"e6fa9ad47c5f71ff733744a029d1dc472c618de53804eae08ffc243b936f87ff","affectsGlobalScope":true,"impliedFormat":1},{"version":"a6f137d651076822d4fe884287e68fd61785a0d3d1fdb250a5059b691fa897db","impliedFormat":1},{"version":"24826ed94a78d5c64bd857570fdbd96229ad41b5cb654c08d75a9845e3ab7dde","impliedFormat":1},{"version":"8b479a130ccb62e98f11f136d3ac80f2984fdc07616516d29881f3061f2dd472","impliedFormat":1},{"version":"928af3d90454bf656a52a48679f199f64c1435247d6189d1caf4c68f2eaf921f","affectsGlobalScope":true,"impliedFormat":1},{"version":"bceb58df66ab8fb00170df20cd813978c5ab84be1d285710c4eb005d8e9d8efb","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f16a7e4deafa527ed9995a772bb380eb7d3c2c0fd4ae178c5263ed18394db2c","impliedFormat":1},{"version":"933921f0bb0ec12ef45d1062a1fc0f27635318f4d294e4d99de9a5493e618ca2","impliedFormat":1},{"version":"71a0f3ad612c123b57239a7749770017ecfe6b66411488000aba83e4546fde25","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"4f9d8ca0c417b67b69eeb54c7ca1bedd7b56034bb9bfd27c5d4f3bc4692daca7","impliedFormat":1},{"version":"0cb167c371eaa8c869f8a7656a7296f2e4fae43b4d8b803a680236b24794e5f9","impliedFormat":1},{"version":"0a839dba0287cc0481ad4beedd48a1c64acf1e212ae865d1315f7007ca215161","impliedFormat":1},{"version":"38dc4655376cd1a4bd6bb3763d92949233e33d38d3dd3cbea7bbf218175a38ef","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"208a6a0bfb227bdf8fb964799d0a206c75cf03ccd72e63bf5495c9331354c6d6","affectsGlobalScope":true,"impliedFormat":1},{"version":"18fd40412d102c5564136f29735e5d1c3b455b8a37f920da79561f1fde068208","impliedFormat":1},{"version":"48a679952eefe4cb776d5a0e1ccba2d3eb53b57448bbb7abc1fcebcbd5440188","impliedFormat":1},{"version":"f0be1b8078cd549d91f37c30c222c2a187ac1cf981d994fb476a1adc61387b14","affectsGlobalScope":true,"impliedFormat":1},{"version":"0aaed1d72199b01234152f7a60046bc947f1f37d78d182e9ae09c4289e06a592","impliedFormat":1},{"version":"2d14da6ecb49bf828d83948765ec2d3a579d476bbb9645e749610baa6ec880ca","impliedFormat":1},{"version":"66ba1b2c3e3a3644a1011cd530fb444a96b1b2dfe2f5e837a002d41a1a799e60","impliedFormat":1},{"version":"7e514f5b852fdbc166b539fdd1f4e9114f29911592a5eb10a94bb3a13ccac3c4","impliedFormat":1},{"version":"5b7aa3c4c1a5d81b411e8cb302b45507fea9358d3569196b27eb1a27ae3a90ef","affectsGlobalScope":true,"impliedFormat":1},{"version":"5987a903da92c7462e0b35704ce7da94d7fdc4b89a984871c0e2b87a8aae9e69","affectsGlobalScope":true,"impliedFormat":1},{"version":"ea08a0345023ade2b47fbff5a76d0d0ed8bff10bc9d22b83f40858a8e941501c","impliedFormat":1},{"version":"0aef708fb4c7a6b915e8305cbfac40cd207b032dbaabe9a01889a5fff3254681","impliedFormat":1},{"version":"ae062ce7d9510060c5d7e7952ae379224fb3f8f2dd74e88959878af2057c143b","impliedFormat":1},{"version":"ad9bdafb4e7abf14cc53ce7970486a84c87831e62891e5dfe798ddcd55e84701","affectsGlobalScope":true,"impliedFormat":1},{"version":"358765d5ea8afd285d4fd1532e78b88273f18cb3f87403a9b16fef61ac9fdcfe","impliedFormat":1},{"version":"71d3ae6a5e73ca4130762560425e00984ebaff64d5353a3333d1bb7eb86ef336","impliedFormat":1},{"version":"cdcc132f207d097d7d3aa75615ab9a2e71d6a478162dde8b67f88ea19f3e54de","impliedFormat":1},{"version":"0d14fa22c41fdc7277e6f71473b20ebc07f40f00e38875142335d5b63cdfc9d2","impliedFormat":1},{"version":"e1028394c1cf96d5d057ecc647e31e457b919092f882ed0c7092152b077fed9d","impliedFormat":1},{"version":"f315e1e65a1f80992f0509e84e4ae2df15ecd9ef73df975f7c98813b71e4c8da","impliedFormat":1},{"version":"5b9586e9b0b6322e5bfbd2c29bd3b8e21ab9d871f82346cb71020e3d84bae73e","impliedFormat":1},{"version":"3e70a7e67c2cb16f8cd49097360c0309fe9d1e3210ff9222e9dac1f8df9d4fb6","impliedFormat":1},{"version":"ab68d2a3e3e8767c3fba8f80de099a1cfc18c0de79e42cb02ae66e22dfe14a66","impliedFormat":1},{"version":"d96cc6598148bf1a98fb2e8dcf01c63a4b3558bdaec6ef35e087fd0562eb40ec","impliedFormat":1},{"version":"f8db4fea512ab759b2223b90ecbbe7dae919c02f8ce95ec03f7fb1cf757cfbeb","affectsGlobalScope":true,"impliedFormat":1}],"root":[[48,52]],"options":{"allowSyntheticDefaultImports":true,"declaration":true,"esModuleInterop":true,"experimentalDecorators":true,"importHelpers":true,"inlineSources":true,"module":99,"noEmitOnError":true,"outDir":"./","rootDir":"..","skipLibCheck":true,"sourceMap":true,"strict":true,"target":99,"useDefineForClassFields":true},"referencedMap":[[159,1],[162,2],[161,1],[167,3],[105,4],[106,4],[107,5],[58,6],[108,7],[109,8],[110,9],[53,1],[56,10],[54,1],[55,1],[111,11],[112,12],[113,13],[114,14],[115,15],[116,16],[117,16],[118,17],[119,18],[120,19],[121,20],[59,1],[57,1],[122,21],[123,22],[124,23],[158,24],[125,25],[126,1],[127,26],[128,27],[129,28],[130,29],[131,30],[132,31],[133,32],[134,33],[135,34],[136,34],[137,35],[138,1],[139,36],[140,37],[142,38],[141,39],[143,40],[144,41],[145,42],[146,43],[147,44],[148,45],[149,46],[150,47],[151,48],[152,49],[153,50],[154,51],[155,52],[60,1],[61,1],[62,1],[101,53],[102,1],[103,1],[104,40],[156,54],[157,55],[63,1],[160,1],[166,56],[164,57],[165,58],[163,59],[47,1],[45,1],[46,1],[8,1],[10,1],[9,1],[2,1],[11,1],[12,1],[13,1],[14,1],[15,1],[16,1],[17,1],[18,1],[3,1],[19,1],[20,1],[4,1],[21,1],[25,1],[22,1],[23,1],[24,1],[26,1],[27,1],[28,1],[5,1],[29,1],[30,1],[31,1],[32,1],[6,1],[36,1],[33,1],[34,1],[35,1],[37,1],[7,1],[38,1],[43,1],[44,1],[39,1],[40,1],[41,1],[42,1],[1,1],[79,60],[89,61],[78,60],[99,62],[70,63],[69,64],[98,65],[92,66],[97,67],[72,68],[86,69],[71,70],[95,71],[67,72],[66,65],[96,73],[68,74],[73,75],[74,1],[77,75],[64,1],[100,76],[90,77],[81,78],[82,79],[84,80],[80,81],[83,82],[93,65],[75,83],[76,84],[85,85],[65,86],[88,77],[87,75],[91,1],[94,87],[50,88],[51,89],[52,90],[48,91],[49,92]],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@operato/scene-ops",
3
+ "version": "10.14.0",
4
+ "type": "module",
5
+ "main": "./dist/cjs/index.js",
6
+ "module": "./dist/src/index.js",
7
+ "author": "heartyoh",
8
+ "description": "Scene edit operations — a vocabulary for describing a change to a things-scene model, and the two appliers that carry it out (stored JSON model, live Scene)",
9
+ "license": "MIT",
10
+ "publishConfig": {
11
+ "access": "public",
12
+ "@operato:registry": "https://registry.npmjs.org"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/hatiolab/operato.git",
17
+ "directory": "packages/scene-ops"
18
+ },
19
+ "scripts": {
20
+ "test": "jest",
21
+ "lint": "eslint --ext .ts . --ignore-path .gitignore",
22
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({type:'commonjs'})+'\\n')\"",
23
+ "clean": "rimraf './dist'"
24
+ },
25
+ "devDependencies": {
26
+ "@hatiolab/prettier-config": "^1.0.0",
27
+ "@types/jest": "^29.2.1",
28
+ "jest": "^29.2.1",
29
+ "rimraf": "^5.0.0",
30
+ "ts-jest": "^29.0.3",
31
+ "tslib": "^2.3.1",
32
+ "typescript": "^5.0.4"
33
+ },
34
+ "prettier": "@hatiolab/prettier-config",
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "README.md"
39
+ ],
40
+ "types": "./dist/src/index.d.ts",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/src/index.d.ts",
44
+ "import": "./dist/src/index.js",
45
+ "require": "./dist/cjs/index.js"
46
+ },
47
+ "./package.json": "./package.json"
48
+ },
49
+ "gitHead": "9988320c6a345eaa0b9623d6e641fee0650b6dbc"
50
+ }
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Applying edit operations to a stored scene model.
3
+ *
4
+ * Pure: nothing here mutates its input. This is the applier for a scene that is not open —
5
+ * a file being transformed, a template being instantiated, a proposal being previewed. For a
6
+ * scene that is open on screen, use `apply-scene.ts`; things-scene's own API is the authority
7
+ * there and several operations are deliberately no-ops here.
8
+ */
9
+ import type { SceneComponentModel, SceneModel } from './model.js'
10
+ import type { SceneEditOp, SceneEditPatch } from './ops.js'
11
+
12
+ const EMPTY_SCENE: SceneModel = {
13
+ width: 1000,
14
+ height: 600,
15
+ components: []
16
+ }
17
+
18
+ export interface PatchApplyReport {
19
+ /** The scene after the patch. Unchanged input is returned as-is when every op was a no-op. */
20
+ model: SceneModel
21
+ /** The ops that actually changed something. */
22
+ applied: SceneEditOp[]
23
+ /** The ops that did nothing — a refid that is not there, most often. */
24
+ missed: SceneEditOp[]
25
+ }
26
+
27
+ export function applyScenePatch(model: SceneModel | undefined, patch: SceneEditPatch): SceneModel {
28
+ return applyScenePatchVerbose(model, patch).model
29
+ }
30
+
31
+ /**
32
+ * The same, but reporting which ops landed.
33
+ *
34
+ * `modify` and `remove` do nothing at all when the refid is not in the scene. Without this
35
+ * report the host answers "done" to a change that never happened — which is exactly what a
36
+ * language model producing a wrong refid looks like from the user's seat.
37
+ */
38
+ export function applyScenePatchVerbose(
39
+ model: SceneModel | undefined,
40
+ patch: SceneEditPatch
41
+ ): PatchApplyReport {
42
+ let result: SceneModel = model ?? EMPTY_SCENE
43
+ const applied: SceneEditOp[] = []
44
+ const missed: SceneEditOp[] = []
45
+
46
+ for (const op of patch.ops) {
47
+ if (SCENE_ONLY_OPS.has(op.op)) {
48
+ /* A no-op here, but the scene applier really does carry it out — count it as applied. */
49
+ applied.push(op)
50
+ continue
51
+ }
52
+ const next = applyOp(result, op)
53
+ /*
54
+ * `componentsUnchanged` looks at the component list and at width/height/fillStyle only.
55
+ * It is here to catch a `modify`/`remove` that silently hit nothing, so it must not run
56
+ * for `modifyScene`, which legitimately changes other root keys such as `sky`.
57
+ */
58
+ const componentMutating = op.op === 'modify' || op.op === 'remove'
59
+ if (next === result || (componentMutating && componentsUnchanged(result, next))) {
60
+ missed.push(op)
61
+ } else {
62
+ applied.push(op)
63
+ result = next
64
+ }
65
+ }
66
+
67
+ return { model: result, applied, missed }
68
+ }
69
+
70
+ function componentsUnchanged(prev: SceneModel, next: SceneModel): boolean {
71
+ /*
72
+ * `applyOp` always builds a new object, so comparing references tells us nothing and we
73
+ * compare contents. The component arrays come out of map/filter, so we compare element by
74
+ * element rather than by array identity.
75
+ */
76
+ const a = prev.components ?? []
77
+ const b = next.components ?? []
78
+ if (a.length !== b.length) return false
79
+ for (let i = 0; i < a.length; i++) {
80
+ if (a[i] !== b[i]) return false
81
+ }
82
+ return prev.width === next.width && prev.height === next.height && prev.fillStyle === next.fillStyle
83
+ }
84
+
85
+ /**
86
+ * The inverse of one operation, against the scene as it was before that operation ran.
87
+ *
88
+ * This is what makes undo possible for a proposed change: a host that applies ops one at a
89
+ * time and keeps the inverses can restore the scene by running them backwards.
90
+ *
91
+ * Returns null when the inverse cannot be known from the model alone. `add` is the clear
92
+ * case — its inverse needs the refid that things-scene issues on insert, so the host captures
93
+ * that itself. The scene-only operations are the same story: their result depends on
94
+ * coordinates and parenting that only the live scene knows.
95
+ */
96
+ export function computeInverseOp(model: SceneModel | undefined, op: SceneEditOp): SceneEditOp | null {
97
+ if (!model) return null
98
+ const components = model.components ?? []
99
+
100
+ switch (op.op) {
101
+ case 'add':
102
+ return null
103
+
104
+ case 'remove': {
105
+ const target = findComponentDeep(components, op.refid)
106
+ if (!target) return null /* nothing was removed, so there is nothing to put back */
107
+ return { op: 'add', component: JSON.parse(JSON.stringify(target)) }
108
+ }
109
+
110
+ case 'modify': {
111
+ const target = findComponentDeep(components, op.refid)
112
+ if (!target) return null
113
+ /* Keep only the keys the patch touches; nested values are kept whole. */
114
+ const oldValues: any = {}
115
+ for (const k of Object.keys(op.patch || {})) {
116
+ const v = (target as any)[k]
117
+ oldValues[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))
118
+ }
119
+ return { op: 'modify', refid: op.refid, patch: oldValues }
120
+ }
121
+
122
+ case 'modifyScene': {
123
+ const oldValues: any = {}
124
+ const patch = op.patch || {}
125
+ for (const k of Object.keys(patch)) {
126
+ if (k === 'components') continue
127
+ const v = (model as any)[k]
128
+ oldValues[k] = v === undefined ? null : JSON.parse(JSON.stringify(v))
129
+ }
130
+ return { op: 'modifyScene', patch: oldValues }
131
+ }
132
+
133
+ case 'replace':
134
+ return { op: 'replace', model: JSON.parse(JSON.stringify(model)) }
135
+
136
+ case 'zorder': {
137
+ /*
138
+ * forward ↔ backward and front ↔ back. front/back is best effort — sending something
139
+ * to the front and then to the back does not put it where it started. A host that needs
140
+ * an exact inverse captures the index beforehand and writes explicit modifies.
141
+ */
142
+ const opp: Record<string, ZorderInverse> = {
143
+ forward: 'backward',
144
+ backward: 'forward',
145
+ front: 'back',
146
+ back: 'front'
147
+ }
148
+ const dir = opp[op.direction]
149
+ if (!dir) return null
150
+ return { op: 'zorder', refid: op.refid, direction: dir }
151
+ }
152
+
153
+ case 'align':
154
+ case 'distribute':
155
+ case 'group':
156
+ case 'ungroup':
157
+ case 'arrange':
158
+ return null
159
+
160
+ default:
161
+ return null
162
+ }
163
+ }
164
+
165
+ type ZorderInverse = 'front' | 'back' | 'forward' | 'backward'
166
+
167
+ /**
168
+ * The operations whose result only the live scene can produce — exact coordinates, refid
169
+ * issuing, reparenting. Simulating them here would give an answer that disagrees with what
170
+ * the user sees, so they do nothing and `apply-scene.ts` calls things-scene's own API.
171
+ */
172
+ const SCENE_ONLY_OPS = new Set<SceneEditOp['op']>([
173
+ 'align',
174
+ 'distribute',
175
+ 'group',
176
+ 'ungroup',
177
+ 'zorder',
178
+ 'arrange'
179
+ ])
180
+
181
+ export function applyOp(model: SceneModel, op: SceneEditOp): SceneModel {
182
+ const components = model.components ?? []
183
+ switch (op.op) {
184
+ case 'replace':
185
+ return op.model
186
+
187
+ case 'add':
188
+ /* Top level only — adding into a container is a separate operation we do not have yet. */
189
+ return { ...model, components: [...components, op.component] }
190
+
191
+ case 'remove':
192
+ return { ...model, components: removeComponentDeep(components, op.refid) }
193
+
194
+ case 'modify':
195
+ return { ...model, components: modifyComponentDeep(components, op.refid, op.patch) }
196
+
197
+ case 'modifyScene': {
198
+ /* Root properties only. `components` is ignored — children move by add/remove/modify. */
199
+ const patch = { ...op.patch } as any
200
+ delete patch.components
201
+ return mergeSceneRoot(model, patch)
202
+ }
203
+
204
+ case 'align':
205
+ case 'distribute':
206
+ case 'group':
207
+ case 'ungroup':
208
+ case 'zorder':
209
+ case 'arrange':
210
+ return model
211
+
212
+ default:
213
+ return model
214
+ }
215
+ }
216
+
217
+ /**
218
+ * `null` in a patch means **remove this key**, not "set it to null".
219
+ *
220
+ * Three reasons this is the right reading:
221
+ * 1. Fields such as fillStyle and strokeStyle are `string | object | undefined`. Setting
222
+ * one to null walks straight into `typeof null === 'object'` downstream.
223
+ * 2. `computeInverseOp` emits null for a key the original did not have, which is exactly
224
+ * "remove it again" — so undo comes out right without a special case.
225
+ * 3. Clearing a field that a mode switch left behind is then just another key in the patch.
226
+ *
227
+ * A caller that genuinely wants to store null would need a new operation. Nothing in the
228
+ * scene model means anything by a stored null today.
229
+ */
230
+ function mergeSceneRoot(model: SceneModel, patch: any): SceneModel {
231
+ const out: any = { ...model }
232
+ for (const key of Object.keys(patch)) {
233
+ const bv = (model as any)[key]
234
+ const pv = patch[key]
235
+ if (pv === null) {
236
+ delete out[key]
237
+ } else if (isPlainObject(bv) && isPlainObject(pv)) {
238
+ out[key] = deepMerge(bv, pv)
239
+ } else {
240
+ out[key] = pv
241
+ }
242
+ }
243
+ return out
244
+ }
245
+
246
+ /**
247
+ * Apply a partial patch to one component.
248
+ *
249
+ * Nested objects such as `threeD` are merged rather than replaced, so that changing a colour
250
+ * does not take the geometry with it. A null value removes the key, as above.
251
+ */
252
+ export function mergeComponent(
253
+ base: SceneComponentModel,
254
+ patch: Partial<SceneComponentModel>
255
+ ): SceneComponentModel {
256
+ const out: any = { ...base }
257
+ for (const key of Object.keys(patch)) {
258
+ const baseVal = (base as any)[key]
259
+ const patchVal = (patch as any)[key]
260
+ if (patchVal === null) {
261
+ delete out[key]
262
+ } else if (isPlainObject(baseVal) && isPlainObject(patchVal)) {
263
+ out[key] = deepMerge(baseVal, patchVal)
264
+ } else {
265
+ out[key] = patchVal
266
+ }
267
+ }
268
+ return out as SceneComponentModel
269
+ }
270
+
271
+ function deepMerge(a: any, b: any): any {
272
+ const out: any = { ...a }
273
+ for (const key of Object.keys(b)) {
274
+ const av = a[key]
275
+ const bv = b[key]
276
+ if (bv === null) {
277
+ delete out[key]
278
+ } else if (isPlainObject(av) && isPlainObject(bv)) {
279
+ out[key] = deepMerge(av, bv)
280
+ } else {
281
+ out[key] = bv
282
+ }
283
+ }
284
+ return out
285
+ }
286
+
287
+ function isPlainObject(v: any): boolean {
288
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
289
+ }
290
+
291
+ /* ── Reaching into containers ─────────────────────────────────────────────────────────── */
292
+
293
+ /** Find a component by refid anywhere in the tree, not only at the top level. */
294
+ function findComponentDeep(
295
+ components: SceneComponentModel[],
296
+ refid: number
297
+ ): SceneComponentModel | undefined {
298
+ for (const c of components) {
299
+ if (!c) continue
300
+ if ((c as any).refid === refid) return c
301
+ const children = (c as any).components
302
+ if (Array.isArray(children) && children.length > 0) {
303
+ const sub = findComponentDeep(children, refid)
304
+ if (sub) return sub
305
+ }
306
+ }
307
+ return undefined
308
+ }
309
+
310
+ /**
311
+ * `modify`, walking into containers. Branches that did not change come back as the same
312
+ * reference, so a caller can tell what moved by identity.
313
+ */
314
+ function modifyComponentDeep(
315
+ components: SceneComponentModel[],
316
+ refid: number,
317
+ patch: any
318
+ ): SceneComponentModel[] {
319
+ let changed = false
320
+ const out = components.map(c => {
321
+ if (!c) return c
322
+ if ((c as any).refid === refid) {
323
+ changed = true
324
+ return mergeComponent(c, patch)
325
+ }
326
+ const children = (c as any).components
327
+ if (Array.isArray(children) && children.length > 0) {
328
+ const updated = modifyComponentDeep(children, refid, patch)
329
+ if (updated !== children) {
330
+ changed = true
331
+ return { ...c, components: updated }
332
+ }
333
+ }
334
+ return c
335
+ })
336
+ return changed ? out : components
337
+ }
338
+
339
+ /**
340
+ * `remove`, walking into containers. The parent stays, and a group left empty stays too —
341
+ * the user made that container on purpose and removing one child is not a reason to take it.
342
+ */
343
+ function removeComponentDeep(
344
+ components: SceneComponentModel[],
345
+ refid: number
346
+ ): SceneComponentModel[] {
347
+ let changed = false
348
+ const out: SceneComponentModel[] = []
349
+ for (const c of components) {
350
+ if (!c) {
351
+ out.push(c)
352
+ continue
353
+ }
354
+ if ((c as any).refid === refid) {
355
+ changed = true
356
+ continue
357
+ }
358
+ const children = (c as any).components
359
+ if (Array.isArray(children) && children.length > 0) {
360
+ const updated = removeComponentDeep(children, refid)
361
+ if (updated !== children) {
362
+ changed = true
363
+ out.push({ ...c, components: updated })
364
+ continue
365
+ }
366
+ }
367
+ out.push(c)
368
+ }
369
+ return changed ? out : components
370
+ }