@unofficialbox/box-open-elements 0.5.0 → 0.6.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 (219) hide show
  1. package/README.md +3 -3
  2. package/dist/components/collections/table.d.ts +57 -3
  3. package/dist/components/collections/table.js +339 -20
  4. package/dist/components/feedback/due-badge.d.ts +38 -0
  5. package/dist/components/feedback/due-badge.js +168 -0
  6. package/dist/components/feedback/due-types.d.ts +32 -0
  7. package/dist/components/feedback/due-types.js +91 -0
  8. package/dist/components/feedback/progress-steps.d.ts +51 -5
  9. package/dist/components/feedback/progress-steps.js +200 -22
  10. package/dist/components/feedback/stage-path.d.ts +44 -0
  11. package/dist/components/feedback/stage-path.js +235 -0
  12. package/dist/components/forms/select.d.ts +21 -0
  13. package/dist/components/forms/select.js +100 -3
  14. package/dist/components/forms/text-field.d.ts +21 -0
  15. package/dist/components/forms/text-field.js +100 -1
  16. package/dist/components/layout/nav-sidebar.d.ts +14 -0
  17. package/dist/components/layout/nav-sidebar.js +49 -0
  18. package/dist/components/layout/split-view.d.ts +14 -0
  19. package/dist/components/layout/split-view.js +80 -1
  20. package/dist/components/overlays/command-palette.d.ts +62 -0
  21. package/dist/components/overlays/command-palette.js +537 -0
  22. package/dist/components/overlays/command-types.d.ts +90 -0
  23. package/dist/components/overlays/command-types.js +259 -0
  24. package/dist/components/overlays/drawer.d.ts +18 -0
  25. package/dist/components/overlays/drawer.js +135 -9
  26. package/dist/components/overlays/shortcuts-overlay.d.ts +48 -0
  27. package/dist/components/overlays/shortcuts-overlay.js +385 -0
  28. package/dist/components/overlays/tooltip.js +1 -1
  29. package/dist/element-maps.d.ts +292 -0
  30. package/dist/element-maps.js +1 -0
  31. package/dist/entries/activity-density.d.ts +1 -0
  32. package/dist/entries/activity-density.js +1 -0
  33. package/dist/entries/agent-chat.d.ts +1 -0
  34. package/dist/entries/agent-chat.js +1 -0
  35. package/dist/entries/audit-log.d.ts +1 -0
  36. package/dist/entries/audit-log.js +1 -0
  37. package/dist/entries/command-palette.d.ts +1 -0
  38. package/dist/entries/command-palette.js +1 -0
  39. package/dist/entries/compare-view.d.ts +1 -0
  40. package/dist/entries/compare-view.js +1 -0
  41. package/dist/entries/diff-viewer.d.ts +1 -0
  42. package/dist/entries/diff-viewer.js +1 -0
  43. package/dist/entries/due-badge.d.ts +1 -0
  44. package/dist/entries/due-badge.js +1 -0
  45. package/dist/entries/form-wizard.d.ts +1 -0
  46. package/dist/entries/form-wizard.js +1 -0
  47. package/dist/entries/lineage-graph.d.ts +1 -0
  48. package/dist/entries/lineage-graph.js +1 -0
  49. package/dist/entries/notification-bell.d.ts +1 -0
  50. package/dist/entries/notification-bell.js +1 -0
  51. package/dist/entries/notification-inbox.d.ts +1 -0
  52. package/dist/entries/notification-inbox.js +1 -0
  53. package/dist/entries/provenance-strip.d.ts +1 -0
  54. package/dist/entries/provenance-strip.js +1 -0
  55. package/dist/entries/run-trace.d.ts +1 -0
  56. package/dist/entries/run-trace.js +1 -0
  57. package/dist/entries/shortcuts-overlay.d.ts +1 -0
  58. package/dist/entries/shortcuts-overlay.js +1 -0
  59. package/dist/entries/signature-ceremony.d.ts +1 -0
  60. package/dist/entries/signature-ceremony.js +1 -0
  61. package/dist/entries/stage-path.d.ts +1 -0
  62. package/dist/entries/stage-path.js +1 -0
  63. package/dist/entries/timeline.d.ts +1 -0
  64. package/dist/entries/timeline.js +1 -0
  65. package/dist/entries/version-graph.d.ts +1 -0
  66. package/dist/entries/version-graph.js +1 -0
  67. package/dist/entries/version-list.d.ts +1 -0
  68. package/dist/entries/version-list.js +1 -0
  69. package/dist/entries/wizard-summary.d.ts +1 -0
  70. package/dist/entries/wizard-summary.js +1 -0
  71. package/dist/entries/work-queue.d.ts +1 -0
  72. package/dist/entries/work-queue.js +1 -0
  73. package/dist/entries/workload-board.d.ts +1 -0
  74. package/dist/entries/workload-board.js +1 -0
  75. package/dist/index.d.ts +21 -0
  76. package/dist/index.js +21 -0
  77. package/dist/patterns/agent-chat/agent-chat.d.ts +75 -0
  78. package/dist/patterns/agent-chat/agent-chat.js +820 -0
  79. package/dist/patterns/agent-chat/controller.d.ts +27 -0
  80. package/dist/patterns/agent-chat/controller.js +182 -0
  81. package/dist/patterns/agent-chat/index.d.ts +3 -0
  82. package/dist/patterns/agent-chat/index.js +6 -0
  83. package/dist/patterns/agent-chat/types.d.ts +109 -0
  84. package/dist/patterns/agent-chat/types.js +1 -0
  85. package/dist/patterns/audit/activity-density.d.ts +68 -0
  86. package/dist/patterns/audit/activity-density.js +428 -0
  87. package/dist/patterns/audit/audit-log.d.ts +81 -0
  88. package/dist/patterns/audit/audit-log.js +794 -0
  89. package/dist/patterns/audit/index.d.ts +3 -0
  90. package/dist/patterns/audit/index.js +7 -0
  91. package/dist/patterns/audit/shared.d.ts +10 -0
  92. package/dist/patterns/audit/shared.js +50 -0
  93. package/dist/patterns/audit/types.d.ts +143 -0
  94. package/dist/patterns/audit/types.js +327 -0
  95. package/dist/patterns/content-explorer/actions/controller.js +12 -2
  96. package/dist/patterns/content-explorer/box-transport.js +117 -25
  97. package/dist/patterns/content-explorer/content-explorer.d.ts +13 -1
  98. package/dist/patterns/content-explorer/content-explorer.js +36 -3
  99. package/dist/patterns/content-explorer/contracts.d.ts +24 -1
  100. package/dist/patterns/content-explorer/contracts.js +68 -11
  101. package/dist/patterns/content-explorer/controller.d.ts +16 -1
  102. package/dist/patterns/content-explorer/controller.js +130 -4
  103. package/dist/patterns/content-explorer/navigation/controller.js +11 -9
  104. package/dist/patterns/content-explorer/types.d.ts +58 -1
  105. package/dist/patterns/content-picker/content-picker.d.ts +71 -0
  106. package/dist/patterns/content-picker/content-picker.js +747 -0
  107. package/dist/patterns/content-picker/controller.d.ts +38 -0
  108. package/dist/patterns/content-picker/controller.js +141 -0
  109. package/dist/patterns/content-picker/index.d.ts +3 -0
  110. package/dist/patterns/content-picker/index.js +6 -0
  111. package/dist/patterns/content-picker/types.d.ts +52 -0
  112. package/dist/patterns/content-picker/types.js +26 -0
  113. package/dist/patterns/content-sidebar/content-sidebar.d.ts +43 -0
  114. package/dist/patterns/content-sidebar/content-sidebar.js +293 -0
  115. package/dist/patterns/content-sidebar/index.d.ts +2 -0
  116. package/dist/patterns/content-sidebar/index.js +4 -0
  117. package/dist/patterns/content-sidebar/types.d.ts +18 -0
  118. package/dist/patterns/content-sidebar/types.js +20 -0
  119. package/dist/patterns/content-uploader/box-transport.d.ts +14 -0
  120. package/dist/patterns/content-uploader/box-transport.js +54 -0
  121. package/dist/patterns/content-uploader/content-uploader.d.ts +62 -0
  122. package/dist/patterns/content-uploader/content-uploader.js +559 -0
  123. package/dist/patterns/content-uploader/controller.d.ts +39 -0
  124. package/dist/patterns/content-uploader/controller.js +227 -0
  125. package/dist/patterns/content-uploader/index.d.ts +4 -0
  126. package/dist/patterns/content-uploader/index.js +8 -0
  127. package/dist/patterns/content-uploader/types.d.ts +120 -0
  128. package/dist/patterns/content-uploader/types.js +38 -0
  129. package/dist/patterns/diff/compare-view.d.ts +47 -0
  130. package/dist/patterns/diff/compare-view.js +276 -0
  131. package/dist/patterns/diff/diff-viewer.d.ts +42 -0
  132. package/dist/patterns/diff/diff-viewer.js +434 -0
  133. package/dist/patterns/diff/engine.d.ts +22 -0
  134. package/dist/patterns/diff/engine.js +276 -0
  135. package/dist/patterns/diff/index.d.ts +4 -0
  136. package/dist/patterns/diff/index.js +8 -0
  137. package/dist/patterns/diff/types.d.ts +69 -0
  138. package/dist/patterns/diff/types.js +24 -0
  139. package/dist/patterns/form-wizard/controller.d.ts +39 -0
  140. package/dist/patterns/form-wizard/controller.js +157 -0
  141. package/dist/patterns/form-wizard/form-wizard.d.ts +63 -0
  142. package/dist/patterns/form-wizard/form-wizard.js +468 -0
  143. package/dist/patterns/form-wizard/index.d.ts +4 -0
  144. package/dist/patterns/form-wizard/index.js +8 -0
  145. package/dist/patterns/form-wizard/types.d.ts +109 -0
  146. package/dist/patterns/form-wizard/types.js +83 -0
  147. package/dist/patterns/form-wizard/wizard-summary.d.ts +62 -0
  148. package/dist/patterns/form-wizard/wizard-summary.js +324 -0
  149. package/dist/patterns/internal/safe-href.d.ts +17 -0
  150. package/dist/patterns/internal/safe-href.js +17 -0
  151. package/dist/patterns/lineage/index.d.ts +3 -0
  152. package/dist/patterns/lineage/index.js +6 -0
  153. package/dist/patterns/lineage/lineage-graph.d.ts +30 -0
  154. package/dist/patterns/lineage/lineage-graph.js +434 -0
  155. package/dist/patterns/lineage/provenance-strip.d.ts +22 -0
  156. package/dist/patterns/lineage/provenance-strip.js +194 -0
  157. package/dist/patterns/lineage/types.d.ts +53 -0
  158. package/dist/patterns/lineage/types.js +49 -0
  159. package/dist/patterns/notifications/index.d.ts +3 -0
  160. package/dist/patterns/notifications/index.js +6 -0
  161. package/dist/patterns/notifications/notification-bell.d.ts +38 -0
  162. package/dist/patterns/notifications/notification-bell.js +198 -0
  163. package/dist/patterns/notifications/notification-inbox.d.ts +46 -0
  164. package/dist/patterns/notifications/notification-inbox.js +537 -0
  165. package/dist/patterns/notifications/types.d.ts +61 -0
  166. package/dist/patterns/notifications/types.js +108 -0
  167. package/dist/patterns/preview/annotation-inspector.d.ts +25 -6
  168. package/dist/patterns/preview/annotation-inspector.js +239 -94
  169. package/dist/patterns/preview/annotation-thread.d.ts +25 -5
  170. package/dist/patterns/preview/annotation-thread.js +233 -82
  171. package/dist/patterns/preview/annotation-toolbar.d.ts +20 -6
  172. package/dist/patterns/preview/annotation-toolbar.js +162 -96
  173. package/dist/patterns/preview/content-preview-adapter.d.ts +10 -1
  174. package/dist/patterns/preview/content-preview-adapter.js +26 -1
  175. package/dist/patterns/preview/preview-element.d.ts +15 -2
  176. package/dist/patterns/preview/preview-element.js +223 -52
  177. package/dist/patterns/preview/provider-adapter.d.ts +47 -0
  178. package/dist/patterns/preview/provider-adapter.js +17 -0
  179. package/dist/patterns/run/index.d.ts +2 -0
  180. package/dist/patterns/run/index.js +4 -0
  181. package/dist/patterns/run/run-trace.d.ts +40 -0
  182. package/dist/patterns/run/run-trace.js +441 -0
  183. package/dist/patterns/run/types.d.ts +66 -0
  184. package/dist/patterns/run/types.js +152 -0
  185. package/dist/patterns/signature/index.d.ts +2 -0
  186. package/dist/patterns/signature/index.js +4 -0
  187. package/dist/patterns/signature/signature-ceremony.d.ts +33 -0
  188. package/dist/patterns/signature/signature-ceremony.js +317 -0
  189. package/dist/patterns/signature/types.d.ts +68 -0
  190. package/dist/patterns/signature/types.js +89 -0
  191. package/dist/patterns/timeline/index.d.ts +2 -0
  192. package/dist/patterns/timeline/index.js +4 -0
  193. package/dist/patterns/timeline/timeline.d.ts +27 -0
  194. package/dist/patterns/timeline/timeline.js +430 -0
  195. package/dist/patterns/timeline/types.d.ts +36 -0
  196. package/dist/patterns/timeline/types.js +27 -0
  197. package/dist/patterns/versions/graph-layout.d.ts +43 -0
  198. package/dist/patterns/versions/graph-layout.js +159 -0
  199. package/dist/patterns/versions/index.d.ts +4 -0
  200. package/dist/patterns/versions/index.js +8 -0
  201. package/dist/patterns/versions/shared.d.ts +4 -0
  202. package/dist/patterns/versions/shared.js +13 -0
  203. package/dist/patterns/versions/types.d.ts +37 -0
  204. package/dist/patterns/versions/types.js +39 -0
  205. package/dist/patterns/versions/version-graph.d.ts +36 -0
  206. package/dist/patterns/versions/version-graph.js +438 -0
  207. package/dist/patterns/versions/version-list.d.ts +39 -0
  208. package/dist/patterns/versions/version-list.js +407 -0
  209. package/dist/patterns/work-queue/controller.d.ts +26 -0
  210. package/dist/patterns/work-queue/controller.js +150 -0
  211. package/dist/patterns/work-queue/index.d.ts +4 -0
  212. package/dist/patterns/work-queue/index.js +7 -0
  213. package/dist/patterns/work-queue/types.d.ts +128 -0
  214. package/dist/patterns/work-queue/types.js +45 -0
  215. package/dist/patterns/work-queue/work-queue.d.ts +48 -0
  216. package/dist/patterns/work-queue/work-queue.js +534 -0
  217. package/dist/patterns/work-queue/workload-board.d.ts +53 -0
  218. package/dist/patterns/work-queue/workload-board.js +608 -0
  219. package/package.json +89 -5
@@ -0,0 +1,794 @@
1
+ import { AUDIT_GROUP_BY_LABELS, AUDIT_GROUP_BY_VALUES, filterAuditEvents, groupAuditEvents, hasAuditFacets, resolveAuditGroupBy, summarizeAuditFacets, toAuditCsv, } from "./types.js";
2
+ import { auditToneColor, escapeHtml, formatAuditTimestamp, resolveReferenceTime, } from "./shared.js";
3
+ import { isSafeHref } from "../internal/safe-href.js";
4
+ import { isTimelineEventRecord, resolveTimelineTone } from "../timeline/types.js";
5
+ import { BaseElement } from "../../core/index.js";
6
+ import { boeInputControlStyles, boePanel, boeRadius } from "../../foundations/geometry/index.js";
7
+ import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
8
+ const DEFAULT_TAG_NAME = "box-audit-log";
9
+ /** Facet attribute ↔ `AuditFacets` key. One row per facet, no other mapping. */
10
+ const FACET_ATTRIBUTES = [
11
+ ["actor", "facet-actor"],
12
+ ["action", "facet-action"],
13
+ ["correlationId", "facet-correlation-id"],
14
+ ["from", "facet-from"],
15
+ ["to", "facet-to"],
16
+ ];
17
+ const elementStyles = `
18
+ [hidden] {
19
+ display: none !important;
20
+ }
21
+
22
+ :host {
23
+ display: block;
24
+ color: inherit;
25
+ font: inherit;
26
+ }
27
+
28
+ [part="panel"] {
29
+ display: grid;
30
+ gap: ${boePanel.gap};
31
+ padding: ${boePanel.padding};
32
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 82%, transparent);
33
+ border-radius: ${boePanel.radius};
34
+ background: ${boePanel.background};
35
+ }
36
+
37
+ [part="title"] {
38
+ margin: 0;
39
+ font: inherit;
40
+ font-size: 1.1rem;
41
+ font-weight: 700;
42
+ color: var(--boe-token-text-text, #1f1e1b);
43
+ }
44
+
45
+ [part="toolbar"] {
46
+ display: flex;
47
+ flex-wrap: wrap;
48
+ align-items: end;
49
+ gap: 0.6rem;
50
+ }
51
+
52
+ [part="group-by"] {
53
+ display: inline-flex;
54
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
55
+ border-radius: 999px;
56
+ overflow: hidden;
57
+ }
58
+
59
+ [part="group-by-option"] {
60
+ appearance: none;
61
+ font: inherit;
62
+ font-size: 0.8rem;
63
+ font-weight: 600;
64
+ padding: 0.3rem 0.7rem;
65
+ border: none;
66
+ background: transparent;
67
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
68
+ cursor: pointer;
69
+ transition: background ${boeMotionDuration.interactive} ${boeMotionEasing.standard};
70
+ }
71
+
72
+ [part="group-by-option"][aria-pressed="true"] {
73
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 12%, transparent);
74
+ color: var(--boe-token-surface-surface-brand, #0061d5);
75
+ }
76
+
77
+ [part="facet"] {
78
+ display: grid;
79
+ gap: 0.2rem;
80
+ font-size: 0.72rem;
81
+ font-weight: 700;
82
+ letter-spacing: 0.06em;
83
+ text-transform: uppercase;
84
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
85
+ }
86
+
87
+ ${boeInputControlStyles('[part="facet"] select, [part="facet"] input')}
88
+
89
+ [part="facet"] select,
90
+ [part="facet"] input {
91
+ font: inherit;
92
+ font-size: 0.85rem;
93
+ text-transform: none;
94
+ letter-spacing: normal;
95
+ font-weight: 400;
96
+ }
97
+
98
+ [part="clear-filters"],
99
+ [part="export"],
100
+ [part="drilldown-clear"] {
101
+ appearance: none;
102
+ font: inherit;
103
+ font-size: 0.8rem;
104
+ font-weight: 600;
105
+ padding: 0.35rem 0.7rem;
106
+ border-radius: ${boeRadius.control};
107
+ border: 1px solid var(--boe-token-stroke-stroke, #e8e8e8);
108
+ background: var(--boe-token-surface-surface, #ffffff);
109
+ color: var(--boe-token-text-text, #222222);
110
+ cursor: pointer;
111
+ }
112
+
113
+ [part="clear-filters"]:hover,
114
+ [part="export"]:hover,
115
+ [part="drilldown-clear"]:hover {
116
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
117
+ border-color: var(--boe-token-stroke-stroke-hover, #bcbcbc);
118
+ }
119
+
120
+ [part="drilldown"] {
121
+ display: flex;
122
+ flex-wrap: wrap;
123
+ align-items: center;
124
+ gap: 0.5rem;
125
+ padding: 0.4rem 0.6rem;
126
+ border-radius: ${boeRadius.med};
127
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 8%, transparent);
128
+ font-size: 0.82rem;
129
+ }
130
+
131
+ [part="drilldown-id"] {
132
+ font-family: ui-monospace, monospace;
133
+ font-weight: 700;
134
+ }
135
+
136
+ [part="result-summary"] {
137
+ margin: 0;
138
+ font-size: 0.8rem;
139
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
140
+ }
141
+
142
+ [part="groups"] {
143
+ display: grid;
144
+ gap: 0.5rem;
145
+ }
146
+
147
+ [part="group"] {
148
+ border: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
149
+ border-radius: ${boeRadius.med};
150
+ overflow: hidden;
151
+ }
152
+
153
+ [part="group-heading"] {
154
+ margin: 0;
155
+ font: inherit;
156
+ }
157
+
158
+ [part="group-toggle"] {
159
+ appearance: none;
160
+ display: flex;
161
+ inline-size: 100%;
162
+ align-items: baseline;
163
+ gap: 0.5rem;
164
+ padding: 0.5rem 0.65rem;
165
+ border: none;
166
+ background: var(--boe-token-surface-surface-hover, #f4f4f4);
167
+ color: var(--boe-token-text-text, #1f1e1b);
168
+ font: inherit;
169
+ font-weight: 700;
170
+ text-align: start;
171
+ cursor: pointer;
172
+ }
173
+
174
+ [part="group-toggle"]::before {
175
+ content: "▾";
176
+ font-size: 0.7rem;
177
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
178
+ }
179
+
180
+ [part="group-toggle"][aria-expanded="false"]::before {
181
+ content: "▸";
182
+ }
183
+
184
+ [part="group-count"] {
185
+ margin-left: auto;
186
+ font-size: 0.76rem;
187
+ font-weight: 600;
188
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
189
+ }
190
+
191
+ [part="events"] {
192
+ list-style: none;
193
+ margin: 0;
194
+ padding: 0;
195
+ display: grid;
196
+ }
197
+
198
+ [part="event"] {
199
+ display: grid;
200
+ gap: 0.25rem;
201
+ padding: 0.5rem 0.65rem;
202
+ border-top: 1px solid color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 55%, transparent);
203
+ border-left: 3px solid var(--audit-tone, transparent);
204
+ }
205
+
206
+ [part="event-topline"] {
207
+ display: flex;
208
+ flex-wrap: wrap;
209
+ align-items: baseline;
210
+ gap: 0.4rem;
211
+ }
212
+
213
+ [part="event-action"] {
214
+ appearance: none;
215
+ padding: 0;
216
+ border: none;
217
+ background: none;
218
+ font: inherit;
219
+ font-weight: 600;
220
+ color: var(--boe-token-text-text, #1f1e1b);
221
+ text-align: start;
222
+ cursor: pointer;
223
+ }
224
+
225
+ [part="event-action"]:hover {
226
+ text-decoration: underline;
227
+ }
228
+
229
+ [part="actor"] {
230
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
231
+ font-size: 0.82rem;
232
+ }
233
+
234
+ [part="badge"] {
235
+ display: inline-flex;
236
+ padding: 0.12rem 0.42rem;
237
+ border-radius: 999px;
238
+ background: color-mix(in srgb, var(--audit-tone, var(--boe-token-surface-surface-brand, #0061d5)) 12%, transparent);
239
+ color: color-mix(in srgb, var(--audit-tone, var(--boe-token-surface-surface-brand, #0061d5)) 74%, black 26%);
240
+ font-size: 0.72rem;
241
+ font-weight: 600;
242
+ }
243
+
244
+ [part="timestamp"] {
245
+ margin-left: auto;
246
+ white-space: nowrap;
247
+ font-size: 0.75rem;
248
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
249
+ }
250
+
251
+ [part="event-summary"] {
252
+ margin: 0;
253
+ font-size: 0.85rem;
254
+ line-height: 1.5;
255
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
256
+ }
257
+
258
+ [part="evidence"] {
259
+ display: flex;
260
+ flex-wrap: wrap;
261
+ gap: 0.35rem;
262
+ }
263
+
264
+ [part="evidence-link"] {
265
+ appearance: none;
266
+ display: inline-flex;
267
+ align-items: center;
268
+ padding: 0.18rem 0.5rem;
269
+ border: 1px solid color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 30%, transparent);
270
+ border-radius: 999px;
271
+ background: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 6%, transparent);
272
+ color: var(--boe-token-surface-surface-brand, #0061d5);
273
+ font: inherit;
274
+ font-size: 0.74rem;
275
+ font-weight: 600;
276
+ text-decoration: none;
277
+ cursor: pointer;
278
+ }
279
+
280
+ [part="correlation"] {
281
+ appearance: none;
282
+ justify-self: start;
283
+ padding: 0.1rem 0.35rem;
284
+ border: 1px dashed color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 90%, transparent);
285
+ border-radius: ${boeRadius.size};
286
+ background: none;
287
+ font-family: ui-monospace, monospace;
288
+ font-size: 0.7rem;
289
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
290
+ cursor: pointer;
291
+ }
292
+
293
+ [part="correlation"]:hover {
294
+ color: var(--boe-token-surface-surface-brand, #0061d5);
295
+ border-color: color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 45%, transparent);
296
+ }
297
+
298
+ [part="empty"] {
299
+ padding: ${boePanel.padding};
300
+ border-radius: ${boeRadius.large};
301
+ border: 1px dashed color-mix(in srgb, var(--boe-token-stroke-stroke, #e8e8e8) 70%, transparent);
302
+ color: var(--boe-token-text-text-secondary, #6f6f6f);
303
+ }
304
+
305
+ [part="group-by-option"]:focus-visible,
306
+ [part="group-toggle"]:focus-visible,
307
+ [part="event-action"]:focus-visible,
308
+ [part="evidence-link"]:focus-visible,
309
+ [part="correlation"]:focus-visible,
310
+ [part="clear-filters"]:focus-visible,
311
+ [part="export"]:focus-visible,
312
+ [part="drilldown-clear"]:focus-visible {
313
+ outline: none;
314
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--boe-token-surface-surface-brand, #0061d5) 18%, transparent);
315
+ }
316
+ `;
317
+ /**
318
+ * The aggregation, faceting, and export layer over the `box-timeline` event
319
+ * contract (opportunity 3 of `plans/component-opportunities.md`): the same
320
+ * records the flat feed renders, grouped into collapsible day/actor/action
321
+ * sections with counts, narrowed by facets and a date range, drillable to one
322
+ * workflow run by correlation id, and exportable as CSV.
323
+ *
324
+ * Aggregation is client-side and pure. Server-side paging and row
325
+ * virtualization for production-scale logs are tracked depth limitations.
326
+ */
327
+ export class AuditLog extends BaseElement {
328
+ static tagName = DEFAULT_TAG_NAME;
329
+ static get observedAttributes() {
330
+ return [
331
+ "events",
332
+ "exportable",
333
+ "facet-action",
334
+ "facet-actor",
335
+ "facet-correlation-id",
336
+ "facet-from",
337
+ "facet-to",
338
+ "group-by",
339
+ "heading",
340
+ "reference-time",
341
+ ];
342
+ }
343
+ toolbarEl;
344
+ groupByEl;
345
+ actorSelectEl;
346
+ actionSelectEl;
347
+ fromInputEl;
348
+ toInputEl;
349
+ clearFiltersEl;
350
+ exportEl;
351
+ drilldownEl;
352
+ drilldownIdEl;
353
+ summaryEl;
354
+ groupsEl;
355
+ emptyEl;
356
+ titleEl;
357
+ /** Section keys the reader has collapsed. Absent means expanded. */
358
+ collapsedGroups = new Set();
359
+ contentSignature = "";
360
+ optionsSignature = "";
361
+ /** Raw `events` attribute the parse cache was built from. */
362
+ eventsRaw = null;
363
+ eventsCache = [];
364
+ get heading() {
365
+ return this.getAttribute("heading") ?? "Audit log";
366
+ }
367
+ set heading(value) {
368
+ this.setAttribute("heading", value);
369
+ }
370
+ /**
371
+ * Audit records; JSON payloads are validated per record before rendering.
372
+ *
373
+ * Memoized on the raw attribute so a single keystroke in the date field
374
+ * does not re-parse and re-validate the whole payload once per reader
375
+ * (`update`, `visibleEvents`, and each click-handler branch all read it).
376
+ * The cache invalidates itself when the attribute changes; the returned
377
+ * array is a copy, so a caller cannot mutate the log out from under it.
378
+ */
379
+ get events() {
380
+ const raw = this.getAttribute("events");
381
+ if (!raw) {
382
+ return [];
383
+ }
384
+ if (raw !== this.eventsRaw) {
385
+ this.eventsRaw = raw;
386
+ try {
387
+ const parsed = JSON.parse(raw);
388
+ this.eventsCache =
389
+ Array.isArray(parsed) && parsed.every(isTimelineEventRecord)
390
+ ? parsed
391
+ : [];
392
+ }
393
+ catch {
394
+ this.eventsCache = [];
395
+ }
396
+ }
397
+ return [...this.eventsCache];
398
+ }
399
+ set events(value) {
400
+ if (value.length) {
401
+ this.setAttribute("events", JSON.stringify(value));
402
+ return;
403
+ }
404
+ this.removeAttribute("events");
405
+ }
406
+ get groupBy() {
407
+ return resolveAuditGroupBy(this.getAttribute("group-by"));
408
+ }
409
+ set groupBy(value) {
410
+ this.setAttribute("group-by", resolveAuditGroupBy(value));
411
+ }
412
+ get facets() {
413
+ const facets = {};
414
+ for (const [key, attribute] of FACET_ATTRIBUTES) {
415
+ const value = this.getAttribute(attribute);
416
+ if (value) {
417
+ facets[key] = value;
418
+ }
419
+ }
420
+ return facets;
421
+ }
422
+ /** Replaces the whole selection — an omitted or empty facet is cleared. */
423
+ set facets(value) {
424
+ for (const [key, attribute] of FACET_ATTRIBUTES) {
425
+ const next = value[key];
426
+ if (next) {
427
+ this.setAttribute(attribute, next);
428
+ }
429
+ else {
430
+ this.removeAttribute(attribute);
431
+ }
432
+ }
433
+ }
434
+ get exportable() {
435
+ return this.hasAttribute("exportable");
436
+ }
437
+ set exportable(value) {
438
+ this.toggleAttribute("exportable", value);
439
+ }
440
+ get referenceTime() {
441
+ return this.getAttribute("reference-time") ?? "";
442
+ }
443
+ set referenceTime(value) {
444
+ this.setAttribute("reference-time", value);
445
+ }
446
+ /** The events currently on screen: the full set narrowed by the facets. */
447
+ get visibleEvents() {
448
+ return filterAuditEvents(this.events, this.facets);
449
+ }
450
+ toggleGroup(key, expanded) {
451
+ const next = expanded ?? this.collapsedGroups.has(key);
452
+ if (next) {
453
+ this.collapsedGroups.delete(key);
454
+ }
455
+ else {
456
+ this.collapsedGroups.add(key);
457
+ }
458
+ this.dispatchEvent(new CustomEvent("group-toggled", {
459
+ bubbles: true,
460
+ composed: true,
461
+ detail: { key, expanded: next },
462
+ }));
463
+ if (this.isRendered) {
464
+ this.update();
465
+ }
466
+ }
467
+ expandAll() {
468
+ this.collapsedGroups.clear();
469
+ if (this.isRendered) {
470
+ this.update();
471
+ }
472
+ }
473
+ collapseAll() {
474
+ for (const group of this.currentGroups()) {
475
+ this.collapsedGroups.add(group.key);
476
+ }
477
+ if (this.isRendered) {
478
+ this.update();
479
+ }
480
+ }
481
+ /**
482
+ * Export what is on screen, not the whole set — an export that silently
483
+ * widens past the reader's filters is a compliance hazard. Returns the CSV
484
+ * and emits `export-requested` so the host owns delivery (download, mail,
485
+ * archive API).
486
+ */
487
+ exportCsv() {
488
+ const events = this.visibleEvents;
489
+ const csv = toAuditCsv(events);
490
+ this.dispatchEvent(new CustomEvent("export-requested", {
491
+ bubbles: true,
492
+ composed: true,
493
+ detail: { format: "csv", csv, events },
494
+ }));
495
+ return csv;
496
+ }
497
+ attributeChangedCallback(name, oldValue, newValue) {
498
+ // A different grouping dimension mints different section keys, so the
499
+ // collapse state from the old dimension cannot be carried over.
500
+ if (name === "group-by" && oldValue !== newValue) {
501
+ this.collapsedGroups.clear();
502
+ }
503
+ super.attributeChangedCallback(name, oldValue, newValue);
504
+ }
505
+ now() {
506
+ return resolveReferenceTime(this.getAttribute("reference-time"));
507
+ }
508
+ currentGroups() {
509
+ return groupAuditEvents(this.visibleEvents, this.groupBy, this.now());
510
+ }
511
+ patchFacets(patch) {
512
+ this.facets = { ...this.facets, ...patch };
513
+ this.dispatchEvent(new CustomEvent("facets-changed", {
514
+ bubbles: true,
515
+ composed: true,
516
+ detail: { facets: this.facets },
517
+ }));
518
+ }
519
+ eventHtml(event) {
520
+ const tone = resolveTimelineTone(event.tone);
521
+ const timestamp = formatAuditTimestamp(event.timestamp);
522
+ const evidence = (event.evidence ?? [])
523
+ .map(entry => {
524
+ const attrs = `part="evidence-link" data-event-id="${escapeHtml(event.id)}" data-evidence-id="${escapeHtml(entry.id)}"`;
525
+ // An unsafe scheme renders as a button: the chip still works as an
526
+ // intent, it just never becomes a navigable link.
527
+ return entry.href && isSafeHref(entry.href)
528
+ ? `<a ${attrs} href="${escapeHtml(entry.href)}">${escapeHtml(entry.label)}</a>`
529
+ : `<button type="button" ${attrs}>${escapeHtml(entry.label)}</button>`;
530
+ })
531
+ .join("");
532
+ return `
533
+ <li part="event" data-event-id="${escapeHtml(event.id)}" data-tone="${escapeHtml(tone)}" style="--audit-tone:${auditToneColor(tone)};">
534
+ <div part="event-topline">
535
+ <button type="button" part="event-action" data-event-id="${escapeHtml(event.id)}">${escapeHtml(event.action)}</button>
536
+ ${event.actor ? `<span part="actor">${escapeHtml(event.actor.name)}</span>` : ""}
537
+ ${event.badge ? `<span part="badge">${escapeHtml(event.badge)}</span>` : ""}
538
+ ${timestamp ? `<time part="timestamp" datetime="${escapeHtml(event.timestamp ?? "")}">${escapeHtml(timestamp)}</time>` : ""}
539
+ </div>
540
+ ${event.summary ? `<p part="event-summary">${escapeHtml(event.summary)}</p>` : ""}
541
+ ${evidence ? `<div part="evidence">${evidence}</div>` : ""}
542
+ ${event.correlationId
543
+ ? `<button type="button" part="correlation" data-correlation-id="${escapeHtml(event.correlationId)}" aria-label="Show only workflow run ${escapeHtml(event.correlationId)}">${escapeHtml(event.correlationId)}</button>`
544
+ : ""}
545
+ </li>
546
+ `;
547
+ }
548
+ groupHtml(group, index) {
549
+ const toggleId = `audit-group-toggle-${String(index)}`;
550
+ const bodyId = `audit-group-body-${String(index)}`;
551
+ const actors = group.actorCount
552
+ ? ` · ${String(group.actorCount)} ${group.actorCount === 1 ? "actor" : "actors"}`
553
+ : "";
554
+ return `
555
+ <section part="group" data-group-key="${escapeHtml(group.key)}">
556
+ <h3 part="group-heading">
557
+ <button type="button" part="group-toggle" id="${toggleId}" aria-controls="${bodyId}" aria-expanded="true" data-group-key="${escapeHtml(group.key)}">
558
+ <span part="group-label">${escapeHtml(group.label)}</span>
559
+ <span part="group-count">${String(group.count)} ${group.count === 1 ? "event" : "events"}${actors}</span>
560
+ </button>
561
+ </h3>
562
+ <div part="group-body" id="${bodyId}" role="region" aria-labelledby="${toggleId}">
563
+ <ol part="events" role="list">${group.events.map(event => this.eventHtml(event)).join("")}</ol>
564
+ </div>
565
+ </section>
566
+ `;
567
+ }
568
+ renderOptions(select, values, allLabel) {
569
+ select.innerHTML = [
570
+ `<option value="">${escapeHtml(allLabel)}</option>`,
571
+ // The empty value is the all-values option, so unattributed events are
572
+ // reachable by grouping rather than by faceting.
573
+ ...values
574
+ .filter(entry => entry.value !== "")
575
+ .map(entry => `<option value="${escapeHtml(entry.value)}">${escapeHtml(entry.label)} (${String(entry.count)})</option>`),
576
+ ].join("");
577
+ }
578
+ renderTemplate() {
579
+ if (!this.shadowRoot) {
580
+ return;
581
+ }
582
+ this.shadowRoot.innerHTML = `
583
+ <style>${elementStyles}</style>
584
+ <section part="panel" aria-labelledby="audit-title">
585
+ <h2 part="title" id="audit-title"></h2>
586
+ <div part="toolbar" role="group" aria-label="Audit filters">
587
+ <div part="group-by" role="group" aria-label="Group by">
588
+ ${AUDIT_GROUP_BY_VALUES.map(value => `<button type="button" part="group-by-option" data-group-by="${value}" aria-pressed="false">${AUDIT_GROUP_BY_LABELS[value]}</button>`).join("")}
589
+ </div>
590
+ <label part="facet">Actor<select part="facet-actor"></select></label>
591
+ <label part="facet">Action<select part="facet-action"></select></label>
592
+ <label part="facet">From<input type="date" part="facet-from"></label>
593
+ <label part="facet">To<input type="date" part="facet-to"></label>
594
+ <button type="button" part="clear-filters" hidden>Clear filters</button>
595
+ <button type="button" part="export" hidden>Export CSV</button>
596
+ </div>
597
+ <div part="drilldown" hidden>
598
+ <span>Workflow run</span>
599
+ <span part="drilldown-id"></span>
600
+ <button type="button" part="drilldown-clear">Clear drill-down</button>
601
+ </div>
602
+ <p part="result-summary" role="status"></p>
603
+ <div part="groups"></div>
604
+ <div part="empty" hidden>No audit events match these filters.</div>
605
+ </section>
606
+ `;
607
+ const root = this.shadowRoot;
608
+ this.titleEl = root.querySelector('[part="title"]');
609
+ this.toolbarEl = root.querySelector('[part="toolbar"]');
610
+ this.groupByEl = root.querySelector('[part="group-by"]');
611
+ this.actorSelectEl = root.querySelector('[part="facet-actor"]');
612
+ this.actionSelectEl = root.querySelector('[part="facet-action"]');
613
+ this.fromInputEl = root.querySelector('[part="facet-from"]');
614
+ this.toInputEl = root.querySelector('[part="facet-to"]');
615
+ this.clearFiltersEl = root.querySelector('[part="clear-filters"]');
616
+ this.exportEl = root.querySelector('[part="export"]');
617
+ this.drilldownEl = root.querySelector('[part="drilldown"]');
618
+ this.drilldownIdEl = root.querySelector('[part="drilldown-id"]');
619
+ this.summaryEl = root.querySelector('[part="result-summary"]');
620
+ this.groupsEl = root.querySelector('[part="groups"]');
621
+ this.emptyEl = root.querySelector('[part="empty"]');
622
+ }
623
+ setupListeners() {
624
+ // The toolbar is built once and patched in place, so a select stays open
625
+ // and a half-typed date survives every re-render of the sections below.
626
+ this.toolbarEl.addEventListener("click", event => {
627
+ const target = event.target;
628
+ const option = target.closest('[part="group-by-option"]');
629
+ if (option && this.toolbarEl.contains(option)) {
630
+ const next = resolveAuditGroupBy(option.getAttribute("data-group-by"));
631
+ if (next !== this.groupBy) {
632
+ this.groupBy = next;
633
+ this.dispatchEvent(new CustomEvent("group-by-changed", {
634
+ bubbles: true,
635
+ composed: true,
636
+ detail: { groupBy: next },
637
+ }));
638
+ }
639
+ return;
640
+ }
641
+ if (target.closest('[part="clear-filters"]')) {
642
+ this.patchFacets({
643
+ actor: undefined,
644
+ action: undefined,
645
+ correlationId: undefined,
646
+ from: undefined,
647
+ to: undefined,
648
+ });
649
+ return;
650
+ }
651
+ if (target.closest('[part="export"]')) {
652
+ this.exportCsv();
653
+ }
654
+ });
655
+ this.toolbarEl.addEventListener("change", event => {
656
+ const target = event.target;
657
+ if (target === this.actorSelectEl) {
658
+ this.patchFacets({ actor: this.actorSelectEl.value || undefined });
659
+ }
660
+ else if (target === this.actionSelectEl) {
661
+ this.patchFacets({ action: this.actionSelectEl.value || undefined });
662
+ }
663
+ else if (target === this.fromInputEl) {
664
+ this.patchFacets({ from: this.fromInputEl.value || undefined });
665
+ }
666
+ else if (target === this.toInputEl) {
667
+ this.patchFacets({ to: this.toInputEl.value || undefined });
668
+ }
669
+ });
670
+ this.drilldownEl.addEventListener("click", event => {
671
+ if (event.target.closest('[part="drilldown-clear"]')) {
672
+ this.patchFacets({ correlationId: undefined });
673
+ }
674
+ });
675
+ this.groupsEl.addEventListener("click", event => {
676
+ const target = event.target;
677
+ const toggle = target.closest('[part="group-toggle"]');
678
+ if (toggle && this.groupsEl.contains(toggle)) {
679
+ this.toggleGroup(toggle.getAttribute("data-group-key") ?? "");
680
+ return;
681
+ }
682
+ const correlation = target.closest('[part="correlation"]');
683
+ if (correlation && this.groupsEl.contains(correlation)) {
684
+ const correlationId = correlation.getAttribute("data-correlation-id") ?? "";
685
+ this.patchFacets({ correlationId });
686
+ this.dispatchEvent(new CustomEvent("correlation-selected", {
687
+ bubbles: true,
688
+ composed: true,
689
+ detail: {
690
+ correlationId,
691
+ events: filterAuditEvents(this.events, { correlationId }),
692
+ },
693
+ }));
694
+ return;
695
+ }
696
+ const evidenceLink = target.closest('[part="evidence-link"]');
697
+ if (evidenceLink && this.groupsEl.contains(evidenceLink)) {
698
+ const source = this.events.find(entry => entry.id === (evidenceLink.getAttribute("data-event-id") ?? ""));
699
+ const evidence = source?.evidence?.find(entry => entry.id === (evidenceLink.getAttribute("data-evidence-id") ?? ""));
700
+ if (evidence) {
701
+ this.dispatchEvent(new CustomEvent("evidence-selected", {
702
+ bubbles: true,
703
+ composed: true,
704
+ detail: { event: source, evidence },
705
+ }));
706
+ }
707
+ return;
708
+ }
709
+ const action = target.closest('[part="event-action"]');
710
+ if (action && this.groupsEl.contains(action)) {
711
+ const source = this.events.find(entry => entry.id === (action.getAttribute("data-event-id") ?? ""));
712
+ if (source) {
713
+ this.dispatchEvent(new CustomEvent("event-selected", {
714
+ bubbles: true,
715
+ composed: true,
716
+ detail: { event: source },
717
+ }));
718
+ }
719
+ }
720
+ });
721
+ }
722
+ update() {
723
+ if (!this.groupsEl) {
724
+ return;
725
+ }
726
+ this.titleEl.textContent = this.heading;
727
+ const all = this.events;
728
+ const facets = this.facets;
729
+ const visible = filterAuditEvents(all, facets);
730
+ const groupBy = this.groupBy;
731
+ const groups = groupAuditEvents(visible, groupBy, this.now());
732
+ for (const option of Array.from(this.groupByEl.querySelectorAll('[part="group-by-option"]'))) {
733
+ option.setAttribute("aria-pressed", option.getAttribute("data-group-by") === groupBy ? "true" : "false");
734
+ }
735
+ // Facet options come from the unfiltered set, so choosing one facet never
736
+ // empties another's list.
737
+ const summary = summarizeAuditFacets(all);
738
+ const optionsSignature = JSON.stringify(summary);
739
+ if (optionsSignature !== this.optionsSignature) {
740
+ this.optionsSignature = optionsSignature;
741
+ this.renderOptions(this.actorSelectEl, summary.actors, "All actors");
742
+ this.renderOptions(this.actionSelectEl, summary.actions, "All actions");
743
+ }
744
+ this.actorSelectEl.value = facets.actor ?? "";
745
+ this.actionSelectEl.value = facets.action ?? "";
746
+ this.fromInputEl.value = facets.from ?? "";
747
+ this.toInputEl.value = facets.to ?? "";
748
+ this.clearFiltersEl.hidden = !hasAuditFacets(facets);
749
+ this.exportEl.hidden = !this.exportable;
750
+ this.drilldownEl.hidden = !facets.correlationId;
751
+ this.drilldownIdEl.textContent = facets.correlationId ?? "";
752
+ this.summaryEl.textContent =
753
+ visible.length === all.length
754
+ ? `${String(all.length)} ${all.length === 1 ? "event" : "events"} in ${String(groups.length)} ${groups.length === 1 ? "group" : "groups"}`
755
+ : `Showing ${String(visible.length)} of ${String(all.length)} events in ${String(groups.length)} ${groups.length === 1 ? "group" : "groups"}`;
756
+ // The section markup depends on content only. Collapse state is applied
757
+ // separately below, so expanding a section never rebuilds the log.
758
+ const contentSignature = JSON.stringify([
759
+ this.getAttribute("events") ?? "",
760
+ groupBy,
761
+ facets,
762
+ this.referenceTime,
763
+ ]);
764
+ if (contentSignature !== this.contentSignature) {
765
+ this.contentSignature = contentSignature;
766
+ const active = this.shadowRoot?.activeElement;
767
+ const focusKey = active && this.groupsEl.contains(active)
768
+ ? {
769
+ part: active.getAttribute("part") ?? "",
770
+ groupKey: active.getAttribute("data-group-key"),
771
+ eventId: active.getAttribute("data-event-id"),
772
+ }
773
+ : null;
774
+ this.groupsEl.innerHTML = groups.map((group, index) => this.groupHtml(group, index)).join("");
775
+ if (focusKey?.part) {
776
+ const restored = Array.from(this.groupsEl.querySelectorAll(`[part="${focusKey.part}"]`)).find(node => node.getAttribute("data-group-key") === focusKey.groupKey &&
777
+ node.getAttribute("data-event-id") === focusKey.eventId);
778
+ restored?.focus();
779
+ }
780
+ }
781
+ for (const section of Array.from(this.groupsEl.querySelectorAll('[part="group"]'))) {
782
+ const key = section.getAttribute("data-group-key") ?? "";
783
+ const expanded = !this.collapsedGroups.has(key);
784
+ section.querySelector('[part="group-toggle"]')?.setAttribute("aria-expanded", String(expanded));
785
+ const body = section.querySelector('[part="group-body"]');
786
+ if (body) {
787
+ body.hidden = !expanded;
788
+ }
789
+ }
790
+ this.groupsEl.hidden = groups.length === 0;
791
+ this.emptyEl.hidden = groups.length > 0;
792
+ }
793
+ }
794
+ AuditLog.register();