@issuegraph/editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +238 -0
  3. package/dist/audit/findings.d.ts +225 -0
  4. package/dist/audit/findings.d.ts.map +1 -0
  5. package/dist/audit/findings.js +348 -0
  6. package/dist/audit/findings.js.map +1 -0
  7. package/dist/audit/styles.d.ts +37 -0
  8. package/dist/audit/styles.d.ts.map +1 -0
  9. package/dist/audit/styles.js +72 -0
  10. package/dist/audit/styles.js.map +1 -0
  11. package/dist/audit/surface.d.ts +155 -0
  12. package/dist/audit/surface.d.ts.map +1 -0
  13. package/dist/audit/surface.js +202 -0
  14. package/dist/audit/surface.js.map +1 -0
  15. package/dist/create/draft.d.ts +119 -0
  16. package/dist/create/draft.d.ts.map +1 -0
  17. package/dist/create/draft.js +114 -0
  18. package/dist/create/draft.js.map +1 -0
  19. package/dist/create/keys.d.ts +225 -0
  20. package/dist/create/keys.d.ts.map +1 -0
  21. package/dist/create/keys.js +228 -0
  22. package/dist/create/keys.js.map +1 -0
  23. package/dist/create/placement.d.ts +69 -0
  24. package/dist/create/placement.d.ts.map +1 -0
  25. package/dist/create/placement.js +76 -0
  26. package/dist/create/placement.js.map +1 -0
  27. package/dist/firstpass/batch.d.ts +190 -0
  28. package/dist/firstpass/batch.d.ts.map +1 -0
  29. package/dist/firstpass/batch.js +143 -0
  30. package/dist/firstpass/batch.js.map +1 -0
  31. package/dist/firstpass/candidates.d.ts +128 -0
  32. package/dist/firstpass/candidates.d.ts.map +1 -0
  33. package/dist/firstpass/candidates.js +39 -0
  34. package/dist/firstpass/candidates.js.map +1 -0
  35. package/dist/firstpass/keys.d.ts +90 -0
  36. package/dist/firstpass/keys.d.ts.map +1 -0
  37. package/dist/firstpass/keys.js +112 -0
  38. package/dist/firstpass/keys.js.map +1 -0
  39. package/dist/firstpass/queue.d.ts +183 -0
  40. package/dist/firstpass/queue.d.ts.map +1 -0
  41. package/dist/firstpass/queue.js +159 -0
  42. package/dist/firstpass/queue.js.map +1 -0
  43. package/dist/firstpass/render.d.ts +100 -0
  44. package/dist/firstpass/render.d.ts.map +1 -0
  45. package/dist/firstpass/render.js +216 -0
  46. package/dist/firstpass/render.js.map +1 -0
  47. package/dist/firstpass/styles.d.ts +36 -0
  48. package/dist/firstpass/styles.d.ts.map +1 -0
  49. package/dist/firstpass/styles.js +148 -0
  50. package/dist/firstpass/styles.js.map +1 -0
  51. package/dist/firstpass/view.d.ts +82 -0
  52. package/dist/firstpass/view.d.ts.map +1 -0
  53. package/dist/firstpass/view.js +60 -0
  54. package/dist/firstpass/view.js.map +1 -0
  55. package/dist/firstpass/words.d.ts +105 -0
  56. package/dist/firstpass/words.d.ts.map +1 -0
  57. package/dist/firstpass/words.js +28 -0
  58. package/dist/firstpass/words.js.map +1 -0
  59. package/dist/index.d.ts +150 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +147 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/overlay/grammar.d.ts +259 -0
  64. package/dist/overlay/grammar.d.ts.map +1 -0
  65. package/dist/overlay/grammar.js +223 -0
  66. package/dist/overlay/grammar.js.map +1 -0
  67. package/dist/overlay/render.d.ts +125 -0
  68. package/dist/overlay/render.d.ts.map +1 -0
  69. package/dist/overlay/render.js +439 -0
  70. package/dist/overlay/render.js.map +1 -0
  71. package/dist/overlay/styles.d.ts +41 -0
  72. package/dist/overlay/styles.d.ts.map +1 -0
  73. package/dist/overlay/styles.js +124 -0
  74. package/dist/overlay/styles.js.map +1 -0
  75. package/dist/picker/render.d.ts +80 -0
  76. package/dist/picker/render.d.ts.map +1 -0
  77. package/dist/picker/render.js +112 -0
  78. package/dist/picker/render.js.map +1 -0
  79. package/dist/picker/styles.d.ts +26 -0
  80. package/dist/picker/styles.d.ts.map +1 -0
  81. package/dist/picker/styles.js +116 -0
  82. package/dist/picker/styles.js.map +1 -0
  83. package/dist/picker/view.d.ts +135 -0
  84. package/dist/picker/view.d.ts.map +1 -0
  85. package/dist/picker/view.js +117 -0
  86. package/dist/picker/view.js.map +1 -0
  87. package/dist/picker/words.d.ts +53 -0
  88. package/dist/picker/words.d.ts.map +1 -0
  89. package/dist/picker/words.js +28 -0
  90. package/dist/picker/words.js.map +1 -0
  91. package/dist/reevaluate/render.d.ts +77 -0
  92. package/dist/reevaluate/render.d.ts.map +1 -0
  93. package/dist/reevaluate/render.js +211 -0
  94. package/dist/reevaluate/render.js.map +1 -0
  95. package/dist/reevaluate/styles.d.ts +24 -0
  96. package/dist/reevaluate/styles.d.ts.map +1 -0
  97. package/dist/reevaluate/styles.js +182 -0
  98. package/dist/reevaluate/styles.js.map +1 -0
  99. package/dist/reevaluate/view.d.ts +102 -0
  100. package/dist/reevaluate/view.d.ts.map +1 -0
  101. package/dist/reevaluate/view.js +111 -0
  102. package/dist/reevaluate/view.js.map +1 -0
  103. package/dist/reevaluate/words.d.ts +54 -0
  104. package/dist/reevaluate/words.d.ts.map +1 -0
  105. package/dist/reevaluate/words.js +29 -0
  106. package/dist/reevaluate/words.js.map +1 -0
  107. package/dist/scale/commands.d.ts +60 -0
  108. package/dist/scale/commands.d.ts.map +1 -0
  109. package/dist/scale/commands.js +49 -0
  110. package/dist/scale/commands.js.map +1 -0
  111. package/dist/scale/ladder.d.ts +141 -0
  112. package/dist/scale/ladder.d.ts.map +1 -0
  113. package/dist/scale/ladder.js +243 -0
  114. package/dist/scale/ladder.js.map +1 -0
  115. package/dist/scale/render.d.ts +85 -0
  116. package/dist/scale/render.d.ts.map +1 -0
  117. package/dist/scale/render.js +194 -0
  118. package/dist/scale/render.js.map +1 -0
  119. package/dist/scale/styles.d.ts +21 -0
  120. package/dist/scale/styles.d.ts.map +1 -0
  121. package/dist/scale/styles.js +105 -0
  122. package/dist/scale/styles.js.map +1 -0
  123. package/dist/workspace/inspector.d.ts +117 -0
  124. package/dist/workspace/inspector.d.ts.map +1 -0
  125. package/dist/workspace/inspector.js +140 -0
  126. package/dist/workspace/inspector.js.map +1 -0
  127. package/dist/workspace/rail.d.ts +122 -0
  128. package/dist/workspace/rail.d.ts.map +1 -0
  129. package/dist/workspace/rail.js +216 -0
  130. package/dist/workspace/rail.js.map +1 -0
  131. package/dist/workspace/render.d.ts +139 -0
  132. package/dist/workspace/render.d.ts.map +1 -0
  133. package/dist/workspace/render.js +366 -0
  134. package/dist/workspace/render.js.map +1 -0
  135. package/dist/workspace/selection.d.ts +92 -0
  136. package/dist/workspace/selection.d.ts.map +1 -0
  137. package/dist/workspace/selection.js +83 -0
  138. package/dist/workspace/selection.js.map +1 -0
  139. package/dist/workspace/styles.d.ts +39 -0
  140. package/dist/workspace/styles.d.ts.map +1 -0
  141. package/dist/workspace/styles.js +313 -0
  142. package/dist/workspace/styles.js.map +1 -0
  143. package/package.json +58 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The type picker, the direction statement and the flip control, as markup.
3
+ *
4
+ * ## Every readable byte comes from the host or from the document
5
+ *
6
+ * This surface renders no word of its own — not in a text node, and not in an
7
+ * `aria-label`. That is stricter than the rest of this package (the scale
8
+ * ladder writes its own chip labels), and it is stricter deliberately: what is
9
+ * being worded here is a RELATIONSHIP, so a phrase written in would be an
10
+ * English clause in the one place the design says a reader most often gets the
11
+ * encoding wrong. `render.test.ts` enforces it by asserting that every text node
12
+ * is either a {@link ./words.ts PickerWords} entry or an issue reference — a
13
+ * total claim rather than a spot check, so a word added later fails rather than
14
+ * slipping in.
15
+ *
16
+ * ## The default word order is a DEFAULT
17
+ *
18
+ * The statement is drawn subject · phrase · object, which is a word order and
19
+ * therefore a presentation choice this layer is not entitled to impose. It is
20
+ * offered as the ordinary case and bypassed the same way the re-evaluate
21
+ * summary's is: {@link ./view.ts PickerView} carries the ordered pair and the
22
+ * kind and no order beyond that, so a host that needs another arrangement
23
+ * renders the view model. Nothing here is load-bearing for it.
24
+ *
25
+ * ## It publishes commands and wires nothing
26
+ *
27
+ * `data-ig-command="retype"` with a `data-ig-kind`, and
28
+ * `data-ig-command="flip"`. Listener wiring, focus and dispatch belong to the
29
+ * mount, exactly as the scale ladder and the re-evaluate surface already defer
30
+ * them — and the proposal each command stands for is on the view model, so a
31
+ * mount reads it rather than reconstructing it from attributes.
32
+ *
33
+ * ## It names itself without claiming a heading level
34
+ *
35
+ * The label is a paragraph, and the list carries the same words as its
36
+ * `aria-label`. A heading would be a claim about the HOST's document outline —
37
+ * whether this picker is a section of something, and at what depth — which a
38
+ * package rendered into an unknown page cannot make. A host that wants it in
39
+ * the outline wraps it in a heading of its own choosing.
40
+ */
41
+ import { type Theme } from '@issuegraph/viewer';
42
+ import type { EdgeId, GraphDocument } from '@issuegraph/store';
43
+ import { type PickerView } from './view.ts';
44
+ import type { PickerWords } from './words.ts';
45
+ export interface PickerOptions {
46
+ /**
47
+ * The words. Required — see {@link PickerWords} for why this package will not
48
+ * invent them.
49
+ */
50
+ readonly words: PickerWords;
51
+ readonly theme?: Theme | undefined;
52
+ /** The selector the theme's custom properties are written onto. */
53
+ readonly themeSelector?: string | undefined;
54
+ }
55
+ export interface PickerResult {
56
+ readonly view: PickerView;
57
+ /** The picker, the statement and the flip control, under one root. */
58
+ readonly markup: string;
59
+ /**
60
+ * The theme and this surface's own stylesheet. Install both.
61
+ *
62
+ * The VIEWER's stylesheet is deliberately not among them, which is where the
63
+ * scale ladder and the re-evaluate surface differ — both draw viewer elements
64
+ * and so must ship the rules for them. This surface draws none, so including
65
+ * them would ship a sheet whose every selector matches nothing here, and a
66
+ * host that installs the picker alone would take the whole canvas stylesheet
67
+ * with it. A host drawing a viewer installs the viewer's own.
68
+ */
69
+ readonly styles: string;
70
+ readonly diagnostics: readonly string[];
71
+ }
72
+ /**
73
+ * The picker for one existing edge.
74
+ *
75
+ * Takes a DOCUMENT and an edge id — never a store and never a source. A host
76
+ * hands the proposals on {@link PickerResult.view} to `Store.propose`, which is
77
+ * the only thing in the family that dispatches.
78
+ */
79
+ export declare function renderPicker(document: GraphDocument, edgeId: EdgeId, options: PickerOptions): PickerResult;
80
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/picker/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAEL,KAAK,KAAK,EAKX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,EAA4C,KAAK,UAAU,EAAc,MAAM,WAAW,CAAC;AAElG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAiDD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,aAAa,GACrB,YAAY,CAiCd"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The type picker, the direction statement and the flip control, as markup.
3
+ *
4
+ * ## Every readable byte comes from the host or from the document
5
+ *
6
+ * This surface renders no word of its own — not in a text node, and not in an
7
+ * `aria-label`. That is stricter than the rest of this package (the scale
8
+ * ladder writes its own chip labels), and it is stricter deliberately: what is
9
+ * being worded here is a RELATIONSHIP, so a phrase written in would be an
10
+ * English clause in the one place the design says a reader most often gets the
11
+ * encoding wrong. `render.test.ts` enforces it by asserting that every text node
12
+ * is either a {@link ./words.ts PickerWords} entry or an issue reference — a
13
+ * total claim rather than a spot check, so a word added later fails rather than
14
+ * slipping in.
15
+ *
16
+ * ## The default word order is a DEFAULT
17
+ *
18
+ * The statement is drawn subject · phrase · object, which is a word order and
19
+ * therefore a presentation choice this layer is not entitled to impose. It is
20
+ * offered as the ordinary case and bypassed the same way the re-evaluate
21
+ * summary's is: {@link ./view.ts PickerView} carries the ordered pair and the
22
+ * kind and no order beyond that, so a host that needs another arrangement
23
+ * renders the view model. Nothing here is load-bearing for it.
24
+ *
25
+ * ## It publishes commands and wires nothing
26
+ *
27
+ * `data-ig-command="retype"` with a `data-ig-kind`, and
28
+ * `data-ig-command="flip"`. Listener wiring, focus and dispatch belong to the
29
+ * mount, exactly as the scale ladder and the re-evaluate surface already defer
30
+ * them — and the proposal each command stands for is on the view model, so a
31
+ * mount reads it rather than reconstructing it from attributes.
32
+ *
33
+ * ## It names itself without claiming a heading level
34
+ *
35
+ * The label is a paragraph, and the list carries the same words as its
36
+ * `aria-label`. A heading would be a claim about the HOST's document outline —
37
+ * whether this picker is a section of something, and at what depth — which a
38
+ * package rendered into an unknown page cannot make. A host that wants it in
39
+ * the outline wraps it in a heading of its own choosing.
40
+ */
41
+ import { element, renderMarkup, resolveTheme, themeCss, } from '@issuegraph/viewer';
42
+ import { pickerView } from "./view.js";
43
+ import { pickerStylesheet } from "./styles.js";
44
+ /** One row of the picker. The phrase is the host's; everything else is data. */
45
+ function optionSpec(option, words) {
46
+ return element('li', {
47
+ class: 'ig-picker-kind',
48
+ 'data-ig-kind': option.kind,
49
+ 'data-directed': option.directed ? 'true' : 'false',
50
+ 'data-current': option.current ? 'true' : 'false',
51
+ }, [
52
+ element('button', {
53
+ type: 'button',
54
+ class: 'ig-picker-choice',
55
+ 'data-ig-command': 'retype',
56
+ 'data-ig-kind': option.kind,
57
+ // A radio group's checked member, spelled for assistive technology.
58
+ // The current kind is still OFFERED — hiding it would be a second
59
+ // validity rule out here, and the store already refuses the edit.
60
+ 'aria-pressed': option.current ? 'true' : 'false',
61
+ }, [
62
+ element('span', { class: 'ig-picker-phrase' }, [words.kinds[option.kind]]),
63
+ option.current ? element('span', { class: 'ig-picker-current' }, [words.current]) : null,
64
+ ]),
65
+ ]);
66
+ }
67
+ /**
68
+ * The direction statement.
69
+ *
70
+ * The two references are the DOCUMENT's, not this package's words, and each
71
+ * carries the role it plays so a host can restyle or reorder them without
72
+ * parsing the sentence back apart.
73
+ */
74
+ function directionSpec(direction, words) {
75
+ return element('p', { class: 'ig-picker-direction', 'data-ig-kind': direction.kind }, [
76
+ element('span', { class: 'ig-picker-ref', 'data-ig-role': 'from' }, [direction.from]),
77
+ element('span', { class: 'ig-picker-phrase' }, [words.kinds[direction.kind]]),
78
+ element('span', { class: 'ig-picker-ref', 'data-ig-role': 'to' }, [direction.to]),
79
+ ]);
80
+ }
81
+ /**
82
+ * The picker for one existing edge.
83
+ *
84
+ * Takes a DOCUMENT and an edge id — never a store and never a source. A host
85
+ * hands the proposals on {@link PickerResult.view} to `Store.propose`, which is
86
+ * the only thing in the family that dispatches.
87
+ */
88
+ export function renderPicker(document, edgeId, options) {
89
+ const view = pickerView(document, edgeId);
90
+ const theme = resolveTheme(options.theme);
91
+ const { words } = options;
92
+ const root = element('section', { class: 'ig-picker', 'data-ig-edge': view.edgeId }, [
93
+ element('p', { class: 'ig-picker-heading' }, [words.heading]),
94
+ view.options.length === 0
95
+ ? null
96
+ : element('ul', { class: 'ig-picker-kinds', 'aria-label': words.heading }, view.options.map((option) => optionSpec(option, words))),
97
+ // Symmetric kinds render NEITHER of the next two. The absence is the
98
+ // finding: a control to reverse a relationship that carries no direction
99
+ // would claim the format says something it does not.
100
+ view.direction === null ? null : directionSpec(view.direction, words),
101
+ view.flip === null
102
+ ? null
103
+ : element('button', { type: 'button', class: 'ig-picker-flip', 'data-ig-command': 'flip' }, [words.flip]),
104
+ ]);
105
+ return {
106
+ view,
107
+ markup: renderMarkup(root),
108
+ styles: `${themeCss(theme, options.themeSelector ?? ':root')}\n${pickerStylesheet}`,
109
+ diagnostics: [...view.diagnostics],
110
+ };
111
+ }
112
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/picker/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The picker's structural stylesheet.
3
+ *
4
+ * Same contract as the ladder's and the overlay's: layout, weight and state,
5
+ * never a value. Every colour, length and font is a var(--ig-...) the host's
6
+ * theme resolves, and styles.test.ts scans these bytes for a literal colour, a
7
+ * fixed length, and a token the theme does not define — so the rule is enforced
8
+ * rather than remembered.
9
+ *
10
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
11
+ * viewer's own classes; this file styles only what the picker introduces.
12
+ *
13
+ * ## The statement is laid out as a FLEX ROW, and that is load-bearing
14
+ *
15
+ * render.ts draws the statement subject, phrase, object, and calls that word
16
+ * order a default a host may bypass. A flex row is what makes the bypass cheap:
17
+ * the CSS order property reorders flex children, so a host whose language puts
18
+ * the relationship elsewhere restyles rather than re-implements. Laying it out
19
+ * as inline text would have made the claim in render.ts true only in principle.
20
+ *
21
+ * Shipped as a string for the same reason the viewer's is: an entry that
22
+ * imports CSS cannot be loaded by a bare Node runtime, and a string needs no
23
+ * bundler.
24
+ */
25
+ export declare const pickerStylesheet = "\n.ig-picker {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n line-height: var(--ig-line-height);\n color: var(--ig-text-body);\n}\n\n.ig-picker-heading {\n margin: 0;\n font-weight: normal;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n.ig-picker-kinds {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-picker-choice {\n display: flex;\n align-items: baseline;\n gap: var(--ig-space-tight);\n width: 100%;\n text-align: left;\n background: var(--ig-surface);\n color: var(--ig-text-body);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n.ig-picker-choice:focus-visible,\n.ig-picker-flip:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-stroke);\n}\n\n/* The kind the edge already carries. Marked on the ROW rather than on the\n button, so a host restyling the control keeps the distinction. */\n.ig-picker-kind[data-current='true'] .ig-picker-choice {\n background: var(--ig-surface-2);\n border-color: var(--ig-accent);\n color: var(--ig-text);\n}\n\n.ig-picker-current {\n margin-left: auto;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n/* Wraps rather than truncates: a qualified reference is long, and a statement\n whose object is cut off says something other than what the format holds. */\n.ig-picker-direction {\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--ig-space-tight);\n color: var(--ig-text-body);\n}\n\n.ig-picker-ref {\n font-family: var(--ig-font-mono);\n color: var(--ig-accent);\n}\n\n.ig-picker-flip {\n align-self: flex-start;\n background: var(--ig-surface);\n color: var(--ig-text-body);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: inherit;\n font-size: var(--ig-font-size-small);\n line-height: inherit;\n}\n";
26
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/picker/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,gBAAgB,ktEA0F5B,CAAC"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The picker's structural stylesheet.
3
+ *
4
+ * Same contract as the ladder's and the overlay's: layout, weight and state,
5
+ * never a value. Every colour, length and font is a var(--ig-...) the host's
6
+ * theme resolves, and styles.test.ts scans these bytes for a literal colour, a
7
+ * fixed length, and a token the theme does not define — so the rule is enforced
8
+ * rather than remembered.
9
+ *
10
+ * IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
11
+ * viewer's own classes; this file styles only what the picker introduces.
12
+ *
13
+ * ## The statement is laid out as a FLEX ROW, and that is load-bearing
14
+ *
15
+ * render.ts draws the statement subject, phrase, object, and calls that word
16
+ * order a default a host may bypass. A flex row is what makes the bypass cheap:
17
+ * the CSS order property reorders flex children, so a host whose language puts
18
+ * the relationship elsewhere restyles rather than re-implements. Laying it out
19
+ * as inline text would have made the claim in render.ts true only in principle.
20
+ *
21
+ * Shipped as a string for the same reason the viewer's is: an entry that
22
+ * imports CSS cannot be loaded by a bare Node runtime, and a string needs no
23
+ * bundler.
24
+ */
25
+ export const pickerStylesheet = `
26
+ .ig-picker {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--ig-space);
30
+ font-family: var(--ig-font-ui);
31
+ font-size: var(--ig-font-size);
32
+ line-height: var(--ig-line-height);
33
+ color: var(--ig-text-body);
34
+ }
35
+
36
+ .ig-picker-heading {
37
+ margin: 0;
38
+ font-weight: normal;
39
+ font-size: var(--ig-font-size-small);
40
+ color: var(--ig-text-muted);
41
+ }
42
+
43
+ .ig-picker-kinds {
44
+ list-style: none;
45
+ margin: 0;
46
+ padding: 0;
47
+ display: flex;
48
+ flex-direction: column;
49
+ gap: var(--ig-space-tight);
50
+ }
51
+
52
+ .ig-picker-choice {
53
+ display: flex;
54
+ align-items: baseline;
55
+ gap: var(--ig-space-tight);
56
+ width: 100%;
57
+ text-align: left;
58
+ background: var(--ig-surface);
59
+ color: var(--ig-text-body);
60
+ border: var(--ig-stroke) solid var(--ig-line);
61
+ border-radius: var(--ig-radius);
62
+ padding: var(--ig-space-tight);
63
+ font-family: inherit;
64
+ font-size: inherit;
65
+ line-height: inherit;
66
+ }
67
+
68
+ .ig-picker-choice:focus-visible,
69
+ .ig-picker-flip:focus-visible {
70
+ outline: var(--ig-focus-ring) solid var(--ig-focus);
71
+ outline-offset: var(--ig-stroke);
72
+ }
73
+
74
+ /* The kind the edge already carries. Marked on the ROW rather than on the
75
+ button, so a host restyling the control keeps the distinction. */
76
+ .ig-picker-kind[data-current='true'] .ig-picker-choice {
77
+ background: var(--ig-surface-2);
78
+ border-color: var(--ig-accent);
79
+ color: var(--ig-text);
80
+ }
81
+
82
+ .ig-picker-current {
83
+ margin-left: auto;
84
+ font-size: var(--ig-font-size-small);
85
+ color: var(--ig-text-muted);
86
+ }
87
+
88
+ /* Wraps rather than truncates: a qualified reference is long, and a statement
89
+ whose object is cut off says something other than what the format holds. */
90
+ .ig-picker-direction {
91
+ margin: 0;
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ align-items: baseline;
95
+ gap: var(--ig-space-tight);
96
+ color: var(--ig-text-body);
97
+ }
98
+
99
+ .ig-picker-ref {
100
+ font-family: var(--ig-font-mono);
101
+ color: var(--ig-accent);
102
+ }
103
+
104
+ .ig-picker-flip {
105
+ align-self: flex-start;
106
+ background: var(--ig-surface);
107
+ color: var(--ig-text-body);
108
+ border: var(--ig-stroke) solid var(--ig-line);
109
+ border-radius: var(--ig-radius);
110
+ padding: var(--ig-space-tight);
111
+ font-family: inherit;
112
+ font-size: var(--ig-font-size-small);
113
+ line-height: inherit;
114
+ }
115
+ `;
116
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/picker/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0F/B,CAAC"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The type picker, as a view model: which kinds an edge can be retyped to,
3
+ * which way round it currently reads, and the single proposal each act emits.
4
+ *
5
+ * ## One user act, one `Proposal`
6
+ *
7
+ * `@issuegraph/store` closed the operation set at four — `create`, `delete`,
8
+ * `retype`, `flip` — and `mutation.ts` says why in as many words: `retype` and
9
+ * `flip` "are their own operations rather than a delete followed by a create,
10
+ * because the design requires a retype to be one operation, one round trip and
11
+ * one undo entry". This module is the surface that emits them, so its whole job
12
+ * is to hand out ONE proposal per affordance. Composing two here would undo the
13
+ * property the store went out of its way to model.
14
+ *
15
+ * ## Directedness is READ, never listed
16
+ *
17
+ * §17b: a directed type renders as a sentence with an explicit flip control,
18
+ * "because 'which way round' is the most common encoding mistake"; a symmetric
19
+ * type shows no flip. Which kinds are which is a fact about the FORMAT, and
20
+ * `@issuegraph/core` already owns it — `isSymmetricEdgeField` over
21
+ * `EDGE_FIELDS`. So this module imports it rather than restating the split.
22
+ *
23
+ * That is why `@issuegraph/core` is a dependency of this package and not only
24
+ * of its two siblings. Core is the layer both the viewer and the store already
25
+ * sit on, and the seam rule in `eslint.config.mjs` refuses a sibling's SUBPATH,
26
+ * never its bare specifier — reaching for the shared foundation is not reaching
27
+ * past a surface. The rejected alternative was a local
28
+ * `const DIRECTED = ['blocked-by', …]`, which is the drifting second
29
+ * implementation the package family removes everywhere else it appears: a sixth
30
+ * field added to the format would leave it silently wrong, and nothing would
31
+ * fail.
32
+ *
33
+ * ## It offers the kind the edge already has, on purpose
34
+ *
35
+ * A picker that hid the current kind would be a SECOND validity rule living out
36
+ * here, and the store already refuses that edit — `structuralRefusal` answers
37
+ * `unchanged-kind` and the record never reaches a `DataSource`. So the option
38
+ * is offered and marked {@link KindOption.current}, which is what a radio group
39
+ * needs anyway: the current value is the checked one. Validity stays in one
40
+ * place, and this module stays presentation.
41
+ *
42
+ * ## Retyping ACROSS the split keeps the pair, and that is a decision
43
+ *
44
+ * A `serialize-with` edge retyped to `blocked-by` becomes directed, and the
45
+ * direction it lands with is the carrier pair the format already recorded —
46
+ * `StoredEdge` keeps that pair even for a symmetric kind, precisely so an
47
+ * editor knows which issue carries the field. Nothing here infers a direction;
48
+ * it carries the one that was already written down.
49
+ *
50
+ * The rejected shape was a per-option PREVIEW: every option carrying the
51
+ * statement it would produce, so the direction is chosen at the same moment as
52
+ * the kind. It was rejected because it doubles what the picker says in order to
53
+ * pre-empt a case the surface already answers — the retype lands, the picker
54
+ * re-derives from the new document, and the direction is then STATED with a
55
+ * flip beside it, one act from correct. §17b's rule is that direction is stated
56
+ * rather than inferred, and it is stated. Revisit this if the flip turns out to
57
+ * be reached often enough to be the real cost.
58
+ *
59
+ * ## It writes no words and holds no state
60
+ *
61
+ * {@link DirectionStatement} is the ordered pair and the kind. The sentence is
62
+ * the host's, for the same reason `change.ts` ships counts rather than prose and
63
+ * `overlay/grammar.ts` ships an `InvalidCode` rather than a message: a default
64
+ * sentence here would be a language choice a consumer could not theme away.
65
+ */
66
+ import { type EdgeId, type EdgeKind, type GraphDocument, type IssueRef, type Proposal } from '@issuegraph/store';
67
+ /**
68
+ * Which way round a relationship reads, as data.
69
+ *
70
+ * `from` and `to` are the edge's own pair, which for a directed kind IS the
71
+ * reading order — `StoredEdge` keeps "A blocked-by B" as `from: A`, `to: B`.
72
+ * The kind travels with them because a pair alone cannot be worded.
73
+ *
74
+ * There is deliberately no `text`. A host renders "#530 is blocked by #602" in
75
+ * its own language and its own word order; see {@link ./words.ts PickerWords}.
76
+ */
77
+ export interface DirectionStatement {
78
+ readonly kind: EdgeKind;
79
+ readonly from: IssueRef;
80
+ readonly to: IssueRef;
81
+ }
82
+ /** One entry in the type picker. */
83
+ export interface KindOption {
84
+ readonly kind: EdgeKind;
85
+ /**
86
+ * Whether this kind carries a direction, read from the format rather than
87
+ * decided here. A host uses it to word the option; the picker uses the
88
+ * TARGET's directedness to decide whether a flip control exists at all.
89
+ */
90
+ readonly directed: boolean;
91
+ /**
92
+ * The kind the edge already carries. Presentation only — the store owns
93
+ * whether choosing it is a valid edit, and it refuses it as `unchanged-kind`.
94
+ */
95
+ readonly current: boolean;
96
+ /** The one proposal choosing this option emits. Never two. */
97
+ readonly proposal: Proposal;
98
+ }
99
+ /**
100
+ * The control that reverses a directed relationship.
101
+ *
102
+ * `null` on a symmetric kind — not "present but disabled". A symmetric edge has
103
+ * no direction to reverse, so a control for it is a claim the format does not
104
+ * make, and the store would refuse the edit as `symmetric-edge` anyway.
105
+ */
106
+ export interface FlipControl {
107
+ /** What the relationship would read as afterwards. The reverse pair. */
108
+ readonly reversed: DirectionStatement;
109
+ /** Exactly one proposal. `flip` is an operation, not a delete plus a create. */
110
+ readonly proposal: Proposal;
111
+ }
112
+ export interface PickerView {
113
+ /** The edge this picker was opened on, whether or not it was found. */
114
+ readonly edgeId: EdgeId;
115
+ /** Every kind in the format's order. Empty only when the edge is gone. */
116
+ readonly options: readonly KindOption[];
117
+ /** `null` for a symmetric kind: there is no direction to state. */
118
+ readonly direction: DirectionStatement | null;
119
+ /** `null` exactly when {@link direction} is. The two travel together. */
120
+ readonly flip: FlipControl | null;
121
+ readonly diagnostics: readonly string[];
122
+ }
123
+ /**
124
+ * The picker for one existing edge.
125
+ *
126
+ * Pure and synchronous, and it takes a document rather than a store — which is
127
+ * what makes "emits proposals, never touches a `DataSource`" structural. There
128
+ * is no port in scope here to touch. A host hands the proposals it gets back to
129
+ * `Store.propose`, which is the only thing that dispatches.
130
+ *
131
+ * The create path is not here. It lands with the change that assembles the
132
+ * workspace, alongside the other affordances `index.ts` defers.
133
+ */
134
+ export declare function pickerView(document: GraphDocument, edgeId: EdgeId): PickerView;
135
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/picker/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAGH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,QAAQ,EAEd,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACvB;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAwB9E"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The type picker, as a view model: which kinds an edge can be retyped to,
3
+ * which way round it currently reads, and the single proposal each act emits.
4
+ *
5
+ * ## One user act, one `Proposal`
6
+ *
7
+ * `@issuegraph/store` closed the operation set at four — `create`, `delete`,
8
+ * `retype`, `flip` — and `mutation.ts` says why in as many words: `retype` and
9
+ * `flip` "are their own operations rather than a delete followed by a create,
10
+ * because the design requires a retype to be one operation, one round trip and
11
+ * one undo entry". This module is the surface that emits them, so its whole job
12
+ * is to hand out ONE proposal per affordance. Composing two here would undo the
13
+ * property the store went out of its way to model.
14
+ *
15
+ * ## Directedness is READ, never listed
16
+ *
17
+ * §17b: a directed type renders as a sentence with an explicit flip control,
18
+ * "because 'which way round' is the most common encoding mistake"; a symmetric
19
+ * type shows no flip. Which kinds are which is a fact about the FORMAT, and
20
+ * `@issuegraph/core` already owns it — `isSymmetricEdgeField` over
21
+ * `EDGE_FIELDS`. So this module imports it rather than restating the split.
22
+ *
23
+ * That is why `@issuegraph/core` is a dependency of this package and not only
24
+ * of its two siblings. Core is the layer both the viewer and the store already
25
+ * sit on, and the seam rule in `eslint.config.mjs` refuses a sibling's SUBPATH,
26
+ * never its bare specifier — reaching for the shared foundation is not reaching
27
+ * past a surface. The rejected alternative was a local
28
+ * `const DIRECTED = ['blocked-by', …]`, which is the drifting second
29
+ * implementation the package family removes everywhere else it appears: a sixth
30
+ * field added to the format would leave it silently wrong, and nothing would
31
+ * fail.
32
+ *
33
+ * ## It offers the kind the edge already has, on purpose
34
+ *
35
+ * A picker that hid the current kind would be a SECOND validity rule living out
36
+ * here, and the store already refuses that edit — `structuralRefusal` answers
37
+ * `unchanged-kind` and the record never reaches a `DataSource`. So the option
38
+ * is offered and marked {@link KindOption.current}, which is what a radio group
39
+ * needs anyway: the current value is the checked one. Validity stays in one
40
+ * place, and this module stays presentation.
41
+ *
42
+ * ## Retyping ACROSS the split keeps the pair, and that is a decision
43
+ *
44
+ * A `serialize-with` edge retyped to `blocked-by` becomes directed, and the
45
+ * direction it lands with is the carrier pair the format already recorded —
46
+ * `StoredEdge` keeps that pair even for a symmetric kind, precisely so an
47
+ * editor knows which issue carries the field. Nothing here infers a direction;
48
+ * it carries the one that was already written down.
49
+ *
50
+ * The rejected shape was a per-option PREVIEW: every option carrying the
51
+ * statement it would produce, so the direction is chosen at the same moment as
52
+ * the kind. It was rejected because it doubles what the picker says in order to
53
+ * pre-empt a case the surface already answers — the retype lands, the picker
54
+ * re-derives from the new document, and the direction is then STATED with a
55
+ * flip beside it, one act from correct. §17b's rule is that direction is stated
56
+ * rather than inferred, and it is stated. Revisit this if the flip turns out to
57
+ * be reached often enough to be the real cost.
58
+ *
59
+ * ## It writes no words and holds no state
60
+ *
61
+ * {@link DirectionStatement} is the ordered pair and the kind. The sentence is
62
+ * the host's, for the same reason `change.ts` ships counts rather than prose and
63
+ * `overlay/grammar.ts` ships an `InvalidCode` rather than a message: a default
64
+ * sentence here would be a language choice a consumer could not theme away.
65
+ */
66
+ import { EDGE_FIELDS, isSymmetricEdgeField } from '@issuegraph/core';
67
+ import { findEdge, } from '@issuegraph/store';
68
+ /** An empty picker, for a target that is not in the document. */
69
+ function nothingToPick(edgeId) {
70
+ return {
71
+ edgeId,
72
+ options: [],
73
+ direction: null,
74
+ flip: null,
75
+ // REPORTED RATHER THAN DRAWN AS AN EMPTY PICKER. A picker with no options
76
+ // and no reason looks like a kind vocabulary that came back empty, and the
77
+ // false conclusion is "this edge can be nothing" rather than "this edge is
78
+ // no longer here". The state is reachable in ordinary use: a sibling write
79
+ // can land a delete of the edge while its picker is open.
80
+ diagnostics: [`${edgeId} is no longer an edge in this document; there is nothing to retype`],
81
+ };
82
+ }
83
+ /**
84
+ * The picker for one existing edge.
85
+ *
86
+ * Pure and synchronous, and it takes a document rather than a store — which is
87
+ * what makes "emits proposals, never touches a `DataSource`" structural. There
88
+ * is no port in scope here to touch. A host hands the proposals it gets back to
89
+ * `Store.propose`, which is the only thing that dispatches.
90
+ *
91
+ * The create path is not here. It lands with the change that assembles the
92
+ * workspace, alongside the other affordances `index.ts` defers.
93
+ */
94
+ export function pickerView(document, edgeId) {
95
+ const edge = findEdge(document, edgeId);
96
+ if (edge === undefined)
97
+ return nothingToPick(edgeId);
98
+ const options = EDGE_FIELDS.map((kind) => {
99
+ const proposal = { op: 'retype', edgeId: edge.id, nextKind: kind };
100
+ return { kind, directed: !isSymmetricEdgeField(kind), current: kind === edge.kind, proposal };
101
+ });
102
+ if (isSymmetricEdgeField(edge.kind)) {
103
+ return { edgeId, options, direction: null, flip: null, diagnostics: [] };
104
+ }
105
+ const proposal = { op: 'flip', edgeId: edge.id };
106
+ return {
107
+ edgeId,
108
+ options,
109
+ direction: { kind: edge.kind, from: edge.from, to: edge.to },
110
+ flip: {
111
+ reversed: { kind: edge.kind, from: edge.to, to: edge.from },
112
+ proposal,
113
+ },
114
+ diagnostics: [],
115
+ };
116
+ }
117
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/picker/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAML,QAAQ,GACT,MAAM,mBAAmB,CAAC;AA8D3B,iEAAiE;AACjE,SAAS,aAAa,CAAC,MAAc;IACnC,OAAO;QACL,MAAM;QACN,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,IAAI;QACV,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,0DAA0D;QAC1D,WAAW,EAAE,CAAC,GAAG,MAAM,oEAAoE,CAAC;KAC7F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,QAAuB,EAAE,MAAc;IAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAc,EAAE;QACnD,MAAM,QAAQ,GAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC7E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IAC3D,OAAO;QACL,MAAM;QACN,OAAO;QACP,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;QAC5D,IAAI,EAAE;YACJ,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE;YAC3D,QAAQ;SACT;QACD,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC"}