@x12i/memorix-service 3.7.1 → 3.7.7

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 (169) hide show
  1. package/dist/activity-ring.d.ts +185 -0
  2. package/dist/activity-ring.d.ts.map +1 -0
  3. package/dist/activity-ring.js +249 -0
  4. package/dist/activity-ring.js.map +1 -0
  5. package/dist/activity-routes.d.ts +3 -0
  6. package/dist/activity-routes.d.ts.map +1 -0
  7. package/dist/activity-routes.js +47 -0
  8. package/dist/activity-routes.js.map +1 -0
  9. package/dist/activity-ui.d.ts +3 -0
  10. package/dist/activity-ui.d.ts.map +1 -0
  11. package/dist/activity-ui.js +1105 -0
  12. package/dist/activity-ui.js.map +1 -0
  13. package/dist/app.d.ts.map +1 -1
  14. package/dist/app.js +21 -2
  15. package/dist/app.js.map +1 -1
  16. package/dist/caller-identity.d.ts +3 -2
  17. package/dist/caller-identity.d.ts.map +1 -1
  18. package/dist/caller-identity.js.map +1 -1
  19. package/dist/catalog-bridge.d.ts.map +1 -1
  20. package/dist/catalog-bridge.js +7 -1
  21. package/dist/catalog-bridge.js.map +1 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +12 -0
  24. package/dist/cli.js.map +1 -1
  25. package/dist/connector-catalog.d.ts +1 -1
  26. package/dist/connector-ops/attempt-store.d.ts.map +1 -1
  27. package/dist/connector-ops/attempt-store.js +1 -3
  28. package/dist/connector-ops/attempt-store.js.map +1 -1
  29. package/dist/connector-ops/checkpoint-store.d.ts.map +1 -1
  30. package/dist/connector-ops/checkpoint-store.js +1 -3
  31. package/dist/connector-ops/checkpoint-store.js.map +1 -1
  32. package/dist/connector-ops/override-store.d.ts.map +1 -1
  33. package/dist/connector-ops/override-store.js +1 -3
  34. package/dist/connector-ops/override-store.js.map +1 -1
  35. package/dist/connector-ops/run-store.d.ts.map +1 -1
  36. package/dist/connector-ops/run-store.js +1 -3
  37. package/dist/connector-ops/run-store.js.map +1 -1
  38. package/dist/connector-ops/schedule-lease-store.d.ts.map +1 -1
  39. package/dist/connector-ops/schedule-lease-store.js +1 -3
  40. package/dist/connector-ops/schedule-lease-store.js.map +1 -1
  41. package/dist/data-changes-routes.d.ts +7 -0
  42. package/dist/data-changes-routes.d.ts.map +1 -0
  43. package/dist/data-changes-routes.js +27 -0
  44. package/dist/data-changes-routes.js.map +1 -0
  45. package/dist/data-changes-ui.d.ts +3 -0
  46. package/dist/data-changes-ui.d.ts.map +1 -0
  47. package/dist/data-changes-ui.js +239 -0
  48. package/dist/data-changes-ui.js.map +1 -0
  49. package/dist/data-changes.d.ts +51 -0
  50. package/dist/data-changes.d.ts.map +1 -0
  51. package/dist/data-changes.js +218 -0
  52. package/dist/data-changes.js.map +1 -0
  53. package/dist/errors.d.ts +3 -0
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +36 -3
  56. package/dist/errors.js.map +1 -1
  57. package/dist/graph-ui.d.ts +3 -0
  58. package/dist/graph-ui.d.ts.map +1 -0
  59. package/dist/graph-ui.js +676 -0
  60. package/dist/graph-ui.js.map +1 -0
  61. package/dist/identity-alias.d.ts +12 -0
  62. package/dist/identity-alias.d.ts.map +1 -0
  63. package/dist/identity-alias.js +22 -0
  64. package/dist/identity-alias.js.map +1 -0
  65. package/dist/incremental-state/state-store.d.ts.map +1 -1
  66. package/dist/incremental-state/state-store.js +1 -3
  67. package/dist/incremental-state/state-store.js.map +1 -1
  68. package/dist/integrations/instance-store.d.ts.map +1 -1
  69. package/dist/integrations/instance-store.js +1 -3
  70. package/dist/integrations/instance-store.js.map +1 -1
  71. package/dist/isolation.d.ts.map +1 -1
  72. package/dist/isolation.js +3 -2
  73. package/dist/isolation.js.map +1 -1
  74. package/dist/knowledge-upstream.d.ts +13 -0
  75. package/dist/knowledge-upstream.d.ts.map +1 -0
  76. package/dist/knowledge-upstream.js +38 -0
  77. package/dist/knowledge-upstream.js.map +1 -0
  78. package/dist/land-v2-guards.d.ts +22 -0
  79. package/dist/land-v2-guards.d.ts.map +1 -0
  80. package/dist/land-v2-guards.js +57 -0
  81. package/dist/land-v2-guards.js.map +1 -0
  82. package/dist/live-ops-nav.d.ts +2 -0
  83. package/dist/live-ops-nav.d.ts.map +1 -0
  84. package/dist/live-ops-nav.js +53 -0
  85. package/dist/live-ops-nav.js.map +1 -0
  86. package/dist/live.d.ts.map +1 -1
  87. package/dist/mcp/nexum-tools.d.ts +28 -0
  88. package/dist/mcp/nexum-tools.d.ts.map +1 -0
  89. package/dist/mcp/nexum-tools.js +257 -0
  90. package/dist/mcp/nexum-tools.js.map +1 -0
  91. package/dist/mcp/service-public-url.d.ts +7 -0
  92. package/dist/mcp/service-public-url.d.ts.map +1 -0
  93. package/dist/mcp/service-public-url.js +19 -0
  94. package/dist/mcp/service-public-url.js.map +1 -0
  95. package/dist/nexum-analysis/backfill.d.ts +59 -0
  96. package/dist/nexum-analysis/backfill.d.ts.map +1 -0
  97. package/dist/nexum-analysis/backfill.js +199 -0
  98. package/dist/nexum-analysis/backfill.js.map +1 -0
  99. package/dist/nexum-analysis/enqueue.d.ts +29 -0
  100. package/dist/nexum-analysis/enqueue.d.ts.map +1 -0
  101. package/dist/nexum-analysis/enqueue.js +190 -0
  102. package/dist/nexum-analysis/enqueue.js.map +1 -0
  103. package/dist/nexum-analysis/outbox.d.ts +44 -0
  104. package/dist/nexum-analysis/outbox.d.ts.map +1 -0
  105. package/dist/nexum-analysis/outbox.js +176 -0
  106. package/dist/nexum-analysis/outbox.js.map +1 -0
  107. package/dist/nexum-analysis/policy.d.ts +48 -0
  108. package/dist/nexum-analysis/policy.d.ts.map +1 -0
  109. package/dist/nexum-analysis/policy.js +165 -0
  110. package/dist/nexum-analysis/policy.js.map +1 -0
  111. package/dist/nexum-analysis/reconcile.d.ts +8 -0
  112. package/dist/nexum-analysis/reconcile.d.ts.map +1 -0
  113. package/dist/nexum-analysis/reconcile.js +37 -0
  114. package/dist/nexum-analysis/reconcile.js.map +1 -0
  115. package/dist/openapi/openapi.json +591 -4
  116. package/dist/operational-db.d.ts +6 -0
  117. package/dist/operational-db.d.ts.map +1 -0
  118. package/dist/operational-db.js +22 -0
  119. package/dist/operational-db.js.map +1 -0
  120. package/dist/platform.d.ts +4 -1
  121. package/dist/platform.d.ts.map +1 -1
  122. package/dist/platform.js +67 -14
  123. package/dist/platform.js.map +1 -1
  124. package/dist/request-countex.d.ts +25 -0
  125. package/dist/request-countex.d.ts.map +1 -1
  126. package/dist/request-countex.js +103 -18
  127. package/dist/request-countex.js.map +1 -1
  128. package/dist/restricted-store/object-store.d.ts.map +1 -1
  129. package/dist/restricted-store/object-store.js +1 -3
  130. package/dist/restricted-store/object-store.js.map +1 -1
  131. package/dist/routes/connector-invocations.d.ts.map +1 -1
  132. package/dist/routes/connector-invocations.js +7 -1
  133. package/dist/routes/connector-invocations.js.map +1 -1
  134. package/dist/routes/data.d.ts.map +1 -1
  135. package/dist/routes/data.js +113 -14
  136. package/dist/routes/data.js.map +1 -1
  137. package/dist/routes/knowledge.d.ts +8 -0
  138. package/dist/routes/knowledge.d.ts.map +1 -0
  139. package/dist/routes/knowledge.js +86 -0
  140. package/dist/routes/knowledge.js.map +1 -0
  141. package/dist/routes/memory.d.ts.map +1 -1
  142. package/dist/routes/memory.js +33 -9
  143. package/dist/routes/memory.js.map +1 -1
  144. package/dist/routes/metadata.d.ts.map +1 -1
  145. package/dist/routes/metadata.js +102 -1
  146. package/dist/routes/metadata.js.map +1 -1
  147. package/dist/routes/mgc-landing.d.ts.map +1 -1
  148. package/dist/routes/mgc-landing.js +90 -1
  149. package/dist/routes/mgc-landing.js.map +1 -1
  150. package/dist/routes/nexum-mcp.d.ts +9 -0
  151. package/dist/routes/nexum-mcp.d.ts.map +1 -0
  152. package/dist/routes/nexum-mcp.js +95 -0
  153. package/dist/routes/nexum-mcp.js.map +1 -0
  154. package/dist/routes/operations.d.ts.map +1 -1
  155. package/dist/routes/operations.js +39 -2
  156. package/dist/routes/operations.js.map +1 -1
  157. package/dist/routes/publications.d.ts +4 -0
  158. package/dist/routes/publications.d.ts.map +1 -0
  159. package/dist/routes/publications.js +111 -0
  160. package/dist/routes/publications.js.map +1 -0
  161. package/dist/routes/workflow.d.ts.map +1 -1
  162. package/dist/routes/workflow.js +18 -9
  163. package/dist/routes/workflow.js.map +1 -1
  164. package/dist/safe-query.d.ts +28 -0
  165. package/dist/safe-query.d.ts.map +1 -0
  166. package/dist/safe-query.js +104 -0
  167. package/dist/safe-query.js.map +1 -0
  168. package/openapi/openapi.json +591 -4
  169. package/package.json +5 -5
@@ -0,0 +1,1105 @@
1
+ /** HTML for GET /_live/activity — pushed / processed / fetched operator feeds. */
2
+ export function activityHtml() {
3
+ return `<!DOCTYPE html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="utf-8" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <title>Memorix · Activity</title>
9
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
12
+ <style>
13
+ :root {
14
+ --bg: #0a0c10; --bg-elev: #10141b; --bg-row: #141922; --line: #242b38;
15
+ --text: #e8ebe6; --muted: #8a9288; --dim: #5c645c;
16
+ --ok: #3dba7a; --bad: #e25a5a; --accent: #c8f082; --accent-ink: #0a0c10;
17
+ --api: #7eb8d4; --ai: #d4a574;
18
+ --sans: "IBM Plex Sans", system-ui, sans-serif;
19
+ --mono: "IBM Plex Mono", ui-monospace, monospace;
20
+ }
21
+ * { box-sizing: border-box; }
22
+ html, body { height: 100%; margin: 0; }
23
+ body {
24
+ font-family: var(--sans);
25
+ background:
26
+ radial-gradient(1200px 600px at 10% -10%, #1a2214 0%, transparent 55%),
27
+ radial-gradient(900px 500px at 100% 0%, #121820 0%, transparent 50%),
28
+ var(--bg);
29
+ color: var(--text); font-size: 14px; line-height: 1.45;
30
+ }
31
+ a { color: var(--accent); }
32
+ .shell { max-width: 1280px; margin: 0 auto; padding: 36px 28px 64px; }
33
+ header {
34
+ display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
35
+ margin-bottom: 22px; padding-bottom: 18px; border-bottom: 1px solid var(--line);
36
+ }
37
+ .brand { font-size: 26px; font-weight: 700; letter-spacing: -0.03em; }
38
+ .brand span { color: var(--accent); }
39
+ .sub { margin-top: 8px; color: var(--muted); font-size: 13px; max-width: 68ch; }
40
+ .meta { text-align: right; font-family: var(--mono); font-size: 11px; color: var(--dim); display: grid; gap: 4px; }
41
+ .section-label {
42
+ margin: 0 0 10px; font-size: 12px; font-family: var(--mono);
43
+ letter-spacing: 0.08em; text-transform: uppercase; color: var(--dim);
44
+ }
45
+ .quiet-banner {
46
+ margin: 0 0 18px; padding: 14px 16px;
47
+ border: 1px solid #e2b25a; background: #1c1810;
48
+ color: var(--text); font-size: 15px; line-height: 1.5;
49
+ }
50
+ .quiet-banner[hidden] { display: none; }
51
+ .quiet-banner strong { color: #e2b25a; font-weight: 600; }
52
+ .health-grid {
53
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
54
+ gap: 14px; margin-bottom: 20px;
55
+ }
56
+ .health-card {
57
+ background: var(--bg-elev); border: 1px solid var(--line); padding: 14px 14px 10px;
58
+ }
59
+ .health-card h3 {
60
+ margin: 0 0 4px; font-size: 12px; font-family: var(--mono);
61
+ letter-spacing: 0.06em; text-transform: uppercase; color: var(--dim); font-weight: 500;
62
+ }
63
+ .health-card .val {
64
+ font-family: var(--mono); font-size: 20px; font-weight: 600; color: var(--text);
65
+ margin-bottom: 8px;
66
+ }
67
+ .health-card .val span { color: var(--muted); font-size: 12px; font-weight: 500; margin-left: 6px; }
68
+ .health-card svg { width: 100%; height: 56px; display: block; }
69
+ .health-card .hint { margin-top: 6px; font-size: 11px; color: var(--dim); font-family: var(--mono); }
70
+ .tabs {
71
+ display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
72
+ gap: 8px; margin: 0 0 16px;
73
+ }
74
+ .tab, .btn {
75
+ appearance: none; border: 1px solid var(--line); background: var(--bg-elev); color: var(--muted);
76
+ font: 500 12px/1.3 var(--sans); padding: 12px 14px; cursor: pointer; border-radius: 2px;
77
+ }
78
+ .tab {
79
+ display: flex; flex-direction: column; align-items: flex-start; gap: 4px; text-align: left;
80
+ }
81
+ .tab-name {
82
+ font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em;
83
+ text-transform: uppercase; font-weight: 600; color: var(--text);
84
+ }
85
+ .tab-desc { font-size: 12px; font-weight: 400; color: var(--muted); }
86
+ .tab[aria-selected="true"] {
87
+ background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
88
+ }
89
+ .tab[aria-selected="true"] .tab-name,
90
+ .tab[aria-selected="true"] .tab-desc { color: var(--accent-ink); }
91
+ @media (max-width: 720px) {
92
+ .tabs { grid-template-columns: 1fr; }
93
+ }
94
+ .btn { background: var(--bg-row); color: var(--text); text-transform: none; letter-spacing: 0; }
95
+ .btn:hover { border-color: var(--accent); }
96
+ .lane-hint { margin: 0 0 14px; color: var(--muted); font-size: 13px; max-width: 72ch; }
97
+ .lane-hint strong { color: var(--text); font-weight: 500; }
98
+ .filters {
99
+ display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
100
+ margin-bottom: 14px; padding: 12px 14px;
101
+ background: var(--bg-elev); border: 1px solid var(--line);
102
+ }
103
+ .filters label {
104
+ display: flex; flex-direction: column; gap: 4px;
105
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
106
+ text-transform: uppercase; color: var(--dim);
107
+ }
108
+ .filters label[hidden] { display: none; }
109
+ .filters input, .filters select {
110
+ appearance: none; border: 1px solid var(--line); background: var(--bg-row); color: var(--text);
111
+ font: 500 12px/1.2 var(--sans); padding: 8px 10px; border-radius: 2px; min-width: 120px;
112
+ }
113
+ .filters input { min-width: 180px; }
114
+ .filters .btn { align-self: flex-end; }
115
+ .status-bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
116
+ .summary { display: flex; gap: 14px; flex-wrap: wrap; font-family: var(--mono); font-size: 12px; color: var(--muted); }
117
+ .summary strong { color: var(--text); font-weight: 500; }
118
+ .pause-bar {
119
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
120
+ margin-bottom: 10px; padding: 8px 12px;
121
+ background: #1a2214; border: 1px solid var(--accent); color: var(--text);
122
+ font-family: var(--mono); font-size: 12px;
123
+ }
124
+ .pause-bar[hidden] { display: none; }
125
+ .table-wrap {
126
+ border: 1px solid var(--line);
127
+ background: var(--bg-elev);
128
+ }
129
+ table.list {
130
+ width: 100%; table-layout: fixed; border-collapse: collapse;
131
+ background: var(--bg-elev); border: 0;
132
+ }
133
+ table.list th, table.list td {
134
+ text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line);
135
+ vertical-align: middle; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
136
+ }
137
+ th {
138
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
139
+ color: var(--dim); font-weight: 500; background: #0d1016;
140
+ position: sticky; top: 0; z-index: 2;
141
+ }
142
+ tr:last-child td { border-bottom: 0; }
143
+ tr:hover td { background: var(--bg-row); }
144
+ tr.clickable { cursor: pointer; }
145
+ .mono { font-family: var(--mono); font-size: 12px; color: var(--muted); }
146
+ .badge {
147
+ display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono);
148
+ font-size: 11px; padding: 3px 8px; border: 1px solid var(--line);
149
+ max-width: 100%;
150
+ }
151
+ .badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex: 0 0 auto; }
152
+ .badge.ok { color: var(--ok); }
153
+ .badge.ok::before { background: var(--ok); }
154
+ .badge.bad { color: var(--bad); }
155
+ .badge.bad::before { background: var(--bad); }
156
+ .badge.queued { color: var(--api); }
157
+ .badge.queued::before { background: var(--api); }
158
+ .badge.ai { color: var(--ai); }
159
+ .badge.ai::before { background: var(--ai); }
160
+ .badge.skip { color: var(--dim); }
161
+ .detail {
162
+ margin-top: 8px; padding: 12px; background: #0d1016; border: 1px solid var(--line);
163
+ font-family: var(--mono); font-size: 11px; color: var(--muted);
164
+ }
165
+ .detail h3 { margin: 0 0 8px; font-size: 12px; color: var(--text); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
166
+ .detail .row { margin: 4px 0; }
167
+ .edge-list { margin: 0; padding-left: 18px; }
168
+ .edge-list li { margin: 2px 0; }
169
+ .graph-svg {
170
+ width: 100%; max-width: 640px; height: 220px; margin-top: 10px;
171
+ background: #0a0c10; border: 1px solid var(--line);
172
+ }
173
+ .graph-svg text { fill: var(--muted); font-family: var(--mono); font-size: 10px; }
174
+ .graph-svg circle { fill: var(--accent); stroke: #2a3218; stroke-width: 1; }
175
+ .graph-svg line { stroke: var(--api); stroke-width: 1.2; opacity: 0.85; }
176
+ .empty { color: var(--muted); padding: 24px 0; }
177
+ .dialog-back {
178
+ position: fixed; inset: 0; z-index: 40;
179
+ background: rgba(6, 8, 10, 0.45);
180
+ }
181
+ .dialog-back[hidden] { display: none !important; }
182
+ .dialog {
183
+ position: fixed; top: 0; right: 0; bottom: 0;
184
+ width: min(440px, 100vw);
185
+ margin: 0;
186
+ display: flex; flex-direction: column;
187
+ background: var(--bg-elev);
188
+ border-left: 1px solid var(--line);
189
+ box-shadow: -20px 0 48px rgba(0, 0, 0, 0.5);
190
+ }
191
+ .dialog-bar {
192
+ flex: 0 0 auto;
193
+ display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
194
+ padding: 16px 16px 14px;
195
+ background: #0d1016;
196
+ border-bottom: 1px solid var(--line);
197
+ }
198
+ .dialog-kicker {
199
+ font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
200
+ text-transform: uppercase; color: var(--accent); margin-bottom: 4px;
201
+ }
202
+ .dialog-bar strong {
203
+ display: block; font-size: 14px; font-weight: 600; letter-spacing: 0;
204
+ text-transform: none; line-height: 1.35; word-break: break-word;
205
+ }
206
+ #dialog-body {
207
+ flex: 1 1 auto; overflow: auto;
208
+ padding: 16px 16px 32px;
209
+ }
210
+ .facts {
211
+ margin: 0; display: grid; grid-template-columns: 96px 1fr; gap: 10px 12px;
212
+ font-size: 13px;
213
+ }
214
+ .facts dt {
215
+ margin: 0; font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
216
+ text-transform: uppercase; color: var(--dim); padding-top: 3px;
217
+ }
218
+ .facts dd { margin: 0; color: var(--text); word-break: break-word; }
219
+ .dialog .detail { margin-top: 16px; }
220
+ .dialog .graph-svg { max-width: none; height: 220px; }
221
+ footer { margin-top: 28px; color: var(--dim); font-size: 12px; }
222
+ footer a { margin-right: 12px; }
223
+ </style>
224
+ </head>
225
+ <body>
226
+ <div class="shell">
227
+ <header>
228
+ <div>
229
+ <div class="brand">Memorix <span>activity</span></div>
230
+ <p class="sub">Three feeds: data pushed in, data Nexum is processing, and data fetched out. In-memory — last ~10 000 per feed; clears on process restart.</p>
231
+ </div>
232
+ <div class="meta">
233
+ <div><strong>UI</strong> /_live/activity</div>
234
+ <div><strong>API</strong> /_live/api/activity</div>
235
+ <div><a href="/_live">← live view</a> · <a href="/_live/graph">stored graph</a> · <a href="/_live/changes">data changes</a></div>
236
+ </div>
237
+ </header>
238
+
239
+ <div class="quiet-banner" id="quiet-banner" hidden></div>
240
+
241
+ <p class="section-label">Activity · last 60 minutes</p>
242
+ <div class="health-grid" id="charts"></div>
243
+
244
+ <nav class="tabs" role="tablist" aria-label="Activity type">
245
+ <button class="tab" role="tab" aria-selected="true" data-lane="land">
246
+ <span class="tab-name">Pushed</span>
247
+ <span class="tab-desc">Data written into Memorix</span>
248
+ </button>
249
+ <button class="tab" role="tab" aria-selected="false" data-lane="index">
250
+ <span class="tab-name">Processed</span>
251
+ <span class="tab-desc">Nexum working on that data</span>
252
+ </button>
253
+ <button class="tab" role="tab" aria-selected="false" data-lane="fetch">
254
+ <span class="tab-name">Fetched</span>
255
+ <span class="tab-desc">Data read out of Memorix</span>
256
+ </button>
257
+ </nav>
258
+ <p class="lane-hint" id="lane-hint"></p>
259
+
260
+ <div class="filters" id="filters">
261
+ <label>Search
262
+ <input id="f-q" type="search" placeholder="record, org, caller…" />
263
+ </label>
264
+ <label>Org
265
+ <select id="f-org"><option value="">All</option></select>
266
+ </label>
267
+ <label>Caller
268
+ <select id="f-caller"><option value="">All</option></select>
269
+ </label>
270
+ <label>Object type
271
+ <select id="f-ot"><option value="">All</option></select>
272
+ </label>
273
+ <label>Content type
274
+ <select id="f-ct"><option value="">All</option></select>
275
+ </label>
276
+ <label id="f-nexum-wrap">Nexum
277
+ <select id="f-nexum">
278
+ <option value="">All</option>
279
+ <option value="queued">queued · analyze</option>
280
+ <option value="skipped">skipped</option>
281
+ </select>
282
+ </label>
283
+ <label id="f-how-wrap" hidden>How
284
+ <select id="f-how">
285
+ <option value="">All</option>
286
+ <option value="rules">rules · no AI</option>
287
+ <option value="ai">AI</option>
288
+ <option value="unknown">unknown</option>
289
+ </select>
290
+ </label>
291
+ <label id="f-hit-wrap" hidden>Hit
292
+ <select id="f-hit">
293
+ <option value="">All</option>
294
+ <option value="hit">hit</option>
295
+ <option value="miss">miss</option>
296
+ </select>
297
+ </label>
298
+ <button class="btn" id="f-clear" type="button">Clear filters</button>
299
+ </div>
300
+
301
+ <div class="status-bar">
302
+ <div class="summary" id="summary"></div>
303
+ <button class="btn" id="refresh" type="button">Refresh</button>
304
+ </div>
305
+ <div class="pause-bar" id="pause-bar" hidden>
306
+ <span id="pause-msg">List held</span>
307
+ <button class="btn" id="resume" type="button">Show new</button>
308
+ </div>
309
+ <div class="table-wrap" id="table-wrap"></div>
310
+ <footer>
311
+ <a href="/_live">/_live</a>
312
+ <a href="/_live/graph">/_live/graph</a>
313
+ <a href="/_live/changes">/_live/changes</a>
314
+ Click a row to open a dialog. Live updates pause while a dialog is open or the page is scrolled down.
315
+ </footer>
316
+ </div>
317
+ <div class="dialog-back" id="dialog-back" hidden>
318
+ <div class="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title">
319
+ <div class="dialog-bar">
320
+ <div>
321
+ <div class="dialog-kicker" id="dialog-kicker">Detail</div>
322
+ <strong id="dialog-title">Item</strong>
323
+ </div>
324
+ <button class="btn" id="dialog-close" type="button">Close</button>
325
+ </div>
326
+ <div id="dialog-body"></div>
327
+ </div>
328
+ </div>
329
+ <script>
330
+ const $ = (id) => document.getElementById(id);
331
+ const LANE_FROM_HASH = {
332
+ pushed: "land", land: "land",
333
+ processed: "index", index: "index", connect: "index",
334
+ fetched: "fetch", fetch: "fetch",
335
+ };
336
+ const HASH_FROM_LANE = { land: "pushed", index: "processed", fetch: "fetched" };
337
+ function hashLane() {
338
+ const key = (location.hash || "").replace(/^#/, "").toLowerCase();
339
+ return LANE_FROM_HASH[key] || "land";
340
+ }
341
+ let lane = hashLane();
342
+ let entries = [];
343
+ let liveEntries = [];
344
+ let followLive = true;
345
+ let pendingNew = 0;
346
+ let ignoreScroll = false;
347
+ let sizes = { land: 0, index: 0, fetch: 0 };
348
+ let capacity = 10000;
349
+ let seriesBuckets = [];
350
+ let startedAt = "";
351
+ let latest = { land: null, index: null, fetch: null };
352
+ let filterState = { q: "", org: "", caller: "", ot: "", ct: "", nexum: "", how: "", hit: "" };
353
+
354
+ function listHeld() {
355
+ return !followLive;
356
+ }
357
+
358
+ function dialogTitle(e) {
359
+ return e.recordId || (e.source && e.source.recordId) || (e.snapshot && e.snapshot.recordId) || "Item";
360
+ }
361
+
362
+ function laneLabel(e) {
363
+ if (e.lane === "index") return "Processed";
364
+ if (e.lane === "fetch") return "Fetched";
365
+ return "Pushed";
366
+ }
367
+
368
+ function fact(label, valueHtml) {
369
+ return "<dt>" + escapeHtml(label) + "</dt><dd>" + valueHtml + "</dd>";
370
+ }
371
+
372
+ function dialogOpen() {
373
+ return !$("dialog-back").hidden;
374
+ }
375
+
376
+ function openDialog(e) {
377
+ followLive = false;
378
+ document.body.style.overflow = "hidden";
379
+ $("dialog-kicker").textContent = laneLabel(e);
380
+ $("dialog-title").textContent = dialogTitle(e);
381
+ $("dialog-body").innerHTML = renderDetail(e);
382
+ $("dialog-back").hidden = false;
383
+ updatePauseBar();
384
+ }
385
+
386
+ function closeDialog() {
387
+ $("dialog-back").hidden = true;
388
+ $("dialog-body").innerHTML = "";
389
+ document.body.style.overflow = "";
390
+ if (window.scrollY < 8) {
391
+ followLive = true;
392
+ applyLive();
393
+ } else {
394
+ updatePauseBar();
395
+ }
396
+ }
397
+
398
+ function applyLaneChrome() {
399
+ document.querySelectorAll(".tab").forEach((t) => {
400
+ t.setAttribute("aria-selected", t.dataset.lane === lane ? "true" : "false");
401
+ });
402
+ const hash = "#" + (HASH_FROM_LANE[lane] || "pushed");
403
+ if (location.hash !== hash) history.replaceState(null, "", hash);
404
+ syncFilterVisibility();
405
+ }
406
+
407
+ function switchLane(next) {
408
+ if (!next || next === lane) return;
409
+ lane = next;
410
+ $("dialog-back").hidden = true;
411
+ $("dialog-body").innerHTML = "";
412
+ document.body.style.overflow = "";
413
+ followLive = true;
414
+ pendingNew = 0;
415
+ applyLaneChrome();
416
+ load(true);
417
+ }
418
+
419
+ document.querySelectorAll(".tab").forEach((tab) => {
420
+ tab.addEventListener("click", () => switchLane(tab.dataset.lane));
421
+ });
422
+ window.addEventListener("hashchange", () => switchLane(hashLane()));
423
+ $("refresh").onclick = () => {
424
+ followLive = true;
425
+ load(true);
426
+ };
427
+ $("resume").onclick = () => {
428
+ $("dialog-back").hidden = true;
429
+ $("dialog-body").innerHTML = "";
430
+ document.body.style.overflow = "";
431
+ followLive = true;
432
+ applyLive();
433
+ };
434
+ $("dialog-close").onclick = () => closeDialog();
435
+ $("dialog-back").addEventListener("click", (ev) => {
436
+ if (ev.target === $("dialog-back")) closeDialog();
437
+ });
438
+ document.addEventListener("keydown", (ev) => {
439
+ if (ev.key === "Escape" && !$("dialog-back").hidden) closeDialog();
440
+ });
441
+ window.addEventListener("scroll", () => {
442
+ if (ignoreScroll || dialogOpen()) return;
443
+ const atTop = window.scrollY < 8;
444
+ if (atTop && !followLive) {
445
+ followLive = true;
446
+ if (pendingNew) applyLive();
447
+ else updatePauseBar();
448
+ } else if (!atTop && followLive) {
449
+ followLive = false;
450
+ updatePauseBar();
451
+ }
452
+ }, { passive: true });
453
+ $("f-clear").onclick = () => {
454
+ filterState = { q: "", org: "", caller: "", ot: "", ct: "", nexum: "", how: "", hit: "" };
455
+ $("f-q").value = "";
456
+ $("f-org").value = "";
457
+ $("f-caller").value = "";
458
+ $("f-ot").value = "";
459
+ $("f-ct").value = "";
460
+ $("f-nexum").value = "";
461
+ $("f-how").value = "";
462
+ $("f-hit").value = "";
463
+ render();
464
+ };
465
+ ["f-q", "f-org", "f-caller", "f-ot", "f-ct", "f-nexum", "f-how", "f-hit"].forEach((id) => {
466
+ $(id).addEventListener("input", () => {
467
+ filterState = {
468
+ q: $("f-q").value.trim().toLowerCase(),
469
+ org: $("f-org").value,
470
+ caller: $("f-caller").value,
471
+ ot: $("f-ot").value,
472
+ ct: $("f-ct").value,
473
+ nexum: $("f-nexum").value,
474
+ how: $("f-how").value,
475
+ hit: $("f-hit").value,
476
+ };
477
+ if (!listHeld() && liveEntries.length) entries = liveEntries;
478
+ render();
479
+ });
480
+ $(id).addEventListener("change", () => $(id).dispatchEvent(new Event("input")));
481
+ });
482
+
483
+ function syncFilterVisibility() {
484
+ $("f-nexum-wrap").hidden = lane !== "land";
485
+ $("f-how-wrap").hidden = lane !== "index";
486
+ $("f-hit-wrap").hidden = lane !== "fetch";
487
+ }
488
+ applyLaneChrome();
489
+
490
+ function escapeHtml(s) {
491
+ return String(s ?? "").replace(/[&<>"']/g, (c) => ({
492
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
493
+ })[c]);
494
+ }
495
+
496
+ function fmtTime(ts) {
497
+ try { return new Date(ts).toISOString().slice(11, 23); }
498
+ catch { return ts; }
499
+ }
500
+
501
+ function statusBadge(status) {
502
+ const s = String(status ?? "");
503
+ let cls = "";
504
+ if (/succeed|connected|hit|created|updated|queued|rules/.test(s) && !/failed|dead/.test(s)) {
505
+ cls = /queued|analyzing/.test(s) ? "queued" : "ok";
506
+ } else if (/fail|dead|miss|error/.test(s)) {
507
+ cls = "bad";
508
+ } else if (/ai/i.test(s)) {
509
+ cls = "ai";
510
+ }
511
+ return '<span class="badge ' + cls + '">' + escapeHtml(s) + "</span>";
512
+ }
513
+
514
+ function skipKind(reason) {
515
+ const r = String(reason || "").toLowerCase();
516
+ if (!r) return { short: "skip", label: "skipped" };
517
+ if (r.includes("no matching")) return { short: "no-pol", label: "skipped · no policy" };
518
+ if (r.includes("disabled")) return { short: "off", label: "skipped · off" };
519
+ if (r.includes("worker unset")) return { short: "no-wk", label: "skipped · no worker" };
520
+ if (r.includes("no analyzeable") || r.includes("no payload")) return { short: "empty", label: "skipped · no payload" };
521
+ if (r.includes("operational")) return { short: "no-db", label: "skipped · no ops db" };
522
+ return { short: "skip", label: "skipped" };
523
+ }
524
+
525
+ /** Compact land-row Nexum badge (full text lives in the dialog). */
526
+ function nexumLandCell(indexing) {
527
+ if (!indexing) return "—";
528
+ if (indexing.status === "skipped") {
529
+ const kind = skipKind(indexing.reason);
530
+ const tip = indexing.reason ? kind.label + " — " + indexing.reason : kind.label;
531
+ return '<span class="badge skip" title="' + escapeHtml(tip) + '">' + escapeHtml(kind.short) + "</span>";
532
+ }
533
+ if (indexing.status === "queued") {
534
+ return '<span class="badge queued" title="queued · analyze">queued</span>';
535
+ }
536
+ const s = String(indexing.status || "");
537
+ return '<span class="badge" title="' + escapeHtml(s) + '">' + escapeHtml(s.slice(0, 10)) + "</span>";
538
+ }
539
+
540
+ function nexumLandFull(indexing) {
541
+ if (!indexing) return "—";
542
+ if (indexing.status === "skipped") {
543
+ const kind = skipKind(indexing.reason);
544
+ return '<span class="badge skip">' + escapeHtml(kind.label) + "</span>" +
545
+ (indexing.reason
546
+ ? ' <span class="mono">' + escapeHtml(indexing.reason) + "</span>"
547
+ : "");
548
+ }
549
+ if (indexing.status === "queued") {
550
+ return '<span class="badge queued">queued · analyze</span>' +
551
+ (indexing.policy ? ' <span class="mono">policy ' + escapeHtml(indexing.policy) + "</span>" : "") +
552
+ (indexing.operationId ? ' <span class="mono">' + escapeHtml(indexing.operationId) + "</span>" : "");
553
+ }
554
+ return statusBadge(indexing.status);
555
+ }
556
+
557
+ function modeBadge(mode, compact) {
558
+ if (mode === "rules") {
559
+ return compact
560
+ ? '<span class="badge ok" title="rules · no AI">rules</span>'
561
+ : '<span class="badge ok">rules · no AI</span>';
562
+ }
563
+ if (mode === "ai") return '<span class="badge ai">AI</span>';
564
+ if (!mode) return "—";
565
+ return '<span class="badge">unknown</span>';
566
+ }
567
+
568
+ function addr(a) {
569
+ if (!a) return "—";
570
+ return escapeHtml(a.objectType + "/" + a.contentType + "/" + a.recordId);
571
+ }
572
+
573
+ function stableKey(e) {
574
+ if (e.lane === "index") {
575
+ const snap = e.snapshot && e.snapshot.recordId || "";
576
+ const src = e.source && e.source.recordId || "";
577
+ return ["index", e.operationId || "", e.status || "", e.ts || "", snap, src].join("|");
578
+ }
579
+ if (e.lane === "fetch") {
580
+ return ["fetch", e.ts || "", e.mode || "", e.hit ? "1" : "0", e.recordId || "", e.objectType || "", e.contentType || ""].join("|");
581
+ }
582
+ return ["land", e.ts || "", e.objectType || "", e.contentType || "", e.recordId || "", e.revision ?? "", e.outcome || ""].join("|");
583
+ }
584
+
585
+ function countNew() {
586
+ const have = new Set(entries.map(stableKey));
587
+ pendingNew = liveEntries.filter((e) => !have.has(stableKey(e))).length;
588
+ }
589
+
590
+ function updatePauseBar() {
591
+ const bar = $("pause-bar");
592
+ if (!listHeld()) {
593
+ bar.hidden = true;
594
+ return;
595
+ }
596
+ bar.hidden = false;
597
+ const why = dialogOpen()
598
+ ? "Dialog open — list held"
599
+ : "Scrolled down — list held";
600
+ $("pause-msg").textContent = pendingNew > 0 ? why + " · " + pendingNew + " new" : why;
601
+ $("resume").textContent = pendingNew > 0 ? "Show " + pendingNew + " new" : "Resume live";
602
+ }
603
+
604
+ function applyLive() {
605
+ entries = liveEntries;
606
+ pendingNew = 0;
607
+ followLive = true;
608
+ ignoreScroll = true;
609
+ render();
610
+ window.scrollTo(0, 0);
611
+ updatePauseBar();
612
+ requestAnimationFrame(() => { ignoreScroll = false; });
613
+ }
614
+
615
+ function sparkline(values, color) {
616
+ const w = 280, h = 56, pad = 4;
617
+ const nums = values.filter((v) => typeof v === "number" && Number.isFinite(v));
618
+ if (nums.length < 2) {
619
+ return '<svg viewBox="0 0 ' + w + " " + h + '" preserveAspectRatio="none" aria-hidden="true">' +
620
+ '<text x="8" y="32" fill="#5c645c" font-size="11" font-family="IBM Plex Mono, monospace">collecting…</text></svg>';
621
+ }
622
+ const min = Math.min(...nums);
623
+ const max = Math.max(...nums);
624
+ const span = max - min || 1;
625
+ const pts = nums.map((v, i) => {
626
+ const x = pad + (i / (nums.length - 1)) * (w - pad * 2);
627
+ const y = h - pad - ((v - min) / span) * (h - pad * 2);
628
+ return x.toFixed(1) + "," + y.toFixed(1);
629
+ }).join(" ");
630
+ const last = nums[nums.length - 1];
631
+ const lastX = pad + (w - pad * 2);
632
+ const lastY = h - pad - ((last - min) / span) * (h - pad * 2);
633
+ return '<svg viewBox="0 0 ' + w + " " + h + '" preserveAspectRatio="none" aria-hidden="true">' +
634
+ '<polyline fill="none" stroke="' + color + '" stroke-width="1.75" points="' + pts + '" />' +
635
+ '<circle cx="' + lastX.toFixed(1) + '" cy="' + lastY.toFixed(1) + '" r="2.5" fill="' + color + '" /></svg>';
636
+ }
637
+
638
+ function renderCharts() {
639
+ const b = seriesBuckets;
640
+ const sum = (key) => b.reduce((n, x) => n + (Number(x[key]) || 0), 0);
641
+ const last = (key) => (b.length ? Number(b[b.length - 1][key]) || 0 : 0);
642
+ const cards = [
643
+ {
644
+ title: "Pushed",
645
+ value: sizes.land,
646
+ unit: "in feed",
647
+ series: b.map((x) => x.land),
648
+ color: "#c8f082",
649
+ hint: "last min " + last("land") + " · Σ60m " + sum("land"),
650
+ },
651
+ {
652
+ title: "Processed",
653
+ value: sizes.index,
654
+ unit: "in feed",
655
+ series: b.map((x) => x.connect),
656
+ color: "#7eb8d4",
657
+ hint: "rules Σ " + sum("connectRules") + " · AI Σ " + sum("connectAi"),
658
+ },
659
+ {
660
+ title: "Relations created",
661
+ value: sum("relations"),
662
+ unit: "Σ60m",
663
+ series: b.map((x) => x.relations),
664
+ color: "#d4a574",
665
+ hint: "Relationship plans from Nexum graphs",
666
+ },
667
+ {
668
+ title: "Fetched",
669
+ value: sizes.fetch,
670
+ unit: "in feed",
671
+ series: b.map((x) => x.fetch),
672
+ color: "#3dba7a",
673
+ hint: "last min " + last("fetch") + " · Σ60m " + sum("fetch"),
674
+ },
675
+ ];
676
+ $("charts").innerHTML = cards.map((c) =>
677
+ '<div class="health-card">' +
678
+ "<h3>" + escapeHtml(c.title) + "</h3>" +
679
+ '<div class="val">' + escapeHtml(c.value) + "<span>" + escapeHtml(c.unit) + "</span></div>" +
680
+ sparkline(c.series, c.color) +
681
+ '<div class="hint">' + escapeHtml(c.hint) + "</div></div>"
682
+ ).join("");
683
+ }
684
+
685
+ function layoutNodes(labels, edges) {
686
+ const ids = [];
687
+ const seen = new Set();
688
+ for (const lab of labels || []) {
689
+ if (!seen.has(lab)) { seen.add(lab); ids.push(lab); }
690
+ }
691
+ for (const e of edges || []) {
692
+ if (e.from && !seen.has(e.from)) { seen.add(e.from); ids.push(e.from); }
693
+ if (e.to && !seen.has(e.to)) { seen.add(e.to); ids.push(e.to); }
694
+ }
695
+ const n = Math.max(ids.length, 1);
696
+ const cx = 320, cy = 110, r = Math.min(90, 40 + n * 6);
697
+ const pos = new Map();
698
+ ids.forEach((id, i) => {
699
+ const ang = (Math.PI * 2 * i) / n - Math.PI / 2;
700
+ pos.set(id, { x: cx + r * Math.cos(ang), y: cy + r * Math.sin(ang), id });
701
+ });
702
+ return { ids, pos };
703
+ }
704
+
705
+ function renderGraphSvg(nodeLabels, edgeSummaries) {
706
+ const { ids, pos } = layoutNodes(nodeLabels, edgeSummaries);
707
+ if (!ids.length) return "";
708
+ let lines = "";
709
+ for (const e of edgeSummaries || []) {
710
+ const a = pos.get(e.from), b = pos.get(e.to);
711
+ if (!a || !b) continue;
712
+ lines += '<line x1="' + a.x + '" y1="' + a.y + '" x2="' + b.x + '" y2="' + b.y + '" />';
713
+ }
714
+ let nodes = "";
715
+ for (const id of ids) {
716
+ const p = pos.get(id);
717
+ const short = id.length > 18 ? id.slice(0, 16) + "…" : id;
718
+ nodes += '<circle cx="' + p.x + '" cy="' + p.y + '" r="6" />';
719
+ nodes += '<text x="' + p.x + '" y="' + (p.y + 18) + '" text-anchor="middle">' + escapeHtml(short) + "</text>";
720
+ }
721
+ return '<svg class="graph-svg" viewBox="0 0 640 220" xmlns="http://www.w3.org/2000/svg">' +
722
+ lines + nodes + "</svg>";
723
+ }
724
+
725
+ function renderDetail(e) {
726
+ const t = typeOf(e);
727
+ if (e.lane === "land") {
728
+ return '<dl class="facts">' +
729
+ fact("Time", escapeHtml(fmtTime(e.ts))) +
730
+ fact("Org", escapeHtml(e.orgId || "—")) +
731
+ fact("Caller", escapeHtml(e.caller || "—")) +
732
+ fact("Object type", '<span class="mono">' + escapeHtml(t.ot || "—") + "</span>") +
733
+ fact("Content type", '<span class="mono">' + escapeHtml(t.ct || "—") + "</span>") +
734
+ fact("Record", '<span class="mono">' + escapeHtml(e.recordId || "—") + "</span>") +
735
+ fact("Revision", escapeHtml(e.revision)) +
736
+ fact("Outcome", escapeHtml(e.outcome || "—")) +
737
+ (e.indexing ? fact("Nexum", nexumLandFull(e.indexing)) : "") +
738
+ "</dl>";
739
+ }
740
+ if (e.lane === "fetch") {
741
+ return '<dl class="facts">' +
742
+ fact("Time", escapeHtml(fmtTime(e.ts))) +
743
+ fact("Org", escapeHtml(e.orgId || "—")) +
744
+ fact("Caller", escapeHtml(e.caller || "—")) +
745
+ fact("Object type", '<span class="mono">' + escapeHtml(t.ot || "—") + "</span>") +
746
+ fact("Content type", '<span class="mono">' + escapeHtml(t.ct || "—") + "</span>") +
747
+ fact("Mode", escapeHtml(e.mode || "—")) +
748
+ fact("Record", '<span class="mono">' + escapeHtml(e.recordId || "—") + "</span>") +
749
+ fact("Hit", e.hit ? "yes" : "no") +
750
+ (e.count != null ? fact("Count", escapeHtml(e.count)) : "") +
751
+ (e.knowledgeGraph ? fact("Kind", "knowledge-graph subject") : "") +
752
+ "</dl>";
753
+ }
754
+ let html = '<dl class="facts">';
755
+ html += fact("Time", escapeHtml(fmtTime(e.ts)));
756
+ html += fact("Org", escapeHtml(e.orgId || "—"));
757
+ html += fact("Status", statusBadge(e.status));
758
+ if (e.analyzeMode || e.analyzeMethod) {
759
+ html += fact("How", modeBadge(e.analyzeMode || "unknown", false) +
760
+ (e.analyzeMethod ? ' <span class="mono">' + escapeHtml(e.analyzeMethod) + "</span>" : ""));
761
+ }
762
+ html += fact("Object type", '<span class="mono">' + escapeHtml(t.ot || "—") + "</span>");
763
+ html += fact("Content type", '<span class="mono">' + escapeHtml(t.ct || "—") + "</span>");
764
+ if (e.operationId) html += fact("Operation", '<span class="mono">' + escapeHtml(e.operationId) + "</span>");
765
+ if (e.source) html += fact("Source", addr(e.source));
766
+ if (e.snapshot) html += fact("Snapshot", addr(e.snapshot));
767
+ if (e.sourceRefs && e.sourceRefs.length) {
768
+ html += fact("Source refs", '<span class="mono">' + escapeHtml(e.sourceRefs.join(", ")) + "</span>");
769
+ }
770
+ html += fact("Nodes", escapeHtml(e.nodeCount ?? 0));
771
+ html += fact("Graph edges", escapeHtml(e.edgeCount ?? 0));
772
+ html += fact("Relations", escapeHtml(e.relationCount ?? 0));
773
+ {
774
+ const snapId = e.snapshot && e.snapshot.recordId;
775
+ const src0 = e.sourceRefs && e.sourceRefs[0];
776
+ const graphHref = snapId
777
+ ? "/_live/graph?recordId=" + encodeURIComponent(snapId)
778
+ : src0
779
+ ? "/_live/graph?source=" + encodeURIComponent(src0)
780
+ : "/_live/graph";
781
+ html += fact("Graph", '<a href="' + escapeHtml(graphHref) + '">Open stored graph</a>');
782
+ }
783
+ if (e.error) html += fact("Error", '<span style="color:var(--bad)">' + escapeHtml(e.error) + "</span>");
784
+ html += "</dl>";
785
+ const edges = e.edgeSummaries || [];
786
+ if (edges.length || (e.nodeLabels && e.nodeLabels.length)) {
787
+ html += '<div class="detail">';
788
+ if (edges.length) {
789
+ html += "<h3>Edges</h3><ul class='edge-list'>";
790
+ for (const ed of edges) {
791
+ html += "<li>" + escapeHtml(ed.from) + " → " + escapeHtml(ed.to) +
792
+ (ed.label ? " <span class='mono'>(" + escapeHtml(ed.label) + ")</span>" : "") +
793
+ "</li>";
794
+ }
795
+ html += "</ul>";
796
+ }
797
+ html += renderGraphSvg(e.nodeLabels || [], edges);
798
+ html += "</div>";
799
+ }
800
+ return html;
801
+ }
802
+
803
+ /** Land/fetch store OT/CT on the event. Connect stores them on source. */
804
+ function typeOf(e) {
805
+ const src = e.source || {};
806
+ return {
807
+ ot: src.objectType || e.objectType || "",
808
+ ct: src.contentType || e.contentType || "",
809
+ };
810
+ }
811
+
812
+ function typeCells(e) {
813
+ const t = typeOf(e);
814
+ return [
815
+ '<span class="mono">' + escapeHtml(t.ot || "—") + "</span>",
816
+ '<span class="mono">' + escapeHtml(t.ct || "—") + "</span>",
817
+ ];
818
+ }
819
+
820
+ function rowCells(e) {
821
+ if (e.lane === "land") {
822
+ return [
823
+ fmtTime(e.ts),
824
+ escapeHtml(e.orgId),
825
+ escapeHtml(e.caller || "—"),
826
+ ...typeCells(e),
827
+ '<span class="mono">' + escapeHtml(e.recordId) + "</span>",
828
+ escapeHtml(e.outcome),
829
+ nexumLandCell(e.indexing),
830
+ ];
831
+ }
832
+ if (e.lane === "fetch") {
833
+ return [
834
+ fmtTime(e.ts),
835
+ escapeHtml(e.orgId),
836
+ escapeHtml(e.caller || "—"),
837
+ ...typeCells(e),
838
+ escapeHtml(e.mode),
839
+ '<span class="mono">' + escapeHtml(e.recordId || "—") + "</span>",
840
+ statusBadge(e.hit ? "hit" : "miss"),
841
+ ];
842
+ }
843
+ return [
844
+ fmtTime(e.ts),
845
+ escapeHtml(e.orgId),
846
+ statusBadge(e.status),
847
+ modeBadge(e.analyzeMode || (e.status === "connected" ? "unknown" : ""), true),
848
+ ...typeCells(e),
849
+ e.snapshot
850
+ ? '<span class="mono">' + escapeHtml(e.snapshot.recordId) + "</span>"
851
+ : "—",
852
+ (e.relationCount != null || e.edgeCount != null)
853
+ ? '<span class="mono" title="' + escapeHtml((e.relationCount ?? 0) + " rel / " + (e.edgeCount ?? 0) + " edges") + '">' +
854
+ escapeHtml((e.relationCount ?? 0) + "/" + (e.edgeCount ?? 0)) + "</span>"
855
+ : "—",
856
+ ];
857
+ }
858
+
859
+ function headersForLane() {
860
+ if (lane === "land") return ["Time", "Org", "Caller", "Object type", "Content type", "Record", "Outcome", "Nexum"];
861
+ if (lane === "fetch") return ["Time", "Org", "Caller", "Object type", "Content type", "Mode", "Record", "Hit"];
862
+ return ["Time", "Org", "Status", "How", "Object type", "Content type", "Snapshot", "Rel/Edges"];
863
+ }
864
+
865
+ const LANE_HINT = {
866
+ land: "<strong>Pushed</strong> — data written into Memorix. The <em>Nexum</em> column is short (<em>no-pol</em>, <em>queued</em>, …): whether this write was sent on to processing. Open a row for the full reason. Rules vs AI show on Processed after Nexum finishes.",
867
+ index: "<strong>Processed</strong> — Nexum working on pushed data. How = <em>rules</em> (no AI) or <em>AI</em>. Rel/Edges = relationship plans / graph edges (full counts in the dialog).",
868
+ fetch: "<strong>Fetched</strong> — data read out of Memorix (up to " + capacity + ").",
869
+ };
870
+
871
+ function fmtWhen(iso) {
872
+ if (!iso) return "";
873
+ const d = new Date(iso);
874
+ return Number.isNaN(d.getTime()) ? String(iso) : d.toLocaleString();
875
+ }
876
+
877
+ function joinOr(names) {
878
+ if (names.length <= 1) return names[0] || "";
879
+ if (names.length === 2) return names[0] + " or " + names[1];
880
+ return names.slice(0, -1).join(", ") + ", or " + names[names.length - 1];
881
+ }
882
+
883
+ function applyFreshness(src) {
884
+ if (!src) return;
885
+ if (src.startedAt) startedAt = src.startedAt;
886
+ if (src.latest) {
887
+ latest = {
888
+ land: src.latest.land || null,
889
+ index: src.latest.index || null,
890
+ fetch: src.latest.fetch || null,
891
+ };
892
+ }
893
+ }
894
+
895
+ function renderBanner() {
896
+ const el = $("quiet-banner");
897
+ if (!el) return;
898
+ const rows = [
899
+ { quiet: "pushed", last: "push", n: sizes.land || 0, ts: latest.land },
900
+ { quiet: "processed", last: "processed event", n: sizes.index || 0, ts: latest.index },
901
+ { quiet: "fetched", last: "fetch", n: sizes.fetch || 0, ts: latest.fetch },
902
+ ];
903
+ const silent = rows.filter((r) => !r.n);
904
+ const active = rows.filter((r) => r.n && r.ts);
905
+ if (!silent.length) {
906
+ el.hidden = true;
907
+ el.innerHTML = "";
908
+ return;
909
+ }
910
+ const since = fmtWhen(startedAt);
911
+ let html = "<strong>Nothing " + escapeHtml(joinOr(silent.map((r) => r.quiet))) + "</strong>";
912
+ html += since
913
+ ? " since " + escapeHtml(since) + ", when this process started."
914
+ : " in this process.";
915
+ if (active.length) {
916
+ html += " " + active.map((r) =>
917
+ "Last " + escapeHtml(r.last) + " " + escapeHtml(fmtWhen(r.ts)) + "."
918
+ ).join(" ");
919
+ }
920
+ el.hidden = false;
921
+ el.innerHTML = html;
922
+ }
923
+
924
+ function emptyMessage() {
925
+ const n = laneSize();
926
+ const since = fmtWhen(startedAt);
927
+ const sinceBit = since ? " since " + since + "." : " in this process.";
928
+ if (!n) {
929
+ if (lane === "land") return "Nothing pushed" + sinceBit;
930
+ if (lane === "index") return "Nothing processed" + sinceBit;
931
+ return "Nothing fetched" + sinceBit;
932
+ }
933
+ if (lane === "land") return "No pushed data matches these filters.";
934
+ if (lane === "index") return "No processed data matches these filters.";
935
+ return "No fetched data matches these filters.";
936
+ }
937
+
938
+ function fillSelect(sel, values, current) {
939
+ const keep = current || "";
940
+ const opts = ['<option value="">All</option>'].concat(
941
+ values.map((v) => '<option value="' + escapeHtml(v) + '"' +
942
+ (v === keep ? " selected" : "") + ">" + escapeHtml(v) + "</option>")
943
+ );
944
+ sel.innerHTML = opts.join("");
945
+ if ([...sel.options].some((o) => o.value === keep)) sel.value = keep;
946
+ else sel.value = "";
947
+ }
948
+
949
+ function refreshFilterOptions(all) {
950
+ const orgs = [...new Set(all.map((e) => e.orgId).filter(Boolean))].sort();
951
+ const callers = [...new Set(all.map((e) => e.caller).filter(Boolean))].sort();
952
+ const ots = [...new Set(all.map((e) => typeOf(e).ot).filter(Boolean))].sort();
953
+ const cts = [...new Set(all.filter((e) => !filterState.ot || typeOf(e).ot === filterState.ot).map((e) => typeOf(e).ct).filter(Boolean))].sort();
954
+ fillSelect($("f-org"), orgs, filterState.org);
955
+ fillSelect($("f-caller"), callers, filterState.caller);
956
+ fillSelect($("f-ot"), ots, filterState.ot);
957
+ fillSelect($("f-ct"), cts, filterState.ct);
958
+ if (filterState.ot && !$("f-ot").value) filterState.ot = "";
959
+ if (filterState.ct && !$("f-ct").value) filterState.ct = "";
960
+ }
961
+
962
+ function filteredEntries() {
963
+ return entries.filter((e) => {
964
+ if (filterState.org && e.orgId !== filterState.org) return false;
965
+ if (filterState.caller && e.caller !== filterState.caller) return false;
966
+ const types = typeOf(e);
967
+ if (filterState.ot && types.ot !== filterState.ot) return false;
968
+ if (filterState.ct && types.ct !== filterState.ct) return false;
969
+ if (filterState.q) {
970
+ const hay = [
971
+ e.orgId, e.caller, e.recordId, e.objectType, e.contentType,
972
+ e.source && e.source.recordId, e.snapshot && e.snapshot.recordId,
973
+ e.status, e.analyzeMode,
974
+ e.indexing && e.indexing.status, e.indexing && e.indexing.reason,
975
+ ].filter(Boolean).join(" ").toLowerCase();
976
+ if (!hay.includes(filterState.q)) return false;
977
+ }
978
+ if (lane === "land" && filterState.nexum) {
979
+ const st = (e.indexing && e.indexing.status) || "";
980
+ if (st !== filterState.nexum) return false;
981
+ }
982
+ if (lane === "index" && filterState.how) {
983
+ const how = e.analyzeMode || "unknown";
984
+ if (how !== filterState.how) return false;
985
+ }
986
+ if (lane === "fetch" && filterState.hit) {
987
+ if (filterState.hit === "hit" && !e.hit) return false;
988
+ if (filterState.hit === "miss" && e.hit) return false;
989
+ }
990
+ return true;
991
+ });
992
+ }
993
+
994
+ function laneSize() {
995
+ if (lane === "land") return sizes.land || 0;
996
+ if (lane === "index") return sizes.index || 0;
997
+ return sizes.fetch || 0;
998
+ }
999
+
1000
+ function updateSummary(shownCount, totalCount) {
1001
+ $("summary").innerHTML =
1002
+ "<span>pushed <strong>" + sizes.land + "/" + capacity + "</strong></span>" +
1003
+ "<span>processed <strong>" + sizes.index + "/" + capacity + "</strong></span>" +
1004
+ "<span>fetched <strong>" + sizes.fetch + "/" + capacity + "</strong></span>" +
1005
+ "<span>showing <strong>" + shownCount + "</strong> latest of <strong>" + totalCount + "</strong></span>";
1006
+ }
1007
+
1008
+ function render() {
1009
+ renderBanner();
1010
+ $("lane-hint").innerHTML = LANE_HINT[lane] || "";
1011
+ refreshFilterOptions(entries);
1012
+ const shown = filteredEntries();
1013
+ updateSummary(shown.length, laneSize());
1014
+ renderCharts();
1015
+ updatePauseBar();
1016
+
1017
+ const wrap = $("table-wrap");
1018
+ const prevY = window.scrollY;
1019
+ ignoreScroll = true;
1020
+
1021
+ if (!shown.length) {
1022
+ wrap.innerHTML = "<p class='empty'>" + escapeHtml(emptyMessage()) + "</p>";
1023
+ requestAnimationFrame(() => { ignoreScroll = false; });
1024
+ return;
1025
+ }
1026
+ const heads = headersForLane();
1027
+ const colW = lane === "fetch"
1028
+ ? ["8%","7%","9%","14%","16%","7%","27%","12%"]
1029
+ : lane === "index"
1030
+ ? ["8%","7%","8%","10%","14%","16%","25%","12%"]
1031
+ : ["8%","7%","10%","14%","16%","26%","9%","10%"];
1032
+ let html = '<table class="list"><colgroup>' +
1033
+ colW.map((w) => '<col style="width:' + w + '">').join("") +
1034
+ "</colgroup><thead><tr>" + heads.map((h) => "<th>" + h + "</th>").join("") + "</tr></thead><tbody>";
1035
+ shown.forEach((e) => {
1036
+ const key = stableKey(e);
1037
+ const cells = rowCells(e);
1038
+ html += '<tr class="clickable" data-key="' + escapeHtml(key) + '">' +
1039
+ cells.map((c) => {
1040
+ const tip = String(c).replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
1041
+ return '<td title="' + escapeHtml(tip) + '">' + c + "</td>";
1042
+ }).join("") + "</tr>";
1043
+ });
1044
+ html += "</tbody></table>";
1045
+ wrap.innerHTML = html;
1046
+ if (listHeld()) window.scrollTo(0, prevY);
1047
+ requestAnimationFrame(() => { ignoreScroll = false; });
1048
+ wrap.querySelectorAll("tr.clickable").forEach((tr) => {
1049
+ tr.addEventListener("click", () => {
1050
+ const k = tr.getAttribute("data-key");
1051
+ const e = shown.find((row) => stableKey(row) === k);
1052
+ if (e) openDialog(e);
1053
+ });
1054
+ });
1055
+ }
1056
+
1057
+ let loadSeq = 0;
1058
+ async function load(applyNow) {
1059
+ const seq = ++loadSeq;
1060
+ const requested = lane;
1061
+ try {
1062
+ const [actRes, serRes] = await Promise.all([
1063
+ fetch("/_live/api/activity?lane=" + encodeURIComponent(requested) + "&limit=400"),
1064
+ fetch("/_live/api/activity-series?minutes=60"),
1065
+ ]);
1066
+ if (seq !== loadSeq || requested !== lane) return;
1067
+ const body = await actRes.json();
1068
+ if (seq !== loadSeq || requested !== lane) return;
1069
+ if (!actRes.ok || body.ok === false) throw new Error(body?.error?.message || ("HTTP " + actRes.status));
1070
+ liveEntries = body.entries || [];
1071
+ sizes = body.sizes || sizes;
1072
+ capacity = body.capacity || 10000;
1073
+ applyFreshness(body);
1074
+ if (serRes.ok) {
1075
+ const ser = await serRes.json();
1076
+ seriesBuckets = ser.buckets || [];
1077
+ if (ser.sizes) sizes = ser.sizes;
1078
+ applyFreshness(ser);
1079
+ }
1080
+ if (seq !== loadSeq || requested !== lane) return;
1081
+ renderCharts();
1082
+ if (applyNow || !listHeld()) {
1083
+ entries = liveEntries;
1084
+ pendingNew = 0;
1085
+ render();
1086
+ } else {
1087
+ countNew();
1088
+ const shown = filteredEntries();
1089
+ updateSummary(shown.length, laneSize());
1090
+ updatePauseBar();
1091
+ renderBanner();
1092
+ }
1093
+ } catch (err) {
1094
+ if (seq !== loadSeq || requested !== lane) return;
1095
+ $("table-wrap").innerHTML = "<p class='empty'>" + escapeHtml(err.message || String(err)) + "</p>";
1096
+ }
1097
+ }
1098
+
1099
+ load();
1100
+ setInterval(load, 1500);
1101
+ </script>
1102
+ </body>
1103
+ </html>`;
1104
+ }
1105
+ //# sourceMappingURL=activity-ui.js.map