@things-factory/figure-ui 10.1.168 → 10.1.170

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.
Files changed (123) hide show
  1. package/client/figure-assistant.ts +10 -1
  2. package/client/modeller/affected-parts.ts +11 -0
  3. package/client/modeller/ai-proposal.ts +40 -9
  4. package/client/modeller/canvas.ts +15 -1
  5. package/client/modeller/editor-styles.ts +98 -6
  6. package/client/modeller/editor.ts +212 -53
  7. package/client/modeller/fasten-geometry.ts +19 -0
  8. package/client/modeller/figure-fasten-drawing.ts +4 -3
  9. package/client/modeller/figure-links.ts +80 -37
  10. package/client/modeller/figure-material-panel.ts +20 -1
  11. package/client/modeller/figure-preview.ts +117 -8
  12. package/client/modeller/figure-properties.ts +1 -2
  13. package/client/modeller/figure-section-panel.ts +5 -0
  14. package/client/modeller/figure-verdict.ts +9 -2
  15. package/client/modeller/gate-findings.ts +12 -4
  16. package/client/modeller/group-menu.ts +57 -0
  17. package/client/modeller/links.ts +22 -1
  18. package/client/modeller/modeller-keys.ts +6 -0
  19. package/client/modeller/preview-size.ts +32 -0
  20. package/client/modeller/product-session.ts +23 -9
  21. package/client/modeller/refusals.ts +14 -0
  22. package/client/modeller/stage-model.ts +11 -0
  23. package/client/pages/figure-modeller-page.ts +15 -6
  24. package/dist-client/figure-assistant.d.ts +6 -0
  25. package/dist-client/figure-assistant.js +9 -1
  26. package/dist-client/figure-assistant.js.map +1 -1
  27. package/dist-client/modeller/affected-parts.d.ts +6 -0
  28. package/dist-client/modeller/affected-parts.js +12 -0
  29. package/dist-client/modeller/affected-parts.js.map +1 -0
  30. package/dist-client/modeller/ai-proposal.d.ts +15 -5
  31. package/dist-client/modeller/ai-proposal.js +39 -10
  32. package/dist-client/modeller/ai-proposal.js.map +1 -1
  33. package/dist-client/modeller/canvas.d.ts +3 -0
  34. package/dist-client/modeller/canvas.js +12 -1
  35. package/dist-client/modeller/canvas.js.map +1 -1
  36. package/dist-client/modeller/editor-styles.js +98 -6
  37. package/dist-client/modeller/editor-styles.js.map +1 -1
  38. package/dist-client/modeller/editor.d.ts +42 -1
  39. package/dist-client/modeller/editor.js +220 -51
  40. package/dist-client/modeller/editor.js.map +1 -1
  41. package/dist-client/modeller/fasten-geometry.d.ts +27 -0
  42. package/dist-client/modeller/fasten-geometry.js +14 -0
  43. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  44. package/dist-client/modeller/figure-fasten-drawing.js +4 -3
  45. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  46. package/dist-client/modeller/figure-links.d.ts +3 -1
  47. package/dist-client/modeller/figure-links.js +84 -38
  48. package/dist-client/modeller/figure-links.js.map +1 -1
  49. package/dist-client/modeller/figure-material-panel.d.ts +8 -1
  50. package/dist-client/modeller/figure-material-panel.js +24 -1
  51. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  52. package/dist-client/modeller/figure-preview.d.ts +12 -0
  53. package/dist-client/modeller/figure-preview.js +116 -5
  54. package/dist-client/modeller/figure-preview.js.map +1 -1
  55. package/dist-client/modeller/figure-properties.js +1 -2
  56. package/dist-client/modeller/figure-properties.js.map +1 -1
  57. package/dist-client/modeller/figure-section-panel.d.ts +1 -1
  58. package/dist-client/modeller/figure-section-panel.js +5 -0
  59. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  60. package/dist-client/modeller/figure-verdict.d.ts +1 -0
  61. package/dist-client/modeller/figure-verdict.js +8 -2
  62. package/dist-client/modeller/figure-verdict.js.map +1 -1
  63. package/dist-client/modeller/gate-findings.d.ts +3 -1
  64. package/dist-client/modeller/gate-findings.js +8 -4
  65. package/dist-client/modeller/gate-findings.js.map +1 -1
  66. package/dist-client/modeller/group-menu.d.ts +18 -0
  67. package/dist-client/modeller/group-menu.js +52 -0
  68. package/dist-client/modeller/group-menu.js.map +1 -0
  69. package/dist-client/modeller/links.d.ts +5 -0
  70. package/dist-client/modeller/links.js +21 -1
  71. package/dist-client/modeller/links.js.map +1 -1
  72. package/dist-client/modeller/modeller-keys.js +7 -0
  73. package/dist-client/modeller/modeller-keys.js.map +1 -1
  74. package/dist-client/modeller/preview-size.d.ts +23 -0
  75. package/dist-client/modeller/preview-size.js +32 -0
  76. package/dist-client/modeller/preview-size.js.map +1 -0
  77. package/dist-client/modeller/product-session.d.ts +0 -1
  78. package/dist-client/modeller/product-session.js +22 -9
  79. package/dist-client/modeller/product-session.js.map +1 -1
  80. package/dist-client/modeller/refusals.d.ts +1 -5
  81. package/dist-client/modeller/refusals.js +14 -1
  82. package/dist-client/modeller/refusals.js.map +1 -1
  83. package/dist-client/modeller/stage-model.d.ts +11 -0
  84. package/dist-client/modeller/stage-model.js +11 -0
  85. package/dist-client/modeller/stage-model.js.map +1 -1
  86. package/dist-client/pages/figure-modeller-page.d.ts +3 -0
  87. package/dist-client/pages/figure-modeller-page.js +18 -5
  88. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  89. package/dist-client/route.d.ts +1 -1
  90. package/dist-client/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +8 -8
  92. package/test/ai-proposal.test.ts +43 -4
  93. package/test/browser/editing-page.js +3 -1
  94. package/test/browser/editing.check.mjs +2 -1
  95. package/test/browser/links-page.js +1 -0
  96. package/test/browser/links.check.mjs +20 -5
  97. package/test/browser/modeller.check.mjs +6 -0
  98. package/test/browser/more-open.js +11 -0
  99. package/test/browser/parts.check.mjs +11 -1
  100. package/test/browser/places-mesh.check.mjs +9 -3
  101. package/test/browser/product-page.check.mjs +9 -4
  102. package/test/browser/props.check.mjs +224 -0
  103. package/test/browser/screen-page.js +17 -0
  104. package/test/browser/screen.check.mjs +171 -29
  105. package/test/browser/section-page.js +1 -0
  106. package/test/browser/section.check.mjs +6 -0
  107. package/test/command-preview.test.ts +2 -2
  108. package/test/fasten-geometry.test.ts +15 -0
  109. package/test/fields.test.ts +4 -2
  110. package/test/figure-assistant-surface.test.ts +38 -0
  111. package/test/first-minute.test.ts +15 -2
  112. package/test/layout.test.ts +1 -1
  113. package/test/preview-size.test.ts +33 -0
  114. package/test/refusals.test.ts +1 -1
  115. package/test/resize-with-placement.test.ts +1 -1
  116. package/test/stage-model.test.ts +11 -1
  117. package/test/verdict.test.ts +7 -0
  118. package/translations/en.json +43 -27
  119. package/translations/es.json +24 -8
  120. package/translations/ja.json +25 -9
  121. package/translations/ko.json +56 -40
  122. package/translations/ms.json +25 -9
  123. package/translations/zh.json +25 -9
@@ -46,7 +46,8 @@ const NO_MARKS = [];
46
46
  import { apexOver, drawnOf } from './section-drawing.js';
47
47
  import { END_FACE } from './section-controls.js';
48
48
  import { nudgeOf, placementOf } from './stage-model.js';
49
- import { roomRefitRowsOf } from './occupancy.js';
49
+ import { occupancyFromParts, roomRefitRowsOf } from './occupancy.js';
50
+ import { gateFindingStyles, renderGateFinding } from './gate-findings.js';
50
51
  import { editRefusalOf, inDeclaredRoom, narrowingRetryOf, partNameRefusalOf, partRefusalOf, placedRefusalOf, recipeRefusalOf, removalNoticeOf } from './refusals.js';
51
52
  import { endPlaceKeyOf, fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
52
53
  import { jointFanOf } from './joint-fan.js';
@@ -54,11 +55,20 @@ import { derivedOf } from './revision.js';
54
55
  import { jointDragOf } from './joint-drag.js';
55
56
  import { liveOverSession, NOTHING_LIVE, scrubbed, scrubStep } from './live.js';
56
57
  import { startingShapeKindOfBody } from './starting-shape.js';
58
+ import { groupMenu, GroupMenuStyles } from './group-menu.js';
59
+ /** The person's own fold of the properties' 「더 보기」, kept in their browser. */
60
+ const MORE_OPEN_KEY = 'figure-properties-more-open';
57
61
  /** Product editor surface. The injected session is shared by page persistence and AI commands. */
58
62
  const NO_OVERHANG = { words: NO_WORDS, note: NO_WORDS };
59
63
  let FigureEditor = class FigureEditor extends LitElement {
60
64
  constructor() {
61
65
  super(...arguments);
66
+ /**
67
+ * What blocks release now, as the page's verdict heard it: said beside 「발행」 with the way out at hand, so an author
68
+ * does not find it at the foot of the verdict tab (first 15 minutes, 2026-10-16; chief architect, Q1 B).
69
+ */
70
+ this.releaseBlockers = [];
71
+ this.blockersOpen = false;
62
72
  /** The recipe templates (ADR-0095), read by the page from the server. */
63
73
  this.templates = [];
64
74
  this.catalogOpen = false;
@@ -68,6 +78,10 @@ let FigureEditor = class FigureEditor extends LitElement {
68
78
  this.selected = '';
69
79
  this.error = '';
70
80
  this.tab = 'properties';
81
+ /** The capability group open: the canvas marks the places only while their group is in sight. */
82
+ this.capabilityOpen = false;
83
+ /** 「더 보기」 open, as this person left it last (chief architect 2026-10-16: kept per person). */
84
+ this.moreOpen = FigureEditor_1.storedMoreOpen();
71
85
  /** What the middle shows: the parts being edited, or the figure placed on a board at eight sizes. */
72
86
  this.middle = 'edit';
73
87
  /** A line saying what a command did besides what was asked; gone at the next command. */
@@ -124,16 +138,35 @@ let FigureEditor = class FigureEditor extends LitElement {
124
138
  this.lastAnchors = {};
125
139
  /** Data tried in the data tab, given to the preview's figures as their `data`; null when none is (ADR-0051 §8). */
126
140
  this.dataSample = null;
127
- /** To the release that exists: the history tab's release section, not a second one here. */
141
+ /** To the release that exists: the history's release section, opened from the bar, not a second one here. */
128
142
  this.goRelease = async () => {
129
- this.tab = 'history';
143
+ this.overlay = 'history';
130
144
  await this.updateComplete;
131
145
  await this.querySelector('figure-history-panel')?.showRelease?.();
132
146
  };
133
147
  }
134
148
  static { FigureEditor_1 = this; }
135
149
  /* The operato scrollbar, as every scrolling panel of the app has it -- the side panel scrolled with the browser's own. */
136
- static { this.styles = [ScrollbarStyles, SidePanelStyles, EditorStyles]; }
150
+ static { this.styles = [ScrollbarStyles, SidePanelStyles, gateFindingStyles, GroupMenuStyles, EditorStyles]; }
151
+ static storedMoreOpen() {
152
+ try {
153
+ return localStorage.getItem(MORE_OPEN_KEY) === '1';
154
+ }
155
+ catch {
156
+ return false;
157
+ }
158
+ }
159
+ setMoreOpen(open) {
160
+ if (open === this.moreOpen)
161
+ return;
162
+ this.moreOpen = open;
163
+ try {
164
+ localStorage.setItem(MORE_OPEN_KEY, open ? '1' : '0');
165
+ }
166
+ catch {
167
+ /* A browser that keeps nothing folds it again next time. */
168
+ }
169
+ }
137
170
  static storedView() {
138
171
  try {
139
172
  const stored = JSON.parse(localStorage.getItem('figure-view') ?? 'null');
@@ -720,7 +753,7 @@ let FigureEditor = class FigureEditor extends LitElement {
720
753
  }
721
754
  }
722
755
  /** Size response, fastening and motion for the selected part, as authoring commands. */
723
- renderLinks(unavailable, section) {
756
+ renderLinks(unavailable, section, slotted = nothing) {
724
757
  const shown = this.shown();
725
758
  if (!shown)
726
759
  return null;
@@ -738,7 +771,122 @@ let FigureEditor = class FigureEditor extends LitElement {
738
771
  @go-fasten=${() => this.goFasten()}
739
772
  @preview-command=${(e) => this.commandPreview.ask(e.detail)}
740
773
  @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
741
- ></figure-links>`;
774
+ >${slotted}</figure-links>`;
775
+ }
776
+ /**
777
+ * A part's groups in one order whatever its state (chief architect 2026-10-16): its size, its place, its row. The links
778
+ * draw the three groups and their rules; the part's own numbers go into them by slot. A fastened part's place and turn
779
+ * are its fastening's: the place group says it in one line and the locked cells are not drawn (user 2026-10-16).
780
+ */
781
+ renderPartGroups(shown, geometry, fields, unavailable, held) {
782
+ const groups = this.withSizeRow(groupFields(fields, this.selected), held);
783
+ const size = groups.vectors.filter(g => g.kind === 'vector' && g.name === 'size');
784
+ const place = groups.vectors.filter(g => g.kind === 'vector' && g.name !== 'size');
785
+ const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(this.selected));
786
+ const placeLocked = fastened && place.every(g => g.kind === 'vector' && ['x', 'y', 'z'].every(axis => !g.cells[axis]));
787
+ return this.renderLinks(unavailable, 'shape', html `${size.length || groups.singles.length
788
+ ? html `<div slot="size" group-fields>
789
+ ${size.map(g => this.renderVector(g, unavailable, held))}
790
+ ${groups.singles.length
791
+ ? html `<div form>${groups.singles.map(g => (g.kind === 'single' ? this.renderSingle(g.name, g.field, unavailable) : nothing))}</div>`
792
+ : nothing}
793
+ </div>`
794
+ : nothing}
795
+ ${place.length && !placeLocked
796
+ ? html `<div slot="place" group-fields>${place.map(g => this.renderVector(g, unavailable, held))}${this.renderHeldAxes(place)}</div>`
797
+ : nothing}
798
+ ${this.renderRepeat(shown, geometry, unavailable)}`);
799
+ }
800
+ /**
801
+ * What the figure does -- its places (ADR-0118) -- as a group of the properties, folded: a declaration made once,
802
+ * whose marks on the canvas show while it is open. A verdict that finds it empty still says where it is.
803
+ */
804
+ renderCapability(asset, unavailable) {
805
+ return html `<section capability>
806
+ <details capability-fold ?open=${this.capabilityOpen} @toggle=${(e) => (this.capabilityOpen = e.target.open)}>
807
+ <summary><h3>${i18next.t('figure.label.tab-capability')}</h3></summary>
808
+ ${this.selected ? this.renderLinks(unavailable, 'places') : nothing}
809
+ <figure-capability-panel
810
+ .asset=${asset}
811
+ .selected=${this.selected}
812
+ ?locked=${unavailable}
813
+ @select-part=${(e) => this.choose(e.detail.id)}
814
+ @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
815
+ ></figure-capability-panel>
816
+ </details>
817
+ </section>`;
818
+ }
819
+ /**
820
+ * 「더 보기」: what few parts need, folded by default and left as this person last left it (chief architect
821
+ * 2026-10-16) -- the section, its corners and inside; transparency, glow and segments; the mirrored copy and the
822
+ * alignment tools.
823
+ */
824
+ renderMore(part, palette, unavailable) {
825
+ const tools = this.renderTools(unavailable);
826
+ return html `<section more>
827
+ <details more-fold ?open=${this.moreOpen} @toggle=${(e) => this.setMoreOpen(e.target.open)}>
828
+ <summary><h3>${i18next.t('figure.label.group-more')}</h3></summary>
829
+ <div more-body>
830
+ ${tools ? html `<div group-fields>${tools}</div>` : nothing}
831
+ <figure-section-panel
832
+ embedded
833
+ .body=${{ ...part, id: this.selected }}
834
+ ?locked=${unavailable}
835
+ .faceMapping=${this.faceMapping?.from === 'panel' ? this.faceMapping.need : undefined}
836
+ @face-mapped=${(e) => this.mapFaces(e.detail.remap)}
837
+ @face-mapping-cancel=${() => (this.faceMapping = undefined)}
838
+ @command=${(e) => this.linkCommand(e.detail.action)}
839
+ @command-live=${(e) => this.holdAction(e.detail.action)}
840
+ @section-editor-open=${() => (this.sectionEditing = this.selected)}
841
+ ></figure-section-panel>
842
+ <figure-material-panel
843
+ show="more"
844
+ .body=${{ ...part, id: this.selected }}
845
+ .palette=${palette}
846
+ ?locked=${unavailable}
847
+ @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
848
+ @part-live=${(e) => this.hold({}, e.detail.edit)}
849
+ ></figure-material-panel>
850
+ </div>
851
+ </details>
852
+ </section>`;
853
+ }
854
+ /** A bar button that opens the view settings or the release history over the work; pressed while it is open. */
855
+ renderOverlayButton(which, icon, label, slot) {
856
+ const open = this.overlay === which;
857
+ return html `<button
858
+ slot=${slot ?? nothing}
859
+ tool
860
+ bar-overlay=${which}
861
+ aria-pressed=${String(open)}
862
+ aria-label=${i18next.t(label)}
863
+ title=${i18next.t(label)}
864
+ @click=${() => (this.overlay = open ? undefined : which)}
865
+ >
866
+ <md-icon>${icon}</md-icon>
867
+ </button>`;
868
+ }
869
+ /**
870
+ * The view settings or the release history, over the work under the bar. Both stay drawn, hidden when closed: the
871
+ * history keeps what it read, as the tabs kept it. Esc closes it.
872
+ */
873
+ renderOverlay() {
874
+ const close = () => (this.overlay = undefined);
875
+ const panel = (which, label, body) => html `<div
876
+ bar-overlay-panel=${which}
877
+ role="dialog"
878
+ aria-label=${i18next.t(label)}
879
+ ?off=${this.overlay !== which}
880
+ @keydown=${(e) => e.key === 'Escape' && (e.stopPropagation(), close())}
881
+ >
882
+ <div overlay-head>
883
+ <h3>${i18next.t(label)}</h3>
884
+ <button quiet aria-label=${i18next.t('figure.button.close')} title=${i18next.t('figure.button.close')} @click=${close}><md-icon>close</md-icon></button>
885
+ </div>
886
+ <div overlay-body>${body}</div>
887
+ </div>`;
888
+ return html `${panel('view', 'figure.label.tab-environment', html `<figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>`)}
889
+ ${panel('history', 'figure.label.history', html `<slot name="history"></slot>`)}`;
742
890
  }
743
891
  /**
744
892
  * The face a size drag keeps for the selected part, figure-model's rule (`figureScaleAnchorOf`): the gizmo shows it
@@ -800,6 +948,7 @@ let FigureEditor = class FigureEditor extends LitElement {
800
948
  this.retry = undefined;
801
949
  this.notice = '';
802
950
  const hadSize = this.hasSize();
951
+ const hadSpace = !!this.shown()?.asset.occupancy;
803
952
  try {
804
953
  this.session.command(action);
805
954
  this.error = '';
@@ -807,7 +956,9 @@ let FigureEditor = class FigureEditor extends LitElement {
807
956
  /* What the command did besides what was asked, in one line: the figure's size declared to follow (ADR ruling Q1). */
808
957
  if (action.kind === 'follow-size' && !hadSize) {
809
958
  const size = this.designValues();
810
- this.notice = i18next.t('figure.text.size-declared-to-follow', {
959
+ /* The same step may have fitted the space the figure takes to its parts (figure-model, ruling 2026-10-16 Q1 A): one line says both. */
960
+ const spaceToo = !hadSpace && !!this.shown()?.asset.occupancy;
961
+ this.notice = i18next.t(spaceToo ? 'figure.text.size-and-space-declared-to-follow' : 'figure.text.size-declared-to-follow', {
811
962
  x: Math.round(size['size.x'] ?? 0).toLocaleString(),
812
963
  y: Math.round(size['size.y'] ?? 0).toLocaleString(),
813
964
  z: Math.round(size['size.z'] ?? 0).toLocaleString()
@@ -1057,8 +1208,12 @@ let FigureEditor = class FigureEditor extends LitElement {
1057
1208
  const longest = model.inputs.find(i => i.id === lengthRef)?.max;
1058
1209
  const limit = model.constants.find(c => c.id === layout?.args[6])?.value;
1059
1210
  const field = { id: own, unit: 'mm', role: 'design', value: pitch ?? 0 };
1060
- return html `<section>
1061
- <h3>${i18next.t('figure.label.repeat')}</h3>
1211
+ const removable = !!asset.joints?.some(j => j.body1.part === this.selected && j.repeat);
1212
+ return html `<section slot="row" row>
1213
+ <h3>
1214
+ ${i18next.t('figure.label.repeat')}
1215
+ ${groupMenu([{ label: i18next.t('figure.button.remove-repeat'), icon: 'close', disabled: unavailable || !removable, run: () => this.run(() => this.session.command({ kind: 'remove-repeat', part: this.selected })) }])}
1216
+ </h3>
1062
1217
  <div inline-field><label>${i18next.t('figure.label.repeat-axis')}</label><span value>${axis.toUpperCase()}</span></div>
1063
1218
  <div inline-field><label>${i18next.t('figure.label.repeat-count')}</label><span value>${repeat.count}</span></div>
1064
1219
  <div inline-field>
@@ -1084,11 +1239,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1084
1239
  : i18next.t(fits ? 'figure.text.repeat-fits-length' : 'figure.text.repeat-fixed-count')}
1085
1240
  </p>
1086
1241
  ${this.renderPublicPitch(asset, pitch ?? 0, unavailable)}
1087
- ${asset.joints?.some(j => j.body1.part === this.selected && j.repeat)
1088
- ? html `<button act ?disabled=${unavailable} @click=${() => this.run(() => this.session.command({ kind: 'remove-repeat', part: this.selected }))}>
1089
- <md-icon>close</md-icon>${i18next.t('figure.button.remove-repeat')}
1090
- </button>`
1091
- : nothing}
1092
1242
  </section>`;
1093
1243
  }
1094
1244
  /** The selected row as it is drawn now, read by figure-model: nothing when it cannot be read. */
@@ -1218,6 +1368,21 @@ let FigureEditor = class FigureEditor extends LitElement {
1218
1368
  return i18next.t('figure.text.release-state-released', { version: String(version) });
1219
1369
  return version > 0 ? i18next.t('figure.text.release-state-draft-over', { version: String(version) }) : i18next.t('figure.text.release-state-draft-new');
1220
1370
  }
1371
+ /** The findings that block release, under the bar beside 「발행」, each with the way out the verdict tab offers for it. */
1372
+ renderReleaseBlockers(asset, unavailable) {
1373
+ const parts = (asset?.appearance ?? []).map(look => look.target);
1374
+ const way = (code) => (code === 'occupancy-undeclared' || code === 'part-outside-occupancy') && asset && parts.length
1375
+ ? html `<button act ?disabled=${unavailable} @click=${() => this.linkCommand(occupancyFromParts(asset))}>
1376
+ <md-icon>fit_screen</md-icon>${i18next.t('figure.button.fit-base-to-parts')}
1377
+ </button>`
1378
+ : nothing;
1379
+ return html `<div release-blockers role="dialog" aria-label=${i18next.t('figure.text.must-fix-to-release')} @keydown=${(e) => e.key === 'Escape' && (this.blockersOpen = false)}>
1380
+ <ul gate>
1381
+ <li head>${i18next.t('figure.text.must-fix-to-release')}</li>
1382
+ ${this.releaseBlockers.map(finding => renderGateFinding(finding, true, way(finding.code), parts))}
1383
+ </ul>
1384
+ </div>`;
1385
+ }
1221
1386
  /*
1222
1387
  Past the edge of the face a part is fastened to (ADR-0097 §5.5, Q3): the selected part's own, and the parts fastened
1223
1388
  to it -- said apart from the room, nothing moved. Read once per version, part and language. The note says it is wider
@@ -1313,7 +1478,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1313
1478
  /* A copy on the canvas selects its repeat: the part the author made. */
1314
1479
  this.selected = id ? ownerOf(id) : id;
1315
1480
  this.faceMapping = undefined;
1316
- if (id && (this.tab === 'verdict' || this.tab === 'view' || this.tab === 'history'))
1481
+ if (id && this.tab === 'verdict')
1317
1482
  this.tab = 'properties';
1318
1483
  }
1319
1484
  asset() {
@@ -1329,6 +1494,9 @@ let FigureEditor = class FigureEditor extends LitElement {
1329
1494
  }
1330
1495
  updated(changed) {
1331
1496
  super.updated?.(changed);
1497
+ /* Nothing blocks release any more: the list under the bar closes, and opens fresh when something does again. */
1498
+ if (changed.has('releaseBlockers') && !this.releaseBlockers.length && this.blockersOpen)
1499
+ this.blockersOpen = false;
1332
1500
  this.refusals.tell(changed);
1333
1501
  this.panelMemory.restore(() => this.selected);
1334
1502
  const panes = this.renderRoot.querySelector('ox-three-pane') ?? undefined;
@@ -1408,7 +1576,6 @@ let FigureEditor = class FigureEditor extends LitElement {
1408
1576
  ${i18next.t(`figure.label.resize-place-${one}`)}
1409
1577
  </button>`)}
1410
1578
  </div>
1411
- <small>${i18next.t('figure.text.placement-is-intent')}</small>
1412
1579
  </section>
1413
1580
  <section>
1414
1581
  <h3>${i18next.t('figure.label.start-shape')}</h3>
@@ -1527,14 +1694,22 @@ let FigureEditor = class FigureEditor extends LitElement {
1527
1694
  @canvas-fit=${() => this.renderRoot.querySelector('figure-canvas')?.fit()}
1528
1695
  >
1529
1696
  ${this.renderGizmoTools(!unavailable && this.middle === 'edit')}
1697
+ ${this.renderOverlayButton('view', 'wb_sunny', 'figure.label.tab-environment')}
1530
1698
  ${['edit', 'preview'].map(mode => html `<button slot="mode" mode aria-pressed=${String(this.middle === mode)} title=${i18next.t('figure.text.mode-' + mode + '-explained')} @click=${() => (this.middle = mode)}>
1531
1699
  ${i18next.t('figure.label.mode-' + mode)}
1532
1700
  </button>`)}
1533
1701
  <span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
1534
1702
  <span slot="status" release-state>${this.releaseWords(view)}</span>
1703
+ ${this.renderOverlayButton('history', 'history', 'figure.label.history', 'commit')}
1704
+ ${this.releaseBlockers.length
1705
+ ? html `<button slot="commit" release-blocked aria-expanded=${String(this.blockersOpen)} @click=${() => (this.blockersOpen = !this.blockersOpen)}>
1706
+ <md-icon>error</md-icon>${i18next.t('figure.button.release-blocked', { count: this.releaseBlockers.length })}
1707
+ </button>`
1708
+ : nothing}
1535
1709
  <button slot="commit" publish ?disabled=${!view.id} title=${view.id ? '' : i18next.t('figure.text.save-the-figure-before-releasing')} @click=${this.goRelease}>${i18next.t('figure.button.release')}</button>
1536
1710
  <button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>${i18next.t('figure.button.save')}</button>
1537
1711
  </ox-canvas-bar>
1712
+ ${this.blockersOpen && this.releaseBlockers.length ? this.renderReleaseBlockers(asset, unavailable) : nothing}
1538
1713
  ${this.catalogOpen
1539
1714
  ? html `<figure-recipe-catalog
1540
1715
  .templates=${this.templates}
@@ -1600,7 +1775,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1600
1775
  .fan=${this.fanOf(shown, live)}
1601
1776
  .ghost=${this.previewShown().ghost}
1602
1777
  .highlight=${this.previewShown().highlight}
1603
- .marks=${this.tab === 'capability' ? this.slotMarks(view, asset) : NO_MARKS}
1778
+ .marks=${this.tab === 'properties' && this.capabilityOpen ? this.slotMarks(view, asset) : NO_MARKS}
1604
1779
  .palette=${view.palette ?? {}}
1605
1780
  .context=${view.context ?? ''}
1606
1781
  .selected=${this.selected}
@@ -1614,12 +1789,9 @@ let FigureEditor = class FigureEditor extends LitElement {
1614
1789
  <aside slot="end">
1615
1790
  <div tabs>
1616
1791
  ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
1617
- ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
1618
1792
  ${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
1619
1793
  ${this.renderTab('data', 'sensors', i18next.t('figure.label.tab-data'))}
1620
1794
  ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
1621
- ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}
1622
- ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
1623
1795
  </div>
1624
1796
  <slot name="notice"></slot>
1625
1797
  <div panel ?off=${tab !== 'properties'}>
@@ -1639,27 +1811,17 @@ let FigureEditor = class FigureEditor extends LitElement {
1639
1811
  </div>
1640
1812
  ${this.renderRepeatNote(view.geometry)}
1641
1813
  </section>
1642
- ${this.renderRepeat(shown, view.geometry, unavailable)}
1643
- ${this.renderGroups(this.withSizeRow(groupFields(partFields, this.selected), held), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable), held)}
1814
+ ${this.renderPartGroups(shown, view.geometry, partFields, unavailable, held)}
1644
1815
  ${roomFields.length ? this.renderRoomOnly(roomFields, unavailable) : nothing}
1645
- ${this.renderLinks(unavailable, 'shape')}
1646
- <figure-section-panel
1647
- .body=${{ ...part, id: this.selected }}
1648
- ?locked=${unavailable}
1649
- .faceMapping=${this.faceMapping?.from === 'panel' ? this.faceMapping.need : undefined}
1650
- @face-mapped=${(e) => this.mapFaces(e.detail.remap)}
1651
- @face-mapping-cancel=${() => (this.faceMapping = undefined)}
1652
- @command=${(e) => this.linkCommand(e.detail.action)}
1653
- @command-live=${(e) => this.holdAction(e.detail.action)}
1654
- @section-editor-open=${() => (this.sectionEditing = this.selected)}
1655
- ></figure-section-panel>
1656
1816
  <figure-material-panel
1657
1817
  .body=${{ ...part, id: this.selected }}
1658
1818
  .palette=${view.palette ?? {}}
1659
1819
  ?locked=${unavailable}
1660
1820
  @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
1661
1821
  @part-live=${(e) => this.hold({}, e.detail.edit)}
1662
- ></figure-material-panel>`
1822
+ ></figure-material-panel>
1823
+ ${this.renderCapability(asset, unavailable)}
1824
+ ${this.renderMore(part, view.palette ?? {}, unavailable)}`
1663
1825
  : view.geometry.length === 0
1664
1826
  ? html `${this.renderSubject()}<section empty-figure>
1665
1827
  <p>${i18next.t('figure.text.empty-figure-next')}</p>
@@ -1670,18 +1832,8 @@ let FigureEditor = class FigureEditor extends LitElement {
1670
1832
  </section>`
1671
1833
  : html `${this.renderSubject()}<slot name="figure"></slot>
1672
1834
  ${this.renderGroups(groupFields(common), 'figure.label.group-figure-size', 'figure.label.design-inputs', unavailable)}
1673
- ${this.renderInstanceRange(shown, unavailable)}`}
1674
- </div>
1675
- <div panel ?off=${tab !== 'capability'}>
1676
- ${this.renderSubject()}
1677
- ${this.selected && part ? this.renderLinks(unavailable, 'places') : null}
1678
- <figure-capability-panel
1679
- .asset=${asset}
1680
- .selected=${this.selected}
1681
- ?locked=${unavailable}
1682
- @select-part=${(e) => this.choose(e.detail.id)}
1683
- @part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
1684
- ></figure-capability-panel>
1835
+ ${this.renderInstanceRange(shown, unavailable)}
1836
+ ${this.renderCapability(asset, unavailable)}`}
1685
1837
  </div>
1686
1838
  <div panel ?off=${tab !== 'motion'}>
1687
1839
  ${this.renderSubject()}
@@ -1699,12 +1851,9 @@ let FigureEditor = class FigureEditor extends LitElement {
1699
1851
  ></figure-data-panel>
1700
1852
  </div>
1701
1853
  <div panel flush ?off=${tab !== 'verdict'}><slot name="verdict"></slot></div>
1702
- <div panel flush ?off=${tab !== 'view'}>
1703
- <figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>
1704
- </div>
1705
- <div panel flush ?off=${tab !== 'history'}><slot name="history"></slot></div>
1706
1854
  </aside>
1707
- </ox-three-pane>`;
1855
+ </ox-three-pane>
1856
+ ${this.renderOverlay()}`;
1708
1857
  }
1709
1858
  /** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
1710
1859
  renderGroups(groups, vectorTitle, singleTitle, unavailable, tools = null, held) {
@@ -1859,6 +2008,14 @@ __decorate([
1859
2008
  property({ attribute: false }),
1860
2009
  __metadata("design:type", Object)
1861
2010
  ], FigureEditor.prototype, "releaseState", void 0);
2011
+ __decorate([
2012
+ property({ attribute: false }),
2013
+ __metadata("design:type", Array)
2014
+ ], FigureEditor.prototype, "releaseBlockers", void 0);
2015
+ __decorate([
2016
+ state(),
2017
+ __metadata("design:type", Object)
2018
+ ], FigureEditor.prototype, "blockersOpen", void 0);
1862
2019
  __decorate([
1863
2020
  property({ attribute: false }),
1864
2021
  __metadata("design:type", Array)
@@ -1919,6 +2076,18 @@ __decorate([
1919
2076
  state(),
1920
2077
  __metadata("design:type", String)
1921
2078
  ], FigureEditor.prototype, "tab", void 0);
2079
+ __decorate([
2080
+ state(),
2081
+ __metadata("design:type", String)
2082
+ ], FigureEditor.prototype, "overlay", void 0);
2083
+ __decorate([
2084
+ state(),
2085
+ __metadata("design:type", Object)
2086
+ ], FigureEditor.prototype, "capabilityOpen", void 0);
2087
+ __decorate([
2088
+ state(),
2089
+ __metadata("design:type", Object)
2090
+ ], FigureEditor.prototype, "moreOpen", void 0);
1922
2091
  __decorate([
1923
2092
  state(),
1924
2093
  __metadata("design:type", Function)