# `PartActions.set`

> Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated from `configurator-runtime.d.ts` by `services/onsite-scripter/scripts/build-contract-docs.mjs`; edit the source, not this file.

Method of [`PartActions`](PartActions.md) · inherited by [`Query`](Query.md), [`Parts`](Parts.md), [`Part`](Part.md) · stable · since 2.0.0 · called as `session.node(nodeId).set`

```ts
set(patch: PartState, options?: C & PartWriteOptions): Promise<Report>;
```

Through [`Parts`](Parts.md): a persistent rule in the [`find()`](Writer.find.md) block (through a nested [`find`](PartActions.find.md), one rule per
match, see [`find`](PartActions.find.md)). It applies to every match now and after reloads, variant switches, undo and redo, until reset with null or its owner is disposed.
Rules apply in creation order, which is the order of their first calls, not of their commits;
a write to an existing rule (same query and owner) merges into it in place (RFC 7396). Through
one [`Part`](Part.md): a rule bound to that part's address. Outcome: one
commit, look-only; not in the write queue. Timing: resolves at the commit point (after textures
decode, ≤ readyTimeoutMs), or when `durationMs`/`fadeMs` end or a newer write to the rule
takes them over (through a `tx`: 'staged' at once). Two writes to one rule both commit, in
call order: the older one shows until the newer commits. For newest wins, give each write its
own AbortController and abort the previous one when a new write starts (it rejects
AbortError; catch it), as the second example does. No match: `matched: 0` and
'target-unmatched', deferred while a scene write called before it is pending
([`Report.matched`](ReportFields.matched.md); spec section 6.10).
Not for: per-frame motion ([`onFrame`](Session.onFrame.md) with [`Frame.pose`](Frame.pose.md)), or a priced or saved change ([`select`](Writer.select.md),
[`setSceneModel`](Writer.setSceneModel.md)). For the whole look from app state, use [`apply()`](Writer.apply.md). Nor for switching between
partial looks laid over the GLB's material: a second write merges, so a field only the old
look set stays; use [`apply({ rules })`](Writer.apply.md), whose block is replaced whole, or null that field in
the same write ([`MaterialPatch`](MaterialPatch.md), spec section 6.10, R10).
Errors: rejects HoloInputError on an invalid patch (both rotations: 'rotation-twice'). No match
records 'target-unmatched'; a removed part records 'handle-disposed' (a [`node()`](Writer.node.md) handle never
does: its write is a rule on the model's address).

## Options

Through [`Session`](Session.md):

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `actor` | `Actor` | the `expectGroup`'s actor when one is given, else 'app' | Who made the write. |
| `dryRun` | `boolean` | false | Runs and reports, then rolls back by design ('dry-run'). |
| `durationMs` | `number` | 0 | Pose tween, ms. |
| `easing` | `Easing` | 'easeInOut' | The tween's curve. |
| `expectGroup` | `HistoryGroup` | undefined | A guard, checked at the call: this write belongs to the undo group you are running. |
| `expectRevision` | `number` | undefined (no check) | If-match on [`session.revision`](Session.revision.md). |
| `fadeMs` | `number` | 0 | Material crossfade, ms: baseColor, normal, orm and alpha crossfade, each side sampled at its own placement (`uv`, `scale` or `sizeM`, `offset`,… |
| `origin` | `string` | null | Your tag, echoed unchanged in [`Report.origin`](ReportFields.origin.md) and `cause.origin`. |
| `owner` | `Collection` | the session | Disposing this collection removes the rule. |
| `signal` | `AbortSignal` | undefined | Cancels this call; the member's JSDoc says what an abort does. |
| `supersede` | `boolean` | false (the write is queued behind the transaction and applies after it closes) | While a transaction is open: roll it back (it resolves 'superseded'); the writes queued behind it apply in call order, then this one, which supersedes an… |
| `undo` | `'rebase' \| (string & {})` | a recorded-state change gets one step labelled 'Change'; a look-only write gets none; the session's first model (cause 'load') gets none, whatever the label: it is the floor of history | An undo label makes this write one undo step. |

Through [`Tx`](Tx.md):

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `durationMs` | `number` | 0 | Pose tween, ms. |
| `easing` | `Easing` | 'easeInOut' | The tween's curve. |
| `fadeMs` | `number` | 0 | Material crossfade, ms: baseColor, normal, orm and alpha crossfade, each side sampled at its own placement (`uv`, `scale` or `sizeM`, `offset`,… |
| `owner` | `Collection` | the session | Disposing this collection removes the rule. |
| `signal` | `AbortSignal` | undefined | Cancels this call; the member's JSDoc says what an abort does. |

Refused here (typed `never`, so passing one fails to compile): `actor`, `checkpoint`, `dryRun`, `expectGroup`, `expectRevision`, `origin`, `supersede`, `undo`.

## Abort (`signal`)

This call is a write (`SignalOption.signal`, [the kinds of call](../read-this-first.md#conventions)).

> A write: before the commit point it rolls back and rejects AbortError; after it, no effect, except that a running camera move stops where the last frame drew it (`CameraMoveOptions`).
>
> Through a `tx` the commit point is the transaction's: aborting a write still loading rejects AbortError and stages nothing (catch it: unobserved, it rolls the transaction back, spec section 6.1, O6); aborting one already staged unstages it, its paths keeping the latest other write to them as after a failed savepoint (O7), and its Promise stays resolved 'staged'.

## Examples

Imports: values (`has`, `validate`, the `Holo*Error` classes, the pure helpers) from `@holostep-configurators/runtime-client`, types from `@holostep-configurators/runtime-client/contract`, `/math` and `/testing` names from those entries; `boot` from the runtime module the root's `loadRuntimeAtFloor` resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (`examples/declaration/06-PartActions.set-1.ts`):

```ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
declare const cabinetId: string;
const fronts = session.find({ node: cabinetId, tag: 'front' });   // every front part of that model
// 'none' is a value: no map at all, whatever the GLB has. The colour then shows plain.
await fronts.set({ material: { baseColor: '#2b2b2b', maps: { baseColor: 'none' } } });
// null removes your value: the GLB's or the scene model's material shows again.
await fronts.set({ material: null });
```

Compiled under the contract's strict settings (`examples/declaration/06-PartActions.set-2.ts`):

```ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// newest wins: each preview aborts the one before it, which then never shows
let preview: AbortController | null = null;
export async function previewFabric(url: string): Promise<void> {
  preview?.abort();
  const mine = new AbortController();
  preview = mine;
  try {
    await session.find({ slot: 'fabric' }).set(
      { material: { maps: { baseColor: { url } } } },
      { signal: mine.signal },
    );
  } catch (error) {
    // a newer preview aborted this one
    if (!(error instanceof DOMException && error.name === 'AbortError')) throw error;
  }
}
```

## Generic-API test (spec 3.1)

| Rule | Result |
| --- | --- |
| 1. Neutral name | pass: `set` names no product |
| 2. A precedent | pass: [DOM Document.getElementById](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById) (row `G31`); [Unity Renderer.bounds](https://docs.unity3d.com/ScriptReference/Renderer-bounds.html) (row `K57`); [DOM Document.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) (row `K58`) |
| 3. Two apps, or labs | pass: 21 apps (ledger: app-04c2774145, app-0c773a4ee3, app-b362a81c85, app-bcb7ef21da, app-c1384707fc, app-f3598fad77, app-f9a32142f1, app-fc07850558 and 2 more; fleet cross-check: app-04c2774145, app-0c773a4ee3, app-37356bf09f, app-4762caacdc, app-4f16d2f6da, app-59c5b5751e, app-822bb9d6aa, app-8675b3225c and 11 more) |
| 4. The cheaper fix first | decided per need in Appendix B: rows `G31`, `K57`, `K58` at tier stable (a member only where no rule, data, asset or app code serves the need) |
