@gaia-ai/addon-gaia-ui 0.9.2 → 0.11.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 (136) hide show
  1. package/dist/src/Component/footer-actions/index.d.ts +58 -0
  2. package/dist/src/Component/footer-actions/index.js +111 -0
  3. package/dist/src/Component/list-chrome/index.d.ts +19 -0
  4. package/dist/src/Component/list-chrome/index.js +61 -0
  5. package/dist/src/Component/markdown/index.d.ts +68 -0
  6. package/dist/src/Component/markdown/index.js +133 -0
  7. package/dist/src/Component/section-list/index.d.ts +115 -23
  8. package/dist/src/Component/section-list/index.js +120 -34
  9. package/dist/src/Component/top-tabs/index.d.ts +110 -7
  10. package/dist/src/Component/top-tabs/index.js +88 -9
  11. package/dist/src/Entity/collection.js +1 -1
  12. package/dist/src/Entity/conductor/Data/conductors.d.ts +4 -3
  13. package/dist/src/Entity/conductor/Data/conductors.js +76 -10
  14. package/dist/src/Entity/conductor/Data/own-conductor.js +7 -1
  15. package/dist/src/Entity/conductor/Screen/conductors.d.ts +26 -41
  16. package/dist/src/Entity/conductor/Screen/conductors.js +65 -123
  17. package/dist/src/Entity/project/Screen/project-picker.d.ts +20 -31
  18. package/dist/src/Entity/project/Screen/project-picker.js +48 -58
  19. package/dist/src/Entity/project/Screen/projects-screen.d.ts +4 -2
  20. package/dist/src/Entity/project/Screen/projects-screen.js +11 -8
  21. package/dist/src/Entity/run/Component/run-detail/index.js +15 -7
  22. package/dist/src/Entity/run/Data/query.d.ts +10 -0
  23. package/dist/src/Entity/run/Data/query.js +16 -1
  24. package/dist/src/Entity/run/Data/runs.d.ts +2 -0
  25. package/dist/src/Entity/run/Data/runs.js +19 -1
  26. package/dist/src/Entity/run/Screen/run-detail.d.ts +16 -23
  27. package/dist/src/Entity/run/Screen/run-detail.js +27 -34
  28. package/dist/src/Entity/run/Screen/runs.d.ts +7 -3
  29. package/dist/src/Entity/run/Screen/runs.js +31 -48
  30. package/dist/src/Entity/run/Screen/user-runs.d.ts +79 -0
  31. package/dist/src/Entity/run/Screen/user-runs.js +98 -0
  32. package/dist/src/Entity/statistics/Data/statistics.d.ts +111 -9
  33. package/dist/src/Entity/statistics/Data/statistics.js +121 -9
  34. package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
  35. package/dist/src/Entity/statistics/Screen/dashboard.js +127 -36
  36. package/dist/src/Entity/team/Data/members.d.ts +30 -0
  37. package/dist/src/Entity/team/Data/members.js +83 -0
  38. package/dist/src/Entity/team/Data/team.d.ts +22 -0
  39. package/dist/src/Entity/team/Data/team.js +38 -0
  40. package/dist/src/Entity/team/Data/teams.d.ts +223 -0
  41. package/dist/src/Entity/team/Data/teams.js +267 -0
  42. package/dist/src/Entity/team/Screen/team-users.d.ts +76 -0
  43. package/dist/src/Entity/team/Screen/team-users.js +215 -0
  44. package/dist/src/Entity/team/Screen/teams-screen.d.ts +116 -0
  45. package/dist/src/Entity/team/Screen/teams-screen.js +349 -0
  46. package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +14 -1
  47. package/dist/src/Entity/ticket/Component/comment-item/index.js +15 -1
  48. package/dist/src/Entity/ticket/Component/edit-form/index.d.ts +8 -3
  49. package/dist/src/Entity/ticket/Component/edit-form/index.js +4 -6
  50. package/dist/src/Entity/ticket/Component/sections/index.js +16 -1
  51. package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +102 -13
  52. package/dist/src/Entity/ticket/Component/ticket-detail/index.js +440 -72
  53. package/dist/src/Entity/ticket/Data/detail.d.ts +19 -3
  54. package/dist/src/Entity/ticket/Data/detail.js +20 -4
  55. package/dist/src/Entity/ticket/Data/query.d.ts +7 -0
  56. package/dist/src/Entity/ticket/Data/query.js +18 -2
  57. package/dist/src/Entity/ticket/Data/tickets.d.ts +24 -0
  58. package/dist/src/Entity/ticket/Data/tickets.js +41 -1
  59. package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +10 -4
  60. package/dist/src/Entity/ticket/Screen/create-ticket-form.js +37 -16
  61. package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +6 -4
  62. package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +16 -16
  63. package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +6 -4
  64. package/dist/src/Entity/ticket/Screen/parent-filter-form.js +14 -11
  65. package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +5 -2
  66. package/dist/src/Entity/ticket/Screen/ticket-agents.js +14 -14
  67. package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +89 -4
  68. package/dist/src/Entity/ticket/Screen/ticket-detail.js +295 -74
  69. package/dist/src/Entity/ticket/Screen/tickets.d.ts +66 -3
  70. package/dist/src/Entity/ticket/Screen/tickets.js +140 -76
  71. package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +91 -0
  72. package/dist/src/Entity/ticket/Screen/user-tickets.js +94 -0
  73. package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +6 -4
  74. package/dist/src/Entity/ticket/Screen/who-filter-form.js +14 -11
  75. package/dist/src/Entity/transport.d.ts +14 -4
  76. package/dist/src/Entity/transport.js +2 -1
  77. package/dist/src/Entity/user/Data/users.d.ts +87 -0
  78. package/dist/src/Entity/user/Data/users.js +60 -0
  79. package/dist/src/Entity/user/Screen/user-overview.d.ts +33 -0
  80. package/dist/src/Entity/user/Screen/user-overview.js +159 -0
  81. package/dist/src/Entity/user/Screen/users.d.ts +39 -0
  82. package/dist/src/Entity/user/Screen/users.js +136 -0
  83. package/dist/src/Extension/contract.d.ts +58 -0
  84. package/dist/src/Extension/contract.js +1 -0
  85. package/dist/src/Extension/load.d.ts +10 -0
  86. package/dist/src/Extension/load.js +21 -0
  87. package/dist/src/Extension/store.d.ts +11 -0
  88. package/dist/src/Extension/store.js +44 -0
  89. package/dist/src/Kernel/chrome.d.ts +49 -12
  90. package/dist/src/Kernel/chrome.js +150 -57
  91. package/dist/src/Kernel/key-input.js +1 -1
  92. package/dist/src/Kernel/mount.d.ts +1 -1
  93. package/dist/src/Kernel/mount.js +1 -1
  94. package/dist/src/Kernel/regions.d.ts +64 -0
  95. package/dist/src/Kernel/regions.js +328 -0
  96. package/dist/src/Kernel/renderer.d.ts +25 -0
  97. package/dist/src/Kernel/renderer.js +142 -0
  98. package/dist/src/Kernel/splash.d.ts +27 -0
  99. package/dist/src/Kernel/splash.js +72 -0
  100. package/dist/src/Kernel/tui-kernel.js +139 -435
  101. package/dist/src/Routing/context.d.ts +5 -0
  102. package/dist/src/Routing/context.js +6 -0
  103. package/dist/src/Routing/registry.js +21 -2
  104. package/dist/src/Routing/router.js +5 -1
  105. package/dist/src/Routing/routes.js +19 -0
  106. package/dist/src/Screen/async-modal-screen.d.ts +26 -0
  107. package/dist/src/Screen/async-modal-screen.js +44 -0
  108. package/dist/src/Screen/async-screen.d.ts +32 -40
  109. package/dist/src/Screen/async-screen.js +45 -41
  110. package/dist/src/Screen/full-screen.d.ts +1 -0
  111. package/dist/src/Screen/full-screen.js +3 -0
  112. package/dist/src/Screen/list-screen.d.ts +7 -1
  113. package/dist/src/Screen/list-screen.js +17 -6
  114. package/dist/src/Screen/modal-screen.d.ts +6 -0
  115. package/dist/src/Screen/modal-screen.js +2 -0
  116. package/dist/src/Screen/screen.d.ts +42 -6
  117. package/dist/src/Screen/screen.js +2 -2
  118. package/dist/src/Widget/textarea.js +10 -14
  119. package/dist/src/Widget/types.d.ts +1 -1
  120. package/dist/src/launcher.d.ts +24 -26
  121. package/dist/src/launcher.js +11 -11
  122. package/dist/src/lib/format.js +1 -1
  123. package/dist/src/lib/markdown.d.ts +53 -0
  124. package/dist/src/lib/markdown.js +522 -0
  125. package/dist/src/lib/refresh-signal.d.ts +56 -0
  126. package/dist/src/lib/refresh-signal.js +85 -0
  127. package/dist/src/lib/theme.d.ts +36 -0
  128. package/dist/src/lib/theme.js +40 -0
  129. package/dist/src/lib/version.d.ts +5 -0
  130. package/dist/src/lib/version.js +46 -0
  131. package/dist/src/lib/vnode.d.ts +86 -63
  132. package/dist/src/lib/vnode.js +112 -85
  133. package/dist/src/types.d.ts +47 -12
  134. package/dist/src/ui.d.ts +2 -0
  135. package/dist/src/ui.js +1 -0
  136. package/package.json +4 -3
@@ -0,0 +1,58 @@
1
+ import { type VNode } from '@opentui/core';
2
+ /**
3
+ * One footer action — the atomic chip screens publish.
4
+ * Rendered as `key` ({@link FOREGROUND_ACTIVE} fg) plus optional muted `:Label`.
5
+ */
6
+ export interface ActionItem {
7
+ /** Stable id — nodes are `${id}-key` / `${id}-sfx`. */
8
+ id: string;
9
+ /** Active key glyph, e.g. `e`, `Tab`, `→`. */
10
+ key: string;
11
+ /**
12
+ * Plain label without a colon (`edit`, `next`). Title-cased and prefixed
13
+ * with `:` at render time. Omit for a bare key (`Enter`).
14
+ */
15
+ label?: string;
16
+ }
17
+ /** Build an {@link ActionItem} — the usual screen-side constructor. */
18
+ export declare const actionItem: (id: string, key: string, label?: string) => ActionItem;
19
+ /** Legacy ladder string for assertions (`n new · e edit`). */
20
+ export declare const formatActionLadder: (items: readonly ActionItem[] | undefined) => string;
21
+ /** Box-drawing pipe — sits optically higher than ASCII `|`. */
22
+ export declare const footerSep: (id: string) => VNode;
23
+ /**
24
+ * Render one action-item as FOREGROUND_ACTIVE-fg key + optional muted `:Label`.
25
+ * Also used by the kernel basics row (`Esc:back`, `q:quit`).
26
+ */
27
+ export declare const renderActionItem: (item: Pick<ActionItem, "id" | "key" | "label"> & {
28
+ suffix?: string;
29
+ }) => VNode[];
30
+ /**
31
+ * Low-level chip used by kernel basics that already pass a ready `:suffix`
32
+ * (e.g. `:quit`). Screen actions prefer {@link actionItem}.
33
+ */
34
+ export declare const footerKey: (id: string, key: string, suffix?: string) => VNode[];
35
+ /**
36
+ * A segment inside {@link withActionPipes}: an {@link ActionItem}, or already
37
+ * rendered nodes (plain text, {@link footerKey} chips, …).
38
+ */
39
+ export type ActionSegment = ActionItem | readonly VNode[];
40
+ /**
41
+ * Wrapper that inserts `│` between segments — action-items or prebuilt node
42
+ * groups. Callers never place separators by hand.
43
+ */
44
+ export declare const withActionPipes: (sepIdPrefix: string, segments: readonly ActionSegment[]) => VNode[];
45
+ /** Pipe-joined chips from an action-item list (thin wrapper over {@link withActionPipes}). */
46
+ export declare const footerActionChips: (items: readonly ActionItem[], sepIdPrefix?: string) => VNode[];
47
+ /**
48
+ * Full screen-actions row: lead pad + piped action-items + width pad.
49
+ * Returns `null` when `items` is empty — caller omits the row.
50
+ */
51
+ export declare const footerActions: (id: string, items: readonly ActionItem[], width: number) => VNode | null;
52
+ /** @deprecated Use {@link footerActions}. */
53
+ export declare const footerActionsRow: (id: string, items: readonly ActionItem[], width: number) => VNode | null;
54
+ /**
55
+ * Parse a legacy ladder string (`n new · e edit`) into action-items.
56
+ * Prefer publishing {@link ActionItem} arrays from screens directly.
57
+ */
58
+ export declare const parseFooterLadder: (ladder: string, idPrefix?: string) => ActionItem[];
@@ -0,0 +1,111 @@
1
+ // GAIA-375 — footer actions: action-item chips + withActionPipes (│ between them).
2
+ // No footer fill — chips sit on the terminal default (same as header).
3
+ import { BoxRenderable, h } from '@opentui/core';
4
+ import { BORDER, FOREGROUND_ACTIVE, textNode } from '../../lib/vnode.js';
5
+ /** Build an {@link ActionItem} — the usual screen-side constructor. */
6
+ export const actionItem = (id, key, label) => (label === undefined ? { id, key } : { id, key, label });
7
+ /** Legacy ladder string for assertions (`n new · e edit`). */
8
+ export const formatActionLadder = (items) => (items ?? [])
9
+ .map((item) => item.label !== undefined ? `${item.key} ${item.label}` : item.key)
10
+ .join(' · ');
11
+ /** Box-drawing pipe — sits optically higher than ASCII `|`. */
12
+ export const footerSep = (id) => textNode({ id, content: ' │ ', fg: BORDER });
13
+ /** First letter upper for labels (`edit` → `Edit`). */
14
+ const titleCase = (label) => label.length === 0 ? label : label.charAt(0).toUpperCase() + label.slice(1);
15
+ /**
16
+ * Render one action-item as FOREGROUND_ACTIVE-fg key + optional muted `:Label`.
17
+ * Also used by the kernel basics row (`Esc:back`, `q:quit`).
18
+ */
19
+ export const renderActionItem = (item) => {
20
+ const suffix = item.suffix ??
21
+ (item.label !== undefined ? `:${titleCase(item.label)}` : undefined);
22
+ const nodes = [
23
+ textNode({
24
+ id: `${item.id}-key`,
25
+ content: item.key,
26
+ fg: FOREGROUND_ACTIVE,
27
+ bold: true,
28
+ }),
29
+ ];
30
+ if (suffix !== undefined) {
31
+ nodes.push(textNode({
32
+ id: `${item.id}-sfx`,
33
+ content: suffix,
34
+ fg: BORDER,
35
+ }));
36
+ }
37
+ return nodes;
38
+ };
39
+ /**
40
+ * Low-level chip used by kernel basics that already pass a ready `:suffix`
41
+ * (e.g. `:quit`). Screen actions prefer {@link actionItem}.
42
+ */
43
+ export const footerKey = (id, key, suffix) => suffix === undefined
44
+ ? renderActionItem({ id, key })
45
+ : renderActionItem({ id, key, suffix });
46
+ const isActionItem = (segment) => !Array.isArray(segment);
47
+ /**
48
+ * Wrapper that inserts `│` between segments — action-items or prebuilt node
49
+ * groups. Callers never place separators by hand.
50
+ */
51
+ export const withActionPipes = (sepIdPrefix, segments) => {
52
+ const parts = [];
53
+ let placed = 0;
54
+ for (const segment of segments) {
55
+ const nodes = isActionItem(segment)
56
+ ? renderActionItem(segment)
57
+ : [...segment];
58
+ if (nodes.length === 0)
59
+ continue;
60
+ if (placed > 0)
61
+ parts.push(footerSep(`${sepIdPrefix}-${placed}`));
62
+ parts.push(...nodes);
63
+ placed += 1;
64
+ }
65
+ return parts;
66
+ };
67
+ /** Pipe-joined chips from an action-item list (thin wrapper over {@link withActionPipes}). */
68
+ export const footerActionChips = (items, sepIdPrefix = 'footer-actions-sep') => withActionPipes(sepIdPrefix, items);
69
+ /**
70
+ * Full screen-actions row: lead pad + piped action-items + width pad.
71
+ * Returns `null` when `items` is empty — caller omits the row.
72
+ */
73
+ export const footerActions = (id, items, width) => {
74
+ if (items.length === 0)
75
+ return null;
76
+ return h(BoxRenderable, {
77
+ id,
78
+ flexDirection: 'row',
79
+ height: 1,
80
+ flexShrink: 0,
81
+ width: '100%',
82
+ }, textNode({
83
+ id: `${id}-lead`,
84
+ content: ' ',
85
+ }), ...withActionPipes(`${id}-sep`, items), textNode({
86
+ id: `${id}-pad`,
87
+ content: ' '.repeat(width),
88
+ }));
89
+ };
90
+ /** @deprecated Use {@link footerActions}. */
91
+ export const footerActionsRow = footerActions;
92
+ /**
93
+ * Parse a legacy ladder string (`n new · e edit`) into action-items.
94
+ * Prefer publishing {@link ActionItem} arrays from screens directly.
95
+ */
96
+ export const parseFooterLadder = (ladder, idPrefix = 'footer-action') => {
97
+ const segments = ladder
98
+ .split(/\s·\s/)
99
+ .map((s) => s.trim())
100
+ .filter((s) => s !== '');
101
+ const items = [];
102
+ for (let i = 0; i < segments.length; i += 1) {
103
+ const m = /^(\S+)(?:\s+(.+))?$/.exec(segments[i]);
104
+ if (m === null)
105
+ continue;
106
+ const key = m[1];
107
+ const label = m[2];
108
+ items.push(actionItem(`${idPrefix}-${i}`, key, label));
109
+ }
110
+ return items;
111
+ };
@@ -0,0 +1,19 @@
1
+ import { type VNode } from '@opentui/core';
2
+ /** One filter/action badge in the strip above a list table. */
3
+ export interface ListFilterBadge {
4
+ /** Stable id suffix — node id is `${stripId}-${id}`. */
5
+ id: string;
6
+ /** Idle label (key hint), e.g. `/ search` or `x closed`. */
7
+ label: string;
8
+ /**
9
+ * When set, the badge is active: {@link FOREGROUND_ACTIVE} fg and this value text
10
+ * (e.g. `closed: hidden`, `who: cw`).
11
+ */
12
+ value?: string;
13
+ }
14
+ /**
15
+ * Rounded filter chips in a row (idle outline + active FOREGROUND_ACTIVE fg).
16
+ * Each badge is its own `borderStyle: 'rounded'` box — the panel border language.
17
+ * Horizontal inset matches the body ScrollBox frame padding (GAIA-375).
18
+ */
19
+ export declare const filterBadgeStrip: (id: string, badges: readonly ListFilterBadge[]) => VNode;
@@ -0,0 +1,61 @@
1
+ // GAIA-375 — shared full-screen body helpers: optional filter badges.
2
+ // Body frame lives on region-body ScrollBox (Kernel/regions.ts).
3
+ import { BoxRenderable, h } from '@opentui/core';
4
+ import { BACKGROUND_ACTIVE, BORDER, DEFAULT_BACKGROUND, FOREGROUND, FOREGROUND_ACTIVE, textNode, } from '../../lib/vnode.js';
5
+ /**
6
+ * Rounded filter chips in a row (idle outline + active FOREGROUND_ACTIVE fg).
7
+ * Each badge is its own `borderStyle: 'rounded'` box — the panel border language.
8
+ * Horizontal inset matches the body ScrollBox frame padding (GAIA-375).
9
+ */
10
+ export const filterBadgeStrip = (id, badges) => {
11
+ const nodes = [];
12
+ for (let index = 0; index < badges.length; index += 1) {
13
+ if (index > 0) {
14
+ nodes.push(textNode({
15
+ id: `${id}-gap-${index}`,
16
+ content: ' ',
17
+ bg: DEFAULT_BACKGROUND,
18
+ fg: BORDER,
19
+ }));
20
+ }
21
+ const badge = badges[index];
22
+ const value = badge.value;
23
+ const active = value !== undefined && value !== '';
24
+ const label = active ? value : badge.label;
25
+ nodes.push(h(BoxRenderable, {
26
+ id: `${id}-${badge.id}-chip`,
27
+ border: true,
28
+ borderStyle: 'rounded',
29
+ borderColor: BORDER,
30
+ backgroundColor: DEFAULT_BACKGROUND,
31
+ flexShrink: 0,
32
+ // One extra column of air inside the pill (rounder optical mass).
33
+ paddingLeft: 2,
34
+ paddingRight: 2,
35
+ flexDirection: 'row',
36
+ }, textNode({
37
+ id: `${id}-${badge.id}`,
38
+ content: label,
39
+ bg: active ? BACKGROUND_ACTIVE : DEFAULT_BACKGROUND,
40
+ fg: active ? FOREGROUND_ACTIVE : FOREGROUND,
41
+ })));
42
+ }
43
+ return h(BoxRenderable, {
44
+ id,
45
+ backgroundColor: DEFAULT_BACKGROUND,
46
+ flexDirection: 'column',
47
+ flexShrink: 0,
48
+ // Match body ScrollBox horizontal inset (GAIA-375).
49
+ paddingLeft: 1,
50
+ paddingRight: 1,
51
+ paddingTop: 0,
52
+ paddingBottom: 0,
53
+ }, h(BoxRenderable, {
54
+ id: `${id}-row`,
55
+ flexDirection: 'row',
56
+ flexWrap: 'wrap',
57
+ flexShrink: 0,
58
+ backgroundColor: DEFAULT_BACKGROUND,
59
+ gap: 0,
60
+ }, ...nodes));
61
+ };
@@ -0,0 +1,68 @@
1
+ import type { MarkdownLine, MarkdownSpan } from '../../lib/markdown.js';
2
+ import { type StyledLine, type StyledRun } from '../../lib/vnode.js';
3
+ /**
4
+ * ONE SPAN AS THE RUN A RENDERER CAN PAINT — D7/D11, and the mapping this
5
+ * component owns.
6
+ *
7
+ * `bold`, `italic` and `dim` are ATTRIBUTES and OR together into one bitmask;
8
+ * `code` is a COLOUR and not an attribute at all, which is why a code span that
9
+ * is also emphasised carries both. The colour is {@link BORDER} (indexed, same
10
+ * slot as ordinary foreground after GAIA-375 dropped cyan) — never a truecolor
11
+ * triplet.
12
+ *
13
+ * `strikethrough` and `underline` are deliberately absent: underline is what a
14
+ * terminal hyperlink looks like and would collide with D6's `text (url)`, and
15
+ * `~~struck~~` is mapped onto `dim` rather than widening the wire vocabulary a
16
+ * second time for a construct GAIA bodies barely use.
17
+ */
18
+ export declare const styledRunOf: (span: MarkdownSpan) => StyledRun;
19
+ /**
20
+ * ONE PARSED LINE AS A NEUTRAL STYLED LINE.
21
+ *
22
+ * `text` is carried through UNTOUCHED — that is the pipe constraint in one line
23
+ * of code. The styling step may not change a glyph, and it cannot: it only ever
24
+ * reads a span's flags.
25
+ */
26
+ export declare const styledLineOf: (line: MarkdownLine) => StyledLine;
27
+ /** A `gaia_rich` body as the physical rows a surface draws or prints — D1. */
28
+ export declare function markdownLines(body: string): StyledLine[];
29
+ /** A line NOTHING parses — one unstyled run (D3's plain path, as a value). */
30
+ export declare function plainLine(text: string): StyledLine;
31
+ /**
32
+ * The same line held `columns` in from the left — D5.
33
+ *
34
+ * The indent is an unstyled run PREPENDED TO A RENDERED LINE, never four spaces
35
+ * prepended to the source — see this file's header for why that ordering is
36
+ * load-bearing rather than tidy.
37
+ */
38
+ export declare function indentLine(source: StyledLine, columns: number): StyledLine;
39
+ /**
40
+ * THE RUN LOG'S OWN MINIMAL RULE — GAIA-372 D9, and it has exactly ONE caller:
41
+ * `Entity/run/Component/run-detail`.
42
+ *
43
+ * An ATX heading (`#`…`######`) renders as bold with the markers stripped;
44
+ * everything else is returned unchanged. Bullets, emphasis and code fences stay
45
+ * raw.
46
+ *
47
+ * ITS DOC COMMENT ONCE CLAIMED THE OPPOSITE OF WHAT IS NOW TRUE — that it is "ONE
48
+ * function, because every renderer of a body line goes through it". Since
49
+ * GAIA-372 no body line goes through it at all: a `gaia_rich` body is rendered by
50
+ * {@link markdownLines} and reaches a row as a `StyledLine` (D3), and
51
+ * `Component/section-list`'s `sectionRows`/`expandableRows` do not parse a plain
52
+ * string.
53
+ *
54
+ * IT LIVED IN `Component/section-list` UNTIL D11 MOVED IT HERE. That was where it
55
+ * sat while it really was the rule for EVERY body row; once it was not, all it left
56
+ * behind was a Markdown rule inside a generic list primitive. The rule is unchanged,
57
+ * character for character; only its address is.
58
+ *
59
+ * The run log keeps the narrow rule DELIBERATELY, not by omission. A log entry is
60
+ * MACHINE OUTPUT, not authored Markdown: an agent line full of `*`, `_` and
61
+ * backticks would be re-styled and have characters deleted by a renderer meant
62
+ * for prose. Routing the log through the full subset is a separate ticket, not a
63
+ * silent side effect of this one.
64
+ */
65
+ export declare function markdownRow(line: string): {
66
+ text: string;
67
+ bold: boolean;
68
+ };
@@ -0,0 +1,133 @@
1
+ // THE MARKDOWN COMPONENT — GAIA-372 D11, and the one place the cockpit knows
2
+ // what Markdown is.
3
+ //
4
+ // WHY IT EXISTS. Markdown arrived in this package as two halves that landed in
5
+ // two wrong places. `lib/markdown.ts` is a pure parser and belongs where it is,
6
+ // but the STYLING half — which span kind means which terminal attribute and which
7
+ // palette colour — ended up inside `lib/vnode.ts`, the package's most-shared
8
+ // render primitive, the one twelve screens draw through. `Component/section-list`
9
+ // then named `MarkdownLine`/`MarkdownSpan` throughout its own contract. So a
10
+ // generic node vocabulary and a generic list primitive both carried Markdown
11
+ // domain knowledge, and no gate caught it: `scripts/assert-acyclic.mjs` enforces
12
+ // PACKAGE layering, and both files live in `lib/`.
13
+ //
14
+ // THE SHAPE. This directory owns all of it, and the import direction inverts:
15
+ //
16
+ // Component/markdown → lib/vnode (attributes, palette, the neutral run)
17
+ // Component/markdown → lib/markdown (the pure parser, no @opentui/core)
18
+ // lib/vnode → nothing about Markdown
19
+ // Component/section-list → lib/vnode (StyledLine, never MarkdownLine)
20
+ //
21
+ // THE HARD CONSTRAINT, and the reason this is not a paint-only component: the
22
+ // non-interactive pipe prints `row.text` and NOTHING ELSE. AC-3's parity is
23
+ // structural precisely because a rendered row carries its rendered TEXT, so
24
+ // `gaia ui <id> | cat` and the interactive tree are the same rendering by
25
+ // construction rather than by agreement (D4). A component that emitted only
26
+ // VNodes would cut the pipe off that rendering and restore the
27
+ // two-renderers-disagree defect this ticket exists to remove. So the output here
28
+ // is DATA — a {@link StyledLine}: rendered text, resolved runs, and the row's own
29
+ // weight — and `Component/section-list`'s `lineText` keeps working unchanged.
30
+ //
31
+ // WHY `plainLine` AND `indentLine` LIVE HERE rather than in the parser: neither
32
+ // parses. `plainLine` is D3's plain path expressed as a value, and `indentLine`
33
+ // prepends blanks to an ALREADY RENDERED line — which is the whole point of it
34
+ // (four leading spaces prepended to SOURCE would be an indented code block in
35
+ // CommonMark, so applying the indent after rendering is what makes that
36
+ // impossible rather than merely unlikely, Fact 9). Both are shell work on a
37
+ // rendered line, so they sit with the shell.
38
+ import { markdownLines as parseMarkdown } from '../../lib/markdown.js';
39
+ import { BOLD, BORDER, DIM, ITALIC, } from '../../lib/vnode.js';
40
+ /**
41
+ * ONE SPAN AS THE RUN A RENDERER CAN PAINT — D7/D11, and the mapping this
42
+ * component owns.
43
+ *
44
+ * `bold`, `italic` and `dim` are ATTRIBUTES and OR together into one bitmask;
45
+ * `code` is a COLOUR and not an attribute at all, which is why a code span that
46
+ * is also emphasised carries both. The colour is {@link BORDER} (indexed, same
47
+ * slot as ordinary foreground after GAIA-375 dropped cyan) — never a truecolor
48
+ * triplet.
49
+ *
50
+ * `strikethrough` and `underline` are deliberately absent: underline is what a
51
+ * terminal hyperlink looks like and would collide with D6's `text (url)`, and
52
+ * `~~struck~~` is mapped onto `dim` rather than widening the wire vocabulary a
53
+ * second time for a construct GAIA bodies barely use.
54
+ */
55
+ export const styledRunOf = (span) => ({
56
+ text: span.text,
57
+ attributes: (span.bold === true ? BOLD : 0) |
58
+ (span.italic === true ? ITALIC : 0) |
59
+ (span.dim === true ? DIM : 0),
60
+ // Spread rather than `fg: undefined`, for `exactOptionalPropertyTypes`: an
61
+ // unstyled run must state no colour, so `lib/vnode.ts`'s early exit can still
62
+ // hand the plain string straight to the renderable.
63
+ ...(span.code === true ? { fg: BORDER } : {}),
64
+ });
65
+ /**
66
+ * ONE PARSED LINE AS A NEUTRAL STYLED LINE.
67
+ *
68
+ * `text` is carried through UNTOUCHED — that is the pipe constraint in one line
69
+ * of code. The styling step may not change a glyph, and it cannot: it only ever
70
+ * reads a span's flags.
71
+ */
72
+ export const styledLineOf = (line) => ({
73
+ text: line.text,
74
+ runs: line.spans.map(styledRunOf),
75
+ heading: line.heading,
76
+ });
77
+ /** A `gaia_rich` body as the physical rows a surface draws or prints — D1. */
78
+ export function markdownLines(body) {
79
+ return parseMarkdown(body).map(styledLineOf);
80
+ }
81
+ /** A line NOTHING parses — one unstyled run (D3's plain path, as a value). */
82
+ export function plainLine(text) {
83
+ return {
84
+ text,
85
+ runs: text === '' ? [] : [{ text, attributes: 0 }],
86
+ heading: false,
87
+ };
88
+ }
89
+ /**
90
+ * The same line held `columns` in from the left — D5.
91
+ *
92
+ * The indent is an unstyled run PREPENDED TO A RENDERED LINE, never four spaces
93
+ * prepended to the source — see this file's header for why that ordering is
94
+ * load-bearing rather than tidy.
95
+ */
96
+ export function indentLine(source, columns) {
97
+ const blanks = ' '.repeat(columns);
98
+ return {
99
+ text: `${blanks}${source.text}`,
100
+ runs: [{ text: blanks, attributes: 0 }, ...source.runs],
101
+ heading: source.heading,
102
+ };
103
+ }
104
+ /**
105
+ * THE RUN LOG'S OWN MINIMAL RULE — GAIA-372 D9, and it has exactly ONE caller:
106
+ * `Entity/run/Component/run-detail`.
107
+ *
108
+ * An ATX heading (`#`…`######`) renders as bold with the markers stripped;
109
+ * everything else is returned unchanged. Bullets, emphasis and code fences stay
110
+ * raw.
111
+ *
112
+ * ITS DOC COMMENT ONCE CLAIMED THE OPPOSITE OF WHAT IS NOW TRUE — that it is "ONE
113
+ * function, because every renderer of a body line goes through it". Since
114
+ * GAIA-372 no body line goes through it at all: a `gaia_rich` body is rendered by
115
+ * {@link markdownLines} and reaches a row as a `StyledLine` (D3), and
116
+ * `Component/section-list`'s `sectionRows`/`expandableRows` do not parse a plain
117
+ * string.
118
+ *
119
+ * IT LIVED IN `Component/section-list` UNTIL D11 MOVED IT HERE. That was where it
120
+ * sat while it really was the rule for EVERY body row; once it was not, all it left
121
+ * behind was a Markdown rule inside a generic list primitive. The rule is unchanged,
122
+ * character for character; only its address is.
123
+ *
124
+ * The run log keeps the narrow rule DELIBERATELY, not by omission. A log entry is
125
+ * MACHINE OUTPUT, not authored Markdown: an agent line full of `*`, `_` and
126
+ * backticks would be re-styled and have characters deleted by a renderer meant
127
+ * for prose. Routing the log through the full subset is a separate ticket, not a
128
+ * silent side effect of this one.
129
+ */
130
+ export function markdownRow(line) {
131
+ const md = /^(#{1,6})\s+(.*)$/.exec(line);
132
+ return md ? { text: md[2] ?? '', bold: true } : { text: line, bold: false };
133
+ }
@@ -1,5 +1,5 @@
1
1
  import type { VNode } from '@opentui/core';
2
- import { type Cell } from '../../lib/vnode.js';
2
+ import { type Cell, type StyledLine, type StyledRun } from '../../lib/vnode.js';
3
3
  /**
4
4
  * A section body row that is COLUMNS rather than prose — GAIA-340 Task 13.
5
5
  *
@@ -14,8 +14,35 @@ import { type Cell } from '../../lib/vnode.js';
14
14
  export interface SectionCells {
15
15
  readonly cells: readonly Cell[];
16
16
  }
17
- /** One body row of a section: prose, or columns a renderer places itself. */
18
- export type SectionLine = string | SectionCells;
17
+ /**
18
+ * One body row of a section — GAIA-372 D3, where the third variant arrived.
19
+ *
20
+ * A body DECLARES ITSELF MARKDOWN; a plain string stays plain:
21
+ *
22
+ * - a `string` is PLAIN TEXT, rendered verbatim and **never parsed**;
23
+ * - a {@link SectionCells} is columns, unchanged (a run number opens with `#`);
24
+ * - a {@link StyledLine} is an ALREADY RENDERED body row, carrying its styled
25
+ * runs and its `heading` flag.
26
+ *
27
+ * GAIA-372 D11 — the third variant is NEUTRAL. It used to be `MarkdownLine`, which
28
+ * made this generic list primitive name a domain it does not own; a producer of
29
+ * styled rows is not necessarily a Markdown renderer, and `Component/markdown/` is
30
+ * where that domain lives now.
31
+ *
32
+ * That is what makes AC-5 STRUCTURAL rather than careful. `Assignment`, `Time`,
33
+ * `Branch`, `Links`, `Labels` and the run table hand over plain strings, so no
34
+ * Markdown rule can reach them — a branch called `feat/a_b_c` or a link title
35
+ * holding a `*` cannot be re-styled or re-spelled by a parser that never sees it.
36
+ * It is also strictly LESS parsing than before GAIA-372, when
37
+ * `Component/markdown/`'s `markdownRow` ran over every one of those rows.
38
+ *
39
+ * The rejected alternative was a block-aware pass inside {@link sectionRows} over
40
+ * `section.lines`, for exactly that reason: it would run the renderer over every
41
+ * non-Markdown section, and the flat `Comments` section would run one stateful
42
+ * pass across ALL comments' bodies, letting an unterminated fence in one comment
43
+ * swallow the next.
44
+ */
45
+ export type SectionLine = string | SectionCells | StyledLine;
19
46
  /**
20
47
  * A line as the text a surface with no layout prints.
21
48
  *
@@ -23,29 +50,29 @@ export type SectionLine = string | SectionCells;
23
50
  * puts between two cells: the pipe and the tree then say the same thing about
24
51
  * what separates two columns, and only the alignment inside a column is the
25
52
  * renderer's own.
53
+ *
54
+ * GAIA-372 D4 — for a {@link StyledLine} this returns the RENDERED text, which is the
55
+ * whole of how parity is achieved: `SectionRow.text` carries it and the pipe
56
+ * prints exactly that, so `gaia ui <id> | cat` and the interactive tree are the
57
+ * same rendering by construction. The tree merely adds the attributes a pipe
58
+ * cannot carry. There is no raw-only pipe path left to disagree (AC-3).
26
59
  */
27
60
  export declare const lineText: (line: SectionLine) => string;
61
+ /**
62
+ * Any body line as a RENDERED line — GAIA-372 D3/D8.
63
+ *
64
+ * A plain or columned row is LIFTED (one unparsed span) rather than refused,
65
+ * because a consumer that needs rendered lines — the collapsible Description
66
+ * entry, whose extract reads `line.heading` — is handed a `Description` section
67
+ * whose empty state is the plain string `No description.` Lifting never parses,
68
+ * so nothing here can re-introduce a Markdown rule on the plain path.
69
+ */
70
+ export declare const styledOf: (line: SectionLine) => StyledLine;
28
71
  /** A titled block of already-flattened physical rows. */
29
72
  export interface Section {
30
73
  heading: string;
31
74
  lines: SectionLine[];
32
75
  }
33
- /**
34
- * The MINIMAL MARKDOWN rule: an ATX heading (`#`…`######`) renders as bold with
35
- * the markers stripped — the same rule a section heading follows. Bullets and
36
- * code fences stay raw by design (explicit v1 decision).
37
- *
38
- * ONE function, because every renderer of a body line goes through it:
39
- * {@link sectionRows} for a flat section, {@link expandableRows} for a
40
- * collapsible one, and `Entity/run/Component/run-detail` for a log entry. The
41
- * Description block is rendered by two of them at once — flat on the piped
42
- * render, expandable on the Overview tab — and a rule living in only one would
43
- * make the same body look different depending on which renderer reached it.
44
- */
45
- export declare function markdownRow(line: string): {
46
- text: string;
47
- bold: boolean;
48
- };
49
76
  /**
50
77
  * One flattened row: its text, whether it is bold, and WHICH ROW IT IS
51
78
  * (GAIA-340 D3/D4).
@@ -69,6 +96,17 @@ export interface SectionRow {
69
96
  * where the difference is made.
70
97
  */
71
98
  readonly cells?: readonly Cell[];
99
+ /**
100
+ * The row's STYLED RUNS, for a row that carries per-run styling — GAIA-372
101
+ * D5/D11.
102
+ *
103
+ * Absent means everything is exactly as it was: one attribute set for the whole
104
+ * row. `text` is the same row for a surface that cannot paint a run, so the
105
+ * pipe never has to know the difference (D4). Already-resolved attributes and
106
+ * colours: WHICH run asks for which is `Component/markdown/`'s business, not
107
+ * this primitive's.
108
+ */
109
+ readonly runs?: readonly StyledRun[];
72
110
  }
73
111
  /**
74
112
  * A heading as an id fragment: `PER USER` → `per-user`.
@@ -79,6 +117,55 @@ export interface SectionRow {
79
117
  * it, and `patchTree` would rebuild it instead of writing to it (GAIA-340 D4).
80
118
  */
81
119
  export declare const sectionKey: (heading: string) => string;
120
+ /**
121
+ * The same, for a heading that is DATA rather than one of the built-in set —
122
+ * GAIA-373 AC-10.
123
+ *
124
+ * {@link sectionKey} is LOSSY: it lowercases and collapses every run of
125
+ * non-alphanumeric characters onto one `-`. That is injective over the closed
126
+ * built-in headings (`Per user`, `Latest summary`), and it stops being injective
127
+ * the moment a contributed panel heads its sections with whatever it is
128
+ * listing. The shipped artifacts viewer heads them with paths under the ticket's
129
+ * tree, so `/`, `.` and `-` all land on the same character and two ordinary
130
+ * captures collapse onto one key:
131
+ *
132
+ * playwright/logs/walk.txt -> playwright-logs-walk-txt
133
+ * playwright/logs-walk.txt -> playwright-logs-walk-txt
134
+ *
135
+ * A key is an IDENTITY — `Kernel/mount.ts` refuses a repeated id, and it refuses
136
+ * it while MOUNTING, past the host's guard around `panel.sections()` and on a
137
+ * draw path with no `catch` — so a lossy key is not a cosmetic collision, it is
138
+ * the cockpit going down on a message naming neither the addon nor the file.
139
+ *
140
+ * SO THE SEPARATORS ARE ENCODED RATHER THAN COLLAPSED. Every UTF-8 byte outside
141
+ * `[a-z0-9]` becomes `_<hex>`, two digits, lower case. The escape has a fixed
142
+ * length and `_` is itself escaped (`_5f`), so the mapping is reversible and
143
+ * therefore injective — including over case (`README.txt` and `readme.txt` are
144
+ * two files on this filesystem) and over a heading that already looks like an
145
+ * escape.
146
+ *
147
+ * WHY THE OUTPUT CARRIES NO `-`, which is a second property and not a side
148
+ * effect. A key never becomes an id on its own: it is always suffixed
149
+ * (`-heading`, `-gap`, `-empty`, `-header`, `-<index>`). Injectivity of the key
150
+ * alone would not settle the id, because the suffix boundary would be
151
+ * ambiguous — with `-` allowed inside a key, section `a` at body index 12 and
152
+ * section `a-1` at body index 2 would both be `a-12`. With no `-` inside a key
153
+ * the first `-` after the screen's own prefix always ends the key, so a key and
154
+ * its suffix are both recovered from an id and two ids can only be equal when
155
+ * both halves are. Within one section the suffixes are distinct by construction:
156
+ * the indices are digits and the four literals are not.
157
+ *
158
+ * NOT THE BUILT-IN PATH. `sectionRows` and the ticket detail's tabs keep
159
+ * {@link sectionKey} as their default, so no built-in node id moves (AC-2) — a
160
+ * `_` in a key is exactly the mark of a heading that came from a contribution.
161
+ *
162
+ * WHAT THIS STILL DOES NOT FIX, said plainly: two panel sections carrying the
163
+ * SAME heading. No injective map over headings can separate two identical
164
+ * headings, so that case remains the loud refusal it is today. It is out of
165
+ * scope for GAIA-373 and belongs to whatever gives a panel section an identity
166
+ * distinct from its title.
167
+ */
168
+ export declare const panelSectionKey: (heading: string) => string;
82
169
  /**
83
170
  * Flatten `{heading, lines}` sections into KEYED rows: the heading, its body,
84
171
  * and the blank line that closes the section.
@@ -89,7 +176,7 @@ export declare const sectionKey: (heading: string) => string;
89
176
  * property that lets the Dashboard hand over renderables while its pipe keeps
90
177
  * emitting the bytes it always did (spec F5).
91
178
  */
92
- export declare function sectionRows(sections: Section[]): SectionRow[];
179
+ export declare function sectionRows(sections: Section[], keyOf?: (heading: string) => string): SectionRow[];
93
180
  /**
94
181
  * One flattened row as the node a converted screen hands over — GAIA-340 D1.
95
182
  *
@@ -120,10 +207,15 @@ export interface ExpandableEntry {
120
207
  key: string;
121
208
  /** The header row's text, after the caret. */
122
209
  header: string;
123
- /** Rows shown while collapsed (indented like the expanded body). */
124
- collapsed: string[];
210
+ /**
211
+ * Rows shown while collapsed (indented like the expanded body).
212
+ *
213
+ * GAIA-372 D3 — a {@link SectionLine}, so a rendered body row keeps its runs
214
+ * on this surface too and a plain string is still never parsed.
215
+ */
216
+ collapsed: SectionLine[];
125
217
  /** Rows shown while expanded. */
126
- expanded: string[];
218
+ expanded: SectionLine[];
127
219
  }
128
220
  /** The rendered rows of a list of expandable entries. */
129
221
  export interface ExpandableView {