@sagmans/dsh-tui 0.1.2 → 0.3.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 (158) hide show
  1. package/README.md +196 -17
  2. package/cordis.patch.yml +4 -2
  3. package/lib/agent/host.d.ts +14 -0
  4. package/lib/agent/host.d.ts.map +1 -1
  5. package/lib/agent/host.js +20 -4
  6. package/lib/agent/host.js.map +1 -1
  7. package/lib/agent/model.d.ts +50 -0
  8. package/lib/agent/model.d.ts.map +1 -1
  9. package/lib/agent/model.js +87 -1
  10. package/lib/agent/model.js.map +1 -1
  11. package/lib/agent/present.d.ts +7 -1
  12. package/lib/agent/present.d.ts.map +1 -1
  13. package/lib/agent/present.js +33 -4
  14. package/lib/agent/present.js.map +1 -1
  15. package/lib/agent/presets.d.ts +7 -1
  16. package/lib/agent/presets.d.ts.map +1 -1
  17. package/lib/agent/presets.js +7 -3
  18. package/lib/agent/presets.js.map +1 -1
  19. package/lib/agent/status.d.ts +10 -0
  20. package/lib/agent/status.d.ts.map +1 -1
  21. package/lib/agent/status.js +2 -0
  22. package/lib/agent/status.js.map +1 -1
  23. package/lib/cards.d.ts +127 -7
  24. package/lib/cards.d.ts.map +1 -1
  25. package/lib/cards.js +381 -52
  26. package/lib/cards.js.map +1 -1
  27. package/lib/export.d.ts +5 -4
  28. package/lib/export.d.ts.map +1 -1
  29. package/lib/export.js +62 -13
  30. package/lib/export.js.map +1 -1
  31. package/lib/fold-cursor.d.ts +23 -0
  32. package/lib/fold-cursor.d.ts.map +1 -0
  33. package/lib/fold-cursor.js +31 -0
  34. package/lib/fold-cursor.js.map +1 -0
  35. package/lib/gates.d.ts +145 -2
  36. package/lib/gates.d.ts.map +1 -1
  37. package/lib/gates.js +335 -40
  38. package/lib/gates.js.map +1 -1
  39. package/lib/index.d.ts +7 -1
  40. package/lib/index.d.ts.map +1 -1
  41. package/lib/index.js +651 -92
  42. package/lib/index.js.map +1 -1
  43. package/lib/input/actions.d.ts +136 -0
  44. package/lib/input/actions.d.ts.map +1 -0
  45. package/lib/input/actions.js +740 -0
  46. package/lib/input/actions.js.map +1 -0
  47. package/lib/input/keymap-settings.d.ts +21 -0
  48. package/lib/input/keymap-settings.d.ts.map +1 -0
  49. package/lib/input/keymap-settings.js +28 -0
  50. package/lib/input/keymap-settings.js.map +1 -0
  51. package/lib/input/keymap.d.ts +91 -0
  52. package/lib/input/keymap.d.ts.map +1 -0
  53. package/lib/input/keymap.js +170 -0
  54. package/lib/input/keymap.js.map +1 -0
  55. package/lib/input/match.d.ts +10 -0
  56. package/lib/input/match.d.ts.map +1 -0
  57. package/lib/input/match.js +83 -0
  58. package/lib/input/match.js.map +1 -0
  59. package/lib/input/submission.d.ts +8 -1
  60. package/lib/input/submission.d.ts.map +1 -1
  61. package/lib/input/submission.js +9 -2
  62. package/lib/input/submission.js.map +1 -1
  63. package/lib/input.d.ts +17 -0
  64. package/lib/input.d.ts.map +1 -0
  65. package/lib/input.js +27 -0
  66. package/lib/input.js.map +1 -0
  67. package/lib/keys-command.d.ts +16 -0
  68. package/lib/keys-command.d.ts.map +1 -0
  69. package/lib/keys-command.js +60 -0
  70. package/lib/keys-command.js.map +1 -0
  71. package/lib/queue.d.ts +6 -0
  72. package/lib/queue.d.ts.map +1 -0
  73. package/lib/queue.js +68 -0
  74. package/lib/queue.js.map +1 -0
  75. package/lib/settings-notice.d.ts +19 -0
  76. package/lib/settings-notice.d.ts.map +1 -0
  77. package/lib/settings-notice.js +30 -0
  78. package/lib/settings-notice.js.map +1 -0
  79. package/lib/terminal/warning-screen.d.ts +8 -0
  80. package/lib/terminal/warning-screen.d.ts.map +1 -0
  81. package/lib/terminal/warning-screen.js +66 -0
  82. package/lib/terminal/warning-screen.js.map +1 -0
  83. package/lib/theme-capability.d.ts +36 -0
  84. package/lib/theme-capability.d.ts.map +1 -0
  85. package/lib/theme-capability.js +190 -0
  86. package/lib/theme-capability.js.map +1 -0
  87. package/lib/theme-command.d.ts +10 -0
  88. package/lib/theme-command.d.ts.map +1 -0
  89. package/lib/theme-command.js +61 -0
  90. package/lib/theme-command.js.map +1 -0
  91. package/lib/theme-settings.d.ts +196 -0
  92. package/lib/theme-settings.d.ts.map +1 -0
  93. package/lib/theme-settings.js +229 -0
  94. package/lib/theme-settings.js.map +1 -0
  95. package/lib/theme-tokens.d.ts +110 -0
  96. package/lib/theme-tokens.d.ts.map +1 -0
  97. package/lib/theme-tokens.js +427 -0
  98. package/lib/theme-tokens.js.map +1 -0
  99. package/lib/theme.d.ts +32 -35
  100. package/lib/theme.d.ts.map +1 -1
  101. package/lib/theme.js +112 -63
  102. package/lib/theme.js.map +1 -1
  103. package/lib/tokens.d.ts +15 -0
  104. package/lib/tokens.d.ts.map +1 -0
  105. package/lib/tokens.js +29 -0
  106. package/lib/tokens.js.map +1 -0
  107. package/lib/transcript.d.ts +26 -1
  108. package/lib/transcript.d.ts.map +1 -1
  109. package/lib/transcript.js +153 -25
  110. package/lib/transcript.js.map +1 -1
  111. package/lib/ui/dock.d.ts.map +1 -1
  112. package/lib/ui/dock.js +67 -28
  113. package/lib/ui/dock.js.map +1 -1
  114. package/lib/ui/editor.d.ts +51 -0
  115. package/lib/ui/editor.d.ts.map +1 -0
  116. package/lib/ui/editor.js +159 -0
  117. package/lib/ui/editor.js.map +1 -0
  118. package/lib/ui/frame.d.ts +54 -0
  119. package/lib/ui/frame.d.ts.map +1 -0
  120. package/lib/ui/frame.js +79 -0
  121. package/lib/ui/frame.js.map +1 -0
  122. package/lib/ui/gate-input.d.ts +20 -0
  123. package/lib/ui/gate-input.d.ts.map +1 -0
  124. package/lib/ui/gate-input.js +96 -0
  125. package/lib/ui/gate-input.js.map +1 -0
  126. package/lib/ui/markdown.d.ts +19 -2
  127. package/lib/ui/markdown.d.ts.map +1 -1
  128. package/lib/ui/markdown.js +37 -6
  129. package/lib/ui/markdown.js.map +1 -1
  130. package/lib/ui/mermaid.d.ts +44 -0
  131. package/lib/ui/mermaid.d.ts.map +1 -0
  132. package/lib/ui/mermaid.js +178 -0
  133. package/lib/ui/mermaid.js.map +1 -0
  134. package/lib/ui/picker.d.ts +66 -7
  135. package/lib/ui/picker.d.ts.map +1 -1
  136. package/lib/ui/picker.js +112 -19
  137. package/lib/ui/picker.js.map +1 -1
  138. package/lib/ui/prompt.d.ts +30 -0
  139. package/lib/ui/prompt.d.ts.map +1 -0
  140. package/lib/ui/prompt.js +45 -0
  141. package/lib/ui/prompt.js.map +1 -0
  142. package/lib/ui/queue.d.ts +24 -0
  143. package/lib/ui/queue.d.ts.map +1 -0
  144. package/lib/ui/queue.js +57 -0
  145. package/lib/ui/queue.js.map +1 -0
  146. package/lib/ui/status.d.ts +7 -1
  147. package/lib/ui/status.d.ts.map +1 -1
  148. package/lib/ui/status.js +84 -24
  149. package/lib/ui/status.js.map +1 -1
  150. package/lib/ui/view.d.ts +65 -2
  151. package/lib/ui/view.d.ts.map +1 -1
  152. package/lib/ui/view.js +319 -59
  153. package/lib/ui/view.js.map +1 -1
  154. package/lib/work.d.ts +48 -0
  155. package/lib/work.d.ts.map +1 -1
  156. package/lib/work.js +44 -0
  157. package/lib/work.js.map +1 -1
  158. package/package.json +5 -2
@@ -0,0 +1,159 @@
1
+ import { Editor, isKittyProtocolActive, matchesKey, stripTerminalSequences, } from '@earendil-works/pi-tui';
2
+ import { ENTER_KEY, defaultKeymap } from "../input/actions.js";
3
+ import { promptKeys } from "../input/keymap.js";
4
+ import { FRAME_COLUMNS, FRAME_GLYPHS, MIN_BOX_WIDTH, PADDING_X } from "./frame.js";
5
+ /**
6
+ * Marks the rule that closes the input while one render is in flight.
7
+ *
8
+ * The base editor paints its completion menu after that rule, and this subclass
9
+ * has to know which rows those are to lift the menu above the box. The base
10
+ * offers no seam for that boundary, so one render pass tags its own closing rule
11
+ * and finds it again. The tag cannot collide with typed text, which the editor
12
+ * strips of control characters, and it never reaches the screen because the same
13
+ * pass removes it.
14
+ */
15
+ const CLOSING_TAG = '\u0000';
16
+ /**
17
+ * The line feed a plain Enter is handed to the base class as.
18
+ *
19
+ * The base owns what a newline does to paste markers, history, and undo, and it
20
+ * reads this byte as one in every terminal mode, including the modes where it
21
+ * no longer calls Enter itself a newline.
22
+ */
23
+ const NEWLINE_BYTE = '\n';
24
+ /** The one sequence a terminal that reports no modifiers sends for alt+enter. */
25
+ const LEGACY_ALT_ENTER = '\u001b\r';
26
+ /**
27
+ * The chords as the keyboard protocol spells them.
28
+ *
29
+ * The base class reads a legacy alt+enter and a bare line feed as line breaks
30
+ * before it looks at any binding, so each press is handed over as the chord the
31
+ * map actually holds for it. The submit path then runs whole, with the same
32
+ * guards a real chord meets, instead of a second sender beside it.
33
+ */
34
+ const KITTY_ALT_ENTER = '\u001b[13;3u';
35
+ const KITTY_CTRL_J = '\u001b[106;5u';
36
+ /**
37
+ * The input bar drawn as a box, with its completion menu above it.
38
+ *
39
+ * The editor already draws the two rules and pads every row to the width it is
40
+ * given, so the frame comes from rendering two columns narrower and wrapping
41
+ * what comes back. The menu is lifted above the box because the reader scans it
42
+ * while writing: a list that grows upward never covers the line being typed.
43
+ */
44
+ export class BoxedEditor extends Editor {
45
+ keymap;
46
+ /** Rows the menu took in the last render; a click there belongs to the list. */
47
+ menuRows = 0;
48
+ /** Rows between the rules in the last render; how the base editor counts its own. */
49
+ textRows = 0;
50
+ /** Whether the last render drew a frame, which is what a click is mapped through. */
51
+ boxed = false;
52
+ constructor(tui, theme, keymap = defaultKeymap) {
53
+ super(tui, theme, { paddingX: PADDING_X });
54
+ this.keymap = keymap;
55
+ }
56
+ /**
57
+ * Read the two presses the base class cannot place on its own.
58
+ *
59
+ * Enter breaks the line while the reader keeps that key for the line, so the
60
+ * press the base would submit with becomes a newline — except while it is
61
+ * picking a completion, the one press that chooses something instead of
62
+ * sending it, and except on a bar a question or a picker has borrowed, whose
63
+ * keys belong to whoever borrowed it.
64
+ */
65
+ handleInput(data) {
66
+ if (this.disableSubmit) {
67
+ super.handleInput(data);
68
+ return;
69
+ }
70
+ const keys = promptKeys(this.keymap());
71
+ // A bare line feed is a line break in the base class whatever the map says,
72
+ // so a reader who moved the line break off ctrl+j and sends with it would
73
+ // keep getting lines. It is read before the Return guard because a terminal
74
+ // without the protocol reports that byte as Return as well — and only
75
+ // without it, because a terminal that speaks the protocol sends the chord
76
+ // itself and reports a line feed for a key the reader meant as a line.
77
+ if (data === NEWLINE_BYTE && !isKittyProtocolActive() && keys.submit.includes('ctrl+j')) {
78
+ super.handleInput(KITTY_CTRL_J);
79
+ return;
80
+ }
81
+ if (keys.enterBreaksLine && matchesKey(data, ENTER_KEY) && !this.isShowingAutocomplete()) {
82
+ super.handleInput(NEWLINE_BYTE);
83
+ return;
84
+ }
85
+ // Only while the terminal cannot tell alt+enter apart from a mapping, and
86
+ // only for the reader who bound that key: with the protocol active the same
87
+ // sequence is the reader's shift+enter, and a bar that answered it as a send
88
+ // because some other chord contains the same bytes would be submitting a key
89
+ // the reader never bound. A reader who keeps the sequence for a line is
90
+ // answered by the library's own rule.
91
+ if (data === LEGACY_ALT_ENTER && !isKittyProtocolActive() && keys.submit.includes('alt+enter')) {
92
+ super.handleInput(KITTY_ALT_ENTER);
93
+ return;
94
+ }
95
+ super.handleInput(data);
96
+ }
97
+ /** Tag the closing rule so {@link render} can find where the box ends. */
98
+ renderBottomBorder(width, hiddenLineCount) {
99
+ return `${super.renderBottomBorder(width, hiddenLineCount)}${CLOSING_TAG}`;
100
+ }
101
+ /** A rule redrawn as a box edge, so the corners carry the border's own colour. */
102
+ edge(open, close, row) {
103
+ return this.borderColor(`${open}${stripTerminalSequences(row)}${close}`);
104
+ }
105
+ /**
106
+ * Rewrite one row of the text before it is framed, so a subclass can hide
107
+ * what it collects without hiding the box the reader types inside.
108
+ */
109
+ decorateText(row) {
110
+ return row;
111
+ }
112
+ render(width) {
113
+ const side = this.borderColor(FRAME_GLYPHS.side);
114
+ // A frame narrower than its own furniture would eat the text it exists to
115
+ // hold, and a hidden border token asks for no frame at all: both cases keep
116
+ // the plain rules rather than reserve columns for what nobody can see.
117
+ if (width < MIN_BOX_WIDTH || side === '') {
118
+ this.boxed = false;
119
+ return super.render(width).map(row => this.decorateText(row));
120
+ }
121
+ const rows = super.render(width - FRAME_COLUMNS);
122
+ const closing = rows.findIndex(row => row.includes(CLOSING_TAG));
123
+ // No tagged rule means the base did not paint the shape this class knows;
124
+ // an unframed render reports that better than a frame drawn on a guess.
125
+ if (closing < 0)
126
+ return rows.map(row => row.replace(CLOSING_TAG, ''));
127
+ const menu = rows.slice(closing + 1);
128
+ const text = rows.slice(1, closing);
129
+ this.menuRows = menu.length;
130
+ this.textRows = text.length;
131
+ this.boxed = true;
132
+ // The menu keeps the page's own width; only its rows moved above the box.
133
+ const lines = menu.map(row => row + ' '.repeat(FRAME_COLUMNS));
134
+ lines.push(this.edge(FRAME_GLYPHS.topLeft, FRAME_GLYPHS.topRight, rows[0] ?? ''));
135
+ for (const row of text)
136
+ lines.push(`${side}${this.decorateText(row)}${side}`);
137
+ lines.push(this.edge(FRAME_GLYPHS.bottomLeft, FRAME_GLYPHS.bottomRight, rows[closing].replace(CLOSING_TAG, '')));
138
+ return lines;
139
+ }
140
+ /**
141
+ * Map a click back through the frame and the lifted menu.
142
+ *
143
+ * The base editor hit-tests the rows it painted in its own order — the input
144
+ * under the top rule, the menu after the bottom one — and measures columns
145
+ * from the edge of the width it was asked for. This subclass draws the menu
146
+ * first and indents every line by the frame, so a click has to be translated
147
+ * back before that hit test can say what it hit.
148
+ */
149
+ handleMouse(event) {
150
+ if (!this.boxed)
151
+ return super.handleMouse(event);
152
+ const width = event.width - FRAME_COLUMNS;
153
+ if (event.y < this.menuRows) {
154
+ return super.handleMouse({ ...event, width, y: event.y + this.textRows + 2 });
155
+ }
156
+ return super.handleMouse({ ...event, width, x: event.x - 1, y: event.y - this.menuRows });
157
+ }
158
+ }
159
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/ui/editor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The frame every prompt is closed into.
3
+ *
4
+ * The editor, the prompts queued above it, and the prompts already submitted
5
+ * draw the same glyphs at the same width, so one prompt reads as the same object
6
+ * through its whole life; keeping the shape here is what stops the three from
7
+ * drifting.
8
+ */
9
+ export declare const FRAME_GLYPHS: {
10
+ readonly topLeft: "╭";
11
+ readonly topRight: "╮";
12
+ readonly bottomLeft: "╰";
13
+ readonly bottomRight: "╯";
14
+ readonly side: "│";
15
+ };
16
+ /** The frame costs one column each side, so the text wraps that much narrower. */
17
+ export declare const FRAME_COLUMNS = 2;
18
+ /** One column of air inside the frame, so a full line never touches the border. */
19
+ export declare const PADDING_X = 1;
20
+ /** A box needs both edges, both paddings, and one column left to type in. */
21
+ export declare const MIN_BOX_WIDTH: number;
22
+ /** The columns the text of a bar owns, given the width of everything inside its frame. */
23
+ export declare function textWidth(innerWidth: number): number;
24
+ /** One text row of a bar: the air, the text, and the rest of the inside width. */
25
+ export declare function textRow(text: string, innerWidth: number): string;
26
+ /**
27
+ * The rule that closes a bar, naming what the bar could not fit.
28
+ *
29
+ * A hidden count is a fact about the text, not extra text, so it rides the rule
30
+ * the way the editor's own scroll indicator does: the same label in the same
31
+ * place, at the same width, so both bars report a fold identically.
32
+ */
33
+ export declare function frameRule(innerWidth: number, hiddenRows: number): string;
34
+ /** How one block of text paints itself inside the bar that carries it. */
35
+ export interface FrameFaces {
36
+ /** Paints one row of the block's own text. */
37
+ readonly text: (line: string) => string;
38
+ /** Paints the frame's rules and its two sides. */
39
+ readonly border: (rule: string) => string;
40
+ /** Whether the frame may be drawn at all. */
41
+ readonly framed: boolean;
42
+ }
43
+ /** Whether a bar has the width, and the visible border, to close a frame. */
44
+ export declare function canFrame(width: number, borderVisible: boolean): boolean;
45
+ /**
46
+ * One block of text as a bar draws it: wrapped to the frame's own width, and
47
+ * closed into it when the frame can be drawn.
48
+ *
49
+ * A block with a limit is a preview, and the rows it drops are counted on the
50
+ * closing rule: a reader who cannot see the whole text still has to see that it
51
+ * continues. A block without a limit is the prompt itself, which is never cut.
52
+ */
53
+ export declare function frameText(text: string, width: number, faces: FrameFaces, limit?: number): string[];
54
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/ui/frame.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY;;;;;;CAAyF,CAAA;AAElH,kFAAkF;AAClF,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,mFAAmF;AACnF,eAAO,MAAM,SAAS,IAAI,CAAA;AAC1B,6EAA6E;AAC7E,eAAO,MAAM,aAAa,QAAoC,CAAA;AAE9D,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAgBxE;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACzC,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,SAA2B,GAAG,MAAM,EAAE,CAYpH"}
@@ -0,0 +1,79 @@
1
+ import { visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
2
+ import { displayText } from "../text.js";
3
+ /**
4
+ * The frame every prompt is closed into.
5
+ *
6
+ * The editor, the prompts queued above it, and the prompts already submitted
7
+ * draw the same glyphs at the same width, so one prompt reads as the same object
8
+ * through its whole life; keeping the shape here is what stops the three from
9
+ * drifting.
10
+ */
11
+ export const FRAME_GLYPHS = { topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯', side: '│' };
12
+ /** The frame costs one column each side, so the text wraps that much narrower. */
13
+ export const FRAME_COLUMNS = 2;
14
+ /** One column of air inside the frame, so a full line never touches the border. */
15
+ export const PADDING_X = 1;
16
+ /** A box needs both edges, both paddings, and one column left to type in. */
17
+ export const MIN_BOX_WIDTH = FRAME_COLUMNS + PADDING_X * 2 + 1;
18
+ /** The columns the text of a bar owns, given the width of everything inside its frame. */
19
+ export function textWidth(innerWidth) {
20
+ return Math.max(1, innerWidth - PADDING_X * 2);
21
+ }
22
+ /** One text row of a bar: the air, the text, and the rest of the inside width. */
23
+ export function textRow(text, innerWidth) {
24
+ const room = Math.max(0, textWidth(innerWidth) - visibleWidth(text));
25
+ return `${' '.repeat(PADDING_X)}${text}${' '.repeat(room)}${' '.repeat(PADDING_X)}`;
26
+ }
27
+ /**
28
+ * The rule that closes a bar, naming what the bar could not fit.
29
+ *
30
+ * A hidden count is a fact about the text, not extra text, so it rides the rule
31
+ * the way the editor's own scroll indicator does: the same label in the same
32
+ * place, at the same width, so both bars report a fold identically.
33
+ */
34
+ export function frameRule(innerWidth, hiddenRows) {
35
+ const width = Math.max(0, innerWidth);
36
+ if (hiddenRows <= 0)
37
+ return '─'.repeat(width);
38
+ const label = ` ↓ ${hiddenRows} more `;
39
+ const labelWidth = visibleWidth(label);
40
+ if (labelWidth + 2 <= width) {
41
+ const left = Math.floor((width - labelWidth) / 2);
42
+ return `${'─'.repeat(left)}${label}${'─'.repeat(width - left - labelWidth)}`;
43
+ }
44
+ // Too narrow for a centred label: name it as far as it fits instead of
45
+ // dropping the fact. Both the label and the fallback are ASCII, so slicing by
46
+ // character is slicing by column here.
47
+ const indicator = `─── ↓ ${hiddenRows} more `;
48
+ if (visibleWidth(indicator) <= width)
49
+ return indicator + '─'.repeat(width - visibleWidth(indicator));
50
+ const ellipsis = '...'.slice(0, width);
51
+ return indicator.slice(0, Math.max(0, width - ellipsis.length)) + ellipsis;
52
+ }
53
+ /** Whether a bar has the width, and the visible border, to close a frame. */
54
+ export function canFrame(width, borderVisible) {
55
+ return borderVisible && width >= MIN_BOX_WIDTH;
56
+ }
57
+ /**
58
+ * One block of text as a bar draws it: wrapped to the frame's own width, and
59
+ * closed into it when the frame can be drawn.
60
+ *
61
+ * A block with a limit is a preview, and the rows it drops are counted on the
62
+ * closing rule: a reader who cannot see the whole text still has to see that it
63
+ * continues. A block without a limit is the prompt itself, which is never cut.
64
+ */
65
+ export function frameText(text, width, faces, limit = Number.POSITIVE_INFINITY) {
66
+ const inside = faces.framed ? width - FRAME_COLUMNS : width;
67
+ const wrapped = wrapTextWithAnsi(displayText(text), textWidth(inside));
68
+ const body = wrapped.slice(0, Math.max(0, limit));
69
+ const rows = body.map(line => textRow(faces.text(line), inside));
70
+ if (!faces.framed)
71
+ return rows;
72
+ const side = faces.border(FRAME_GLYPHS.side);
73
+ return [
74
+ faces.border(`${FRAME_GLYPHS.topLeft}${frameRule(inside, 0)}${FRAME_GLYPHS.topRight}`),
75
+ ...rows.map(row => `${side}${row}${side}`),
76
+ faces.border(`${FRAME_GLYPHS.bottomLeft}${frameRule(inside, wrapped.length - body.length)}${FRAME_GLYPHS.bottomRight}`),
77
+ ];
78
+ }
79
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/ui/frame.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { BoxedEditor } from './editor.ts';
2
+ /** How a question's answer is shown while it is written. */
3
+ export type GateInputMode = 'answer' | 'secret';
4
+ /**
5
+ * The input bar a gate collects an answer with.
6
+ *
7
+ * It is the prompt bar's own component, so an answer is written with the
8
+ * movement, deletion, undo, and paste that bar already has. Its mode decides
9
+ * only how the answer is shown: a question that declares a credential is
10
+ * answered in the same bar with the middle of the value hidden, never in a
11
+ * second widget that would have to grow its own editing.
12
+ */
13
+ export declare class GateInputBar extends BoxedEditor {
14
+ private mode;
15
+ /** Switch what the bar shows without changing a character of what it holds. */
16
+ setMode(mode: GateInputMode): void;
17
+ /** Hide a credential's middle while its owner still recognizes both ends. */
18
+ protected decorateText(row: string): string;
19
+ }
20
+ //# sourceMappingURL=gate-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-input.d.ts","sourceRoot":"","sources":["../../src/ui/gate-input.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAA;AA2E/C;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAC3C,OAAO,CAAC,IAAI,CAA0B;IAEtC,+EAA+E;IAC/E,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAMlC,6EAA6E;cAC1D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAGrD"}
@@ -0,0 +1,96 @@
1
+ import { visibleWidth } from '@earendil-works/pi-tui';
2
+ import { BoxedEditor } from "./editor.js";
3
+ /** One character of the mask, repeated to the width of what it hides. */
4
+ const MASKED = '*';
5
+ /** How much of a credential stays readable at each end, so its owner can recognize it. */
6
+ const MASK_HEAD = 4;
7
+ const MASK_TAIL = 4;
8
+ /**
9
+ * A terminal sequence, so masking can tell a row's styling from its text: the
10
+ * cursor is drawn as an escape around a character, and hiding the character
11
+ * must not hide the mark that says where the cursor is.
12
+ *
13
+ * The string forms carry payloads a terminal reads rather than shows, and a
14
+ * cursor marker is one of them: masking a marker's own text would replace the
15
+ * position the terminal was told to place the cursor at with asterisks.
16
+ */
17
+ const SEQUENCE = /\u001b(?:\[[0-9;?]*[ -/]*[@-~]|[\][P_^X][^\u0007\u001b]*(?:\u0007|\u001b\\)|[@-Z\\-_])/gu;
18
+ const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
19
+ /**
20
+ * One visible grapheme, and where it sits among the row's visible characters.
21
+ *
22
+ * Whitespace is not among them: the editor lays the answer out with spaces that
23
+ * belong to its own drawing, and hiding them would move the text under the
24
+ * cursor rather than hide it.
25
+ */
26
+ function visibleAt(text) {
27
+ const found = [];
28
+ let position = 0;
29
+ for (const { segment } of GRAPHEMES.segment(text)) {
30
+ if (segment.trim() === '') {
31
+ found.push({ segment, position: -1 });
32
+ continue;
33
+ }
34
+ found.push({ segment, position });
35
+ position += 1;
36
+ }
37
+ return found;
38
+ }
39
+ /**
40
+ * Hide the middle of a credential and keep its ends readable.
41
+ *
42
+ * The reader is checking which key they pasted, and a row of asterisks answers
43
+ * nothing; a bystander is not meant to read it either, so only the first and
44
+ * last few characters survive. Rows are masked one at a time because that is
45
+ * how the editor hands them over, and a credential is written on one line.
46
+ */
47
+ function maskCredentialRow(row) {
48
+ const segments = [];
49
+ let last = 0;
50
+ for (const match of row.matchAll(SEQUENCE)) {
51
+ segments.push({ escape: false, text: row.slice(last, match.index) });
52
+ segments.push({ escape: true, text: match[0] });
53
+ last = match.index + match[0].length;
54
+ }
55
+ segments.push({ escape: false, text: row.slice(last) });
56
+ const visible = segments.filter(segment => !segment.escape).flatMap(segment => visibleAt(segment.text));
57
+ const total = visible.reduce((count, entry) => count + (entry.position >= 0 ? 1 : 0), 0);
58
+ if (total <= MASK_HEAD + MASK_TAIL)
59
+ return row;
60
+ let seen = 0;
61
+ return segments.map((segment) => {
62
+ if (segment.escape)
63
+ return segment.text;
64
+ return visibleAt(segment.text).map((entry) => {
65
+ if (entry.position < 0)
66
+ return entry.segment;
67
+ const readable = seen < MASK_HEAD || seen >= total - MASK_TAIL;
68
+ seen += 1;
69
+ return readable ? entry.segment : MASKED.repeat(Math.max(1, visibleWidth(entry.segment)));
70
+ }).join('');
71
+ }).join('');
72
+ }
73
+ /**
74
+ * The input bar a gate collects an answer with.
75
+ *
76
+ * It is the prompt bar's own component, so an answer is written with the
77
+ * movement, deletion, undo, and paste that bar already has. Its mode decides
78
+ * only how the answer is shown: a question that declares a credential is
79
+ * answered in the same bar with the middle of the value hidden, never in a
80
+ * second widget that would have to grow its own editing.
81
+ */
82
+ export class GateInputBar extends BoxedEditor {
83
+ mode = 'answer';
84
+ /** Switch what the bar shows without changing a character of what it holds. */
85
+ setMode(mode) {
86
+ if (this.mode === mode)
87
+ return;
88
+ this.mode = mode;
89
+ this.invalidate();
90
+ }
91
+ /** Hide a credential's middle while its owner still recognizes both ends. */
92
+ decorateText(row) {
93
+ return this.mode === 'secret' ? maskCredentialRow(row) : row;
94
+ }
95
+ }
96
+ //# sourceMappingURL=gate-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-input.js","sourceRoot":"","sources":["../../src/ui/gate-input.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { type MarkdownTheme } from '@earendil-works/pi-tui';
2
+ import type { MermaidTransform } from './mermaid.ts';
2
3
  /** Parsed messages kept for redraws; everything above this is cold transcript. */
3
4
  export declare const MARKDOWN_CACHE_LIMIT = 64;
4
5
  /**
@@ -10,8 +11,24 @@ export declare const MARKDOWN_CACHE_LIMIT = 64;
10
11
  */
11
12
  export declare class MarkdownRenderer {
12
13
  private readonly theme;
14
+ private readonly mermaid?;
13
15
  private readonly parsed;
14
- constructor(theme: MarkdownTheme);
15
- render(text: string, width: number): string[];
16
+ constructor(theme: MarkdownTheme, mermaid?: MermaidTransform | undefined);
17
+ render(text: string, width: number, live?: boolean): string[];
18
+ /**
19
+ * The parse options this renderer installs, absent when nothing transforms.
20
+ *
21
+ * The flag is captured rather than read per call: the component caches the
22
+ * rows it drew, so the value that built it is the only one it could honour.
23
+ */
24
+ private options;
25
+ /**
26
+ * Drop parsed messages after a theme change.
27
+ *
28
+ * A `Markdown` caches the lines it rendered for a width, so keeping the
29
+ * instance would keep the old escapes; the reader's new shade would land
30
+ * everywhere except the answer.
31
+ */
32
+ invalidate(): void;
16
33
  }
17
34
  //# sourceMappingURL=markdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAErE,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;gBAExB,KAAK,EAAE,aAAa;IAEjD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAe9C"}
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAiB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAF5E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;gBAExB,KAAK,EAAE,aAAa,EAAmB,OAAO,CAAC,EAAE,gBAAgB,YAAA;IAE9F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,UAAQ,GAAG,MAAM,EAAE;IAqB3D;;;;;OAKG;IACH,OAAO,CAAC,OAAO;IAMf;;;;;;OAMG;IACH,UAAU,IAAI,IAAI;CAInB"}
@@ -10,26 +10,57 @@ export const MARKDOWN_CACHE_LIMIT = 64;
10
10
  */
11
11
  export class MarkdownRenderer {
12
12
  theme;
13
+ mermaid;
13
14
  parsed = new Map();
14
- constructor(theme) {
15
+ constructor(theme, mermaid) {
15
16
  this.theme = theme;
17
+ this.mermaid = mermaid;
16
18
  }
17
- render(text, width) {
18
- let message = this.parsed.get(text);
19
+ render(text, width, live = false) {
20
+ // A streaming reply can settle on the very text it last streamed, and the
21
+ // two renderings are not the same: only a settled one may report what a
22
+ // drawing lost, and a mode may draw one and withhold the other. The flag is
23
+ // part of a message's identity for the same reason the text is.
24
+ const key = live ? `live\u0000${text}` : `settled\u0000${text}`;
25
+ let message = this.parsed.get(key);
19
26
  if (message === undefined) {
20
27
  if (this.parsed.size >= MARKDOWN_CACHE_LIMIT) {
21
28
  const oldest = this.parsed.keys().next();
22
29
  if (oldest.done !== true)
23
30
  this.parsed.delete(oldest.value);
24
31
  }
25
- message = new Markdown(text, 0, 0, this.theme);
32
+ message = new Markdown(text, 0, 0, this.theme, undefined, this.options(live));
26
33
  }
27
34
  else {
28
35
  // Reinserting keeps a message that is still on screen from ageing out.
29
- this.parsed.delete(text);
36
+ this.parsed.delete(key);
30
37
  }
31
- this.parsed.set(text, message);
38
+ this.parsed.set(key, message);
32
39
  return message.render(Math.max(1, Math.floor(width)));
33
40
  }
41
+ /**
42
+ * The parse options this renderer installs, absent when nothing transforms.
43
+ *
44
+ * The flag is captured rather than read per call: the component caches the
45
+ * rows it drew, so the value that built it is the only one it could honour.
46
+ */
47
+ options(live) {
48
+ const mermaid = this.mermaid;
49
+ if (mermaid === undefined)
50
+ return undefined;
51
+ return { transform: (markdown, availableWidth) => mermaid(markdown, availableWidth, live) };
52
+ }
53
+ /**
54
+ * Drop parsed messages after a theme change.
55
+ *
56
+ * A `Markdown` caches the lines it rendered for a width, so keeping the
57
+ * instance would keep the old escapes; the reader's new shade would land
58
+ * everywhere except the answer.
59
+ */
60
+ invalidate() {
61
+ for (const message of this.parsed.values())
62
+ message.invalidate();
63
+ this.parsed.clear();
64
+ }
34
65
  }
35
66
  //# sourceMappingURL=markdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsB,MAAM,wBAAwB,CAAA;AAErE,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAGE;IAFZ,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IAErD,YAA6B,KAAoB;QAApB,UAAK,GAAL,KAAK,CAAe;IAAG,CAAC;IAErD,MAAM,CAAC,IAAY,EAAE,KAAa;QAChC,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;gBACxC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5D,CAAC;YACD,OAAO,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAChD,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC9B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC;CACF"}
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/ui/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAA4C,MAAM,wBAAwB,CAAA;AAG3F,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IAGE;IAAuC;IAFnD,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAA;IAErD,YAA6B,KAAoB,EAAmB,OAA0B;QAAjE,UAAK,GAAL,KAAK,CAAe;QAAmB,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAElG,MAAM,CAAC,IAAY,EAAE,KAAa,EAAE,IAAI,GAAG,KAAK;QAC9C,0EAA0E;QAC1E,wEAAwE;QACxE,4EAA4E;QAC5E,gEAAgE;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,EAAE,CAAA;QAC/D,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,oBAAoB,EAAE,CAAC;gBAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAA;gBACxC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC5D,CAAC;YACD,OAAO,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/E,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC;IAED;;;;;OAKG;IACK,OAAO,CAAC,IAAa;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAC3C,OAAO,EAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,CAAA;IAC7F,CAAC;IAED;;;;;;OAMG;IACH,UAAU;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,OAAO,CAAC,UAAU,EAAE,CAAA;QAChE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF"}
@@ -0,0 +1,44 @@
1
+ import type { MermaidMode } from '../theme-settings.ts';
2
+ import type { TuiTheme } from '../theme.ts';
3
+ /** Drawings kept before the oldest is laid out again; a streaming reply redraws per delta. */
4
+ export declare const MERMAID_CACHE_LIMIT = 32;
5
+ /**
6
+ * The largest source this surface lays out.
7
+ *
8
+ * Layout is synchronous and happens inside a frame, and a streaming reply asks
9
+ * for it again on every delta, so past this the frame costs more than the
10
+ * drawing is worth and the source stays. Well above what the renderer's own
11
+ * node cap can fill, so no drawing a reader actually sees is kept out.
12
+ */
13
+ export declare const MERMAID_MAX_SOURCE: number;
14
+ export interface MermaidOptions {
15
+ readonly theme: TuiTheme;
16
+ /** Read per render, so a settings edit reaches a session that is already on screen. */
17
+ readonly mode: () => MermaidMode;
18
+ }
19
+ /**
20
+ * Rewrite one message's markdown, with `live` telling a streaming reply from a settled one.
21
+ *
22
+ * The width is the columns the message actually has, and a drawing that needs
23
+ * more than that is left as source rather than truncated: a mutilated diagram
24
+ * is worse than the text a reader can still read.
25
+ */
26
+ export type MermaidTransform = (markdown: string, availableWidth: number, live: boolean) => string;
27
+ /**
28
+ * Draw a reply's mermaid fences as terminal art.
29
+ *
30
+ * Replacements are spliced into the message at the offsets the lexer reported,
31
+ * never rebuilt from the tokens: lexing can decide something is not worth a
32
+ * token — a duplicate reference definition, for one — and a message that merely
33
+ * mentions mermaid would then change under a reader who asked for nothing. Text
34
+ * the transform does not draw is copied through byte for byte.
35
+ *
36
+ * A fence is replaced before the markdown is parsed, because that is the only
37
+ * moment the source is still whole: once tokens are flowing the drawing's rows
38
+ * would be re-wrapped as prose and lose the spacing they are made of. Each row
39
+ * is therefore emitted as an inline code span, which the markdown renderer
40
+ * keeps verbatim, and rows are separated by hard breaks so a diagram survives
41
+ * as one block instead of one paragraph.
42
+ */
43
+ export declare function createMermaidTransform(options: MermaidOptions): MermaidTransform;
44
+ //# sourceMappingURL=mermaid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid.d.ts","sourceRoot":"","sources":["../../src/ui/mermaid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAQ3C,8FAA8F;AAC9F,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,QAAY,CAAA;AA8B3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,MAAM,CAAA;AAgElG;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,gBAAgB,CAkEhF"}