@things-factory/figure-ui 10.1.82 → 10.1.83

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.
@@ -6,10 +6,10 @@ import { notify } from '@operato/layout'
6
6
  import { OxPrompt } from '@operato/popup'
7
7
  import { customElement, property, state } from 'lit/decorators.js'
8
8
  import { live } from 'lit/directives/live.js'
9
- import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, planFigureResize, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figurePlaneAlignChanges, type FigurePlaneContact, type FigureClipLoss, figurePartFaceOf, type FigureFaceArea, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model'
9
+ import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figurePlaneAlignChanges, type FigurePlaneContact, type FigureClipLoss, figurePartFaceOf, type FigureFaceArea, figureRowOf, type FigureRowReading, figureFaceOverhangOf, type FigureFaceOverhang, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model'
10
10
  import type { RecipeTemplate } from './recipes.js'
11
11
  import './figure-recipe-catalog.js'
12
- import type { FigureAsset, FigureAuthoringAction, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue, FigureResizeRequest, FigurePlacement } from '@hatiolab/figure-model'
12
+ import type { FigureAsset, FigureAuthoringAction, FigureInputChange, FigurePartEdit, FigurePrimitiveCreation, FigureProposalStep, FigureRecipe, FigureRecipeValue, FigurePlacement } from '@hatiolab/figure-model'
13
13
  import type { FigureProductSession } from './product-session.js'
14
14
  import { captureFigureThumbnail } from './thumbnail.js'
15
15
  import { drawableFigureParts } from './renderer.js'
@@ -39,7 +39,7 @@ import { nudgeOf, placementOf, type ScaleAnchor } from './stage-model.js'
39
39
  /** The numbers a rule gives a part's position, turn and size, per axis, for the cells that have no field. */
40
40
  type HeldValues = { position?: number[]; rotation?: number[]; size?: (number | undefined)[]; sizeRuled?: boolean }
41
41
  import { roomRefitRowsOf } from './occupancy.js'
42
- import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, resizeRefusalOf, sizeRangeRefusalOf } from './refusals.js'
42
+ import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, sizeRangeRefusalOf } from './refusals.js'
43
43
  import type { FigureRenderPart } from './renderer.js'
44
44
  import type { FigureBudgetLine } from './maturity.js'
45
45
  import { fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js'
@@ -1089,7 +1089,6 @@ export class FigureEditor extends LitElement {
1089
1089
  .motionEnds=${section === 'motion' ? this.motionEnds() : null}
1090
1090
  @go-fasten=${() => this.goFasten()}
1091
1091
  @preview-command=${(e: CustomEvent) => this.previewCommand(e.detail)}
1092
- @resize-with-placement=${(e: CustomEvent) => this.resizeWithPlacement(e.detail.request)}
1093
1092
  @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1094
1093
  ></figure-links>`
1095
1094
  }
@@ -1137,30 +1136,6 @@ export class FigureEditor extends LitElement {
1137
1136
  @part-edit=${(e: CustomEvent) => this.run(() => this.session!.edit(e.detail.edit))}
1138
1137
  ></figure-clips>`
1139
1138
  }
1140
- /**
1141
- * "Follow the size and keep where the figure is placed" (ADR-0097): figure-model plans it from the figure as it is now,
1142
- * and the whole plan is one step of the history. A refusal with a way on -- move the part onto the plane, replace how
1143
- * its position is worked out -- asks in the app's prompt and plans again with that choice; any other says why.
1144
- */
1145
- private resizeWithPlacement(request: FigureResizeRequest) {
1146
- this.retry = undefined
1147
- this.notice = ''
1148
- const plan = planFigureResize(parseFigureAsset(this.session!.source()), request)
1149
- const refusal = plan.refusal
1150
- if (refusal) {
1151
- const choice = refusal.choice
1152
- /* The gap is chosen on the screen beside the button, with its distance; only replacing a position rule is asked here. */
1153
- this.retry = choice === 'replacePosition' ? { label: i18next.t('figure.button.resize-choice-replace-position'), run: () => this.resizeWithPlacement({ ...request, replacePosition: true }) } : undefined
1154
- this.error = resizeRefusalOf(plan, request.part, (key, params) => i18next.t(key, params))
1155
- return
1156
- }
1157
- this.run(() => this.session!.applyRecipe(plan.steps))
1158
- /*
1159
- The declared room was measured from the part's old numbers and is not changed: said as a fact, not as a claim that
1160
- the part leaves it -- that is measured where the room is declared again (designer 2026-09-27).
1161
- */
1162
- if (!this.error && plan.roomReads.length) this.notice = i18next.t('figure.text.resize-room-unchanged')
1163
- }
1164
1139
  /** One command, one step of the history. A refusal a person can meet here is said in their words. */
1165
1140
  private linkCommand(action: FigureAuthoringAction) {
1166
1141
  this.retry = undefined
@@ -4,7 +4,7 @@ import { css, html, LitElement, nothing } from 'lit'
4
4
  import { customElement, property, state } from 'lit/decorators.js'
5
5
 
6
6
  import { i18next, localize } from '@operato/i18n'
7
- import { FIGURE_FACES, planFigureResize } from '@hatiolab/figure-model'
7
+ import { FIGURE_FACES } from '@hatiolab/figure-model'
8
8
  import type { FigureAsset, FigureAuthoringAction, FigureDimensionSource, FigureFace } from '@hatiolab/figure-model'
9
9
 
10
10
  import {
@@ -42,7 +42,6 @@ import {
42
42
  fixedJointOf,
43
43
  rowOf,
44
44
  positionsWorkedOut,
45
- resizeAxesOf,
46
45
  fieldWordsOf,
47
46
  measureNow,
48
47
  type Alignment,
@@ -109,10 +108,9 @@ export class FigureLinks extends localize(i18next)(LitElement) {
109
108
  div[resize-now] div[row] { grid-template-columns: 1fr auto auto auto; align-items: center; gap: 8px; }
110
109
  div[resize-now] span[measure] { font-variant-numeric: tabular-nums; }
111
110
  button[link] { background: none; border: none; padding: 0; color: var(--md-sys-color-primary, #477b9e); text-decoration: underline; cursor: pointer; font: inherit; }
112
- /* What will be applied: a pending choice, boxed apart from the rows above. */
113
- fieldset[resize-pending] { display: grid; gap: 8px; margin: 12px 0; padding: 10px 12px; border: 1px dashed var(--border-color, #8888); border-radius: 6px; }
114
- fieldset[resize-pending] label { display: flex; align-items: center; gap: 6px; }
115
- fieldset[resize-pending] small { opacity: 0.7; }
111
+ /* Size behaviour is a choice about dimensions only. It never implies a placement or a fastening. */
112
+ details[resize-choice] { margin: 12px 0; padding: 0; border: 0; }
113
+ details[resize-choice] > div { display: grid; gap: 8px; padding: 8px 0; }
116
114
  div[resize-held] { display: grid; gap: 8px; margin: 12px 0; padding: 10px 12px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); }
117
115
  div[resize-held] p { margin: 0; display: flex; align-items: center; gap: 6px; }
118
116
  p[fasten-mode] { margin: 0; font-weight: 600; }
@@ -170,8 +168,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
170
168
  private fastenCandidate?: { action: FigureAuthoringAction; face?: { part: string; face: string } }
171
169
  private rowCandidate?: FigureAuthoringAction
172
170
 
173
- /** The directions ticked for "follow the size, attached", per part, once the author has ticked or fixed any. */
174
- @state() private pendingAxes: Record<string, Axis[]> = {}
171
+ /** A new fastening starts with a target. Its face and alignment are asked only after that target is chosen. */
172
+ @state() private choosingFastening = false
175
173
  /** The fastening section, marked for a moment when "open attachment settings" brings it into view. */
176
174
  @state() private spotlit = false
177
175
  @state() private fasten = freshFasten()
@@ -198,6 +196,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
198
196
  this.place = freshPlace()
199
197
  this.row = freshRow()
200
198
  this.editingMotion = false
199
+ this.choosingFastening = false
201
200
  }
202
201
  }
203
202
 
@@ -317,41 +316,11 @@ export class FigureLinks extends localize(i18next)(LitElement) {
317
316
  }
318
317
 
319
318
  private renderDimensions(asset: FigureAsset) {
320
- /*
321
- The kernel's plan for "follow the size and keep where the figure is placed" (ADR-0097), with the directions the
322
- author ticked below -- a pending choice, drawn apart from the rows above, which only say what each direction does
323
- now (designer 2026-09-27: the immediate and the pending are not mixed in one look).
324
- */
325
319
  const editable = followableAxes(asset, this.partId)
326
- const choice = resizeAxesOf(asset, this.partId, this.pendingAxes[this.partId])
327
- const axes = choice.axes
328
- const plan = planFigureResize(asset, { part: this.partId, axes })
329
- const mode = plan.mode
330
320
  const round = editable.length === 2
331
321
  const word = (axis: Axis) => i18next.t(round && axis === 'x' ? 'figure.label.resize-diameter' : `figure.label.resize-${axis}`)
332
- const scope =
333
- axes.length && axes.length === editable.length
334
- ? i18next.t(`figure.text.resize-scope-all-${editable.length}`)
335
- : i18next.t('figure.text.resize-scope-only', { axes: axes.map(word).join(' · ') })
336
- const ask = (gap?: 'keep' | 'plane') =>
337
- this.dispatchEvent(new CustomEvent('resize-with-placement', { detail: { request: { part: this.partId, axes, ...(gap ? { gap } : {}) } }, bubbles: true, composed: true }))
338
- const gapChoice = plan.refusal?.code === 'GAP_CHOICE' ? plan.refusal : undefined
339
- /* Brought onto the plane, where it moves -- said on the button before it is pressed. */
340
- const onto = gapChoice ? planFigureResize(asset, { part: this.partId, axes, gap: 'plane' }).moves[0] : undefined
341
- const place = i18next.t(`figure.label.resize-place-${mode}`)
342
- const tick = (axis: Axis, on: boolean) => {
343
- const next = on ? [...new Set([...axes, axis])] : axes.filter(one => one !== axis)
344
- this.pendingAxes = { ...this.pendingAxes, [this.partId]: next }
345
- }
346
322
  const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one))
347
- /* One step for the whole part (figure-model follow-size): the size is declared from the parts if there is none. */
348
323
  const followsAll = editable.every(axis => followOf(asset, this.partId, axis).of === 'instance')
349
- const whole = followsAll
350
- ? nothing
351
- : html`<button act follow-size ?disabled=${this.locked} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
352
- <md-icon>open_in_full</md-icon>${i18next.t('figure.button.follow-size')}
353
- </button>`
354
- const held = plan.refusal?.code === 'HELD'
355
324
  return html`<p hint>${i18next.t('figure.text.resize-intent')}</p>
356
325
  ${asset.sizing === 'uniform' ? html`<p hint>${i18next.t('figure.text.resize-uniform-warning')}</p>` : nothing}
357
326
  <div resize-now>${editable.map(axis => {
@@ -366,46 +335,17 @@ export class FigureLinks extends localize(i18next)(LitElement) {
366
335
  ${following
367
336
  ? html`<button link resize-unfollow=${axis} ?disabled=${this.locked}
368
337
  @click=${() => {
369
- /* Set back to fixed: the author's choice for this part from now on, not a blank to fill with "all". */
370
- this.pendingAxes = { ...this.pendingAxes, [this.partId]: axes.filter(one => one !== axis) }
371
338
  this.follow(asset, axis, 'own', now)
372
339
  }}>${i18next.t('figure.button.resize-unfollow')}</button>`
373
340
  : nothing}
374
341
  </div>`
375
342
  })}</div>
376
- ${held
377
- ? this.renderHeld(asset)
378
- : html`<fieldset resize-pending ?disabled=${this.locked || asset.sizing === 'uniform'}>
379
- <legend>${i18next.t('figure.label.resize-pending')}</legend>
380
- ${editable.map(axis => {
381
- const now = followOf(asset, this.partId, axis)
382
- const following = now.of === 'instance' && now.axis === axis
383
- const custom = now.of !== 'own' && !following
384
- return html`<label>
385
- <input type="checkbox" resize-tick=${axis} .checked=${axes.includes(axis)} ?disabled=${following || custom}
386
- @change=${(e: Event) => tick(axis, (e.target as HTMLInputElement).checked)} />
387
- ${word(axis)}${following ? html` <small>${i18next.t('figure.text.resize-already-following')}</small>` : nothing}
388
- </label>`
389
- })}
390
- <p hint resize-basis>${i18next.t(`figure.text.resize-basis-${choice.basis}`)}</p>
391
- <p hint>${i18next.t('figure.text.placement-intent-' + mode)}</p>
392
- ${gapChoice
393
- ? html`<p hint gap-choice>${i18next.t(gapChoice.mm! > 0 ? 'figure.text.resize-gap-off' : 'figure.text.resize-gap-through', { place, mm: String(Math.abs(Math.round(gapChoice.mm! * 10) / 10)) })}</p>
394
- <button act resize-keep-gap @click=${() => ask('keep')}>
395
- ${i18next.t(gapChoice.mm! > 0 ? 'figure.button.resize-keep-gap' : 'figure.button.resize-keep-through', { scope, mm: String(Math.abs(Math.round(gapChoice.mm! * 10) / 10)) })}
396
- </button>
397
- <button act resize-onto-plane @click=${() => ask('plane')}>
398
- ${i18next.t('figure.button.resize-onto-plane-' + mode, { scope })}
399
- ${onto ? html`<small>${i18next.t('figure.text.resize-move', { axis: onto.axis.toUpperCase(), mm: String(Math.round((onto.to - onto.from) * 10) / 10) })}</small>` : nothing}
400
- </button>`
401
- : html`<button act resize-with-placement ?disabled=${choice.basis === 'none' || plan.refusal?.code === 'UNIFORM_POLICY'} @click=${() => ask()}>
402
- ${i18next.t('figure.button.resize-scoped-' + mode, { scope })}
403
- </button>`}
404
- ${!plan.refusal && plan.replaces.length
405
- ? html`<p hint resize-replaces>${i18next.t('figure.text.resize-replaces', { fields: [...new Set(plan.replaces.map(id => fieldWordsOf(id, key => i18next.t(key))))].join(' · '), place })}</p>`
406
- : nothing}
407
- </fieldset>`}
408
- <details><summary>${i18next.t('figure.label.resize-advanced')}</summary>${whole}${followableAxes(asset, this.partId).map(axis => {
343
+ ${followsAll
344
+ ? html`<p hint>${i18next.t('figure.text.resize-all-following')}</p>`
345
+ : html`<button act follow-size ?disabled=${this.locked || asset.sizing === 'uniform'} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
346
+ <md-icon>open_in_full</md-icon>${i18next.t('figure.button.follow-size')}
347
+ </button>`}
348
+ <details resize-choice><summary>${i18next.t('figure.label.resize-advanced')}</summary><div>${followableAxes(asset, this.partId).map(axis => {
409
349
  const now = followOf(asset, this.partId, axis)
410
350
  const value = choiceOf(now)
411
351
  /* A cylinder's two measures are its radius and its length; its X and Y name them. */
@@ -445,7 +385,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
445
385
  </label>`
446
386
  : nothing}
447
387
  </div>`
448
- })}</details>`
388
+ })}</div></details>`
449
389
  }
450
390
 
451
391
  /**
@@ -631,6 +571,14 @@ export class FigureLinks extends localize(i18next)(LitElement) {
631
571
  target: 'plane' in current.body0 ? i18next.t('figure.label.mounting-plane') : `${current.body0.part} · ${i18next.t(`figure.label.face-${current.body0.face}`)}`
632
572
  })
633
573
  : i18next.t('figure.text.fasten-new')
574
+ /* A blank attachment is not a form to decipher. Ask for the thing to attach to first. */
575
+ if (!current && !this.choosingFastening && !f.target)
576
+ return html`
577
+ <p hint>${i18next.t('figure.text.fasten-start')}</p>
578
+ <button act ?disabled=${this.locked} @click=${() => (this.choosingFastening = true)}>
579
+ <md-icon>link</md-icon>${i18next.t('figure.button.fasten-start')}
580
+ </button>
581
+ `
634
582
  return html`
635
583
  <p fasten-mode ?editing=${!!current}>${editing}</p>
636
584
  ${this.overhangNote.length ? html`<p hint overhang-note>${this.overhangNote.join(' ')}</p>` : nothing}
@@ -651,34 +599,32 @@ export class FigureLinks extends localize(i18next)(LitElement) {
651
599
  </div>
652
600
  ${this.renderDetachWords()}`
653
601
  : nothing}
654
- <div form>
655
- <span>${i18next.t('figure.label.my-face')}</span>
656
- <select @change=${(e: Event) => (this.fasten = { ...f, face: (e.target as HTMLSelectElement).value as FigureFace | 'centre' })}>
657
- ${mineFaces.map(face => html`<option value=${face} ?selected=${face === myFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
658
- </select>
659
- </div>
660
- <label toggle>
661
- <input type="checkbox" .checked=${f.toPlane} @change=${(e: Event) => this.aimAt(asset, { ...f, toPlane: (e.target as HTMLInputElement).checked })} />
662
- ${i18next.t('figure.label.stands-on-plane')}
663
- </label>
664
602
  ${f.toPlane
665
- ? nothing
603
+ ? html`<p hint>${i18next.t('figure.text.fasten-edit-on-plane')}</p>`
666
604
  : html`<div form>
667
- <span>${i18next.t('figure.label.target-part')}</span>
668
- <select @change=${(e: Event) => this.aimAt(asset, { ...f, target: (e.target as HTMLSelectElement).value })}>
669
- <option value="" ?selected=${!f.target}>—</option>
670
- ${targets.map(one =>
671
- loops(one)
672
- ? html`<option value=${one} disabled>${i18next.t('figure.text.target-would-loop', { part: one, mine: this.partId })}</option>`
673
- : html`<option value=${one} ?selected=${one === f.target}>${one}</option>`
674
- )}
675
- </select>
676
- <span>${i18next.t('figure.label.target-face')}</span>
677
- <select @change=${(e: Event) => this.aimAt(asset, { ...f, targetFace: (e.target as HTMLSelectElement).value as FigureFace })}>
678
- ${theirFaces.map(face => html`<option value=${face} ?selected=${face === targetFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
679
- </select>
680
- </div>`}
681
- <details fasten-detail>
605
+ <span>${i18next.t('figure.label.target-part')}</span>
606
+ <select @change=${(e: Event) => this.aimAt(asset, { ...f, target: (e.target as HTMLSelectElement).value })}>
607
+ <option value="" ?selected=${!f.target}>${i18next.t('figure.label.target-part-placeholder')}</option>
608
+ ${targets.map(one =>
609
+ loops(one)
610
+ ? html`<option value=${one} disabled>${i18next.t('figure.text.target-would-loop', { part: one, mine: this.partId })}</option>`
611
+ : html`<option value=${one} ?selected=${one === f.target}>${one}</option>`
612
+ )}
613
+ </select>
614
+ </div>
615
+ ${f.target
616
+ ? html`<div form>
617
+ <span>${i18next.t('figure.label.my-face')}</span>
618
+ <select @change=${(e: Event) => (this.fasten = { ...f, face: (e.target as HTMLSelectElement).value as FigureFace | 'centre' })}>
619
+ ${mineFaces.map(face => html`<option value=${face} ?selected=${face === myFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
620
+ </select>
621
+ <span>${i18next.t('figure.label.target-face')}</span>
622
+ <select @change=${(e: Event) => this.aimAt(asset, { ...f, targetFace: (e.target as HTMLSelectElement).value as FigureFace })}>
623
+ ${theirFaces.map(face => html`<option value=${face} ?selected=${face === targetFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
624
+ </select>
625
+ </div>`
626
+ : html`<p hint>${i18next.t('figure.text.fasten-target-first')}</p>`}`}
627
+ ${(f.toPlane || f.target) ? html`<details fasten-detail>
682
628
  <summary>${i18next.t('figure.label.fasten-detail')} · <span fasten-summary>${detail.length ? detail.join(' · ') : i18next.t('figure.text.fasten-summary-default')}</span></summary>
683
629
  ${f.toPlane
684
630
  ? nothing
@@ -700,7 +646,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
700
646
  ${action && this.working === 'fasten' ? this.renderPreviewWords() : nothing}
701
647
  <button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>
702
648
  <md-icon>link</md-icon>${i18next.t(current ? 'figure.button.fasten-update' : 'figure.button.fasten')}
703
- </button>
649
+ </button>` : nothing}
704
650
  `
705
651
  }
706
652
 
@@ -34,6 +34,6 @@ export declare const trackEdit: (clip: Clip, track: ClipTrack, patch?: Partial<P
34
34
  state: string;
35
35
  clip: string;
36
36
  keys: ClipKey[];
37
- loop: "hold" | "wrap";
37
+ loop: "wrap" | "hold";
38
38
  interpolation: "linear" | "step";
39
39
  };
@@ -196,12 +196,6 @@ export declare class FigureEditor extends LitElement {
196
196
  private lastAnchors;
197
197
  /** The figure's controls: try a value, name it, set its range and travel time, keep the pose. */
198
198
  private renderControls;
199
- /**
200
- * "Follow the size and keep where the figure is placed" (ADR-0097): figure-model plans it from the figure as it is now,
201
- * and the whole plan is one step of the history. A refusal with a way on -- move the part onto the plane, replace how
202
- * its position is worked out -- asks in the app's prompt and plans again with that choice; any other says why.
203
- */
204
- private resizeWithPlacement;
205
199
  /** One command, one step of the history. A refusal a person can meet here is said in their words. */
206
200
  private linkCommand;
207
201
  private changeField;
@@ -7,7 +7,7 @@ import { notify } from '@operato/layout';
7
7
  import { OxPrompt } from '@operato/popup';
8
8
  import { customElement, property, state } from 'lit/decorators.js';
9
9
  import { live } from 'lit/directives/live.js';
10
- import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, planFigureResize, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figurePlaneAlignChanges, figurePartFaceOf, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
10
+ import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, parseFigureAsset, figureSizeEditChanges, figureScaleAnchorOf, figurePlaneAlignChanges, figurePartFaceOf, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
11
11
  import './figure-recipe-catalog.js';
12
12
  import { captureFigureThumbnail } from './thumbnail.js';
13
13
  import { drawableFigureParts } from './renderer.js';
@@ -32,7 +32,7 @@ import { alignChanges, boxOf, canMirror, mirrorChanges } from './align.js';
32
32
  import { paletteRecipe } from './palette.js';
33
33
  import { nudgeOf, placementOf } from './stage-model.js';
34
34
  import { roomRefitRowsOf } from './occupancy.js';
35
- import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, resizeRefusalOf, sizeRangeRefusalOf } from './refusals.js';
35
+ import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, sizeRangeRefusalOf } from './refusals.js';
36
36
  import { fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
37
37
  import { jointFanOf } from './joint-fan.js';
38
38
  import { jointDragOf } from './joint-drag.js';
@@ -1110,7 +1110,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1110
1110
  .motionEnds=${section === 'motion' ? this.motionEnds() : null}
1111
1111
  @go-fasten=${() => this.goFasten()}
1112
1112
  @preview-command=${(e) => this.previewCommand(e.detail)}
1113
- @resize-with-placement=${(e) => this.resizeWithPlacement(e.detail.request)}
1114
1113
  @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
1115
1114
  ></figure-links>`;
1116
1115
  }
@@ -1161,31 +1160,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1161
1160
  @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
1162
1161
  ></figure-clips>`;
1163
1162
  }
1164
- /**
1165
- * "Follow the size and keep where the figure is placed" (ADR-0097): figure-model plans it from the figure as it is now,
1166
- * and the whole plan is one step of the history. A refusal with a way on -- move the part onto the plane, replace how
1167
- * its position is worked out -- asks in the app's prompt and plans again with that choice; any other says why.
1168
- */
1169
- resizeWithPlacement(request) {
1170
- this.retry = undefined;
1171
- this.notice = '';
1172
- const plan = planFigureResize(parseFigureAsset(this.session.source()), request);
1173
- const refusal = plan.refusal;
1174
- if (refusal) {
1175
- const choice = refusal.choice;
1176
- /* The gap is chosen on the screen beside the button, with its distance; only replacing a position rule is asked here. */
1177
- this.retry = choice === 'replacePosition' ? { label: i18next.t('figure.button.resize-choice-replace-position'), run: () => this.resizeWithPlacement({ ...request, replacePosition: true }) } : undefined;
1178
- this.error = resizeRefusalOf(plan, request.part, (key, params) => i18next.t(key, params));
1179
- return;
1180
- }
1181
- this.run(() => this.session.applyRecipe(plan.steps));
1182
- /*
1183
- The declared room was measured from the part's old numbers and is not changed: said as a fact, not as a claim that
1184
- the part leaves it -- that is measured where the room is declared again (designer 2026-09-27).
1185
- */
1186
- if (!this.error && plan.roomReads.length)
1187
- this.notice = i18next.t('figure.text.resize-room-unchanged');
1188
- }
1189
1163
  /** One command, one step of the history. A refusal a person can meet here is said in their words. */
1190
1164
  linkCommand(action) {
1191
1165
  this.retry = undefined;