@things-factory/figure-library 10.1.154 → 10.1.156

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 (60) hide show
  1. package/dist-client/pages/figure-library-page.d.ts +20 -58
  2. package/dist-client/pages/figure-library-page.js +189 -672
  3. package/dist-client/pages/figure-library-page.js.map +1 -1
  4. package/dist-client/tsconfig.tsbuildinfo +1 -1
  5. package/dist-client/viewparts/figure-card-meta.d.ts +19 -0
  6. package/dist-client/viewparts/figure-card-meta.js +122 -0
  7. package/dist-client/viewparts/figure-card-meta.js.map +1 -0
  8. package/dist-client/viewparts/figure-releases-popup.d.ts +34 -0
  9. package/dist-client/viewparts/figure-releases-popup.js +400 -0
  10. package/dist-client/viewparts/figure-releases-popup.js.map +1 -0
  11. package/dist-client/viewparts/release-pair.d.ts +9 -0
  12. package/dist-client/viewparts/release-pair.js +15 -0
  13. package/dist-client/viewparts/release-pair.js.map +1 -0
  14. package/dist-server/service/figure-library-release/figure-library-release.d.ts +5 -3
  15. package/dist-server/service/figure-library-release/figure-library-release.js +13 -3
  16. package/dist-server/service/figure-library-release/figure-library-release.js.map +1 -1
  17. package/dist-server/service/figure-library-resolver.d.ts +9 -9
  18. package/dist-server/service/figure-library-resolver.js +66 -69
  19. package/dist-server/service/figure-library-resolver.js.map +1 -1
  20. package/dist-server/service/figure-library-types.d.ts +17 -4
  21. package/dist-server/service/figure-library-types.js +63 -15
  22. package/dist-server/service/figure-library-types.js.map +1 -1
  23. package/dist-server/service/figure-type-map/figure-type-map-entry.d.ts +5 -4
  24. package/dist-server/service/figure-type-map/figure-type-map-entry.js +8 -14
  25. package/dist-server/service/figure-type-map/figure-type-map-entry.js.map +1 -1
  26. package/dist-server/service/index.d.ts +2 -3
  27. package/dist-server/service/index.js +2 -3
  28. package/dist-server/service/index.js.map +1 -1
  29. package/dist-server/service/library/blueprints.d.ts +3 -8
  30. package/dist-server/service/library/blueprints.js +5 -6
  31. package/dist-server/service/library/blueprints.js.map +1 -1
  32. package/dist-server/service/library/carriers.d.ts +8 -11
  33. package/dist-server/service/library/carriers.js +8 -14
  34. package/dist-server/service/library/carriers.js.map +1 -1
  35. package/dist-server/service/library/derived.d.ts +5 -3
  36. package/dist-server/service/library/derived.js +4 -3
  37. package/dist-server/service/library/derived.js.map +1 -1
  38. package/dist-server/service/library/figures.d.ts +46 -0
  39. package/dist-server/service/library/figures.js +91 -0
  40. package/dist-server/service/library/figures.js.map +1 -0
  41. package/dist-server/service/library/import.d.ts +1 -1
  42. package/dist-server/service/library/import.js +5 -3
  43. package/dist-server/service/library/import.js.map +1 -1
  44. package/dist-server/service/library/inspection.d.ts +1 -48
  45. package/dist-server/service/library/inspection.js +3 -80
  46. package/dist-server/service/library/inspection.js.map +1 -1
  47. package/dist-server/service/library/type-map.d.ts +10 -22
  48. package/dist-server/service/library/type-map.js +44 -108
  49. package/dist-server/service/library/type-map.js.map +1 -1
  50. package/dist-server/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +11 -10
  52. package/translations/en.json +26 -47
  53. package/translations/es.json +26 -47
  54. package/translations/ja.json +26 -47
  55. package/translations/ko.json +26 -47
  56. package/translations/ms.json +26 -47
  57. package/translations/zh.json +26 -47
  58. package/dist-server/service/figure-type-map/figure-type-map.d.ts +0 -34
  59. package/dist-server/service/figure-type-map/figure-type-map.js +0 -120
  60. package/dist-server/service/figure-type-map/figure-type-map.js.map +0 -1
@@ -0,0 +1,19 @@
1
+ import { LitElement, nothing } from 'lit';
2
+ /** A version as operato-figure shows it: v1. `@` stays inside a full release reference only. */
3
+ export declare const versionText: (version: number) => string;
4
+ /** The place roles a release declares, in figure-viewer's words (파지 · 안착); 없음 for none. */
5
+ export declare const capabilityText: (roles: readonly string[] | null | undefined) => string;
6
+ declare const FigureCardMeta_base: (new (...args: any[]) => LitElement) & typeof LitElement;
7
+ /**
8
+ * What a figure card says under its title, as labelled rows: its full name, then the version in use (with 「최신 아님」
9
+ * when the latest differs) and, on the same row from the middle of the card, the latest version when it differs; then
10
+ * its category and its capabilities. A figure with nothing drawable has 사용 버전 「없음」 and the reason. A card's fields are drawn inside the card, out of the page's
11
+ * styles, so the rows carry their own (page-standard §4: one grid, the label column as wide as its longest label).
12
+ */
13
+ export declare class FigureCardMeta extends FigureCardMeta_base {
14
+ static styles: import("lit").CSSResult;
15
+ /** A figure as figureLibraryFigures gives it: name, latestVersion, release (null when none is drawable), latest. */
16
+ figure: any;
17
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
18
+ }
19
+ export {};
@@ -0,0 +1,122 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { css, html, LitElement, nothing } from 'lit';
3
+ import { customElement, property } from 'lit/decorators.js';
4
+ import { i18next, localize } from '@operato/i18n';
5
+ /** A version as operato-figure shows it: v1. `@` stays inside a full release reference only. */
6
+ export const versionText = (version) => `v${version}`;
7
+ /** The place roles a release declares, in figure-viewer's words (파지 · 안착); 없음 for none. */
8
+ export const capabilityText = (roles) => roles?.length
9
+ ? roles.map(role => i18next.t(`figure.label.role-${role}`, { defaultValue: role })).join(', ')
10
+ : i18next.t('figure-library.text.no-capability');
11
+ /**
12
+ * What a figure card says under its title, as labelled rows: its full name, then the version in use (with 「최신 아님」
13
+ * when the latest differs) and, on the same row from the middle of the card, the latest version when it differs; then
14
+ * its category and its capabilities. A figure with nothing drawable has 사용 버전 「없음」 and the reason. A card's fields are drawn inside the card, out of the page's
15
+ * styles, so the rows carry their own (page-standard §4: one grid, the label column as wide as its longest label).
16
+ */
17
+ let FigureCardMeta = class FigureCardMeta extends localize(i18next)(LitElement) {
18
+ static { this.styles = css `
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ width: 100%;
23
+ gap: var(--spacing-small, 4px);
24
+ min-width: 0;
25
+ font-size: var(--ui-t-small, 11.5px);
26
+ line-height: 1.5;
27
+ }
28
+
29
+ .full-name {
30
+ font-family: var(--ui-mono, ui-monospace, Menlo, monospace);
31
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
32
+ overflow: hidden;
33
+ text-overflow: ellipsis;
34
+ white-space: nowrap;
35
+ }
36
+
37
+ /* label · value · label · value: the version pair shares a row; a single fact's value spans to the end. */
38
+ dl {
39
+ display: grid;
40
+ grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
41
+ align-items: center;
42
+ gap: 2px var(--spacing-large, 12px);
43
+ margin: 0;
44
+ }
45
+
46
+ dt {
47
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
48
+ }
49
+
50
+ dd.wide {
51
+ grid-column: 2 / -1;
52
+ }
53
+
54
+ dd {
55
+ display: flex;
56
+ flex-wrap: wrap;
57
+ align-items: center;
58
+ gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
59
+ margin: 0;
60
+ color: var(--ui-ink, var(--md-sys-color-on-surface));
61
+ }
62
+
63
+ /* A chip in a row keeps the row's height: its border sits inside the line. */
64
+ .mark {
65
+ padding: 0 var(--spacing-medium, 8px);
66
+ border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
67
+ border-radius: 999px;
68
+ line-height: calc(1.5em - 2px);
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .mark.warn {
73
+ border-color: color-mix(in srgb, var(--ui-warn, var(--md-sys-color-tertiary)) 50%, transparent);
74
+ color: var(--ui-warn, var(--md-sys-color-tertiary));
75
+ font-weight: 600;
76
+ }
77
+
78
+ .mark.crit {
79
+ border-color: color-mix(in srgb, var(--ui-crit, var(--md-sys-color-error)) 40%, transparent);
80
+ color: var(--ui-crit, var(--md-sys-color-error));
81
+ }
82
+ `; }
83
+ render() {
84
+ const figure = this.figure;
85
+ if (!figure)
86
+ return nothing;
87
+ const release = figure.release;
88
+ const latest = figure.latest;
89
+ const differs = !release || release.version !== figure.latestVersion;
90
+ const unsupported = html `<span class="mark crit">${i18next.t('figure-library.text.format-unsupported')}</span>`;
91
+ return html `
92
+ <span class="full-name" title=${figure.name}>${figure.name}</span>
93
+ <dl>
94
+ <dt>${i18next.t('figure-library.label.version-in-use')}</dt>
95
+ <dd class=${differs ? '' : 'wide'}>
96
+ ${release
97
+ ? html `${versionText(release.version)}
98
+ ${differs ? html `<span class="mark warn">${i18next.t('figure-library.text.not-latest')}</span>` : nothing}
99
+ ${release.deprecated ? html `<span class="mark">${i18next.t('figure-library.text.deprecated')}</span>` : nothing}`
100
+ : html `${i18next.t('figure-library.text.none')} · ${unsupported}`}
101
+ </dd>
102
+ ${differs
103
+ ? html `<dt>${i18next.t('figure-library.label.latest-version')}</dt>
104
+ <dd>${versionText(figure.latestVersion)} ${latest && !latest.formatSupported && release ? unsupported : nothing}</dd>`
105
+ : nothing}
106
+ <dt>${i18next.t('figure-library.label.category')}</dt>
107
+ <dd class="wide">${release?.category || '—'}</dd>
108
+ <dt>${i18next.t('figure-library.label.capability')}</dt>
109
+ <dd class="wide">${release ? capabilityText(release.capabilities) : '—'}</dd>
110
+ </dl>
111
+ `;
112
+ }
113
+ };
114
+ __decorate([
115
+ property({ type: Object }),
116
+ __metadata("design:type", Object)
117
+ ], FigureCardMeta.prototype, "figure", void 0);
118
+ FigureCardMeta = __decorate([
119
+ customElement('figure-card-meta')
120
+ ], FigureCardMeta);
121
+ export { FigureCardMeta };
122
+ //# sourceMappingURL=figure-card-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figure-card-meta.js","sourceRoot":"","sources":["../../client/viewparts/figure-card-meta.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\n\n/** A version as operato-figure shows it: v1. `@` stays inside a full release reference only. */\nexport const versionText = (version: number) => `v${version}`\n\n/** The place roles a release declares, in figure-viewer's words (파지 · 안착); 없음 for none. */\nexport const capabilityText = (roles: readonly string[] | null | undefined) =>\n roles?.length\n ? roles.map(role => i18next.t(`figure.label.role-${role}`, { defaultValue: role })).join(', ')\n : i18next.t('figure-library.text.no-capability')\n\n/**\n * What a figure card says under its title, as labelled rows: its full name, then the version in use (with 「최신 아님」\n * when the latest differs) and, on the same row from the middle of the card, the latest version when it differs; then\n * its category and its capabilities. A figure with nothing drawable has 사용 버전 「없음」 and the reason. A card's fields are drawn inside the card, out of the page's\n * styles, so the rows carry their own (page-standard §4: one grid, the label column as wide as its longest label).\n */\n@customElement('figure-card-meta')\nexport class FigureCardMeta extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n gap: var(--spacing-small, 4px);\n min-width: 0;\n font-size: var(--ui-t-small, 11.5px);\n line-height: 1.5;\n }\n\n .full-name {\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* label · value · label · value: the version pair shares a row; a single fact's value spans to the end. */\n dl {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);\n align-items: center;\n gap: 2px var(--spacing-large, 12px);\n margin: 0;\n }\n\n dt {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n\n dd.wide {\n grid-column: 2 / -1;\n }\n\n dd {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n margin: 0;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n\n /* A chip in a row keeps the row's height: its border sits inside the line. */\n .mark {\n padding: 0 var(--spacing-medium, 8px);\n border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n border-radius: 999px;\n line-height: calc(1.5em - 2px);\n white-space: nowrap;\n }\n\n .mark.warn {\n border-color: color-mix(in srgb, var(--ui-warn, var(--md-sys-color-tertiary)) 50%, transparent);\n color: var(--ui-warn, var(--md-sys-color-tertiary));\n font-weight: 600;\n }\n\n .mark.crit {\n border-color: color-mix(in srgb, var(--ui-crit, var(--md-sys-color-error)) 40%, transparent);\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n `\n\n /** A figure as figureLibraryFigures gives it: name, latestVersion, release (null when none is drawable), latest. */\n @property({ type: Object }) figure: any\n\n render() {\n const figure = this.figure\n if (!figure) return nothing\n const release = figure.release\n const latest = figure.latest\n const differs = !release || release.version !== figure.latestVersion\n const unsupported = html`<span class=\"mark crit\">${i18next.t('figure-library.text.format-unsupported')}</span>`\n\n return html`\n <span class=\"full-name\" title=${figure.name}>${figure.name}</span>\n <dl>\n <dt>${i18next.t('figure-library.label.version-in-use')}</dt>\n <dd class=${differs ? '' : 'wide'}>\n ${release\n ? html`${versionText(release.version)}\n ${differs ? html`<span class=\"mark warn\">${i18next.t('figure-library.text.not-latest')}</span>` : nothing}\n ${release.deprecated ? html`<span class=\"mark\">${i18next.t('figure-library.text.deprecated')}</span>` : nothing}`\n : html`${i18next.t('figure-library.text.none')} · ${unsupported}`}\n </dd>\n ${differs\n ? html`<dt>${i18next.t('figure-library.label.latest-version')}</dt>\n <dd>${versionText(figure.latestVersion)} ${latest && !latest.formatSupported && release ? unsupported : nothing}</dd>`\n : nothing}\n <dt>${i18next.t('figure-library.label.category')}</dt>\n <dd class=\"wide\">${release?.category || '—'}</dd>\n <dt>${i18next.t('figure-library.label.capability')}</dt>\n <dd class=\"wide\">${release ? capabilityText(release.capabilities) : '—'}</dd>\n </dl>\n `\n }\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import '@operato/popup/ox-popup-actions.js';
2
+ import '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js';
3
+ import '@things-factory/figure-viewer/dist-client/figure-facts-list.js';
4
+ import { LitElement } from 'lit';
5
+ declare const FigureReleasesPopup_base: (new (...args: any[]) => LitElement) & typeof LitElement;
6
+ /**
7
+ * Two releases of one figure on live canvases, side by side (ADR-0114, amended 2026-10-12): each drawn as a board draws
8
+ * it, sized within its ranges, posed and played, with what it is and its places beside it -- operato-figure's preview
9
+ * (@things-factory/figure-viewer), not a second one. With more releases, each column picks the release it shows; the
10
+ * one in use and the latest stand first. The operator picks the release the figure draws with and saves; save stays
11
+ * off until the pick differs. A release this server cannot read shows why instead of a canvas and cannot be picked.
12
+ */
13
+ export declare class FigureReleasesPopup extends FigureReleasesPopup_base {
14
+ static styles: import("lit").CSSResult[];
15
+ /** The full figure name. */
16
+ name: string;
17
+ /** The id of the release it draws with now; '' when nothing is in use. */
18
+ drawing: string;
19
+ private releases;
20
+ /** The release each column shows, by id. */
21
+ private shown;
22
+ private picked;
23
+ private saving;
24
+ private failure;
25
+ private assets;
26
+ connectedCallback(): Promise<void>;
27
+ render(): import("lit-html").TemplateResult<1>;
28
+ private assetOf;
29
+ private renderColumn;
30
+ private renderReleaseFacts;
31
+ private show;
32
+ private save;
33
+ }
34
+ export {};
@@ -0,0 +1,400 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@operato/popup/ox-popup-actions.js';
3
+ import '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js';
4
+ import '@things-factory/figure-viewer/dist-client/figure-facts-list.js';
5
+ import gql from 'graphql-tag';
6
+ import { css, html, LitElement, nothing } from 'lit';
7
+ import { customElement, property, state } from 'lit/decorators.js';
8
+ import { client } from '@operato/graphql';
9
+ import { i18next, localize } from '@operato/i18n';
10
+ import { closePopup } from '@operato/popup';
11
+ import { ButtonStyles, CommonPopupStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles';
12
+ import { versionText } from './figure-card-meta.js';
13
+ import { defaultPair, SIDE_BY_SIDE } from './release-pair.js';
14
+ const RELEASES_OF = gql `
15
+ query ($name: String!) {
16
+ figureLibraryReleasesOf(name: $name) {
17
+ id
18
+ name
19
+ ref
20
+ version
21
+ category
22
+ source
23
+ formatSupported
24
+ assetFormat
25
+ kernelVersion
26
+ carrierType
27
+ carrierDimensions
28
+ changes
29
+ deprecated
30
+ supersededBy
31
+ comment
32
+ sourceUrl
33
+ releasedAt
34
+ createdAt
35
+ }
36
+ }
37
+ `;
38
+ const CHOOSE = gql `
39
+ mutation ($name: String!, $releaseId: String!) {
40
+ chooseFigureRelease(name: $name, releaseId: $releaseId) {
41
+ name
42
+ }
43
+ }
44
+ `;
45
+ /** A time in the reader's language (a bare toLocaleString follows the browser, not the app). */
46
+ const when = (value) => (value ? new Date(value).toLocaleString(i18next.language) : '');
47
+ /**
48
+ * A contract change in words. figure-model gives a code and values; `detail` is English for logs and is
49
+ * the sentence only when this module has no words for a new code (open question P8 in figure-library.md).
50
+ */
51
+ const changeText = (change) => change.code
52
+ ? i18next.t(`figure-library.contract.${change.code}`, { ...(change.values ?? {}), defaultValue: change.detail ?? change.code })
53
+ : change.detail ?? '';
54
+ /** The contract changes a release carries against its previous release, as the authoring server measured them. */
55
+ const changesOf = (release) => {
56
+ try {
57
+ const parsed = release.changes ? JSON.parse(release.changes) : [];
58
+ return Array.isArray(parsed) ? parsed : [];
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ };
64
+ /**
65
+ * Two releases of one figure on live canvases, side by side (ADR-0114, amended 2026-10-12): each drawn as a board draws
66
+ * it, sized within its ranges, posed and played, with what it is and its places beside it -- operato-figure's preview
67
+ * (@things-factory/figure-viewer), not a second one. With more releases, each column picks the release it shows; the
68
+ * one in use and the latest stand first. The operator picks the release the figure draws with and saves; save stays
69
+ * off until the pick differs. A release this server cannot read shows why instead of a canvas and cannot be picked.
70
+ */
71
+ let FigureReleasesPopup = class FigureReleasesPopup extends localize(i18next)(LitElement) {
72
+ constructor() {
73
+ super(...arguments);
74
+ /** The full figure name. */
75
+ this.name = '';
76
+ /** The id of the release it draws with now; '' when nothing is in use. */
77
+ this.drawing = '';
78
+ this.releases = [];
79
+ /** The release each column shows, by id. */
80
+ this.shown = [];
81
+ this.picked = '';
82
+ this.saving = false;
83
+ this.failure = '';
84
+ this.assets = new Map();
85
+ this.save = async () => {
86
+ this.saving = true;
87
+ try {
88
+ const response = await client.mutate({ mutation: CHOOSE, variables: { name: this.name, releaseId: this.picked } });
89
+ if (response.errors?.length)
90
+ throw new Error(response.errors[0].message);
91
+ this.dispatchEvent(new CustomEvent('saved', { bubbles: true, composed: true }));
92
+ closePopup(this);
93
+ }
94
+ catch (e) {
95
+ this.failure = e?.message ?? '';
96
+ }
97
+ finally {
98
+ this.saving = false;
99
+ }
100
+ };
101
+ }
102
+ static { this.styles = [
103
+ CommonPopupStyles,
104
+ ScrollbarStyles,
105
+ ButtonStyles,
106
+ FormFieldStyles,
107
+ css `
108
+ /*
109
+ The popup takes the overlay's width (a share of the window, all of it on a narrow one) and never more: the two
110
+ columns sit side by side when they have room and stack when they do not, and the actions stay in view.
111
+ */
112
+ :host {
113
+ min-height: 0;
114
+ width: 100%;
115
+ --popup-min-width: 0;
116
+ }
117
+
118
+ .body {
119
+ flex: 1;
120
+ overflow: auto;
121
+ container-type: inline-size;
122
+ }
123
+
124
+ .columns {
125
+ display: grid;
126
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
127
+ gap: var(--spacing-large, 12px);
128
+ align-items: start;
129
+ }
130
+
131
+ section.column {
132
+ display: flex;
133
+ flex-direction: column;
134
+ min-width: 0;
135
+ border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
136
+ border-radius: var(--ui-r, 10px);
137
+ background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));
138
+ }
139
+
140
+ section.column[picked] {
141
+ border-color: var(--ui-accent, var(--md-sys-color-primary));
142
+ box-shadow: inset 0 0 0 1px var(--ui-accent, var(--md-sys-color-primary));
143
+ }
144
+
145
+ /*
146
+ Side by side, which release a column is stays in view while its facts scroll by. Stacked, each column's canvas
147
+ stands right under its own header and the header leaves with its column (below two columns' width).
148
+ */
149
+ .head {
150
+ position: sticky;
151
+ top: 0;
152
+ z-index: 1;
153
+ border-radius: var(--ui-r, 10px) var(--ui-r, 10px) 0 0;
154
+ background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));
155
+ display: flex;
156
+ flex-wrap: wrap;
157
+ align-items: center;
158
+ gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);
159
+ padding: var(--spacing-medium, 8px) 16px;
160
+ border-bottom: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
161
+ }
162
+
163
+ @container (max-width: 771px) {
164
+ .head {
165
+ position: static;
166
+ }
167
+ }
168
+
169
+ .head label {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ gap: var(--spacing-small, 4px);
173
+ font-weight: 600;
174
+ }
175
+
176
+ .head select {
177
+ height: var(--form-element-height-small, 26px);
178
+ min-width: 72px;
179
+ font-size: var(--ui-t-body, 13px);
180
+ }
181
+
182
+ .head strong {
183
+ font-size: var(--ui-t-title, 15px);
184
+ }
185
+
186
+ .marks {
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ gap: var(--spacing-small, 4px);
190
+ }
191
+
192
+ .mark {
193
+ padding: 0 var(--spacing-medium, 8px);
194
+ border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));
195
+ border-radius: 999px;
196
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
197
+ font-size: var(--ui-t-small, 11.5px);
198
+ white-space: nowrap;
199
+ }
200
+
201
+ .mark.on {
202
+ border-color: var(--ui-accent, var(--md-sys-color-primary));
203
+ color: var(--ui-accent, var(--md-sys-color-primary));
204
+ font-weight: 600;
205
+ }
206
+
207
+ .mark.crit {
208
+ color: var(--ui-crit, var(--md-sys-color-error));
209
+ }
210
+
211
+ .reason {
212
+ margin: 0;
213
+ padding: var(--spacing-huge, 24px) 16px;
214
+ color: var(--ui-crit, var(--md-sys-color-error));
215
+ }
216
+
217
+ /* The release's own facts (note, dates, source, format, contract change) under the figure's. */
218
+ dl.release {
219
+ display: grid;
220
+ grid-template-columns: 96px minmax(0, 1fr);
221
+ gap: 3px 12px;
222
+ margin: 0;
223
+ padding: 10px 16px;
224
+ line-height: 1.45;
225
+ }
226
+
227
+ dl.release dt {
228
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
229
+ }
230
+
231
+ dl.release dd {
232
+ margin: 0;
233
+ overflow-wrap: anywhere;
234
+ }
235
+
236
+ dl.release dd small {
237
+ display: block;
238
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
239
+ }
240
+
241
+ ul {
242
+ margin: 0;
243
+ padding-left: 1.2em;
244
+ }
245
+
246
+ li.breaking {
247
+ color: var(--ui-crit, var(--md-sys-color-error));
248
+ }
249
+
250
+ .finding {
251
+ margin: 0 0 var(--spacing-large, 12px);
252
+ padding-left: var(--spacing-large, 12px);
253
+ border-left: 2px solid var(--ui-crit, var(--md-sys-color-error));
254
+ }
255
+ `
256
+ ]; }
257
+ async connectedCallback() {
258
+ super.connectedCallback();
259
+ this.picked = this.drawing;
260
+ const response = await client.query({ query: RELEASES_OF, variables: { name: this.name }, fetchPolicy: 'network-only' });
261
+ this.releases = response.data?.figureLibraryReleasesOf ?? [];
262
+ this.shown = defaultPair(this.releases, this.drawing);
263
+ }
264
+ render() {
265
+ const changed = !!this.picked && this.picked !== this.drawing;
266
+ const byId = new Map(this.releases.map(release => [release.id, release]));
267
+ return html `
268
+ <div class="body">
269
+ ${this.failure ? html `<p class="finding" role="alert">${this.failure}</p>` : nothing}
270
+ <div class="columns">
271
+ ${this.shown.map((id, column) => (byId.get(id) ? this.renderColumn(byId.get(id), column) : nothing))}
272
+ </div>
273
+ </div>
274
+ <ox-popup-actions>
275
+ <button slot="cancel" @click=${() => closePopup(this)}>${i18next.t('figure-library.button.cancel')}</button>
276
+ <button slot="confirm" raised ?disabled=${!changed || this.saving} @click=${this.save}>${i18next.t('figure-library.button.save')}</button>
277
+ </ox-popup-actions>
278
+ `;
279
+ }
280
+ assetOf(release) {
281
+ if (!this.assets.has(release.id)) {
282
+ let asset = null;
283
+ try {
284
+ asset = release.formatSupported ? JSON.parse(release.source) : null;
285
+ }
286
+ catch {
287
+ asset = null;
288
+ }
289
+ this.assets.set(release.id, asset);
290
+ }
291
+ return this.assets.get(release.id);
292
+ }
293
+ renderColumn(release, column) {
294
+ const picked = release.id === this.picked;
295
+ /* A release this server cannot read is never used: it is shown, not picked (ADR-0114, amended 2026-10-12). */
296
+ const asset = this.assetOf(release);
297
+ const unusable = !asset;
298
+ const latest = release.id === this.releases[0]?.id;
299
+ const choosable = this.releases.length > SIDE_BY_SIDE;
300
+ return html `
301
+ <section class="column" ?picked=${picked}>
302
+ <div class="head">
303
+ <label>
304
+ <input type="radio" name="release" .checked=${picked} ?disabled=${unusable} @change=${() => (this.picked = release.id)} />
305
+ ${choosable
306
+ ? html `<select aria-label=${i18next.t('figure-library.label.release-shown')} @change=${(e) => this.show(column, e.target.value)}>
307
+ ${this.releases.map(other => html `<option value=${other.id} ?selected=${other.id === release.id}>${versionText(other.version)}</option>`)}
308
+ </select>`
309
+ : html `<strong>${versionText(release.version)}</strong>`}
310
+ </label>
311
+ <span class="marks">
312
+ ${release.id === this.drawing ? html `<span class="mark on">${i18next.t('figure-library.text.in-use')}</span>` : nothing}
313
+ ${latest ? html `<span class="mark">${i18next.t('figure-library.text.latest')}</span>` : nothing}
314
+ ${unusable ? html `<span class="mark crit">${i18next.t('figure-library.text.format-unsupported')}</span>` : nothing}
315
+ ${release.deprecated ? html `<span class="mark">${i18next.t('figure-library.text.deprecated')}</span>` : nothing}
316
+ </span>
317
+ </div>
318
+ ${asset
319
+ ? html `<figure-asset-viewer .asset=${asset}>
320
+ <figure-facts-list slot="facts" .asset=${asset} .row=${{ category: release.category }}></figure-facts-list>
321
+ </figure-asset-viewer>`
322
+ : html `<p class="reason" role="note">${i18next.t('figure-library.text.cannot-use-format', { format: release.assetFormat })}</p>`}
323
+ ${this.renderReleaseFacts(release)}
324
+ </section>
325
+ `;
326
+ }
327
+ renderReleaseFacts(release) {
328
+ const changes = changesOf(release);
329
+ const carrier = release.carrierType
330
+ ? `${release.carrierType}${release.carrierDimensions
331
+ ? ` · ${release.carrierDimensions.width} × ${release.carrierDimensions.length} × ${release.carrierDimensions.height} mm`
332
+ : ''}`
333
+ : null;
334
+ return html `<dl class="release">
335
+ ${release.comment ? html `<dt>${i18next.t('figure-library.label.release-note')}</dt><dd>${release.comment}</dd>` : nothing}
336
+ ${release.version > 1
337
+ ? html `<dt>${i18next.t('figure-library.text.contract-since', { version: versionText(release.version - 1) })}</dt>
338
+ <dd>
339
+ ${changes.length
340
+ ? html `<ul>
341
+ ${changes.map(change => html `<li class=${change.blocking ? 'breaking' : ''}>
342
+ ${changeText(change)}${change.blocking ? html ` · ${i18next.t('figure-library.text.breaking')}` : nothing}
343
+ </li>`)}
344
+ </ul>`
345
+ : i18next.t('figure-library.text.no-contract-change')}
346
+ </dd>`
347
+ : nothing}
348
+ ${carrier ? html `<dt>${i18next.t('figure-library.label.carrier')}</dt><dd>${carrier}</dd>` : nothing}
349
+ <dt>${i18next.t('figure-library.label.format')}</dt>
350
+ <dd>
351
+ ${release.assetFormat}
352
+ ${release.kernelVersion ? html `<small>${i18next.t('figure-library.text.kernel', { version: release.kernelVersion })}</small>` : nothing}
353
+ </dd>
354
+ <dt>${i18next.t('figure-library.label.released-at')}</dt>
355
+ <dd>${when(release.releasedAt)}</dd>
356
+ <dt>${i18next.t('figure-library.label.imported-at')}</dt>
357
+ <dd>${when(release.createdAt)}</dd>
358
+ <dt>${i18next.t('figure-library.label.source')}</dt>
359
+ <dd>${release.sourceUrl ?? ''}</dd>
360
+ </dl>`;
361
+ }
362
+ show(column, id) {
363
+ const next = [...this.shown];
364
+ next[column] = id;
365
+ this.shown = next;
366
+ }
367
+ };
368
+ __decorate([
369
+ property(),
370
+ __metadata("design:type", Object)
371
+ ], FigureReleasesPopup.prototype, "name", void 0);
372
+ __decorate([
373
+ property(),
374
+ __metadata("design:type", Object)
375
+ ], FigureReleasesPopup.prototype, "drawing", void 0);
376
+ __decorate([
377
+ state(),
378
+ __metadata("design:type", Array)
379
+ ], FigureReleasesPopup.prototype, "releases", void 0);
380
+ __decorate([
381
+ state(),
382
+ __metadata("design:type", Array)
383
+ ], FigureReleasesPopup.prototype, "shown", void 0);
384
+ __decorate([
385
+ state(),
386
+ __metadata("design:type", Object)
387
+ ], FigureReleasesPopup.prototype, "picked", void 0);
388
+ __decorate([
389
+ state(),
390
+ __metadata("design:type", Object)
391
+ ], FigureReleasesPopup.prototype, "saving", void 0);
392
+ __decorate([
393
+ state(),
394
+ __metadata("design:type", Object)
395
+ ], FigureReleasesPopup.prototype, "failure", void 0);
396
+ FigureReleasesPopup = __decorate([
397
+ customElement('figure-releases-popup')
398
+ ], FigureReleasesPopup);
399
+ export { FigureReleasesPopup };
400
+ //# sourceMappingURL=figure-releases-popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figure-releases-popup.js","sourceRoot":"","sources":["../../client/viewparts/figure-releases-popup.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/popup/ox-popup-actions.js'\nimport '@things-factory/figure-viewer/dist-client/figure-asset-viewer.js'\nimport '@things-factory/figure-viewer/dist-client/figure-facts-list.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport type { FigureAsset } from '@hatiolab/figure-model'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { closePopup } from '@operato/popup'\nimport { ButtonStyles, CommonPopupStyles, FormFieldStyles, ScrollbarStyles } from '@operato/styles'\n\nimport { versionText } from './figure-card-meta.js'\nimport { defaultPair, SIDE_BY_SIDE } from './release-pair.js'\n\nconst RELEASES_OF = gql`\n query ($name: String!) {\n figureLibraryReleasesOf(name: $name) {\n id\n name\n ref\n version\n category\n source\n formatSupported\n assetFormat\n kernelVersion\n carrierType\n carrierDimensions\n changes\n deprecated\n supersededBy\n comment\n sourceUrl\n releasedAt\n createdAt\n }\n }\n`\n\nconst CHOOSE = gql`\n mutation ($name: String!, $releaseId: String!) {\n chooseFigureRelease(name: $name, releaseId: $releaseId) {\n name\n }\n }\n`\n\ntype Change = { code?: string; values?: Record<string, unknown>; detail?: string; blocking?: boolean }\n\n/** A time in the reader's language (a bare toLocaleString follows the browser, not the app). */\nconst when = (value?: string | null) => (value ? new Date(value).toLocaleString(i18next.language) : '')\n\n/**\n * A contract change in words. figure-model gives a code and values; `detail` is English for logs and is\n * the sentence only when this module has no words for a new code (open question P8 in figure-library.md).\n */\nconst changeText = (change: Change) =>\n change.code\n ? i18next.t(`figure-library.contract.${change.code}`, { ...(change.values ?? {}), defaultValue: change.detail ?? change.code })\n : change.detail ?? ''\n\n/** The contract changes a release carries against its previous release, as the authoring server measured them. */\nconst changesOf = (release: any): Change[] => {\n try {\n const parsed = release.changes ? JSON.parse(release.changes) : []\n return Array.isArray(parsed) ? parsed : []\n } catch {\n return []\n }\n}\n\n/**\n * Two releases of one figure on live canvases, side by side (ADR-0114, amended 2026-10-12): each drawn as a board draws\n * it, sized within its ranges, posed and played, with what it is and its places beside it -- operato-figure's preview\n * (@things-factory/figure-viewer), not a second one. With more releases, each column picks the release it shows; the\n * one in use and the latest stand first. The operator picks the release the figure draws with and saves; save stays\n * off until the pick differs. A release this server cannot read shows why instead of a canvas and cannot be picked.\n */\n@customElement('figure-releases-popup')\nexport class FigureReleasesPopup extends localize(i18next)(LitElement) {\n static styles = [\n CommonPopupStyles,\n ScrollbarStyles,\n ButtonStyles,\n FormFieldStyles,\n css`\n /*\n The popup takes the overlay's width (a share of the window, all of it on a narrow one) and never more: the two\n columns sit side by side when they have room and stack when they do not, and the actions stay in view.\n */\n :host {\n min-height: 0;\n width: 100%;\n --popup-min-width: 0;\n }\n\n .body {\n flex: 1;\n overflow: auto;\n container-type: inline-size;\n }\n\n .columns {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));\n gap: var(--spacing-large, 12px);\n align-items: start;\n }\n\n section.column {\n display: flex;\n flex-direction: column;\n min-width: 0;\n border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n border-radius: var(--ui-r, 10px);\n background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));\n }\n\n section.column[picked] {\n border-color: var(--ui-accent, var(--md-sys-color-primary));\n box-shadow: inset 0 0 0 1px var(--ui-accent, var(--md-sys-color-primary));\n }\n\n /*\n Side by side, which release a column is stays in view while its facts scroll by. Stacked, each column's canvas\n stands right under its own header and the header leaves with its column (below two columns' width).\n */\n .head {\n position: sticky;\n top: 0;\n z-index: 1;\n border-radius: var(--ui-r, 10px) var(--ui-r, 10px) 0 0;\n background: var(--ui-panel, var(--md-sys-color-surface-container-lowest));\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n padding: var(--spacing-medium, 8px) 16px;\n border-bottom: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n }\n\n @container (max-width: 771px) {\n .head {\n position: static;\n }\n }\n\n .head label {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n font-weight: 600;\n }\n\n .head select {\n height: var(--form-element-height-small, 26px);\n min-width: 72px;\n font-size: var(--ui-t-body, 13px);\n }\n\n .head strong {\n font-size: var(--ui-t-title, 15px);\n }\n\n .marks {\n display: flex;\n flex-wrap: wrap;\n gap: var(--spacing-small, 4px);\n }\n\n .mark {\n padding: 0 var(--spacing-medium, 8px);\n border: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n border-radius: 999px;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n font-size: var(--ui-t-small, 11.5px);\n white-space: nowrap;\n }\n\n .mark.on {\n border-color: var(--ui-accent, var(--md-sys-color-primary));\n color: var(--ui-accent, var(--md-sys-color-primary));\n font-weight: 600;\n }\n\n .mark.crit {\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n\n .reason {\n margin: 0;\n padding: var(--spacing-huge, 24px) 16px;\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n\n /* The release's own facts (note, dates, source, format, contract change) under the figure's. */\n dl.release {\n display: grid;\n grid-template-columns: 96px minmax(0, 1fr);\n gap: 3px 12px;\n margin: 0;\n padding: 10px 16px;\n line-height: 1.45;\n }\n\n dl.release dt {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n\n dl.release dd {\n margin: 0;\n overflow-wrap: anywhere;\n }\n\n dl.release dd small {\n display: block;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n\n ul {\n margin: 0;\n padding-left: 1.2em;\n }\n\n li.breaking {\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n\n .finding {\n margin: 0 0 var(--spacing-large, 12px);\n padding-left: var(--spacing-large, 12px);\n border-left: 2px solid var(--ui-crit, var(--md-sys-color-error));\n }\n `\n ]\n\n /** The full figure name. */\n @property() name = ''\n /** The id of the release it draws with now; '' when nothing is in use. */\n @property() drawing = ''\n\n @state() private releases: any[] = []\n /** The release each column shows, by id. */\n @state() private shown: string[] = []\n @state() private picked = ''\n @state() private saving = false\n @state() private failure = ''\n\n private assets = new Map<string, FigureAsset | null>()\n\n async connectedCallback() {\n super.connectedCallback()\n this.picked = this.drawing\n const response = await client.query({ query: RELEASES_OF, variables: { name: this.name }, fetchPolicy: 'network-only' })\n this.releases = response.data?.figureLibraryReleasesOf ?? []\n this.shown = defaultPair(this.releases, this.drawing)\n }\n\n render() {\n const changed = !!this.picked && this.picked !== this.drawing\n const byId = new Map(this.releases.map(release => [release.id, release]))\n\n return html`\n <div class=\"body\">\n ${this.failure ? html`<p class=\"finding\" role=\"alert\">${this.failure}</p>` : nothing}\n <div class=\"columns\">\n ${this.shown.map((id, column) => (byId.get(id) ? this.renderColumn(byId.get(id), column) : nothing))}\n </div>\n </div>\n <ox-popup-actions>\n <button slot=\"cancel\" @click=${() => closePopup(this)}>${i18next.t('figure-library.button.cancel')}</button>\n <button slot=\"confirm\" raised ?disabled=${!changed || this.saving} @click=${this.save}>${i18next.t('figure-library.button.save')}</button>\n </ox-popup-actions>\n `\n }\n\n private assetOf(release: any): FigureAsset | null {\n if (!this.assets.has(release.id)) {\n let asset: FigureAsset | null = null\n try {\n asset = release.formatSupported ? (JSON.parse(release.source) as FigureAsset) : null\n } catch {\n asset = null\n }\n this.assets.set(release.id, asset)\n }\n return this.assets.get(release.id)!\n }\n\n private renderColumn(release: any, column: number) {\n const picked = release.id === this.picked\n /* A release this server cannot read is never used: it is shown, not picked (ADR-0114, amended 2026-10-12). */\n const asset = this.assetOf(release)\n const unusable = !asset\n const latest = release.id === this.releases[0]?.id\n const choosable = this.releases.length > SIDE_BY_SIDE\n\n return html`\n <section class=\"column\" ?picked=${picked}>\n <div class=\"head\">\n <label>\n <input type=\"radio\" name=\"release\" .checked=${picked} ?disabled=${unusable} @change=${() => (this.picked = release.id)} />\n ${choosable\n ? html`<select aria-label=${i18next.t('figure-library.label.release-shown')} @change=${(e: Event) => this.show(column, (e.target as HTMLSelectElement).value)}>\n ${this.releases.map(\n other => html`<option value=${other.id} ?selected=${other.id === release.id}>${versionText(other.version)}</option>`\n )}\n </select>`\n : html`<strong>${versionText(release.version)}</strong>`}\n </label>\n <span class=\"marks\">\n ${release.id === this.drawing ? html`<span class=\"mark on\">${i18next.t('figure-library.text.in-use')}</span>` : nothing}\n ${latest ? html`<span class=\"mark\">${i18next.t('figure-library.text.latest')}</span>` : nothing}\n ${unusable ? html`<span class=\"mark crit\">${i18next.t('figure-library.text.format-unsupported')}</span>` : nothing}\n ${release.deprecated ? html`<span class=\"mark\">${i18next.t('figure-library.text.deprecated')}</span>` : nothing}\n </span>\n </div>\n ${asset\n ? html`<figure-asset-viewer .asset=${asset}>\n <figure-facts-list slot=\"facts\" .asset=${asset} .row=${{ category: release.category }}></figure-facts-list>\n </figure-asset-viewer>`\n : html`<p class=\"reason\" role=\"note\">${i18next.t('figure-library.text.cannot-use-format', { format: release.assetFormat })}</p>`}\n ${this.renderReleaseFacts(release)}\n </section>\n `\n }\n\n private renderReleaseFacts(release: any) {\n const changes = changesOf(release)\n const carrier = release.carrierType\n ? `${release.carrierType}${\n release.carrierDimensions\n ? ` · ${release.carrierDimensions.width} × ${release.carrierDimensions.length} × ${release.carrierDimensions.height} mm`\n : ''\n }`\n : null\n\n return html`<dl class=\"release\">\n ${release.comment ? html`<dt>${i18next.t('figure-library.label.release-note')}</dt><dd>${release.comment}</dd>` : nothing}\n ${release.version > 1\n ? html`<dt>${i18next.t('figure-library.text.contract-since', { version: versionText(release.version - 1) })}</dt>\n <dd>\n ${changes.length\n ? html`<ul>\n ${changes.map(\n change => html`<li class=${change.blocking ? 'breaking' : ''}>\n ${changeText(change)}${change.blocking ? html` · ${i18next.t('figure-library.text.breaking')}` : nothing}\n </li>`\n )}\n </ul>`\n : i18next.t('figure-library.text.no-contract-change')}\n </dd>`\n : nothing}\n ${carrier ? html`<dt>${i18next.t('figure-library.label.carrier')}</dt><dd>${carrier}</dd>` : nothing}\n <dt>${i18next.t('figure-library.label.format')}</dt>\n <dd>\n ${release.assetFormat}\n ${release.kernelVersion ? html`<small>${i18next.t('figure-library.text.kernel', { version: release.kernelVersion })}</small>` : nothing}\n </dd>\n <dt>${i18next.t('figure-library.label.released-at')}</dt>\n <dd>${when(release.releasedAt)}</dd>\n <dt>${i18next.t('figure-library.label.imported-at')}</dt>\n <dd>${when(release.createdAt)}</dd>\n <dt>${i18next.t('figure-library.label.source')}</dt>\n <dd>${release.sourceUrl ?? ''}</dd>\n </dl>`\n }\n\n private show(column: number, id: string) {\n const next = [...this.shown]\n next[column] = id\n this.shown = next\n }\n\n private save = async () => {\n this.saving = true\n try {\n const response = await client.mutate({ mutation: CHOOSE, variables: { name: this.name, releaseId: this.picked } })\n if (response.errors?.length) throw new Error(response.errors[0].message)\n this.dispatchEvent(new CustomEvent('saved', { bubbles: true, composed: true }))\n closePopup(this)\n } catch (e: any) {\n this.failure = e?.message ?? ''\n } finally {\n this.saving = false\n }\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /** At most this many releases stand side by side in the compare. */
2
+ export declare const SIDE_BY_SIDE = 2;
3
+ /**
4
+ * The releases a compare opens on (releases newest first): the one in use and the latest; when they are the same
5
+ * release, or nothing is in use, the latest and the one before it.
6
+ */
7
+ export declare function defaultPair(releases: readonly {
8
+ id: string;
9
+ }[], drawing: string): string[];