@runcontext/site 0.3.4 → 0.4.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.
- package/README.md +3 -41
- package/dist/index.cjs +1495 -649
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.mjs +1494 -648
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
package/dist/index.mjs
CHANGED
|
@@ -12,354 +12,486 @@ var HEAD = `<!DOCTYPE html>
|
|
|
12
12
|
<title><%= pageTitle %> \u2014 <%= siteTitle %></title>
|
|
13
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
15
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@300;400;500;600&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
16
16
|
<style>
|
|
17
17
|
:root {
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--bg: #
|
|
23
|
-
--bg-
|
|
24
|
-
--bg-
|
|
25
|
-
--
|
|
26
|
-
--
|
|
27
|
-
--text
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
--
|
|
35
|
-
--
|
|
18
|
+
--accent: #c9a55a;
|
|
19
|
+
--accent-light: #e0be6a;
|
|
20
|
+
--accent-dim: rgba(201, 165, 90, 0.12);
|
|
21
|
+
--accent-border: rgba(201, 165, 90, 0.25);
|
|
22
|
+
--bg: #0a0908;
|
|
23
|
+
--bg-sidebar: #0f0e0d;
|
|
24
|
+
--bg-card: #141312;
|
|
25
|
+
--bg-hover: #1a1918;
|
|
26
|
+
--bg-code: #111010;
|
|
27
|
+
--text: #e8e6e1;
|
|
28
|
+
--text-secondary: #a09d94;
|
|
29
|
+
--text-dim: #6a675e;
|
|
30
|
+
--border: #252320;
|
|
31
|
+
--border-light: #302d28;
|
|
32
|
+
--green: #4aba6a;
|
|
33
|
+
--green-dim: rgba(74, 186, 106, 0.1);
|
|
34
|
+
--red: #d45050;
|
|
35
|
+
--red-dim: rgba(212, 80, 80, 0.08);
|
|
36
|
+
--blue: #5b9cf5;
|
|
37
|
+
--blue-dim: rgba(91, 156, 245, 0.1);
|
|
38
|
+
--purple: #a78bfa;
|
|
39
|
+
--cyan: #22d3ee;
|
|
40
|
+
--orange: #f59e0b;
|
|
41
|
+
--sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
42
|
+
--mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;
|
|
43
|
+
--sidebar-w: 260px;
|
|
44
|
+
--topbar-h: 48px;
|
|
36
45
|
}
|
|
46
|
+
|
|
37
47
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
38
48
|
html { scroll-behavior: smooth; }
|
|
49
|
+
|
|
39
50
|
body {
|
|
40
51
|
font-family: var(--sans);
|
|
41
52
|
background: var(--bg);
|
|
42
53
|
color: var(--text);
|
|
43
54
|
line-height: 1.6;
|
|
44
|
-
overflow-x: hidden;
|
|
45
55
|
}
|
|
46
|
-
|
|
56
|
+
|
|
57
|
+
a { color: var(--accent); text-decoration: none; }
|
|
47
58
|
a:hover { text-decoration: underline; }
|
|
48
59
|
|
|
49
|
-
/* ===
|
|
50
|
-
|
|
51
|
-
content: '';
|
|
60
|
+
/* === TOPBAR === */
|
|
61
|
+
.topbar {
|
|
52
62
|
position: fixed;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
top: 0;
|
|
64
|
+
left: 0;
|
|
65
|
+
right: 0;
|
|
66
|
+
height: var(--topbar-h);
|
|
67
|
+
background: var(--bg-sidebar);
|
|
68
|
+
border-bottom: 1px solid var(--border);
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
padding: 0 1.25rem;
|
|
73
|
+
z-index: 200;
|
|
74
|
+
}
|
|
75
|
+
.topbar-left {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.75rem;
|
|
57
79
|
}
|
|
80
|
+
.topbar-logo {
|
|
81
|
+
font-family: var(--sans);
|
|
82
|
+
font-size: 0.9rem;
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
color: var(--accent);
|
|
85
|
+
text-decoration: none;
|
|
86
|
+
letter-spacing: -0.02em;
|
|
87
|
+
}
|
|
88
|
+
.topbar-logo:hover { text-decoration: none; }
|
|
89
|
+
.topbar-sep {
|
|
90
|
+
color: var(--border-light);
|
|
91
|
+
font-size: 1.1rem;
|
|
92
|
+
font-weight: 300;
|
|
93
|
+
}
|
|
94
|
+
.topbar-page {
|
|
95
|
+
font-size: 0.8rem;
|
|
96
|
+
color: var(--text-secondary);
|
|
97
|
+
}
|
|
98
|
+
.topbar-right {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 1rem;
|
|
102
|
+
}
|
|
103
|
+
.topbar-link {
|
|
104
|
+
font-size: 0.78rem;
|
|
105
|
+
color: var(--text-dim);
|
|
106
|
+
text-decoration: none;
|
|
107
|
+
transition: color 0.15s;
|
|
108
|
+
}
|
|
109
|
+
.topbar-link:hover { color: var(--text); text-decoration: none; }
|
|
110
|
+
.topbar-docs {
|
|
111
|
+
font-size: 0.72rem;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
color: var(--accent);
|
|
114
|
+
border: 1px solid var(--accent-border);
|
|
115
|
+
padding: 0.3rem 0.65rem;
|
|
116
|
+
border-radius: 5px;
|
|
117
|
+
text-decoration: none;
|
|
118
|
+
transition: background 0.15s;
|
|
119
|
+
}
|
|
120
|
+
.topbar-docs:hover { background: var(--accent-dim); text-decoration: none; }
|
|
58
121
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
122
|
+
.menu-toggle {
|
|
123
|
+
display: none;
|
|
124
|
+
background: none;
|
|
125
|
+
border: none;
|
|
126
|
+
color: var(--text-secondary);
|
|
127
|
+
font-size: 1.3rem;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
padding: 0.25rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* === SIDEBAR === */
|
|
133
|
+
.sidebar {
|
|
134
|
+
position: fixed;
|
|
135
|
+
top: var(--topbar-h);
|
|
136
|
+
left: 0;
|
|
137
|
+
bottom: 0;
|
|
138
|
+
width: var(--sidebar-w);
|
|
139
|
+
background: var(--bg-sidebar);
|
|
140
|
+
border-right: 1px solid var(--border);
|
|
141
|
+
overflow-y: auto;
|
|
142
|
+
padding: 1rem 0;
|
|
143
|
+
z-index: 100;
|
|
144
|
+
}
|
|
145
|
+
.sidebar::-webkit-scrollbar { width: 4px; }
|
|
146
|
+
.sidebar::-webkit-scrollbar-track { background: transparent; }
|
|
147
|
+
.sidebar::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 2px; }
|
|
148
|
+
|
|
149
|
+
.sidebar-section {
|
|
150
|
+
padding: 0 0.75rem;
|
|
151
|
+
margin-bottom: 1.25rem;
|
|
152
|
+
}
|
|
153
|
+
.sidebar-heading {
|
|
63
154
|
font-family: var(--mono);
|
|
64
|
-
font-size: 0.
|
|
65
|
-
|
|
155
|
+
font-size: 0.6rem;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
letter-spacing: 0.15em;
|
|
66
158
|
text-transform: uppercase;
|
|
67
|
-
color: var(--
|
|
68
|
-
|
|
159
|
+
color: var(--text-dim);
|
|
160
|
+
padding: 0 0.5rem;
|
|
161
|
+
margin-bottom: 0.4rem;
|
|
69
162
|
}
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
163
|
+
.sidebar-item {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: center;
|
|
166
|
+
gap: 0.5rem;
|
|
167
|
+
padding: 0.35rem 0.5rem;
|
|
168
|
+
border-radius: 5px;
|
|
169
|
+
font-size: 0.82rem;
|
|
170
|
+
color: var(--text-secondary);
|
|
171
|
+
text-decoration: none;
|
|
172
|
+
transition: background 0.12s, color 0.12s;
|
|
173
|
+
}
|
|
174
|
+
.sidebar-item:hover {
|
|
175
|
+
background: var(--bg-hover);
|
|
76
176
|
color: var(--text);
|
|
177
|
+
text-decoration: none;
|
|
77
178
|
}
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
max-width: 600px;
|
|
82
|
-
margin-bottom: 2rem;
|
|
83
|
-
font-weight: 300;
|
|
179
|
+
.sidebar-item.active {
|
|
180
|
+
background: var(--accent-dim);
|
|
181
|
+
color: var(--accent-light);
|
|
84
182
|
}
|
|
85
|
-
.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
183
|
+
.sidebar-badge {
|
|
184
|
+
font-family: var(--mono);
|
|
185
|
+
font-size: 0.55rem;
|
|
186
|
+
font-weight: 600;
|
|
187
|
+
letter-spacing: 0.08em;
|
|
188
|
+
text-transform: uppercase;
|
|
189
|
+
padding: 0.1rem 0.35rem;
|
|
190
|
+
border-radius: 3px;
|
|
191
|
+
margin-left: auto;
|
|
90
192
|
}
|
|
193
|
+
.sidebar-badge-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.12); }
|
|
194
|
+
.sidebar-badge-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.1); }
|
|
195
|
+
.sidebar-badge-gold { color: var(--accent); background: var(--accent-dim); }
|
|
196
|
+
.sidebar-badge-none { color: var(--text-dim); background: rgba(106, 103, 94, 0.1); }
|
|
91
197
|
|
|
92
|
-
/* ===
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
198
|
+
/* === MAIN CONTENT === */
|
|
199
|
+
.main {
|
|
200
|
+
margin-left: var(--sidebar-w);
|
|
201
|
+
margin-top: var(--topbar-h);
|
|
202
|
+
min-height: calc(100vh - var(--topbar-h));
|
|
203
|
+
padding: 2rem 2.5rem 4rem;
|
|
204
|
+
max-width: calc(900px + var(--sidebar-w));
|
|
97
205
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
background: radial-gradient(circle, var(--gold-glow) 0%, transparent 70%);
|
|
107
|
-
pointer-events: none;
|
|
108
|
-
}
|
|
109
|
-
.hero-eyebrow {
|
|
110
|
-
font-family: var(--mono);
|
|
111
|
-
font-size: 0.7rem;
|
|
112
|
-
letter-spacing: 0.3em;
|
|
113
|
-
text-transform: uppercase;
|
|
114
|
-
color: var(--gold);
|
|
206
|
+
|
|
207
|
+
/* === BREADCRUMB === */
|
|
208
|
+
.breadcrumb {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: 0.35rem;
|
|
212
|
+
font-size: 0.78rem;
|
|
213
|
+
color: var(--text-dim);
|
|
115
214
|
margin-bottom: 1.5rem;
|
|
116
|
-
position: relative;
|
|
117
215
|
}
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
216
|
+
.breadcrumb a {
|
|
217
|
+
color: var(--text-secondary);
|
|
218
|
+
text-decoration: none;
|
|
219
|
+
}
|
|
220
|
+
.breadcrumb a:hover { color: var(--text); text-decoration: none; }
|
|
221
|
+
.breadcrumb-sep { color: var(--border-light); }
|
|
222
|
+
|
|
223
|
+
/* === PAGE HEADER === */
|
|
224
|
+
.page-header { margin-bottom: 2rem; }
|
|
225
|
+
.page-header h1 {
|
|
226
|
+
font-size: clamp(1.5rem, 3vw, 2rem);
|
|
227
|
+
font-weight: 700;
|
|
228
|
+
letter-spacing: -0.025em;
|
|
124
229
|
color: var(--text);
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
max-width: 520px;
|
|
132
|
-
margin: 1.5rem auto 0;
|
|
230
|
+
line-height: 1.2;
|
|
231
|
+
}
|
|
232
|
+
.page-header .subtitle {
|
|
233
|
+
font-size: 0.95rem;
|
|
234
|
+
color: var(--text-secondary);
|
|
235
|
+
margin-top: 0.4rem;
|
|
133
236
|
font-weight: 300;
|
|
134
|
-
|
|
237
|
+
max-width: 600px;
|
|
135
238
|
}
|
|
136
239
|
|
|
137
|
-
/* === STATS === */
|
|
138
|
-
.stats {
|
|
139
|
-
display:
|
|
140
|
-
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
240
|
+
/* === STATS ROW === */
|
|
241
|
+
.stats-row {
|
|
242
|
+
display: flex;
|
|
141
243
|
gap: 1px;
|
|
142
244
|
background: var(--border);
|
|
143
245
|
border: 1px solid var(--border);
|
|
144
|
-
border-radius:
|
|
246
|
+
border-radius: 8px;
|
|
145
247
|
overflow: hidden;
|
|
146
|
-
margin-bottom:
|
|
248
|
+
margin-bottom: 2rem;
|
|
147
249
|
}
|
|
148
|
-
.stat {
|
|
250
|
+
.stat-item {
|
|
251
|
+
flex: 1;
|
|
149
252
|
background: var(--bg-card);
|
|
150
|
-
padding: 1.
|
|
253
|
+
padding: 1rem 1.25rem;
|
|
151
254
|
text-align: center;
|
|
255
|
+
min-width: 80px;
|
|
152
256
|
}
|
|
153
|
-
.stat-
|
|
154
|
-
font-family: var(--
|
|
155
|
-
font-size:
|
|
156
|
-
font-weight:
|
|
157
|
-
color: var(--
|
|
257
|
+
.stat-val {
|
|
258
|
+
font-family: var(--mono);
|
|
259
|
+
font-size: 1.5rem;
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
color: var(--accent);
|
|
158
262
|
line-height: 1;
|
|
159
263
|
}
|
|
160
|
-
.stat-
|
|
161
|
-
font-size: 0.
|
|
162
|
-
color: var(--text-
|
|
163
|
-
margin-top: 0.
|
|
264
|
+
.stat-lbl {
|
|
265
|
+
font-size: 0.65rem;
|
|
266
|
+
color: var(--text-dim);
|
|
267
|
+
margin-top: 0.3rem;
|
|
164
268
|
text-transform: uppercase;
|
|
165
269
|
letter-spacing: 0.1em;
|
|
166
270
|
}
|
|
167
271
|
|
|
272
|
+
/* === SECTION === */
|
|
273
|
+
.section { margin-bottom: 2.5rem; }
|
|
274
|
+
.section-label {
|
|
275
|
+
font-family: var(--mono);
|
|
276
|
+
font-size: 0.6rem;
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
letter-spacing: 0.2em;
|
|
279
|
+
text-transform: uppercase;
|
|
280
|
+
color: var(--text-dim);
|
|
281
|
+
margin-bottom: 0.5rem;
|
|
282
|
+
}
|
|
283
|
+
.section-title {
|
|
284
|
+
font-size: 1.15rem;
|
|
285
|
+
font-weight: 600;
|
|
286
|
+
letter-spacing: -0.015em;
|
|
287
|
+
margin-bottom: 1rem;
|
|
288
|
+
color: var(--text);
|
|
289
|
+
}
|
|
290
|
+
|
|
168
291
|
/* === CARDS === */
|
|
169
292
|
.card {
|
|
170
293
|
border: 1px solid var(--border);
|
|
171
|
-
border-radius:
|
|
172
|
-
padding: 1.
|
|
294
|
+
border-radius: 8px;
|
|
295
|
+
padding: 1rem 1.25rem;
|
|
173
296
|
background: var(--bg-card);
|
|
174
|
-
transition: border-color 0.
|
|
297
|
+
transition: border-color 0.15s;
|
|
175
298
|
}
|
|
176
|
-
.card:hover {
|
|
177
|
-
|
|
178
|
-
|
|
299
|
+
.card:hover { border-color: var(--border-light); }
|
|
300
|
+
.card-link {
|
|
301
|
+
text-decoration: none;
|
|
302
|
+
display: block;
|
|
179
303
|
}
|
|
180
|
-
.card-
|
|
181
|
-
.card-
|
|
304
|
+
.card-link:hover { text-decoration: none; }
|
|
305
|
+
.card-link:hover .card { border-color: var(--accent-border); }
|
|
306
|
+
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
|
|
182
307
|
|
|
183
308
|
/* === TAGS === */
|
|
184
309
|
.tag {
|
|
185
|
-
display: inline-
|
|
310
|
+
display: inline-flex;
|
|
311
|
+
align-items: center;
|
|
186
312
|
font-family: var(--mono);
|
|
187
313
|
font-size: 0.6rem;
|
|
188
|
-
|
|
314
|
+
font-weight: 500;
|
|
315
|
+
letter-spacing: 0.04em;
|
|
189
316
|
text-transform: uppercase;
|
|
190
|
-
padding: 0.
|
|
191
|
-
border-radius:
|
|
192
|
-
background:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
.tag-gold { color: var(--
|
|
197
|
-
.tag-silver { color: #
|
|
198
|
-
.tag-bronze { color: #
|
|
199
|
-
.tag-
|
|
200
|
-
.tag-
|
|
201
|
-
.tag-
|
|
202
|
-
.tag-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
text-decoration: none;
|
|
208
|
-
transition: border-color 0.2s;
|
|
209
|
-
}
|
|
210
|
-
.tag-nav:hover { border-color: var(--gold); text-decoration: none; }
|
|
211
|
-
|
|
212
|
-
/* === TABLES (dark) === */
|
|
213
|
-
.table-dark { width: 100%; border-collapse: collapse; }
|
|
214
|
-
.table-dark th {
|
|
317
|
+
padding: 0.15rem 0.45rem;
|
|
318
|
+
border-radius: 3px;
|
|
319
|
+
background: rgba(106, 103, 94, 0.1);
|
|
320
|
+
color: var(--text-dim);
|
|
321
|
+
border: 1px solid transparent;
|
|
322
|
+
}
|
|
323
|
+
.tag-gold { color: var(--accent); background: var(--accent-dim); }
|
|
324
|
+
.tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }
|
|
325
|
+
.tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }
|
|
326
|
+
.tag-green { color: var(--green); background: var(--green-dim); }
|
|
327
|
+
.tag-red { color: var(--red); background: var(--red-dim); }
|
|
328
|
+
.tag-blue { color: var(--blue); background: var(--blue-dim); }
|
|
329
|
+
.tag-purple { color: var(--purple); background: rgba(167, 139, 250, 0.1); }
|
|
330
|
+
|
|
331
|
+
/* === TABLES === */
|
|
332
|
+
.data-table { width: 100%; border-collapse: collapse; }
|
|
333
|
+
.data-table th {
|
|
215
334
|
font-family: var(--mono);
|
|
216
|
-
font-size: 0.
|
|
217
|
-
|
|
335
|
+
font-size: 0.6rem;
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
letter-spacing: 0.12em;
|
|
218
338
|
text-transform: uppercase;
|
|
219
339
|
color: var(--text-dim);
|
|
220
340
|
text-align: left;
|
|
221
|
-
padding: 0.75rem
|
|
341
|
+
padding: 0.6rem 0.75rem;
|
|
222
342
|
border-bottom: 1px solid var(--border);
|
|
223
343
|
}
|
|
224
|
-
.table
|
|
225
|
-
padding: 0.75rem
|
|
226
|
-
border-bottom: 1px solid rgba(
|
|
227
|
-
font-size: 0.
|
|
344
|
+
.data-table td {
|
|
345
|
+
padding: 0.55rem 0.75rem;
|
|
346
|
+
border-bottom: 1px solid rgba(37, 35, 32, 0.6);
|
|
347
|
+
font-size: 0.82rem;
|
|
228
348
|
vertical-align: top;
|
|
229
349
|
}
|
|
230
|
-
.table
|
|
231
|
-
.mono { font-family: var(--mono);
|
|
350
|
+
.data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }
|
|
351
|
+
.mono { font-family: var(--mono); }
|
|
232
352
|
|
|
233
|
-
/* === SEMANTIC ROLE
|
|
234
|
-
.role-identifier { color: var(--
|
|
235
|
-
.role-metric { color:
|
|
236
|
-
.role-dimension { color:
|
|
237
|
-
.role-date { color: var(--green);
|
|
238
|
-
.role-attribute { color: var(--text-
|
|
353
|
+
/* === SEMANTIC ROLE TAGS === */
|
|
354
|
+
.role-identifier { color: var(--accent); background: var(--accent-dim); }
|
|
355
|
+
.role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }
|
|
356
|
+
.role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }
|
|
357
|
+
.role-date { color: var(--green); background: var(--green-dim); }
|
|
358
|
+
.role-attribute { color: var(--text-dim); background: rgba(106, 103, 94, 0.08); }
|
|
239
359
|
|
|
240
|
-
/* === DS TYPE
|
|
241
|
-
.ds-
|
|
360
|
+
/* === DS TYPE TAGS === */
|
|
361
|
+
.ds-fact { color: var(--purple); background: rgba(167, 139, 250, 0.08); }
|
|
362
|
+
.ds-dimension { color: var(--green); background: var(--green-dim); }
|
|
363
|
+
.ds-event { color: var(--orange); background: rgba(245, 158, 11, 0.08); }
|
|
364
|
+
.ds-view { color: var(--blue); background: var(--blue-dim); }
|
|
365
|
+
|
|
366
|
+
/* === GOV GRID === */
|
|
367
|
+
.gov-grid {
|
|
368
|
+
display: grid;
|
|
369
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
370
|
+
gap: 1px;
|
|
371
|
+
background: var(--border);
|
|
372
|
+
border: 1px solid var(--border);
|
|
373
|
+
border-radius: 8px;
|
|
374
|
+
overflow: hidden;
|
|
375
|
+
}
|
|
376
|
+
.gov-cell {
|
|
377
|
+
background: var(--bg-card);
|
|
378
|
+
padding: 0.75rem 1rem;
|
|
379
|
+
}
|
|
380
|
+
.gov-label {
|
|
242
381
|
font-family: var(--mono);
|
|
243
|
-
font-size: 0.
|
|
382
|
+
font-size: 0.55rem;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
letter-spacing: 0.12em;
|
|
244
385
|
text-transform: uppercase;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
border-radius: 4px;
|
|
248
|
-
display: inline-block;
|
|
386
|
+
color: var(--text-dim);
|
|
387
|
+
margin-bottom: 0.2rem;
|
|
249
388
|
}
|
|
250
|
-
.
|
|
251
|
-
.ds-type-dimension { color: #34D399; background: rgba(52,211,153,0.1); }
|
|
252
|
-
.ds-type-event { color: #FB923C; background: rgba(251,146,60,0.1); }
|
|
253
|
-
.ds-type-view { color: #A78BFA; background: rgba(167,139,250,0.1); }
|
|
389
|
+
.gov-value { font-size: 0.85rem; color: var(--text); }
|
|
254
390
|
|
|
255
|
-
/* ===
|
|
256
|
-
.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.metric-desc {
|
|
263
|
-
color: var(--text-muted);
|
|
264
|
-
font-size: 0.85rem;
|
|
265
|
-
line-height: 1.6;
|
|
266
|
-
margin-bottom: 1rem;
|
|
391
|
+
/* === EXPANDABLE === */
|
|
392
|
+
.expandable-header {
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
display: flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
justify-content: space-between;
|
|
397
|
+
user-select: none;
|
|
267
398
|
}
|
|
268
|
-
.
|
|
399
|
+
.expand-icon {
|
|
269
400
|
font-family: var(--mono);
|
|
270
|
-
font-size: 0.
|
|
401
|
+
font-size: 0.85rem;
|
|
271
402
|
color: var(--text-dim);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
403
|
+
width: 18px;
|
|
404
|
+
text-align: center;
|
|
405
|
+
transition: transform 0.15s;
|
|
406
|
+
}
|
|
407
|
+
.expandable-content {
|
|
408
|
+
max-height: 0;
|
|
409
|
+
overflow: hidden;
|
|
410
|
+
transition: max-height 0.25s ease;
|
|
278
411
|
}
|
|
279
412
|
|
|
280
|
-
/* === QUERY
|
|
413
|
+
/* === QUERY CARD === */
|
|
281
414
|
.query-card {
|
|
282
415
|
border: 1px solid var(--border);
|
|
283
|
-
border-radius:
|
|
416
|
+
border-radius: 8px;
|
|
284
417
|
overflow: hidden;
|
|
285
|
-
margin-bottom: 1.5rem;
|
|
286
418
|
background: var(--bg-card);
|
|
287
|
-
|
|
419
|
+
margin-bottom: 1rem;
|
|
420
|
+
transition: border-color 0.15s;
|
|
288
421
|
}
|
|
289
|
-
.query-card:hover { border-color: var(--
|
|
290
|
-
.query-
|
|
291
|
-
padding: 1.25rem
|
|
292
|
-
font-
|
|
293
|
-
font-
|
|
294
|
-
font-weight: 400;
|
|
422
|
+
.query-card:hover { border-color: var(--border-light); }
|
|
423
|
+
.query-q {
|
|
424
|
+
padding: 1rem 1.25rem;
|
|
425
|
+
font-size: 0.95rem;
|
|
426
|
+
font-weight: 500;
|
|
295
427
|
font-style: italic;
|
|
296
428
|
color: var(--text);
|
|
297
429
|
border-bottom: 1px solid var(--border);
|
|
298
430
|
display: flex;
|
|
299
431
|
align-items: flex-start;
|
|
300
|
-
gap: 0.
|
|
432
|
+
gap: 0.5rem;
|
|
301
433
|
}
|
|
302
|
-
.query-
|
|
303
|
-
content: 'Q';
|
|
434
|
+
.query-q-badge {
|
|
304
435
|
font-family: var(--mono);
|
|
305
|
-
font-size: 0.
|
|
436
|
+
font-size: 0.55rem;
|
|
306
437
|
font-style: normal;
|
|
307
|
-
|
|
308
|
-
color: var(--
|
|
309
|
-
background:
|
|
310
|
-
border: 1px solid
|
|
311
|
-
padding: 0.
|
|
438
|
+
font-weight: 600;
|
|
439
|
+
color: var(--accent);
|
|
440
|
+
background: var(--accent-dim);
|
|
441
|
+
border: 1px solid var(--accent-border);
|
|
442
|
+
padding: 0.1rem 0.3rem;
|
|
312
443
|
border-radius: 3px;
|
|
313
444
|
flex-shrink: 0;
|
|
314
|
-
margin-top: 0.
|
|
445
|
+
margin-top: 0.2rem;
|
|
315
446
|
}
|
|
316
447
|
.query-sql {
|
|
317
|
-
padding:
|
|
448
|
+
padding: 0.85rem 1.25rem;
|
|
318
449
|
font-family: var(--mono);
|
|
319
|
-
font-size: 0.
|
|
320
|
-
color: var(--text-
|
|
321
|
-
line-height: 1.
|
|
322
|
-
background: var(--bg);
|
|
450
|
+
font-size: 0.75rem;
|
|
451
|
+
color: var(--text-secondary);
|
|
452
|
+
line-height: 1.7;
|
|
453
|
+
background: var(--bg-code);
|
|
323
454
|
overflow-x: auto;
|
|
324
455
|
white-space: pre-wrap;
|
|
325
456
|
}
|
|
326
457
|
.query-meta {
|
|
327
|
-
padding: 0.
|
|
458
|
+
padding: 0.5rem 1.25rem;
|
|
328
459
|
display: flex;
|
|
329
|
-
gap:
|
|
330
|
-
font-size: 0.
|
|
460
|
+
gap: 0.75rem;
|
|
461
|
+
font-size: 0.65rem;
|
|
331
462
|
color: var(--text-dim);
|
|
332
463
|
border-top: 1px solid var(--border);
|
|
333
464
|
}
|
|
334
465
|
|
|
335
|
-
/* ===
|
|
466
|
+
/* === GUARDRAIL === */
|
|
336
467
|
.guardrail {
|
|
337
|
-
border: 1px solid rgba(
|
|
338
|
-
border-radius:
|
|
339
|
-
padding: 1.
|
|
340
|
-
background:
|
|
341
|
-
margin-bottom:
|
|
468
|
+
border: 1px solid rgba(212, 80, 80, 0.15);
|
|
469
|
+
border-radius: 8px;
|
|
470
|
+
padding: 1rem 1.25rem;
|
|
471
|
+
background: var(--red-dim);
|
|
472
|
+
margin-bottom: 0.75rem;
|
|
342
473
|
}
|
|
343
474
|
.guardrail-name {
|
|
344
475
|
font-family: var(--mono);
|
|
345
|
-
font-size: 0.
|
|
476
|
+
font-size: 0.8rem;
|
|
346
477
|
color: var(--red);
|
|
347
|
-
margin-bottom: 0.
|
|
478
|
+
margin-bottom: 0.35rem;
|
|
479
|
+
font-weight: 500;
|
|
348
480
|
}
|
|
349
481
|
.guardrail-filter {
|
|
350
482
|
font-family: var(--mono);
|
|
351
|
-
font-size: 0.
|
|
483
|
+
font-size: 0.75rem;
|
|
352
484
|
color: var(--text);
|
|
353
|
-
background: var(--bg);
|
|
354
|
-
padding: 0.
|
|
485
|
+
background: var(--bg-code);
|
|
486
|
+
padding: 0.35rem 0.6rem;
|
|
355
487
|
border-radius: 4px;
|
|
356
488
|
border: 1px solid var(--border);
|
|
357
489
|
display: inline-block;
|
|
358
|
-
margin-bottom: 0.
|
|
490
|
+
margin-bottom: 0.4rem;
|
|
359
491
|
}
|
|
360
492
|
.guardrail-reason {
|
|
361
|
-
font-size: 0.
|
|
362
|
-
color: var(--text-
|
|
493
|
+
font-size: 0.8rem;
|
|
494
|
+
color: var(--text-secondary);
|
|
363
495
|
font-weight: 300;
|
|
364
496
|
}
|
|
365
497
|
|
|
@@ -369,48 +501,33 @@ var HEAD = `<!DOCTYPE html>
|
|
|
369
501
|
align-items: stretch;
|
|
370
502
|
gap: 0;
|
|
371
503
|
overflow-x: auto;
|
|
372
|
-
padding:
|
|
373
|
-
}
|
|
374
|
-
.lineage-col {
|
|
375
|
-
display: flex;
|
|
376
|
-
flex-direction: column;
|
|
377
|
-
gap: 0.75rem;
|
|
378
|
-
min-width: 220px;
|
|
504
|
+
padding: 1rem 0;
|
|
379
505
|
}
|
|
506
|
+
.lineage-col { display: flex; flex-direction: column; gap: 0.5rem; min-width: 200px; }
|
|
380
507
|
.lineage-col-label {
|
|
381
508
|
font-family: var(--mono);
|
|
382
|
-
font-size: 0.
|
|
383
|
-
letter-spacing: 0.
|
|
509
|
+
font-size: 0.55rem;
|
|
510
|
+
letter-spacing: 0.15em;
|
|
384
511
|
text-transform: uppercase;
|
|
385
512
|
color: var(--text-dim);
|
|
386
|
-
margin-bottom: 0.
|
|
513
|
+
margin-bottom: 0.15rem;
|
|
387
514
|
padding-left: 0.5rem;
|
|
388
515
|
}
|
|
389
516
|
.lineage-node {
|
|
390
517
|
border: 1px solid var(--border);
|
|
391
|
-
border-radius:
|
|
392
|
-
padding: 0.
|
|
518
|
+
border-radius: 6px;
|
|
519
|
+
padding: 0.6rem 0.85rem;
|
|
393
520
|
background: var(--bg-card);
|
|
394
|
-
transition: border-color 0.3s;
|
|
395
|
-
}
|
|
396
|
-
.lineage-node:hover { border-color: var(--gold-dark); }
|
|
397
|
-
.lineage-node-name {
|
|
398
|
-
font-family: var(--mono);
|
|
399
|
-
font-size: 0.78rem;
|
|
400
|
-
color: var(--text);
|
|
401
|
-
margin-bottom: 0.15rem;
|
|
402
|
-
}
|
|
403
|
-
.lineage-node-detail {
|
|
404
|
-
font-size: 0.7rem;
|
|
405
|
-
color: var(--text-dim);
|
|
406
521
|
}
|
|
522
|
+
.lineage-node-name { font-family: var(--mono); font-size: 0.75rem; color: var(--text); }
|
|
523
|
+
.lineage-node-detail { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.1rem; }
|
|
407
524
|
.lineage-arrow {
|
|
408
525
|
display: flex;
|
|
409
526
|
align-items: center;
|
|
410
527
|
justify-content: center;
|
|
411
|
-
min-width:
|
|
412
|
-
color: var(--
|
|
413
|
-
font-size:
|
|
528
|
+
min-width: 40px;
|
|
529
|
+
color: var(--text-dim);
|
|
530
|
+
font-size: 1rem;
|
|
414
531
|
}
|
|
415
532
|
|
|
416
533
|
/* === SCORECARD === */
|
|
@@ -420,230 +537,242 @@ var HEAD = `<!DOCTYPE html>
|
|
|
420
537
|
gap: 1px;
|
|
421
538
|
background: var(--border);
|
|
422
539
|
border: 1px solid var(--border);
|
|
423
|
-
border-radius:
|
|
540
|
+
border-radius: 8px;
|
|
424
541
|
overflow: hidden;
|
|
425
542
|
}
|
|
426
543
|
@media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }
|
|
427
|
-
.
|
|
428
|
-
|
|
429
|
-
padding: 1.5rem;
|
|
430
|
-
}
|
|
431
|
-
.scorecard-tier-header {
|
|
544
|
+
.sc-tier { background: var(--bg-card); padding: 1.25rem; }
|
|
545
|
+
.sc-tier-head {
|
|
432
546
|
display: flex;
|
|
433
547
|
align-items: center;
|
|
434
548
|
justify-content: space-between;
|
|
435
|
-
margin-bottom:
|
|
436
|
-
}
|
|
437
|
-
.scorecard-tier-name {
|
|
438
|
-
font-family: var(--serif);
|
|
439
|
-
font-size: 1.3rem;
|
|
440
|
-
font-weight: 500;
|
|
441
|
-
text-transform: capitalize;
|
|
549
|
+
margin-bottom: 0.75rem;
|
|
442
550
|
}
|
|
443
|
-
.
|
|
444
|
-
.
|
|
445
|
-
.
|
|
446
|
-
.
|
|
551
|
+
.sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }
|
|
552
|
+
.sc-tier-name.bronze { color: #b87a4a; }
|
|
553
|
+
.sc-tier-name.silver { color: #a0a8b8; }
|
|
554
|
+
.sc-tier-name.gold { color: var(--accent); }
|
|
555
|
+
.sc-status {
|
|
447
556
|
font-family: var(--mono);
|
|
448
|
-
font-size: 0.
|
|
449
|
-
|
|
557
|
+
font-size: 0.55rem;
|
|
558
|
+
font-weight: 600;
|
|
559
|
+
letter-spacing: 0.08em;
|
|
450
560
|
text-transform: uppercase;
|
|
451
|
-
padding: 0.
|
|
452
|
-
border-radius:
|
|
561
|
+
padding: 0.12rem 0.4rem;
|
|
562
|
+
border-radius: 3px;
|
|
453
563
|
}
|
|
454
|
-
.
|
|
455
|
-
.
|
|
564
|
+
.sc-status.pass { color: var(--green); background: var(--green-dim); }
|
|
565
|
+
.sc-status.fail { color: var(--red); background: var(--red-dim); }
|
|
456
566
|
.check-list { list-style: none; }
|
|
457
567
|
.check-item {
|
|
458
568
|
display: flex;
|
|
459
569
|
align-items: flex-start;
|
|
460
|
-
gap: 0.
|
|
461
|
-
padding: 0.
|
|
462
|
-
font-size: 0.
|
|
463
|
-
color: var(--text-
|
|
570
|
+
gap: 0.35rem;
|
|
571
|
+
padding: 0.2rem 0;
|
|
572
|
+
font-size: 0.72rem;
|
|
573
|
+
color: var(--text-secondary);
|
|
464
574
|
line-height: 1.4;
|
|
465
575
|
}
|
|
466
|
-
.check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.
|
|
576
|
+
.check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }
|
|
467
577
|
.check-icon.pass { color: var(--green); }
|
|
468
578
|
.check-icon.fail { color: var(--red); }
|
|
469
579
|
|
|
470
580
|
/* === GLOSSARY === */
|
|
471
581
|
.glossary-grid {
|
|
472
582
|
display: grid;
|
|
473
|
-
grid-template-columns: repeat(auto-
|
|
474
|
-
gap:
|
|
475
|
-
}
|
|
476
|
-
.glossary-card {
|
|
477
|
-
border: 1px solid var(--border);
|
|
478
|
-
border-radius: 12px;
|
|
479
|
-
padding: 1.5rem;
|
|
480
|
-
background: var(--bg-card);
|
|
481
|
-
position: relative;
|
|
482
|
-
overflow: hidden;
|
|
483
|
-
transition: border-color 0.3s;
|
|
583
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
584
|
+
gap: 0.75rem;
|
|
484
585
|
}
|
|
485
|
-
.glossary-card
|
|
586
|
+
.glossary-card { position: relative; overflow: hidden; }
|
|
486
587
|
.glossary-card::before {
|
|
487
588
|
content: '';
|
|
488
589
|
position: absolute;
|
|
489
|
-
top: 0; left: 0; right: 0;
|
|
490
|
-
height: 2px;
|
|
491
|
-
background: linear-gradient(90deg, var(--
|
|
492
|
-
}
|
|
493
|
-
.glossary-term {
|
|
494
|
-
font-family: var(--serif);
|
|
495
|
-
font-size: 1.3rem;
|
|
496
|
-
font-weight: 500;
|
|
497
|
-
margin-bottom: 0.5rem;
|
|
498
|
-
color: var(--text);
|
|
499
|
-
}
|
|
500
|
-
.glossary-def {
|
|
501
|
-
font-size: 0.85rem;
|
|
502
|
-
color: var(--text-muted);
|
|
503
|
-
line-height: 1.7;
|
|
504
|
-
font-weight: 300;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/* === EXPANDABLE === */
|
|
508
|
-
.expandable-header {
|
|
509
|
-
cursor: pointer;
|
|
510
|
-
display: flex;
|
|
511
|
-
align-items: center;
|
|
512
|
-
justify-content: space-between;
|
|
513
|
-
user-select: none;
|
|
514
|
-
}
|
|
515
|
-
.expand-icon {
|
|
516
|
-
font-family: var(--mono);
|
|
517
|
-
font-size: 0.9rem;
|
|
518
|
-
color: var(--text-dim);
|
|
519
|
-
transition: transform 0.2s;
|
|
520
|
-
width: 20px;
|
|
521
|
-
text-align: center;
|
|
590
|
+
top: 0; left: 0; right: 0;
|
|
591
|
+
height: 2px;
|
|
592
|
+
background: linear-gradient(90deg, var(--accent-border), transparent);
|
|
522
593
|
}
|
|
523
|
-
.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
594
|
+
.glossary-term {
|
|
595
|
+
font-size: 1rem;
|
|
596
|
+
font-weight: 600;
|
|
597
|
+
margin-bottom: 0.35rem;
|
|
598
|
+
color: var(--text);
|
|
599
|
+
}
|
|
600
|
+
.glossary-def {
|
|
601
|
+
font-size: 0.82rem;
|
|
602
|
+
color: var(--text-secondary);
|
|
603
|
+
line-height: 1.6;
|
|
604
|
+
font-weight: 300;
|
|
527
605
|
}
|
|
528
606
|
|
|
529
|
-
/* === SEARCH === */
|
|
607
|
+
/* === SEARCH INPUT === */
|
|
530
608
|
.search-input {
|
|
531
609
|
width: 100%;
|
|
532
610
|
background: var(--bg-card);
|
|
533
611
|
border: 1px solid var(--border);
|
|
534
|
-
border-radius:
|
|
535
|
-
padding: 0.
|
|
612
|
+
border-radius: 6px;
|
|
613
|
+
padding: 0.6rem 1rem;
|
|
536
614
|
font-family: var(--sans);
|
|
537
|
-
font-size:
|
|
615
|
+
font-size: 0.9rem;
|
|
538
616
|
color: var(--text);
|
|
539
617
|
outline: none;
|
|
540
|
-
transition: border-color 0.
|
|
618
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
541
619
|
}
|
|
542
620
|
.search-input::placeholder { color: var(--text-dim); }
|
|
543
621
|
.search-input:focus {
|
|
544
|
-
border-color: var(--
|
|
545
|
-
box-shadow: 0 0 0 3px var(--
|
|
622
|
+
border-color: var(--accent-border);
|
|
623
|
+
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
546
624
|
}
|
|
547
625
|
|
|
548
|
-
/* ===
|
|
549
|
-
.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
align-items: center;
|
|
554
|
-
gap: 0.3rem;
|
|
555
|
-
margin-bottom: 1.5rem;
|
|
556
|
-
text-decoration: none;
|
|
557
|
-
}
|
|
558
|
-
.back-link:hover { color: var(--gold); text-decoration: none; }
|
|
626
|
+
/* === SQL HIGHLIGHT === */
|
|
627
|
+
.sql-kw { color: var(--purple); }
|
|
628
|
+
.sql-fn { color: var(--green); }
|
|
629
|
+
.sql-str { color: var(--orange); }
|
|
630
|
+
.sql-num { color: var(--orange); }
|
|
559
631
|
|
|
560
|
-
/* ===
|
|
561
|
-
.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
border-radius: 8px;
|
|
568
|
-
overflow: hidden;
|
|
569
|
-
}
|
|
570
|
-
.gov-item {
|
|
571
|
-
background: var(--bg-card);
|
|
572
|
-
padding: 1rem 1.25rem;
|
|
632
|
+
/* === METRIC === */
|
|
633
|
+
.metric-name {
|
|
634
|
+
font-family: var(--mono);
|
|
635
|
+
font-size: 0.85rem;
|
|
636
|
+
font-weight: 500;
|
|
637
|
+
color: var(--accent-light);
|
|
638
|
+
margin-bottom: 0.35rem;
|
|
573
639
|
}
|
|
574
|
-
.
|
|
640
|
+
.metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }
|
|
641
|
+
.metric-formula {
|
|
575
642
|
font-family: var(--mono);
|
|
576
|
-
font-size: 0.
|
|
577
|
-
letter-spacing: 0.15em;
|
|
578
|
-
text-transform: uppercase;
|
|
643
|
+
font-size: 0.7rem;
|
|
579
644
|
color: var(--text-dim);
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
645
|
+
background: var(--bg-code);
|
|
646
|
+
border: 1px solid var(--border);
|
|
647
|
+
border-radius: 5px;
|
|
648
|
+
padding: 0.5rem 0.7rem;
|
|
649
|
+
overflow-x: auto;
|
|
585
650
|
}
|
|
586
651
|
|
|
587
|
-
/* ===
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
.reveal {
|
|
597
|
-
opacity: 0;
|
|
598
|
-
transform: translateY(25px);
|
|
599
|
-
transition: opacity 0.6s ease, transform 0.6s ease;
|
|
600
|
-
}
|
|
601
|
-
.reveal.visible {
|
|
602
|
-
opacity: 1;
|
|
603
|
-
transform: translateY(0);
|
|
652
|
+
/* === FOOTER === */
|
|
653
|
+
.site-footer {
|
|
654
|
+
margin-left: var(--sidebar-w);
|
|
655
|
+
text-align: center;
|
|
656
|
+
padding: 2rem 1.5rem;
|
|
657
|
+
color: var(--text-dim);
|
|
658
|
+
font-size: 0.72rem;
|
|
659
|
+
border-top: 1px solid var(--border);
|
|
604
660
|
}
|
|
661
|
+
.site-footer a { color: var(--text-dim); }
|
|
662
|
+
.site-footer a:hover { color: var(--accent); }
|
|
605
663
|
|
|
606
|
-
/* ===
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
664
|
+
/* === RESPONSIVE === */
|
|
665
|
+
@media (max-width: 860px) {
|
|
666
|
+
.sidebar {
|
|
667
|
+
transform: translateX(-100%);
|
|
668
|
+
transition: transform 0.25s ease;
|
|
669
|
+
z-index: 300;
|
|
670
|
+
}
|
|
671
|
+
.sidebar.open { transform: translateX(0); }
|
|
672
|
+
.main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }
|
|
673
|
+
.site-footer { margin-left: 0; }
|
|
674
|
+
.menu-toggle { display: block; }
|
|
675
|
+
.stats-row { flex-wrap: wrap; }
|
|
676
|
+
.stat-item { min-width: 60px; }
|
|
677
|
+
.overlay {
|
|
678
|
+
position: fixed;
|
|
679
|
+
inset: 0;
|
|
680
|
+
background: rgba(0,0,0,0.5);
|
|
681
|
+
z-index: 250;
|
|
682
|
+
display: none;
|
|
683
|
+
}
|
|
684
|
+
.overlay.open { display: block; }
|
|
685
|
+
}
|
|
612
686
|
</style>
|
|
687
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
688
|
+
<style>
|
|
689
|
+
.edit-btn { background: none; border: 1px solid #c9a55a; color: #c9a55a; border-radius: 4px; padding: 2px 8px; font-size: 12px; cursor: pointer; margin-left: 8px; opacity: 0.6; transition: opacity 0.2s; }
|
|
690
|
+
.edit-btn:hover { opacity: 1; }
|
|
691
|
+
.editable { cursor: text; border-bottom: 1px dashed #c9a55a40; transition: border-color 0.2s; }
|
|
692
|
+
.editable:hover { border-bottom-color: #c9a55a; }
|
|
693
|
+
.editable:focus { outline: none; border-bottom: 2px solid #c9a55a; background: #c9a55a10; }
|
|
694
|
+
.staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid #c9a55a; padding: 12px 24px; display: flex; align-items: center; justify-content: space-between; z-index: 1000; box-shadow: 0 -4px 12px rgba(0,0,0,0.3); }
|
|
695
|
+
.staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }
|
|
696
|
+
.staged-btn.primary { background: #c9a55a; color: #0a0a0f; }
|
|
697
|
+
.staged-btn.primary:hover { background: #d4b06a; }
|
|
698
|
+
.staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }
|
|
699
|
+
.staged-btn.secondary:hover { border-color: #999; color: #fff; }
|
|
700
|
+
.diff-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 2000; display: flex; align-items: center; justify-content: center; }
|
|
701
|
+
.diff-modal-content { background: #1a1a2e; border: 1px solid #333; border-radius: 12px; padding: 24px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
|
702
|
+
.diff-modal-content h2 { margin: 0 0 16px; color: #c9a55a; }
|
|
703
|
+
.diff-file { margin: 12px 0; padding: 12px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 13px; white-space: pre-wrap; }
|
|
704
|
+
.diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }
|
|
705
|
+
.diff-add { color: #4ade80; }
|
|
706
|
+
.diff-del { color: #f87171; }
|
|
707
|
+
.diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }
|
|
708
|
+
.toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid #c9a55a; color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }
|
|
709
|
+
@keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
710
|
+
.studio-add-btn { background: none; border: 1px dashed #c9a55a60; color: #c9a55a; border-radius: 8px; padding: 12px; width: 100%; cursor: pointer; font-size: 14px; transition: all 0.2s; }
|
|
711
|
+
.studio-add-btn:hover { border-color: #c9a55a; background: #c9a55a10; }
|
|
712
|
+
</style>
|
|
713
|
+
<% } %>
|
|
613
714
|
</head>`;
|
|
614
|
-
var
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
715
|
+
var SIDEBAR_DATA = `<%
|
|
716
|
+
var _sidebarModels = typeof models !== 'undefined' ? Object.keys(models) : (typeof model !== 'undefined' ? [model.name] : []);
|
|
717
|
+
var _sidebarTiers = typeof tiers !== 'undefined' ? tiers : (typeof tier !== 'undefined' && typeof model !== 'undefined' ? (function(){ var t = {}; t[model.name] = tier; return t; })() : {});
|
|
718
|
+
%>`;
|
|
719
|
+
var TOPBAR = `<div class="topbar">
|
|
720
|
+
<div class="topbar-left">
|
|
721
|
+
<button class="menu-toggle" onclick="toggleSidebar()" aria-label="Toggle menu">☰</button>
|
|
722
|
+
<a href="<%= basePath %>/index.html" class="topbar-logo"><%- siteTitle %></a>
|
|
723
|
+
<span class="topbar-sep">/</span>
|
|
724
|
+
<span class="topbar-page"><%= pageTitle %></span>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="topbar-right">
|
|
727
|
+
<a href="<%= basePath %>/search.html" class="topbar-link">Search</a>
|
|
728
|
+
<a href="https://contextkit.dev" class="topbar-docs" target="_blank" rel="noopener">Docs ↗</a>
|
|
729
|
+
</div>
|
|
730
|
+
</div>`;
|
|
731
|
+
var SIDEBAR = `<div class="overlay" id="sidebar-overlay" onclick="toggleSidebar()"></div>
|
|
732
|
+
<nav class="sidebar" id="sidebar">
|
|
733
|
+
<div class="sidebar-section">
|
|
734
|
+
<div class="sidebar-heading">Navigation</div>
|
|
735
|
+
<a href="<%= basePath %>/index.html" class="sidebar-item<%= pageTitle === 'Home' ? ' active' : '' %>">
|
|
736
|
+
<span>Home</span>
|
|
737
|
+
</a>
|
|
738
|
+
<a href="<%= basePath %>/glossary.html" class="sidebar-item<%= pageTitle === 'Glossary' ? ' active' : '' %>">
|
|
739
|
+
<span>Glossary</span>
|
|
740
|
+
</a>
|
|
741
|
+
<a href="<%= basePath %>/search.html" class="sidebar-item<%= pageTitle === 'Search' ? ' active' : '' %>">
|
|
742
|
+
<span>Search</span>
|
|
743
|
+
</a>
|
|
620
744
|
</div>
|
|
745
|
+
<% if (_sidebarModels.length > 0) { %>
|
|
746
|
+
<div class="sidebar-section">
|
|
747
|
+
<div class="sidebar-heading">Models</div>
|
|
748
|
+
<% for (var _sm of _sidebarModels) { %>
|
|
749
|
+
<a href="<%= basePath %>/models/<%= _sm %>.html" class="sidebar-item<%= (typeof model !== 'undefined' && model.name === _sm) ? ' active' : '' %>">
|
|
750
|
+
<span class="mono" style="font-size:0.78rem;"><%= _sm %></span>
|
|
751
|
+
<% if (_sidebarTiers[_sm]) { %>
|
|
752
|
+
<span class="sidebar-badge sidebar-badge-<%= _sidebarTiers[_sm].tier || 'none' %>"><%= _sidebarTiers[_sm].tier || '\\u2014' %></span>
|
|
753
|
+
<% } %>
|
|
754
|
+
</a>
|
|
755
|
+
<% } %>
|
|
756
|
+
</div>
|
|
757
|
+
<% } %>
|
|
621
758
|
</nav>`;
|
|
622
|
-
var FOOTER = `<footer
|
|
623
|
-
Generated by <a href="https://github.com/erickittelson/ContextKit"
|
|
759
|
+
var FOOTER = `<footer class="site-footer">
|
|
760
|
+
Generated by <a href="https://github.com/erickittelson/ContextKit">ContextKit</a>
|
|
761
|
+
·
|
|
762
|
+
<a href="https://contextkit.dev" target="_blank" rel="noopener">Documentation</a>
|
|
624
763
|
</footer>`;
|
|
625
764
|
var TIER_BADGE = `<% function tierBadge(tier) {
|
|
626
|
-
var cls = { none: '
|
|
627
|
-
var c = cls[tier] ||
|
|
628
|
-
return '<span class="tag ' + c + '">' + tier + '</span>';
|
|
765
|
+
var cls = { none: '', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };
|
|
766
|
+
var c = cls[tier] || '';
|
|
767
|
+
return '<span class="tag ' + c + '">' + (tier || 'none') + '</span>';
|
|
629
768
|
} %>`;
|
|
630
769
|
var SCRIPTS = `<script>
|
|
631
770
|
(function() {
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
if (e.isIntersecting) {
|
|
638
|
-
e.target.classList.add('visible');
|
|
639
|
-
observer.unobserve(e.target);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
}, { threshold: 0.1 });
|
|
643
|
-
reveals.forEach(function(el) { observer.observe(el); });
|
|
644
|
-
} else {
|
|
645
|
-
reveals.forEach(function(el) { el.classList.add('visible'); });
|
|
646
|
-
}
|
|
771
|
+
// Sidebar toggle (mobile)
|
|
772
|
+
window.toggleSidebar = function() {
|
|
773
|
+
document.getElementById('sidebar').classList.toggle('open');
|
|
774
|
+
document.getElementById('sidebar-overlay').classList.toggle('open');
|
|
775
|
+
};
|
|
647
776
|
|
|
648
777
|
// Expandable sections
|
|
649
778
|
window.toggleExpand = function(id) {
|
|
@@ -659,20 +788,25 @@ var SCRIPTS = `<script>
|
|
|
659
788
|
}
|
|
660
789
|
};
|
|
661
790
|
|
|
662
|
-
//
|
|
791
|
+
// SQL syntax highlighter \u2014 operates on trusted, template-rendered content only.
|
|
792
|
+
// The SQL blocks contain server-rendered code snippets from the user's own
|
|
793
|
+
// golden queries / business rules, not arbitrary user input.
|
|
663
794
|
window.highlightSQL = function() {
|
|
664
795
|
var blocks = document.querySelectorAll('.sql-highlight');
|
|
665
796
|
var kw = /\\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|CROSS|ON|AND|OR|NOT|IN|AS|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|UNION|ALL|DISTINCT|CASE|WHEN|THEN|ELSE|END|IS|NULL|BETWEEN|LIKE|EXISTS|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SET|VALUES|INTO|WITH|DESC|ASC|OVER|PARTITION|WINDOW|FILTER|LATERAL|UNNEST|TRUE|FALSE)\\b/gi;
|
|
666
797
|
var fn = /\\b(SUM|COUNT|AVG|MIN|MAX|ROUND|COALESCE|CAST|NULLIF|ABS|UPPER|LOWER|LENGTH|TRIM|SUBSTRING|CONCAT|ROW_NUMBER|RANK|DENSE_RANK|LAG|LEAD|FIRST_VALUE|LAST_VALUE|NTILE|PERCENTILE_CONT|STRING_AGG|ARRAY_AGG|LIST|STRUCT_PACK)\\b/gi;
|
|
667
798
|
var str = /('(?:[^'\\\\]|\\\\.)*')/g;
|
|
668
|
-
var num = /\\b(\\d+\\.?\\d*)\\b/g;
|
|
669
|
-
|
|
670
799
|
blocks.forEach(function(block) {
|
|
671
800
|
var text = block.textContent || '';
|
|
801
|
+
// Escape HTML entities first to prevent injection
|
|
802
|
+
var div = document.createElement('div');
|
|
803
|
+
div.appendChild(document.createTextNode(text));
|
|
804
|
+
text = div.textContent || '';
|
|
672
805
|
text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
673
806
|
text = text.replace(str, '<span class="sql-str">$1</span>');
|
|
674
807
|
text = text.replace(kw, '<span class="sql-kw">$&</span>');
|
|
675
808
|
text = text.replace(fn, '<span class="sql-fn">$&</span>');
|
|
809
|
+
// Safe: content is from trusted template-rendered golden queries
|
|
676
810
|
block.innerHTML = text;
|
|
677
811
|
});
|
|
678
812
|
};
|
|
@@ -682,23 +816,236 @@ var SCRIPTS = `<script>
|
|
|
682
816
|
window.highlightSQL();
|
|
683
817
|
}
|
|
684
818
|
})();
|
|
685
|
-
</script
|
|
819
|
+
</script>
|
|
820
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
821
|
+
<div class="staged-bar" id="staged-bar" style="display:none;">
|
|
822
|
+
<span id="staged-count" style="color:#c9a55a;font-weight:500;">0 changes staged</span>
|
|
823
|
+
<div>
|
|
824
|
+
<button onclick="previewAndSave()" class="staged-btn primary">Preview & Save</button>
|
|
825
|
+
<button onclick="discardEdits()" class="staged-btn secondary" style="margin-left:8px;">Discard</button>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
<div class="diff-modal" id="diff-modal" style="display:none;">
|
|
829
|
+
<div class="diff-modal-content">
|
|
830
|
+
<h2>Review Changes</h2>
|
|
831
|
+
<div id="diff-container"></div>
|
|
832
|
+
<div class="diff-actions">
|
|
833
|
+
<button onclick="confirmSave()" class="staged-btn primary">Save All</button>
|
|
834
|
+
<button onclick="closeDiffModal()" class="staged-btn secondary">Cancel</button>
|
|
835
|
+
</div>
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
<script>
|
|
839
|
+
window.studioState = { edits: [] };
|
|
840
|
+
|
|
841
|
+
function stageEdit(file, path, value, label) {
|
|
842
|
+
window.studioState.edits = window.studioState.edits.filter(
|
|
843
|
+
e => !(e.file === file && e.path === path)
|
|
844
|
+
);
|
|
845
|
+
window.studioState.edits.push({ file, path, value, label });
|
|
846
|
+
updateStagedBar();
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function updateStagedBar() {
|
|
850
|
+
const bar = document.getElementById('staged-bar');
|
|
851
|
+
const count = document.getElementById('staged-count');
|
|
852
|
+
if (!bar || !count) return;
|
|
853
|
+
const n = window.studioState.edits.length;
|
|
854
|
+
bar.style.display = n > 0 ? 'flex' : 'none';
|
|
855
|
+
count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function discardEdits() {
|
|
859
|
+
window.studioState.edits = [];
|
|
860
|
+
updateStagedBar();
|
|
861
|
+
document.querySelectorAll('.editable[contenteditable="true"]').forEach(el => {
|
|
862
|
+
el.contentEditable = 'false';
|
|
863
|
+
if (el.dataset.original) el.textContent = el.dataset.original;
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async function previewAndSave() {
|
|
868
|
+
const edits = window.studioState.edits;
|
|
869
|
+
if (edits.length === 0) return;
|
|
870
|
+
|
|
871
|
+
const fileGroups = {};
|
|
872
|
+
for (const edit of edits) {
|
|
873
|
+
if (!fileGroups[edit.file]) fileGroups[edit.file] = [];
|
|
874
|
+
fileGroups[edit.file].push(edit);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const previews = [];
|
|
878
|
+
for (const [file, fileEdits] of Object.entries(fileGroups)) {
|
|
879
|
+
try {
|
|
880
|
+
const res = await fetch('/api/preview', {
|
|
881
|
+
method: 'POST',
|
|
882
|
+
headers: { 'Content-Type': 'application/json' },
|
|
883
|
+
body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),
|
|
884
|
+
});
|
|
885
|
+
const data = await res.json();
|
|
886
|
+
previews.push({ file, edits: fileEdits, ...data });
|
|
887
|
+
} catch (err) {
|
|
888
|
+
previews.push({ file, edits: fileEdits, error: err.message });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
showDiffModal(previews);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function showDiffModal(previews) {
|
|
896
|
+
const modal = document.getElementById('diff-modal');
|
|
897
|
+
const container = document.getElementById('diff-container');
|
|
898
|
+
if (!modal || !container) return;
|
|
899
|
+
|
|
900
|
+
container.innerHTML = previews.map(p => {
|
|
901
|
+
if (p.error) {
|
|
902
|
+
return '<div class="diff-file"><div class="diff-file-name">' + p.file + '</div><span class="diff-del">Error: ' + p.error + '</span></div>';
|
|
903
|
+
}
|
|
904
|
+
const editList = p.edits.map(e => '<div class="diff-add"> ' + e.label + ': ' + JSON.stringify(e.value) + '</div>').join('');
|
|
905
|
+
return '<div class="diff-file"><div class="diff-file-name">' + p.file + '</div>' + editList + '</div>';
|
|
906
|
+
}).join('');
|
|
907
|
+
|
|
908
|
+
modal.style.display = 'flex';
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function closeDiffModal() {
|
|
912
|
+
const modal = document.getElementById('diff-modal');
|
|
913
|
+
if (modal) modal.style.display = 'none';
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function confirmSave() {
|
|
917
|
+
const edits = window.studioState.edits;
|
|
918
|
+
if (edits.length === 0) return;
|
|
919
|
+
|
|
920
|
+
try {
|
|
921
|
+
const res = await fetch('/api/save', {
|
|
922
|
+
method: 'POST',
|
|
923
|
+
headers: { 'Content-Type': 'application/json' },
|
|
924
|
+
body: JSON.stringify({ edits }),
|
|
925
|
+
});
|
|
926
|
+
const data = await res.json();
|
|
927
|
+
const ok = data.results.filter(r => r.ok).length;
|
|
928
|
+
showToast(ok + ' file(s) saved');
|
|
929
|
+
window.studioState.edits = [];
|
|
930
|
+
updateStagedBar();
|
|
931
|
+
closeDiffModal();
|
|
932
|
+
} catch (err) {
|
|
933
|
+
showToast('Save failed: ' + err.message);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function showToast(msg) {
|
|
938
|
+
const existing = document.querySelector('.toast');
|
|
939
|
+
if (existing) existing.remove();
|
|
940
|
+
const toast = document.createElement('div');
|
|
941
|
+
toast.className = 'toast';
|
|
942
|
+
toast.textContent = msg;
|
|
943
|
+
document.body.appendChild(toast);
|
|
944
|
+
setTimeout(() => toast.remove(), 3000);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function initSSE() {
|
|
948
|
+
const es = new EventSource('/api/events');
|
|
949
|
+
es.addEventListener('update', function(e) {
|
|
950
|
+
try {
|
|
951
|
+
const data = JSON.parse(e.data);
|
|
952
|
+
showToast('Recompiled \u2014 ' + data.diagnosticCount + ' diagnostics');
|
|
953
|
+
setTimeout(() => location.reload(), 500);
|
|
954
|
+
} catch {}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function makeEditable(el) {
|
|
959
|
+
el.addEventListener('click', function() {
|
|
960
|
+
if (el.contentEditable === 'true') return;
|
|
961
|
+
el.dataset.original = el.textContent;
|
|
962
|
+
el.contentEditable = 'true';
|
|
963
|
+
el.focus();
|
|
964
|
+
});
|
|
965
|
+
el.addEventListener('blur', function() {
|
|
966
|
+
el.contentEditable = 'false';
|
|
967
|
+
const newVal = el.textContent.trim();
|
|
968
|
+
if (newVal !== el.dataset.original) {
|
|
969
|
+
stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);
|
|
970
|
+
el.style.borderBottomColor = '#4ade80';
|
|
971
|
+
setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
el.addEventListener('keydown', function(e) {
|
|
975
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
976
|
+
e.preventDefault();
|
|
977
|
+
el.blur();
|
|
978
|
+
}
|
|
979
|
+
if (e.key === 'Escape') {
|
|
980
|
+
el.textContent = el.dataset.original;
|
|
981
|
+
el.contentEditable = 'false';
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function makeDropdown(el) {
|
|
987
|
+
el.addEventListener('click', function(e) {
|
|
988
|
+
document.querySelectorAll('.studio-dropdown').forEach(d => d.remove());
|
|
989
|
+
|
|
990
|
+
const options = (el.dataset.options || '').split(',');
|
|
991
|
+
const dropdown = document.createElement('div');
|
|
992
|
+
dropdown.className = 'studio-dropdown';
|
|
993
|
+
dropdown.style.cssText = 'position:absolute;background:#1a1a2e;border:1px solid #c9a55a;border-radius:6px;padding:4px 0;z-index:500;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.4);';
|
|
994
|
+
|
|
995
|
+
options.forEach(opt => {
|
|
996
|
+
const item = document.createElement('div');
|
|
997
|
+
item.textContent = opt.trim();
|
|
998
|
+
item.style.cssText = 'padding:6px 16px;cursor:pointer;color:#e0e0e0;font-size:13px;';
|
|
999
|
+
item.addEventListener('mouseenter', () => { item.style.background = '#c9a55a20'; });
|
|
1000
|
+
item.addEventListener('mouseleave', () => { item.style.background = 'none'; });
|
|
1001
|
+
item.addEventListener('click', (ev) => {
|
|
1002
|
+
ev.stopPropagation();
|
|
1003
|
+
const val = opt.trim();
|
|
1004
|
+
el.textContent = val;
|
|
1005
|
+
stageEdit(el.dataset.file, el.dataset.path, val, el.dataset.label || el.dataset.path);
|
|
1006
|
+
dropdown.remove();
|
|
1007
|
+
el.style.borderBottomColor = '#4ade80';
|
|
1008
|
+
setTimeout(() => { el.style.borderBottomColor = ''; }, 1000);
|
|
1009
|
+
});
|
|
1010
|
+
dropdown.appendChild(item);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
const rect = el.getBoundingClientRect();
|
|
1014
|
+
dropdown.style.top = (rect.bottom + window.scrollY + 4) + 'px';
|
|
1015
|
+
dropdown.style.left = (rect.left + window.scrollX) + 'px';
|
|
1016
|
+
document.body.appendChild(dropdown);
|
|
1017
|
+
|
|
1018
|
+
setTimeout(() => {
|
|
1019
|
+
document.addEventListener('click', function closer() {
|
|
1020
|
+
dropdown.remove();
|
|
1021
|
+
document.removeEventListener('click', closer);
|
|
1022
|
+
}, { once: true });
|
|
1023
|
+
}, 0);
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1028
|
+
document.querySelectorAll('.editable').forEach(makeEditable);
|
|
1029
|
+
document.querySelectorAll('.dropdown-editable').forEach(makeDropdown);
|
|
1030
|
+
initSSE();
|
|
1031
|
+
});
|
|
1032
|
+
</script>
|
|
1033
|
+
<% } %>`;
|
|
686
1034
|
|
|
687
1035
|
// src/templates/index.ts
|
|
688
1036
|
var indexTemplate = `${HEAD}
|
|
689
1037
|
<body>
|
|
690
|
-
${
|
|
1038
|
+
${TOPBAR}
|
|
1039
|
+
${SIDEBAR_DATA}
|
|
1040
|
+
${SIDEBAR}
|
|
691
1041
|
${TIER_BADGE}
|
|
692
1042
|
|
|
693
|
-
<
|
|
694
|
-
<div class="
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
</
|
|
698
|
-
|
|
699
|
-
<div class="divider"></div>
|
|
1043
|
+
<div class="main">
|
|
1044
|
+
<div class="page-header">
|
|
1045
|
+
<h1>Metadata Catalog</h1>
|
|
1046
|
+
<p class="subtitle">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>
|
|
1047
|
+
</div>
|
|
700
1048
|
|
|
701
|
-
<main class="page">
|
|
702
1049
|
<%
|
|
703
1050
|
var modelNames = Object.keys(models);
|
|
704
1051
|
var totalDatasets = 0;
|
|
@@ -716,83 +1063,85 @@ ${TIER_BADGE}
|
|
|
716
1063
|
var totalOwners = Object.keys(owners).length;
|
|
717
1064
|
%>
|
|
718
1065
|
|
|
719
|
-
<div class="stats
|
|
720
|
-
<div class="stat">
|
|
721
|
-
<div class="stat-
|
|
722
|
-
<div class="stat-
|
|
1066
|
+
<div class="stats-row">
|
|
1067
|
+
<div class="stat-item">
|
|
1068
|
+
<div class="stat-val"><%= modelNames.length %></div>
|
|
1069
|
+
<div class="stat-lbl">Models</div>
|
|
723
1070
|
</div>
|
|
724
|
-
<div class="stat">
|
|
725
|
-
<div class="stat-
|
|
726
|
-
<div class="stat-
|
|
1071
|
+
<div class="stat-item">
|
|
1072
|
+
<div class="stat-val"><%= totalDatasets %></div>
|
|
1073
|
+
<div class="stat-lbl">Datasets</div>
|
|
727
1074
|
</div>
|
|
728
|
-
<div class="stat">
|
|
729
|
-
<div class="stat-
|
|
730
|
-
<div class="stat-
|
|
1075
|
+
<div class="stat-item">
|
|
1076
|
+
<div class="stat-val"><%= totalFields %></div>
|
|
1077
|
+
<div class="stat-lbl">Fields</div>
|
|
731
1078
|
</div>
|
|
732
|
-
<div class="stat">
|
|
733
|
-
<div class="stat-
|
|
734
|
-
<div class="stat-
|
|
1079
|
+
<div class="stat-item">
|
|
1080
|
+
<div class="stat-val"><%= totalTerms %></div>
|
|
1081
|
+
<div class="stat-lbl">Terms</div>
|
|
735
1082
|
</div>
|
|
736
|
-
<div class="stat">
|
|
737
|
-
<div class="stat-
|
|
738
|
-
<div class="stat-
|
|
1083
|
+
<div class="stat-item">
|
|
1084
|
+
<div class="stat-val"><%= totalOwners %></div>
|
|
1085
|
+
<div class="stat-lbl">Owners</div>
|
|
739
1086
|
</div>
|
|
740
1087
|
</div>
|
|
741
1088
|
|
|
742
|
-
<div class="section
|
|
1089
|
+
<div class="section">
|
|
743
1090
|
<div class="section-label">Semantic Models</div>
|
|
744
1091
|
<h2 class="section-title">Models</h2>
|
|
745
1092
|
<% if (modelNames.length === 0) { %>
|
|
746
|
-
<p style="color:var(--text-
|
|
1093
|
+
<p style="color:var(--text-secondary);">No models found. Run <code style="font-family:var(--mono);background:var(--bg-card);padding:0.15rem 0.4rem;border-radius:3px;">context introspect</code> to get started.</p>
|
|
747
1094
|
<% } else { %>
|
|
748
1095
|
<div class="card-grid">
|
|
749
1096
|
<% for (var name of modelNames) { %>
|
|
750
|
-
<
|
|
751
|
-
<div
|
|
752
|
-
<
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
<% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>
|
|
756
|
-
</div>
|
|
757
|
-
<% if (models[name].description) { %>
|
|
758
|
-
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;font-weight:300;"><%= models[name].description %></p>
|
|
759
|
-
<% } %>
|
|
760
|
-
<% if (governance[name]) { %>
|
|
761
|
-
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;">
|
|
762
|
-
<% if (governance[name].owner) { %>
|
|
763
|
-
<a href="<%= basePath %>/owners/<%= governance[name].owner %>.html" class="tag tag-nav" style="text-decoration:none;"><%= governance[name].owner %></a>
|
|
764
|
-
<% } %>
|
|
765
|
-
<% if (governance[name].trust) { %>
|
|
766
|
-
<span class="tag tag-green"><%= governance[name].trust %></span>
|
|
767
|
-
<% } %>
|
|
768
|
-
<% if (governance[name].tags) { for (var t of governance[name].tags) { %>
|
|
769
|
-
<span class="tag"><%= t %></span>
|
|
770
|
-
<% } } %>
|
|
1097
|
+
<a href="<%= basePath %>/models/<%= name %>.html" class="card-link">
|
|
1098
|
+
<div class="card">
|
|
1099
|
+
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.4rem;">
|
|
1100
|
+
<span class="mono" style="font-size:0.88rem;color:var(--accent-light);"><%= name %></span>
|
|
1101
|
+
<% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>
|
|
771
1102
|
</div>
|
|
772
|
-
|
|
773
|
-
|
|
1103
|
+
<% if (models[name].description) { %>
|
|
1104
|
+
<p style="color:var(--text-secondary);font-size:0.82rem;margin-bottom:0.5rem;font-weight:300;"><%= models[name].description %></p>
|
|
1105
|
+
<% } %>
|
|
1106
|
+
<% if (governance[name]) { %>
|
|
1107
|
+
<div style="display:flex;gap:0.35rem;flex-wrap:wrap;align-items:center;">
|
|
1108
|
+
<% if (governance[name].owner) { %>
|
|
1109
|
+
<span class="tag"><%= governance[name].owner %></span>
|
|
1110
|
+
<% } %>
|
|
1111
|
+
<% if (governance[name].trust) { %>
|
|
1112
|
+
<span class="tag tag-green"><%= governance[name].trust %></span>
|
|
1113
|
+
<% } %>
|
|
1114
|
+
<% if (governance[name].tags) { for (var t of governance[name].tags) { %>
|
|
1115
|
+
<span class="tag"><%= t %></span>
|
|
1116
|
+
<% } } %>
|
|
1117
|
+
</div>
|
|
1118
|
+
<% } %>
|
|
1119
|
+
</div>
|
|
1120
|
+
</a>
|
|
774
1121
|
<% } %>
|
|
775
1122
|
</div>
|
|
776
1123
|
<% } %>
|
|
777
1124
|
</div>
|
|
778
1125
|
|
|
779
1126
|
<% if (Object.keys(owners).length > 0) { %>
|
|
780
|
-
<div class="section
|
|
1127
|
+
<div class="section">
|
|
781
1128
|
<div class="section-label">Data Stewardship</div>
|
|
782
1129
|
<h2 class="section-title">Owners</h2>
|
|
783
|
-
<div class="card-grid
|
|
1130
|
+
<div class="card-grid">
|
|
784
1131
|
<% for (var oid of Object.keys(owners)) { %>
|
|
785
|
-
<a href="<%= basePath %>/owners/<%= oid %>.html" class="card
|
|
786
|
-
<div
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1132
|
+
<a href="<%= basePath %>/owners/<%= oid %>.html" class="card-link">
|
|
1133
|
+
<div class="card">
|
|
1134
|
+
<div style="font-size:0.9rem;font-weight:500;color:var(--text);margin-bottom:0.15rem;"><%= owners[oid].display_name %></div>
|
|
1135
|
+
<% if (owners[oid].team) { %>
|
|
1136
|
+
<div style="font-size:0.75rem;color:var(--text-dim);"><%= owners[oid].team %></div>
|
|
1137
|
+
<% } %>
|
|
1138
|
+
</div>
|
|
790
1139
|
</a>
|
|
791
1140
|
<% } %>
|
|
792
1141
|
</div>
|
|
793
1142
|
</div>
|
|
794
1143
|
<% } %>
|
|
795
|
-
</
|
|
1144
|
+
</div>
|
|
796
1145
|
|
|
797
1146
|
${FOOTER}
|
|
798
1147
|
${SCRIPTS}
|
|
@@ -802,49 +1151,93 @@ ${SCRIPTS}
|
|
|
802
1151
|
// src/templates/model.ts
|
|
803
1152
|
var modelTemplate = `${HEAD}
|
|
804
1153
|
<body>
|
|
805
|
-
${
|
|
1154
|
+
${TOPBAR}
|
|
1155
|
+
${SIDEBAR_DATA}
|
|
1156
|
+
${SIDEBAR}
|
|
806
1157
|
${TIER_BADGE}
|
|
807
1158
|
|
|
808
|
-
<
|
|
809
|
-
<
|
|
1159
|
+
<div class="main">
|
|
1160
|
+
<div class="breadcrumb">
|
|
1161
|
+
<a href="<%= basePath %>/index.html">Home</a>
|
|
1162
|
+
<span class="breadcrumb-sep">/</span>
|
|
1163
|
+
<span><%= model.name %></span>
|
|
1164
|
+
</div>
|
|
1165
|
+
|
|
1166
|
+
<div class="page-header">
|
|
1167
|
+
<div style="display:flex;align-items:center;gap:0.6rem;">
|
|
1168
|
+
<h1><%= model.name %></h1>
|
|
1169
|
+
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
1170
|
+
</div>
|
|
1171
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1172
|
+
<p class="subtitle"><span class="editable" data-file="context/models/<%= model.name %>.osi.yaml" data-path="semantic_model.0.description" data-label="Model description"><%= model.description || 'Add description...' %></span></p>
|
|
1173
|
+
<% } else { %>
|
|
1174
|
+
<% if (model.description) { %>
|
|
1175
|
+
<p class="subtitle"><%= model.description %></p>
|
|
1176
|
+
<% } %>
|
|
1177
|
+
<% } %>
|
|
1178
|
+
</div>
|
|
810
1179
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1180
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1181
|
+
<div class="section">
|
|
1182
|
+
<div class="section-label">AI Context</div>
|
|
1183
|
+
<div class="card" style="margin-bottom:1rem;">
|
|
1184
|
+
<span class="editable" data-file="context/models/<%= model.name %>.osi.yaml" data-path="semantic_model.0.ai_context" data-label="AI context"><%= (typeof model.ai_context === 'string' ? model.ai_context : (model.ai_context ? JSON.stringify(model.ai_context) : 'Add AI context...')) %></span>
|
|
1185
|
+
</div>
|
|
814
1186
|
</div>
|
|
815
|
-
<%
|
|
816
|
-
|
|
1187
|
+
<% } else { %>
|
|
1188
|
+
<% if (model.ai_context) { %>
|
|
1189
|
+
<div class="section">
|
|
1190
|
+
<div class="section-label">AI Context</div>
|
|
1191
|
+
<div class="card" style="margin-bottom:1rem;">
|
|
1192
|
+
<p style="font-size:0.85rem;color:var(--text-secondary);font-weight:300;line-height:1.6;"><%= typeof model.ai_context === 'string' ? model.ai_context : JSON.stringify(model.ai_context) %></p>
|
|
1193
|
+
</div>
|
|
1194
|
+
</div>
|
|
1195
|
+
<% } %>
|
|
817
1196
|
<% } %>
|
|
818
1197
|
|
|
819
|
-
<div style="display:flex;gap:0.
|
|
820
|
-
<a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="tag tag-
|
|
821
|
-
<a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="tag tag-
|
|
1198
|
+
<div style="display:flex;gap:0.5rem;margin-bottom:2rem;">
|
|
1199
|
+
<a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="tag tag-blue" style="padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;">Schema Browser</a>
|
|
1200
|
+
<a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="tag tag-gold" style="padding:0.3rem 0.6rem;font-size:0.65rem;text-decoration:none;">Rules & Queries</a>
|
|
822
1201
|
</div>
|
|
823
1202
|
|
|
824
1203
|
<% if (gov) { %>
|
|
825
|
-
<div class="section
|
|
1204
|
+
<div class="section">
|
|
826
1205
|
<div class="section-label">Governance</div>
|
|
827
1206
|
<div class="gov-grid">
|
|
828
|
-
<div class="gov-
|
|
1207
|
+
<div class="gov-cell">
|
|
829
1208
|
<div class="gov-label">Owner</div>
|
|
830
1209
|
<div class="gov-value"><a href="<%= basePath %>/owners/<%= gov.owner %>.html"><%= gov.owner %></a></div>
|
|
831
1210
|
</div>
|
|
832
|
-
|
|
833
|
-
<div class="gov-item">
|
|
1211
|
+
<div class="gov-cell">
|
|
834
1212
|
<div class="gov-label">Trust</div>
|
|
835
|
-
<div class="gov-value"
|
|
1213
|
+
<div class="gov-value">
|
|
1214
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1215
|
+
<span class="dropdown-editable" data-file="context/governance/<%= model.name %>.governance.yaml" data-path="trust" data-label="Trust" data-options="draft,reviewed,endorsed,certified"><%= gov.trust || 'Select...' %></span>
|
|
1216
|
+
<% } else { %>
|
|
1217
|
+
<%= gov.trust || '' %>
|
|
1218
|
+
<% } %>
|
|
1219
|
+
</div>
|
|
836
1220
|
</div>
|
|
837
|
-
<% } %>
|
|
838
1221
|
<% if (gov.security) { %>
|
|
839
|
-
<div class="gov-
|
|
1222
|
+
<div class="gov-cell">
|
|
840
1223
|
<div class="gov-label">Security</div>
|
|
841
1224
|
<div class="gov-value"><%= gov.security %></div>
|
|
842
1225
|
</div>
|
|
843
1226
|
<% } %>
|
|
1227
|
+
<div class="gov-cell">
|
|
1228
|
+
<div class="gov-label">Refresh Cadence</div>
|
|
1229
|
+
<div class="gov-value">
|
|
1230
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1231
|
+
<span class="editable" data-file="context/governance/<%= model.name %>.governance.yaml" data-path="refresh" data-label="Refresh cadence"><%= gov.refresh || 'Add refresh cadence...' %></span>
|
|
1232
|
+
<% } else { %>
|
|
1233
|
+
<%= gov.refresh || '' %>
|
|
1234
|
+
<% } %>
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
844
1237
|
<% if (gov.tags && gov.tags.length > 0) { %>
|
|
845
|
-
<div class="gov-
|
|
1238
|
+
<div class="gov-cell">
|
|
846
1239
|
<div class="gov-label">Tags</div>
|
|
847
|
-
<div class="gov-value" style="display:flex;gap:0.
|
|
1240
|
+
<div class="gov-value" style="display:flex;gap:0.3rem;flex-wrap:wrap;">
|
|
848
1241
|
<% for (var t of gov.tags) { %><span class="tag"><%= t %></span><% } %>
|
|
849
1242
|
</div>
|
|
850
1243
|
</div>
|
|
@@ -854,44 +1247,37 @@ ${TIER_BADGE}
|
|
|
854
1247
|
<% } %>
|
|
855
1248
|
|
|
856
1249
|
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
857
|
-
<div class="section
|
|
1250
|
+
<div class="section">
|
|
858
1251
|
<div class="section-label">Data Explorer</div>
|
|
859
1252
|
<h2 class="section-title">Datasets</h2>
|
|
860
|
-
<
|
|
861
|
-
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
1253
|
+
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
|
862
1254
|
<% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
|
|
863
1255
|
<% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
|
|
864
1256
|
<div class="card">
|
|
865
1257
|
<div class="expandable-header" onclick="toggleExpand('ds-<%= i %>')">
|
|
866
1258
|
<div>
|
|
867
|
-
<div style="display:flex;align-items:center;gap:0.
|
|
868
|
-
<span class="mono" style="color:var(--text);"><%= ds.name %></span>
|
|
1259
|
+
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.15rem;">
|
|
1260
|
+
<span class="mono" style="font-size:0.85rem;color:var(--text);"><%= ds.name %></span>
|
|
869
1261
|
<% if (dsGov && dsGov.table_type) { %>
|
|
870
|
-
<span class="
|
|
1262
|
+
<span class="tag ds-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
|
|
871
1263
|
<% } %>
|
|
872
1264
|
<% if (ds.fields) { %><span class="tag"><%= ds.fields.length %> fields</span><% } %>
|
|
873
1265
|
</div>
|
|
874
|
-
<div style="font-size:0.
|
|
1266
|
+
<div style="font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);">
|
|
875
1267
|
<%= ds.source %>
|
|
876
|
-
<% if (dsGov && dsGov.grain) { %> · <span style="
|
|
877
|
-
<% if (dsGov && dsGov.refresh) { %> · <span style="color:var(--text-muted);font-family:var(--sans);"><%= dsGov.refresh %></span><% } %>
|
|
1268
|
+
<% if (dsGov && dsGov.grain) { %> · <span style="font-family:var(--sans);color:var(--text-secondary);"><%= dsGov.grain %></span><% } %>
|
|
878
1269
|
</div>
|
|
879
1270
|
<% if (ds.description) { %>
|
|
880
|
-
<p style="font-size:0.
|
|
1271
|
+
<p style="font-size:0.8rem;color:var(--text-secondary);margin-top:0.3rem;font-weight:300;"><%= ds.description %></p>
|
|
881
1272
|
<% } %>
|
|
882
1273
|
</div>
|
|
883
1274
|
<span class="expand-icon" id="ds-<%= i %>-icon">+</span>
|
|
884
1275
|
</div>
|
|
885
1276
|
<div class="expandable-content" id="ds-<%= i %>">
|
|
886
1277
|
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
887
|
-
<table class="table
|
|
1278
|
+
<table class="data-table" style="margin-top:0.75rem;">
|
|
888
1279
|
<thead>
|
|
889
|
-
<tr>
|
|
890
|
-
<th>Field</th>
|
|
891
|
-
<th>Description</th>
|
|
892
|
-
<th>Role</th>
|
|
893
|
-
<th>Aggregation</th>
|
|
894
|
-
</tr>
|
|
1280
|
+
<tr><th>Field</th><th>Description</th><th>Role</th><th>Aggregation</th></tr>
|
|
895
1281
|
</thead>
|
|
896
1282
|
<tbody>
|
|
897
1283
|
<% for (var field of ds.fields) { %>
|
|
@@ -899,10 +1285,10 @@ ${TIER_BADGE}
|
|
|
899
1285
|
<% var fGov = gov && gov.fields && gov.fields[fKey]; %>
|
|
900
1286
|
<% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
|
|
901
1287
|
<tr>
|
|
902
|
-
<td class="mono" style="font-size:0.
|
|
903
|
-
<td style="color:var(--text-
|
|
1288
|
+
<td class="mono" style="font-size:0.75rem;"><%= field.name %></td>
|
|
1289
|
+
<td style="color:var(--text-secondary);font-size:0.8rem;"><%= field.description || '' %></td>
|
|
904
1290
|
<td><% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %></td>
|
|
905
|
-
<td style="font-
|
|
1291
|
+
<td class="mono" style="font-size:0.75rem;color:var(--text-dim);"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
|
|
906
1292
|
</tr>
|
|
907
1293
|
<% } %>
|
|
908
1294
|
</tbody>
|
|
@@ -916,20 +1302,20 @@ ${TIER_BADGE}
|
|
|
916
1302
|
<% } %>
|
|
917
1303
|
|
|
918
1304
|
<% if (model.relationships && model.relationships.length > 0) { %>
|
|
919
|
-
<div class="section
|
|
1305
|
+
<div class="section">
|
|
920
1306
|
<div class="section-label">Data Model</div>
|
|
921
1307
|
<h2 class="section-title">Relationships</h2>
|
|
922
|
-
<table class="table
|
|
1308
|
+
<table class="data-table">
|
|
923
1309
|
<thead>
|
|
924
1310
|
<tr><th>Name</th><th>From</th><th></th><th>To</th></tr>
|
|
925
1311
|
</thead>
|
|
926
1312
|
<tbody>
|
|
927
1313
|
<% for (var rel of model.relationships) { %>
|
|
928
1314
|
<tr>
|
|
929
|
-
<td style="color:var(--text-
|
|
930
|
-
<td class="mono" style="font-size:0.
|
|
931
|
-
<td style="color:var(--
|
|
932
|
-
<td class="mono" style="font-size:0.
|
|
1315
|
+
<td style="color:var(--text-secondary);font-size:0.8rem;"><%= rel.name %></td>
|
|
1316
|
+
<td class="mono" style="font-size:0.75rem;"><%= rel.from %></td>
|
|
1317
|
+
<td style="color:var(--text-dim);text-align:center;">→</td>
|
|
1318
|
+
<td class="mono" style="font-size:0.75rem;"><%= rel.to %></td>
|
|
933
1319
|
</tr>
|
|
934
1320
|
<% } %>
|
|
935
1321
|
</tbody>
|
|
@@ -938,7 +1324,7 @@ ${TIER_BADGE}
|
|
|
938
1324
|
<% } %>
|
|
939
1325
|
|
|
940
1326
|
<% if (model.metrics && model.metrics.length > 0) { %>
|
|
941
|
-
<div class="section
|
|
1327
|
+
<div class="section">
|
|
942
1328
|
<div class="section-label">Computed Metrics</div>
|
|
943
1329
|
<h2 class="section-title">Metrics</h2>
|
|
944
1330
|
<div class="card-grid">
|
|
@@ -955,8 +1341,229 @@ ${TIER_BADGE}
|
|
|
955
1341
|
</div>
|
|
956
1342
|
<% } %>
|
|
957
1343
|
|
|
1344
|
+
<% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>
|
|
1345
|
+
<div class="section">
|
|
1346
|
+
<div class="section-label">Golden Queries</div>
|
|
1347
|
+
<h2 class="section-title">Pre-validated Queries</h2>
|
|
1348
|
+
<% for (var gq of rules.golden_queries) { %>
|
|
1349
|
+
<div class="query-card">
|
|
1350
|
+
<div class="query-q">
|
|
1351
|
+
<span class="query-q-badge">Q</span>
|
|
1352
|
+
<span><%= gq.question %></span>
|
|
1353
|
+
</div>
|
|
1354
|
+
<div class="query-sql sql-highlight"><%= gq.sql %></div>
|
|
1355
|
+
<% if (gq.description) { %>
|
|
1356
|
+
<div class="query-meta"><span><%= gq.description %></span></div>
|
|
1357
|
+
<% } %>
|
|
1358
|
+
</div>
|
|
1359
|
+
<% } %>
|
|
1360
|
+
</div>
|
|
1361
|
+
<% } %>
|
|
1362
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1363
|
+
<div class="section" id="golden-queries-studio">
|
|
1364
|
+
<% if (!(rules && rules.golden_queries && rules.golden_queries.length > 0)) { %>
|
|
1365
|
+
<div class="section-label">Golden Queries</div>
|
|
1366
|
+
<h2 class="section-title">Pre-validated Queries</h2>
|
|
1367
|
+
<% } %>
|
|
1368
|
+
<div id="golden-query-forms"></div>
|
|
1369
|
+
<button class="studio-add-btn" onclick="addGoldenQuery('<%= model.name %>')">+ Add Golden Query</button>
|
|
1370
|
+
</div>
|
|
1371
|
+
<script>
|
|
1372
|
+
function addGoldenQuery(modelName) {
|
|
1373
|
+
var container = document.getElementById('golden-query-forms');
|
|
1374
|
+
var card = document.createElement('div');
|
|
1375
|
+
card.className = 'card';
|
|
1376
|
+
card.style.marginBottom = '1rem';
|
|
1377
|
+
|
|
1378
|
+
var questionLabel = document.createElement('label');
|
|
1379
|
+
questionLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1380
|
+
questionLabel.textContent = 'Question';
|
|
1381
|
+
var questionInput = document.createElement('input');
|
|
1382
|
+
questionInput.type = 'text';
|
|
1383
|
+
questionInput.className = 'search-input gq-question';
|
|
1384
|
+
questionInput.placeholder = 'e.g. What is the total revenue by region?';
|
|
1385
|
+
questionInput.style.fontSize = '0.85rem';
|
|
1386
|
+
var questionDiv = document.createElement('div');
|
|
1387
|
+
questionDiv.style.marginBottom = '0.75rem';
|
|
1388
|
+
questionDiv.appendChild(questionLabel);
|
|
1389
|
+
questionDiv.appendChild(questionInput);
|
|
1390
|
+
|
|
1391
|
+
var sqlLabel = document.createElement('label');
|
|
1392
|
+
sqlLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1393
|
+
sqlLabel.textContent = 'SQL';
|
|
1394
|
+
var sqlInput = document.createElement('textarea');
|
|
1395
|
+
sqlInput.className = 'search-input gq-sql';
|
|
1396
|
+
sqlInput.rows = 4;
|
|
1397
|
+
sqlInput.placeholder = 'SELECT ...';
|
|
1398
|
+
sqlInput.style.cssText = 'font-family:var(--mono);font-size:0.8rem;resize:vertical;';
|
|
1399
|
+
var sqlDiv = document.createElement('div');
|
|
1400
|
+
sqlDiv.style.marginBottom = '0.75rem';
|
|
1401
|
+
sqlDiv.appendChild(sqlLabel);
|
|
1402
|
+
sqlDiv.appendChild(sqlInput);
|
|
1403
|
+
|
|
1404
|
+
var descLabel = document.createElement('label');
|
|
1405
|
+
descLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1406
|
+
descLabel.textContent = 'Description';
|
|
1407
|
+
var descInput = document.createElement('input');
|
|
1408
|
+
descInput.type = 'text';
|
|
1409
|
+
descInput.className = 'search-input gq-desc';
|
|
1410
|
+
descInput.placeholder = 'Optional description';
|
|
1411
|
+
descInput.style.fontSize = '0.85rem';
|
|
1412
|
+
var descDiv = document.createElement('div');
|
|
1413
|
+
descDiv.style.marginBottom = '0.75rem';
|
|
1414
|
+
descDiv.appendChild(descLabel);
|
|
1415
|
+
descDiv.appendChild(descInput);
|
|
1416
|
+
|
|
1417
|
+
var stageBtn = document.createElement('button');
|
|
1418
|
+
stageBtn.className = 'staged-btn primary';
|
|
1419
|
+
stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1420
|
+
stageBtn.textContent = 'Stage';
|
|
1421
|
+
stageBtn.addEventListener('click', function() { stageGoldenQuery(stageBtn, modelName); });
|
|
1422
|
+
|
|
1423
|
+
var cancelBtn = document.createElement('button');
|
|
1424
|
+
cancelBtn.className = 'staged-btn secondary';
|
|
1425
|
+
cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1426
|
+
cancelBtn.textContent = 'Cancel';
|
|
1427
|
+
cancelBtn.addEventListener('click', function() { card.remove(); });
|
|
1428
|
+
|
|
1429
|
+
var btnDiv = document.createElement('div');
|
|
1430
|
+
btnDiv.style.cssText = 'display:flex;gap:0.5rem;';
|
|
1431
|
+
btnDiv.appendChild(stageBtn);
|
|
1432
|
+
btnDiv.appendChild(cancelBtn);
|
|
1433
|
+
|
|
1434
|
+
card.appendChild(questionDiv);
|
|
1435
|
+
card.appendChild(sqlDiv);
|
|
1436
|
+
card.appendChild(descDiv);
|
|
1437
|
+
card.appendChild(btnDiv);
|
|
1438
|
+
container.appendChild(card);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function stageGoldenQuery(btn, modelName) {
|
|
1442
|
+
var card = btn.closest('.card');
|
|
1443
|
+
var question = card.querySelector('.gq-question').value.trim();
|
|
1444
|
+
var sql = card.querySelector('.gq-sql').value.trim();
|
|
1445
|
+
var desc = card.querySelector('.gq-desc').value.trim();
|
|
1446
|
+
if (!question || !sql) { showToast('Question and SQL are required'); return; }
|
|
1447
|
+
var entry = { question: question, sql: sql };
|
|
1448
|
+
if (desc) entry.description = desc;
|
|
1449
|
+
stageEdit('context/rules/' + modelName + '.rules.yaml', 'golden_queries.+', entry, 'Add golden query: ' + question);
|
|
1450
|
+
card.style.borderColor = '#4ade80';
|
|
1451
|
+
btn.textContent = 'Staged';
|
|
1452
|
+
btn.disabled = true;
|
|
1453
|
+
}
|
|
1454
|
+
</script>
|
|
1455
|
+
<% } %>
|
|
1456
|
+
|
|
1457
|
+
<% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>
|
|
1458
|
+
<div class="section">
|
|
1459
|
+
<div class="section-label">Safety</div>
|
|
1460
|
+
<h2 class="section-title">Guardrail Filters</h2>
|
|
1461
|
+
<% for (var gf of rules.guardrail_filters) { %>
|
|
1462
|
+
<div class="guardrail">
|
|
1463
|
+
<div class="guardrail-name"><%= gf.name %></div>
|
|
1464
|
+
<div class="guardrail-filter"><%= gf.filter %></div>
|
|
1465
|
+
<div class="guardrail-reason"><%= gf.reason %></div>
|
|
1466
|
+
</div>
|
|
1467
|
+
<% } %>
|
|
1468
|
+
</div>
|
|
1469
|
+
<% } %>
|
|
1470
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1471
|
+
<div class="section" id="guardrails-studio">
|
|
1472
|
+
<% if (!(rules && rules.guardrail_filters && rules.guardrail_filters.length > 0)) { %>
|
|
1473
|
+
<div class="section-label">Safety</div>
|
|
1474
|
+
<h2 class="section-title">Guardrail Filters</h2>
|
|
1475
|
+
<% } %>
|
|
1476
|
+
<div id="guardrail-forms"></div>
|
|
1477
|
+
<button class="studio-add-btn" onclick="addGuardrail('<%= model.name %>')">+ Add Guardrail</button>
|
|
1478
|
+
</div>
|
|
1479
|
+
<script>
|
|
1480
|
+
function addGuardrail(modelName) {
|
|
1481
|
+
var container = document.getElementById('guardrail-forms');
|
|
1482
|
+
var card = document.createElement('div');
|
|
1483
|
+
card.className = 'card';
|
|
1484
|
+
card.style.marginBottom = '1rem';
|
|
1485
|
+
|
|
1486
|
+
var nameLabel = document.createElement('label');
|
|
1487
|
+
nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1488
|
+
nameLabel.textContent = 'Name';
|
|
1489
|
+
var nameInput = document.createElement('input');
|
|
1490
|
+
nameInput.type = 'text';
|
|
1491
|
+
nameInput.className = 'search-input gr-name';
|
|
1492
|
+
nameInput.placeholder = 'e.g. pii_filter';
|
|
1493
|
+
nameInput.style.fontSize = '0.85rem';
|
|
1494
|
+
var nameDiv = document.createElement('div');
|
|
1495
|
+
nameDiv.style.marginBottom = '0.75rem';
|
|
1496
|
+
nameDiv.appendChild(nameLabel);
|
|
1497
|
+
nameDiv.appendChild(nameInput);
|
|
1498
|
+
|
|
1499
|
+
var filterLabel = document.createElement('label');
|
|
1500
|
+
filterLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1501
|
+
filterLabel.textContent = 'Filter Expression';
|
|
1502
|
+
var filterInput = document.createElement('input');
|
|
1503
|
+
filterInput.type = 'text';
|
|
1504
|
+
filterInput.className = 'search-input gr-filter';
|
|
1505
|
+
filterInput.placeholder = 'e.g. WHERE email IS NOT NULL';
|
|
1506
|
+
filterInput.style.cssText = 'font-family:var(--mono);font-size:0.85rem;';
|
|
1507
|
+
var filterDiv = document.createElement('div');
|
|
1508
|
+
filterDiv.style.marginBottom = '0.75rem';
|
|
1509
|
+
filterDiv.appendChild(filterLabel);
|
|
1510
|
+
filterDiv.appendChild(filterInput);
|
|
1511
|
+
|
|
1512
|
+
var reasonLabel = document.createElement('label');
|
|
1513
|
+
reasonLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1514
|
+
reasonLabel.textContent = 'Reason';
|
|
1515
|
+
var reasonInput = document.createElement('input');
|
|
1516
|
+
reasonInput.type = 'text';
|
|
1517
|
+
reasonInput.className = 'search-input gr-reason';
|
|
1518
|
+
reasonInput.placeholder = 'Why this guardrail exists';
|
|
1519
|
+
reasonInput.style.fontSize = '0.85rem';
|
|
1520
|
+
var reasonDiv = document.createElement('div');
|
|
1521
|
+
reasonDiv.style.marginBottom = '0.75rem';
|
|
1522
|
+
reasonDiv.appendChild(reasonLabel);
|
|
1523
|
+
reasonDiv.appendChild(reasonInput);
|
|
1524
|
+
|
|
1525
|
+
var stageBtn = document.createElement('button');
|
|
1526
|
+
stageBtn.className = 'staged-btn primary';
|
|
1527
|
+
stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1528
|
+
stageBtn.textContent = 'Stage';
|
|
1529
|
+
stageBtn.addEventListener('click', function() { stageGuardrail(stageBtn, modelName); });
|
|
1530
|
+
|
|
1531
|
+
var cancelBtn = document.createElement('button');
|
|
1532
|
+
cancelBtn.className = 'staged-btn secondary';
|
|
1533
|
+
cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1534
|
+
cancelBtn.textContent = 'Cancel';
|
|
1535
|
+
cancelBtn.addEventListener('click', function() { card.remove(); });
|
|
1536
|
+
|
|
1537
|
+
var btnDiv = document.createElement('div');
|
|
1538
|
+
btnDiv.style.cssText = 'display:flex;gap:0.5rem;';
|
|
1539
|
+
btnDiv.appendChild(stageBtn);
|
|
1540
|
+
btnDiv.appendChild(cancelBtn);
|
|
1541
|
+
|
|
1542
|
+
card.appendChild(nameDiv);
|
|
1543
|
+
card.appendChild(filterDiv);
|
|
1544
|
+
card.appendChild(reasonDiv);
|
|
1545
|
+
card.appendChild(btnDiv);
|
|
1546
|
+
container.appendChild(card);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
function stageGuardrail(btn, modelName) {
|
|
1550
|
+
var card = btn.closest('.card');
|
|
1551
|
+
var name = card.querySelector('.gr-name').value.trim();
|
|
1552
|
+
var filter = card.querySelector('.gr-filter').value.trim();
|
|
1553
|
+
var reason = card.querySelector('.gr-reason').value.trim();
|
|
1554
|
+
if (!name || !filter) { showToast('Name and filter expression are required'); return; }
|
|
1555
|
+
var entry = { name: name, filter: filter };
|
|
1556
|
+
if (reason) entry.reason = reason;
|
|
1557
|
+
stageEdit('context/rules/' + modelName + '.rules.yaml', 'guardrail_filters.+', entry, 'Add guardrail: ' + name);
|
|
1558
|
+
card.style.borderColor = '#4ade80';
|
|
1559
|
+
btn.textContent = 'Staged';
|
|
1560
|
+
btn.disabled = true;
|
|
1561
|
+
}
|
|
1562
|
+
</script>
|
|
1563
|
+
<% } %>
|
|
1564
|
+
|
|
958
1565
|
<% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>
|
|
959
|
-
<div class="section
|
|
1566
|
+
<div class="section">
|
|
960
1567
|
<div class="section-label">Data Lineage</div>
|
|
961
1568
|
<h2 class="section-title">Lineage</h2>
|
|
962
1569
|
<div class="lineage-flow">
|
|
@@ -974,8 +1581,8 @@ ${TIER_BADGE}
|
|
|
974
1581
|
<% } %>
|
|
975
1582
|
<div class="lineage-col">
|
|
976
1583
|
<div class="lineage-col-label">This Model</div>
|
|
977
|
-
<div class="lineage-node" style="border-color:var(--
|
|
978
|
-
<div class="lineage-node-name" style="color:var(--
|
|
1584
|
+
<div class="lineage-node" style="border-color:var(--accent-border);">
|
|
1585
|
+
<div class="lineage-node-name" style="color:var(--accent);"><%= model.name %></div>
|
|
979
1586
|
</div>
|
|
980
1587
|
</div>
|
|
981
1588
|
<% if (lineage.downstream && lineage.downstream.length > 0) { %>
|
|
@@ -993,21 +1600,127 @@ ${TIER_BADGE}
|
|
|
993
1600
|
</div>
|
|
994
1601
|
</div>
|
|
995
1602
|
<% } %>
|
|
1603
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1604
|
+
<div class="section" id="lineage-studio">
|
|
1605
|
+
<% if (!(lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0))) { %>
|
|
1606
|
+
<div class="section-label">Data Lineage</div>
|
|
1607
|
+
<h2 class="section-title">Lineage</h2>
|
|
1608
|
+
<% } %>
|
|
1609
|
+
<div id="upstream-forms"></div>
|
|
1610
|
+
<button class="studio-add-btn" onclick="addUpstreamSource('<%= model.name %>')" style="margin-bottom:0.75rem;">+ Add Upstream Source</button>
|
|
1611
|
+
<div id="downstream-forms"></div>
|
|
1612
|
+
<button class="studio-add-btn" onclick="addDownstreamTarget('<%= model.name %>')">+ Add Downstream Target</button>
|
|
1613
|
+
</div>
|
|
1614
|
+
<script>
|
|
1615
|
+
function createLineageForm(container, direction, modelName) {
|
|
1616
|
+
var card = document.createElement('div');
|
|
1617
|
+
card.className = 'card';
|
|
1618
|
+
card.style.marginBottom = '1rem';
|
|
1619
|
+
|
|
1620
|
+
var nameLabel = document.createElement('label');
|
|
1621
|
+
nameLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1622
|
+
nameLabel.textContent = direction === 'upstream' ? 'Source Name' : 'Target Name';
|
|
1623
|
+
var nameInput = document.createElement('input');
|
|
1624
|
+
nameInput.type = 'text';
|
|
1625
|
+
nameInput.className = 'search-input lin-name';
|
|
1626
|
+
nameInput.placeholder = direction === 'upstream' ? 'e.g. raw_orders_db' : 'e.g. revenue_dashboard';
|
|
1627
|
+
nameInput.style.fontSize = '0.85rem';
|
|
1628
|
+
var nameDiv = document.createElement('div');
|
|
1629
|
+
nameDiv.style.marginBottom = '0.75rem';
|
|
1630
|
+
nameDiv.appendChild(nameLabel);
|
|
1631
|
+
nameDiv.appendChild(nameInput);
|
|
1632
|
+
|
|
1633
|
+
var typeLabel = document.createElement('label');
|
|
1634
|
+
typeLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1635
|
+
typeLabel.textContent = 'Type';
|
|
1636
|
+
var typeSelect = document.createElement('select');
|
|
1637
|
+
typeSelect.className = 'search-input lin-type';
|
|
1638
|
+
typeSelect.style.fontSize = '0.85rem';
|
|
1639
|
+
['pipeline', 'dashboard', 'api', 'file', 'derived'].forEach(function(opt) {
|
|
1640
|
+
var option = document.createElement('option');
|
|
1641
|
+
option.value = opt;
|
|
1642
|
+
option.textContent = opt;
|
|
1643
|
+
typeSelect.appendChild(option);
|
|
1644
|
+
});
|
|
1645
|
+
var typeDiv = document.createElement('div');
|
|
1646
|
+
typeDiv.style.marginBottom = '0.75rem';
|
|
1647
|
+
typeDiv.appendChild(typeLabel);
|
|
1648
|
+
typeDiv.appendChild(typeSelect);
|
|
1649
|
+
|
|
1650
|
+
var notesLabel = document.createElement('label');
|
|
1651
|
+
notesLabel.style.cssText = 'display:block;font-size:0.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:0.25rem;font-family:var(--mono);';
|
|
1652
|
+
notesLabel.textContent = 'Notes';
|
|
1653
|
+
var notesInput = document.createElement('input');
|
|
1654
|
+
notesInput.type = 'text';
|
|
1655
|
+
notesInput.className = 'search-input lin-notes';
|
|
1656
|
+
notesInput.placeholder = 'Optional notes';
|
|
1657
|
+
notesInput.style.fontSize = '0.85rem';
|
|
1658
|
+
var notesDiv = document.createElement('div');
|
|
1659
|
+
notesDiv.style.marginBottom = '0.75rem';
|
|
1660
|
+
notesDiv.appendChild(notesLabel);
|
|
1661
|
+
notesDiv.appendChild(notesInput);
|
|
1662
|
+
|
|
1663
|
+
var stageBtn = document.createElement('button');
|
|
1664
|
+
stageBtn.className = 'staged-btn primary';
|
|
1665
|
+
stageBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1666
|
+
stageBtn.textContent = 'Stage';
|
|
1667
|
+
stageBtn.addEventListener('click', function() {
|
|
1668
|
+
var entryName = card.querySelector('.lin-name').value.trim();
|
|
1669
|
+
var entryType = card.querySelector('.lin-type').value;
|
|
1670
|
+
var entryNotes = card.querySelector('.lin-notes').value.trim();
|
|
1671
|
+
if (!entryName) { showToast((direction === 'upstream' ? 'Source' : 'Target') + ' name is required'); return; }
|
|
1672
|
+
var entry = {};
|
|
1673
|
+
if (direction === 'upstream') { entry.source = entryName; } else { entry.target = entryName; }
|
|
1674
|
+
entry.type = entryType;
|
|
1675
|
+
if (entryNotes) entry.notes = entryNotes;
|
|
1676
|
+
stageEdit('context/lineage/' + modelName + '.lineage.yaml', direction + '.+', entry, 'Add ' + direction + ': ' + entryName);
|
|
1677
|
+
card.style.borderColor = '#4ade80';
|
|
1678
|
+
stageBtn.textContent = 'Staged';
|
|
1679
|
+
stageBtn.disabled = true;
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
var cancelBtn = document.createElement('button');
|
|
1683
|
+
cancelBtn.className = 'staged-btn secondary';
|
|
1684
|
+
cancelBtn.style.cssText = 'font-size:0.8rem;padding:6px 16px;';
|
|
1685
|
+
cancelBtn.textContent = 'Cancel';
|
|
1686
|
+
cancelBtn.addEventListener('click', function() { card.remove(); });
|
|
1687
|
+
|
|
1688
|
+
var btnDiv = document.createElement('div');
|
|
1689
|
+
btnDiv.style.cssText = 'display:flex;gap:0.5rem;';
|
|
1690
|
+
btnDiv.appendChild(stageBtn);
|
|
1691
|
+
btnDiv.appendChild(cancelBtn);
|
|
1692
|
+
|
|
1693
|
+
card.appendChild(nameDiv);
|
|
1694
|
+
card.appendChild(typeDiv);
|
|
1695
|
+
card.appendChild(notesDiv);
|
|
1696
|
+
card.appendChild(btnDiv);
|
|
1697
|
+
container.appendChild(card);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function addUpstreamSource(modelName) {
|
|
1701
|
+
createLineageForm(document.getElementById('upstream-forms'), 'upstream', modelName);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function addDownstreamTarget(modelName) {
|
|
1705
|
+
createLineageForm(document.getElementById('downstream-forms'), 'downstream', modelName);
|
|
1706
|
+
}
|
|
1707
|
+
</script>
|
|
1708
|
+
<% } %>
|
|
996
1709
|
|
|
997
1710
|
<% if (tier) { %>
|
|
998
|
-
<div class="section
|
|
1711
|
+
<div class="section">
|
|
999
1712
|
<div class="section-label">Data Quality</div>
|
|
1000
1713
|
<h2 class="section-title">Tier Scorecard</h2>
|
|
1001
1714
|
<div class="scorecard">
|
|
1002
1715
|
<% var tierLevels = ['bronze', 'silver', 'gold']; %>
|
|
1003
1716
|
<% for (var lvl of tierLevels) { %>
|
|
1004
|
-
<div class="
|
|
1005
|
-
<div class="
|
|
1006
|
-
<span class="
|
|
1717
|
+
<div class="sc-tier">
|
|
1718
|
+
<div class="sc-tier-head">
|
|
1719
|
+
<span class="sc-tier-name <%= lvl %>"><%= lvl %></span>
|
|
1007
1720
|
<% if (tier[lvl].passed) { %>
|
|
1008
|
-
<span class="
|
|
1721
|
+
<span class="sc-status pass">Passed</span>
|
|
1009
1722
|
<% } else { %>
|
|
1010
|
-
<span class="
|
|
1723
|
+
<span class="sc-status fail">Not passed</span>
|
|
1011
1724
|
<% } %>
|
|
1012
1725
|
</div>
|
|
1013
1726
|
<% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
|
|
@@ -1025,7 +1738,7 @@ ${TIER_BADGE}
|
|
|
1025
1738
|
</div>
|
|
1026
1739
|
</div>
|
|
1027
1740
|
<% } %>
|
|
1028
|
-
</
|
|
1741
|
+
</div>
|
|
1029
1742
|
|
|
1030
1743
|
${FOOTER}
|
|
1031
1744
|
${SCRIPTS}
|
|
@@ -1035,77 +1748,102 @@ ${SCRIPTS}
|
|
|
1035
1748
|
// src/templates/schema.ts
|
|
1036
1749
|
var schemaTemplate = `${HEAD}
|
|
1037
1750
|
<body>
|
|
1038
|
-
${
|
|
1751
|
+
${TOPBAR}
|
|
1752
|
+
${SIDEBAR_DATA}
|
|
1753
|
+
${SIDEBAR}
|
|
1039
1754
|
${TIER_BADGE}
|
|
1040
1755
|
|
|
1041
|
-
<
|
|
1042
|
-
<
|
|
1756
|
+
<div class="main">
|
|
1757
|
+
<div class="breadcrumb">
|
|
1758
|
+
<a href="<%= basePath %>/index.html">Home</a>
|
|
1759
|
+
<span class="breadcrumb-sep">/</span>
|
|
1760
|
+
<a href="<%= basePath %>/models/<%= model.name %>.html"><%= model.name %></a>
|
|
1761
|
+
<span class="breadcrumb-sep">/</span>
|
|
1762
|
+
<span>Schema</span>
|
|
1763
|
+
</div>
|
|
1043
1764
|
|
|
1044
|
-
<div
|
|
1045
|
-
<
|
|
1046
|
-
|
|
1047
|
-
|
|
1765
|
+
<div class="page-header">
|
|
1766
|
+
<div style="display:flex;align-items:center;gap:0.6rem;">
|
|
1767
|
+
<h1><%= model.name %></h1>
|
|
1768
|
+
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
1769
|
+
<span style="font-size:0.8rem;color:var(--text-dim);">Schema Browser</span>
|
|
1770
|
+
</div>
|
|
1048
1771
|
</div>
|
|
1049
1772
|
|
|
1050
1773
|
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
1051
1774
|
<% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
|
|
1052
1775
|
<% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
|
|
1053
|
-
<div class="section
|
|
1776
|
+
<div class="section">
|
|
1054
1777
|
<div class="card" style="padding:0;overflow:hidden;">
|
|
1055
|
-
<div style="padding:1.25rem
|
|
1056
|
-
<div style="display:flex;align-items:center;gap:0.
|
|
1057
|
-
<span class="mono" style="font-size:
|
|
1778
|
+
<div style="padding:1rem 1.25rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.4rem;">
|
|
1779
|
+
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
1780
|
+
<span class="mono" style="font-size:0.9rem;color:var(--text);"><%= ds.name %></span>
|
|
1058
1781
|
<% if (dsGov && dsGov.table_type) { %>
|
|
1059
|
-
<span class="
|
|
1782
|
+
<span class="tag ds-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
|
|
1060
1783
|
<% } %>
|
|
1061
1784
|
</div>
|
|
1062
|
-
<div style="display:flex;gap:0.
|
|
1785
|
+
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;">
|
|
1063
1786
|
<% if (dsGov && dsGov.grain) { %><span class="tag"><%= dsGov.grain %></span><% } %>
|
|
1064
1787
|
<% if (dsGov && dsGov.refresh) { %><span class="tag"><%= dsGov.refresh %></span><% } %>
|
|
1065
1788
|
<% if (dsGov && dsGov.security) { %><span class="tag"><%= dsGov.security %></span><% } %>
|
|
1066
1789
|
</div>
|
|
1067
1790
|
</div>
|
|
1068
|
-
<div style="padding:0
|
|
1791
|
+
<div style="padding:0.5rem 1.25rem;font-size:0.72rem;color:var(--text-dim);font-family:var(--mono);">
|
|
1069
1792
|
Source: <%= ds.source %>
|
|
1070
1793
|
</div>
|
|
1071
|
-
<% if (
|
|
1072
|
-
<div style="padding:0 1.
|
|
1794
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1795
|
+
<div style="padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;"><span class="editable" data-file="context/models/<%= model.name %>.osi.yaml" data-path="semantic_model.0.datasets.<%= i %>.description" data-label="<%= ds.name %> description"><%= ds.description || 'Add description' %></span></div>
|
|
1796
|
+
<% } else if (ds.description) { %>
|
|
1797
|
+
<div style="padding:0 1.25rem 0.75rem;font-size:0.82rem;color:var(--text-secondary);font-weight:300;"><%= ds.description %></div>
|
|
1073
1798
|
<% } %>
|
|
1074
1799
|
|
|
1075
1800
|
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
1076
|
-
<table class="table
|
|
1801
|
+
<table class="data-table">
|
|
1077
1802
|
<thead>
|
|
1078
|
-
<tr>
|
|
1079
|
-
<th>Field</th>
|
|
1080
|
-
<th>Description</th>
|
|
1081
|
-
<th>Semantic Role</th>
|
|
1082
|
-
<th>Aggregation</th>
|
|
1083
|
-
</tr>
|
|
1803
|
+
<tr><th>Field</th><th>Description</th><th>Semantic Role</th><th>Aggregation</th></tr>
|
|
1084
1804
|
</thead>
|
|
1085
1805
|
<tbody>
|
|
1086
|
-
<% for (var
|
|
1806
|
+
<% for (var j = 0; j < ds.fields.length; j++) { var field = ds.fields[j]; %>
|
|
1087
1807
|
<% var fieldKey = ds.name + '.' + field.name; %>
|
|
1088
1808
|
<% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
|
|
1089
1809
|
<% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
|
|
1090
1810
|
<tr>
|
|
1091
|
-
<td class="mono" style="font-size:0.
|
|
1092
|
-
<td style="color:var(--text-
|
|
1093
|
-
|
|
1094
|
-
|
|
1811
|
+
<td class="mono" style="font-size:0.75rem;"><%= field.name %></td>
|
|
1812
|
+
<td style="color:var(--text-secondary);font-size:0.8rem;">
|
|
1813
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1814
|
+
<span class="editable" data-file="context/models/<%= model.name %>.osi.yaml" data-path="semantic_model.0.datasets.<%= i %>.fields.<%= j %>.description" data-label="<%= field.name %> description"><%= field.description || 'Add description' %></span>
|
|
1815
|
+
<% } else { %>
|
|
1816
|
+
<%= field.description || '' %>
|
|
1817
|
+
<% } %>
|
|
1818
|
+
</td>
|
|
1819
|
+
<td>
|
|
1820
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1821
|
+
<span class="dropdown-editable" data-file="context/governance/<%= model.name %>.governance.yaml" data-path="datasets.<%= ds.name %>.fields.<%= field.name %>.semantic_role" data-options="identifier,metric,dimension,date,attribute" data-label="<%= field.name %> semantic role"><%= role || 'Select role' %></span>
|
|
1822
|
+
<% } else { %>
|
|
1823
|
+
<% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %>
|
|
1824
|
+
<% } %>
|
|
1825
|
+
</td>
|
|
1826
|
+
<td class="mono" style="font-size:0.75rem;color:var(--text-dim);">
|
|
1827
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1828
|
+
<span class="dropdown-editable" data-file="context/governance/<%= model.name %>.governance.yaml" data-path="datasets.<%= ds.name %>.fields.<%= field.name %>.default_aggregation" data-options="SUM,AVG,COUNT,MIN,MAX,NONE" data-label="<%= field.name %> aggregation"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : 'Select' %></span>
|
|
1829
|
+
<% } else { %>
|
|
1830
|
+
<%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %>
|
|
1831
|
+
<% } %>
|
|
1832
|
+
</td>
|
|
1095
1833
|
</tr>
|
|
1096
1834
|
<% } %>
|
|
1097
1835
|
</tbody>
|
|
1098
1836
|
</table>
|
|
1099
1837
|
<% } else { %>
|
|
1100
|
-
<p style="padding:1.
|
|
1838
|
+
<p style="padding:1.25rem;color:var(--text-dim);font-size:0.82rem;">No fields defined.</p>
|
|
1101
1839
|
<% } %>
|
|
1102
1840
|
</div>
|
|
1103
1841
|
</div>
|
|
1104
1842
|
<% } %>
|
|
1105
1843
|
<% } else { %>
|
|
1106
|
-
<p style="color:var(--text-
|
|
1844
|
+
<p style="color:var(--text-secondary);">No datasets found.</p>
|
|
1107
1845
|
<% } %>
|
|
1108
|
-
</
|
|
1846
|
+
</div>
|
|
1109
1847
|
|
|
1110
1848
|
${FOOTER}
|
|
1111
1849
|
${SCRIPTS}
|
|
@@ -1115,22 +1853,33 @@ ${SCRIPTS}
|
|
|
1115
1853
|
// src/templates/rules.ts
|
|
1116
1854
|
var rulesTemplate = `${HEAD}
|
|
1117
1855
|
<body>
|
|
1118
|
-
${
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1856
|
+
${TOPBAR}
|
|
1857
|
+
${SIDEBAR_DATA}
|
|
1858
|
+
${SIDEBAR}
|
|
1859
|
+
|
|
1860
|
+
<div class="main">
|
|
1861
|
+
<div class="breadcrumb">
|
|
1862
|
+
<a href="<%= basePath %>/index.html">Home</a>
|
|
1863
|
+
<span class="breadcrumb-sep">/</span>
|
|
1864
|
+
<a href="<%= basePath %>/models/<%= modelName %>.html"><%= modelName %></a>
|
|
1865
|
+
<span class="breadcrumb-sep">/</span>
|
|
1866
|
+
<span>Rules</span>
|
|
1867
|
+
</div>
|
|
1122
1868
|
|
|
1123
|
-
<
|
|
1124
|
-
|
|
1125
|
-
</
|
|
1869
|
+
<div class="page-header">
|
|
1870
|
+
<h1><%= modelName %> <span style="color:var(--text-dim);font-weight:300;">— Rules & Queries</span></h1>
|
|
1871
|
+
</div>
|
|
1126
1872
|
|
|
1127
1873
|
<% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>
|
|
1128
|
-
<div class="section
|
|
1874
|
+
<div class="section">
|
|
1129
1875
|
<div class="section-label">Golden Queries</div>
|
|
1130
1876
|
<h2 class="section-title">Pre-validated questions</h2>
|
|
1131
1877
|
<% for (var gq of rules.golden_queries) { %>
|
|
1132
1878
|
<div class="query-card">
|
|
1133
|
-
<div class="query-
|
|
1879
|
+
<div class="query-q">
|
|
1880
|
+
<span class="query-q-badge">Q</span>
|
|
1881
|
+
<span><%= gq.question %></span>
|
|
1882
|
+
</div>
|
|
1134
1883
|
<div class="query-sql sql-highlight"><%= gq.sql %></div>
|
|
1135
1884
|
<% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>
|
|
1136
1885
|
<div class="query-meta">
|
|
@@ -1144,21 +1893,21 @@ ${NAV}
|
|
|
1144
1893
|
<% } %>
|
|
1145
1894
|
|
|
1146
1895
|
<% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>
|
|
1147
|
-
<div class="section
|
|
1896
|
+
<div class="section">
|
|
1148
1897
|
<div class="section-label">Business Rules</div>
|
|
1149
1898
|
<h2 class="section-title">Business Rules</h2>
|
|
1150
|
-
<div style="display:flex;flex-direction:column;gap:
|
|
1899
|
+
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
|
1151
1900
|
<% for (var br of rules.business_rules) { %>
|
|
1152
1901
|
<div class="card">
|
|
1153
|
-
<div style="font-family:var(--mono);font-size:0.
|
|
1154
|
-
<p style="font-size:0.
|
|
1902
|
+
<div style="font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;"><%= br.name %></div>
|
|
1903
|
+
<p style="font-size:0.82rem;color:var(--text-secondary);line-height:1.6;font-weight:300;"><%= br.definition %></p>
|
|
1155
1904
|
<% if (br.enforcement && br.enforcement.length > 0) { %>
|
|
1156
|
-
<div style="display:flex;gap:0.
|
|
1905
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;">
|
|
1157
1906
|
<% for (var e of br.enforcement) { %><span class="tag tag-green"><%= e %></span><% } %>
|
|
1158
1907
|
</div>
|
|
1159
1908
|
<% } %>
|
|
1160
1909
|
<% if (br.avoid && br.avoid.length > 0) { %>
|
|
1161
|
-
<div style="display:flex;gap:0.
|
|
1910
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.3rem;flex-wrap:wrap;">
|
|
1162
1911
|
<% for (var a of br.avoid) { %><span class="tag tag-red"><%= a %></span><% } %>
|
|
1163
1912
|
</div>
|
|
1164
1913
|
<% } %>
|
|
@@ -1169,7 +1918,7 @@ ${NAV}
|
|
|
1169
1918
|
<% } %>
|
|
1170
1919
|
|
|
1171
1920
|
<% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>
|
|
1172
|
-
<div class="section
|
|
1921
|
+
<div class="section">
|
|
1173
1922
|
<div class="section-label">Safety</div>
|
|
1174
1923
|
<h2 class="section-title">Guardrail Filters</h2>
|
|
1175
1924
|
<% for (var gf of rules.guardrail_filters) { %>
|
|
@@ -1178,7 +1927,7 @@ ${NAV}
|
|
|
1178
1927
|
<div class="guardrail-filter"><%= gf.filter %></div>
|
|
1179
1928
|
<div class="guardrail-reason"><%= gf.reason %></div>
|
|
1180
1929
|
<% if (gf.tables && gf.tables.length > 0) { %>
|
|
1181
|
-
<div style="display:flex;gap:0.
|
|
1930
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.4rem;">
|
|
1182
1931
|
<% for (var tb of gf.tables) { %><span class="tag"><%= tb %></span><% } %>
|
|
1183
1932
|
</div>
|
|
1184
1933
|
<% } %>
|
|
@@ -1188,18 +1937,18 @@ ${NAV}
|
|
|
1188
1937
|
<% } %>
|
|
1189
1938
|
|
|
1190
1939
|
<% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>
|
|
1191
|
-
<div class="section
|
|
1940
|
+
<div class="section">
|
|
1192
1941
|
<div class="section-label">Drill Paths</div>
|
|
1193
1942
|
<h2 class="section-title">Hierarchies</h2>
|
|
1194
|
-
<div style="display:flex;flex-direction:column;gap:
|
|
1943
|
+
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
|
1195
1944
|
<% for (var h of rules.hierarchies) { %>
|
|
1196
1945
|
<div class="card">
|
|
1197
|
-
<div style="font-family:var(--mono);font-size:0.
|
|
1198
|
-
<div style="font-size:0.
|
|
1199
|
-
<div style="display:flex;align-items:center;gap:0.
|
|
1946
|
+
<div style="font-family:var(--mono);font-size:0.82rem;color:var(--accent-light);margin-bottom:0.3rem;font-weight:500;"><%= h.name %></div>
|
|
1947
|
+
<div style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:0.3rem;">Dataset: <span class="mono"><%= h.dataset %></span></div>
|
|
1948
|
+
<div style="display:flex;align-items:center;gap:0.4rem;flex-wrap:wrap;">
|
|
1200
1949
|
<% for (var li = 0; li < h.levels.length; li++) { %>
|
|
1201
1950
|
<span class="tag tag-blue"><%= h.levels[li] %></span>
|
|
1202
|
-
<% if (li < h.levels.length - 1) { %><span style="color:var(--
|
|
1951
|
+
<% if (li < h.levels.length - 1) { %><span style="color:var(--text-dim);">→</span><% } %>
|
|
1203
1952
|
<% } %>
|
|
1204
1953
|
</div>
|
|
1205
1954
|
</div>
|
|
@@ -1209,9 +1958,9 @@ ${NAV}
|
|
|
1209
1958
|
<% } %>
|
|
1210
1959
|
|
|
1211
1960
|
<% if (!rules || ((!rules.golden_queries || rules.golden_queries.length === 0) && (!rules.business_rules || rules.business_rules.length === 0) && (!rules.guardrail_filters || rules.guardrail_filters.length === 0) && (!rules.hierarchies || rules.hierarchies.length === 0))) { %>
|
|
1212
|
-
<p style="color:var(--text-
|
|
1961
|
+
<p style="color:var(--text-secondary);">No rules or queries defined for this model.</p>
|
|
1213
1962
|
<% } %>
|
|
1214
|
-
</
|
|
1963
|
+
</div>
|
|
1215
1964
|
|
|
1216
1965
|
${FOOTER}
|
|
1217
1966
|
${SCRIPTS}
|
|
@@ -1221,47 +1970,90 @@ ${SCRIPTS}
|
|
|
1221
1970
|
// src/templates/glossary.ts
|
|
1222
1971
|
var glossaryTemplate = `${HEAD}
|
|
1223
1972
|
<body>
|
|
1224
|
-
${
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1973
|
+
${TOPBAR}
|
|
1974
|
+
${SIDEBAR_DATA}
|
|
1975
|
+
${SIDEBAR}
|
|
1976
|
+
|
|
1977
|
+
<div class="main">
|
|
1978
|
+
<div class="page-header">
|
|
1979
|
+
<h1>Glossary</h1>
|
|
1980
|
+
<p class="subtitle">Business term definitions and mappings.</p>
|
|
1981
|
+
</div>
|
|
1229
1982
|
|
|
1230
1983
|
<% var termIds = Object.keys(terms).sort(); %>
|
|
1231
1984
|
<% if (termIds.length === 0) { %>
|
|
1232
|
-
<p style="color:var(--text-
|
|
1985
|
+
<p style="color:var(--text-secondary);">No terms defined.</p>
|
|
1233
1986
|
<% } else { %>
|
|
1234
1987
|
<input type="text" id="glossary-filter"
|
|
1235
1988
|
placeholder="Filter terms..."
|
|
1236
1989
|
class="search-input"
|
|
1237
|
-
style="margin-bottom:
|
|
1990
|
+
style="margin-bottom:1.5rem;max-width:360px;" />
|
|
1238
1991
|
|
|
1239
1992
|
<div class="glossary-grid" id="glossary-grid">
|
|
1240
1993
|
<% for (var tid of termIds) { %>
|
|
1241
1994
|
<% var term = terms[tid]; %>
|
|
1242
|
-
<div class="glossary-card" id="term-<%= tid %>" data-term="<%= tid %>">
|
|
1995
|
+
<div class="glossary-card card" id="term-<%= tid %>" data-term="<%= tid %>">
|
|
1243
1996
|
<div class="glossary-term"><%= tid %></div>
|
|
1244
|
-
<div class="glossary-def"
|
|
1245
|
-
|
|
1246
|
-
|
|
1997
|
+
<div class="glossary-def">
|
|
1998
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
1999
|
+
<span class="editable" data-file="context/glossary/<%= tid %>.term.yaml" data-path="definition" data-label="<%= tid %> definition"><%= term.definition || 'Add definition' %></span>
|
|
2000
|
+
<% } else { %>
|
|
2001
|
+
<%= term.definition || '' %>
|
|
2002
|
+
<% } %>
|
|
2003
|
+
</div>
|
|
2004
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
2005
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;">
|
|
2006
|
+
<span class="editable" data-file="context/glossary/<%= tid %>.term.yaml" data-path="synonyms" data-label="<%= tid %> synonyms" style="font-size:0.78rem;color:var(--text-dim);"><%= (term.synonyms && term.synonyms.length > 0) ? term.synonyms.join(', ') : 'Add synonyms (comma-separated)' %></span>
|
|
2007
|
+
</div>
|
|
2008
|
+
<% } else if (term.synonyms && term.synonyms.length > 0) { %>
|
|
2009
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.5rem;flex-wrap:wrap;">
|
|
1247
2010
|
<% for (var s of term.synonyms) { %><span class="tag"><%= s %></span><% } %>
|
|
1248
2011
|
</div>
|
|
1249
2012
|
<% } %>
|
|
1250
2013
|
<% if (term.maps_to && term.maps_to.length > 0) { %>
|
|
1251
|
-
<div style="display:flex;gap:0.
|
|
2014
|
+
<div style="display:flex;gap:0.3rem;margin-top:0.35rem;flex-wrap:wrap;">
|
|
1252
2015
|
<% for (var m of term.maps_to) { %><span class="tag tag-blue"><%= m %></span><% } %>
|
|
1253
2016
|
</div>
|
|
1254
2017
|
<% } %>
|
|
1255
2018
|
<% if (term.owner) { %>
|
|
1256
|
-
<div style="font-size:0.
|
|
2019
|
+
<div style="font-size:0.72rem;color:var(--text-dim);margin-top:0.4rem;">
|
|
1257
2020
|
Owner: <a href="<%= basePath %>/owners/<%= term.owner %>.html"><%= term.owner %></a>
|
|
1258
2021
|
</div>
|
|
1259
2022
|
<% } %>
|
|
1260
2023
|
</div>
|
|
1261
2024
|
<% } %>
|
|
1262
2025
|
</div>
|
|
2026
|
+
|
|
2027
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
2028
|
+
<div id="add-term-container" style="margin-top:1.5rem;">
|
|
2029
|
+
<button class="studio-add-btn" id="add-term-btn" onclick="document.getElementById('add-term-form').style.display='block';this.style.display='none';">+ Add Term</button>
|
|
2030
|
+
<div id="add-term-form" class="card" style="display:none;padding:1.25rem;margin-top:0.5rem;">
|
|
2031
|
+
<div style="font-size:0.85rem;color:#c9a55a;margin-bottom:1rem;font-weight:500;">New Glossary Term</div>
|
|
2032
|
+
<div style="margin-bottom:0.75rem;">
|
|
2033
|
+
<label style="display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;">Term ID (slug)</label>
|
|
2034
|
+
<input type="text" id="new-term-id" placeholder="e.g. churn_rate" style="width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;" />
|
|
2035
|
+
</div>
|
|
2036
|
+
<div style="margin-bottom:0.75rem;">
|
|
2037
|
+
<label style="display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;">Definition</label>
|
|
2038
|
+
<input type="text" id="new-term-def" placeholder="What this term means" style="width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;" />
|
|
2039
|
+
</div>
|
|
2040
|
+
<div style="margin-bottom:0.75rem;">
|
|
2041
|
+
<label style="display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;">Synonyms (comma-separated)</label>
|
|
2042
|
+
<input type="text" id="new-term-synonyms" placeholder="e.g. attrition, turnover" style="width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;" />
|
|
2043
|
+
</div>
|
|
2044
|
+
<div style="margin-bottom:0.75rem;">
|
|
2045
|
+
<label style="display:block;font-size:0.75rem;color:var(--text-dim);margin-bottom:0.25rem;">Tags (comma-separated)</label>
|
|
2046
|
+
<input type="text" id="new-term-tags" placeholder="e.g. finance, metrics" style="width:100%;padding:6px 10px;background:var(--bg-card);border:1px solid #333;border-radius:6px;color:var(--text-primary);font-size:0.85rem;" />
|
|
2047
|
+
</div>
|
|
2048
|
+
<div style="display:flex;gap:0.5rem;justify-content:flex-end;">
|
|
2049
|
+
<button onclick="document.getElementById('add-term-form').style.display='none';document.getElementById('add-term-btn').style.display='';" style="background:none;border:1px solid #555;color:var(--text-secondary);border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;">Cancel</button>
|
|
2050
|
+
<button onclick="submitNewTerm()" style="background:#c9a55a;border:none;color:#000;border-radius:6px;padding:6px 14px;cursor:pointer;font-size:0.82rem;font-weight:500;">Stage Term</button>
|
|
2051
|
+
</div>
|
|
2052
|
+
</div>
|
|
2053
|
+
</div>
|
|
2054
|
+
<% } %>
|
|
1263
2055
|
<% } %>
|
|
1264
|
-
</
|
|
2056
|
+
</div>
|
|
1265
2057
|
|
|
1266
2058
|
${FOOTER}
|
|
1267
2059
|
${SCRIPTS}
|
|
@@ -1279,48 +2071,96 @@ ${SCRIPTS}
|
|
|
1279
2071
|
});
|
|
1280
2072
|
})();
|
|
1281
2073
|
</script>
|
|
2074
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
2075
|
+
<script>
|
|
2076
|
+
function submitNewTerm() {
|
|
2077
|
+
var id = document.getElementById('new-term-id').value.trim();
|
|
2078
|
+
var def = document.getElementById('new-term-def').value.trim();
|
|
2079
|
+
var syns = document.getElementById('new-term-synonyms').value.trim();
|
|
2080
|
+
var tags = document.getElementById('new-term-tags').value.trim();
|
|
2081
|
+
if (!id) { alert('Term ID is required.'); return; }
|
|
2082
|
+
var file = 'context/glossary/' + id + '.term.yaml';
|
|
2083
|
+
if (def) stageEdit(file, 'definition', def, id + ' definition');
|
|
2084
|
+
if (syns) {
|
|
2085
|
+
var synList = syns.split(',').map(function(s){ return s.trim(); }).filter(Boolean);
|
|
2086
|
+
stageEdit(file, 'synonyms', synList, id + ' synonyms');
|
|
2087
|
+
}
|
|
2088
|
+
if (tags) {
|
|
2089
|
+
var tagList = tags.split(',').map(function(t){ return t.trim(); }).filter(Boolean);
|
|
2090
|
+
stageEdit(file, 'tags', tagList, id + ' tags');
|
|
2091
|
+
}
|
|
2092
|
+
document.getElementById('add-term-form').style.display = 'none';
|
|
2093
|
+
document.getElementById('add-term-btn').style.display = '';
|
|
2094
|
+
document.getElementById('new-term-id').value = '';
|
|
2095
|
+
document.getElementById('new-term-def').value = '';
|
|
2096
|
+
document.getElementById('new-term-synonyms').value = '';
|
|
2097
|
+
document.getElementById('new-term-tags').value = '';
|
|
2098
|
+
}
|
|
2099
|
+
</script>
|
|
2100
|
+
<% } %>
|
|
1282
2101
|
</body>
|
|
1283
2102
|
</html>`;
|
|
1284
2103
|
|
|
1285
2104
|
// src/templates/owner.ts
|
|
1286
2105
|
var ownerTemplate = `${HEAD}
|
|
1287
2106
|
<body>
|
|
1288
|
-
${
|
|
2107
|
+
${TOPBAR}
|
|
2108
|
+
${SIDEBAR_DATA}
|
|
2109
|
+
${SIDEBAR}
|
|
1289
2110
|
${TIER_BADGE}
|
|
1290
2111
|
|
|
1291
|
-
<
|
|
1292
|
-
<
|
|
2112
|
+
<div class="main">
|
|
2113
|
+
<div class="breadcrumb">
|
|
2114
|
+
<a href="<%= basePath %>/index.html">Home</a>
|
|
2115
|
+
<span class="breadcrumb-sep">/</span>
|
|
2116
|
+
<span><%= owner.display_name %></span>
|
|
2117
|
+
</div>
|
|
1293
2118
|
|
|
1294
|
-
<
|
|
2119
|
+
<div class="page-header">
|
|
2120
|
+
<h1>
|
|
2121
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
2122
|
+
<span class="editable" data-file="context/owners/<%= owner.id %>.owner.yaml" data-path="display_name" data-label="<%= owner.id %> display name"><%= owner.display_name || 'Add display name' %></span>
|
|
2123
|
+
<% } else { %>
|
|
2124
|
+
<%= owner.display_name %>
|
|
2125
|
+
<% } %>
|
|
2126
|
+
</h1>
|
|
2127
|
+
</div>
|
|
1295
2128
|
|
|
1296
|
-
<div style="display:flex;gap:1.
|
|
1297
|
-
<% if (
|
|
1298
|
-
|
|
2129
|
+
<div style="display:flex;gap:1.25rem;font-size:0.82rem;color:var(--text-dim);margin-bottom:1.5rem;">
|
|
2130
|
+
<% if (typeof studioMode !== 'undefined' && studioMode) { %>
|
|
2131
|
+
<span>Email: <span class="editable" data-file="context/owners/<%= owner.id %>.owner.yaml" data-path="email" data-label="<%= owner.id %> email" style="color:var(--text-secondary);"><%= owner.email || 'Add email' %></span></span>
|
|
2132
|
+
<span>Team: <span class="editable" data-file="context/owners/<%= owner.id %>.owner.yaml" data-path="team" data-label="<%= owner.id %> team" style="color:var(--text-secondary);"><%= owner.team || 'Add team' %></span></span>
|
|
2133
|
+
<% } else { %>
|
|
2134
|
+
<% if (owner.email) { %><span>Email: <span style="color:var(--text-secondary);"><%= owner.email %></span></span><% } %>
|
|
2135
|
+
<% if (owner.team) { %><span>Team: <span style="color:var(--text-secondary);"><%= owner.team %></span></span><% } %>
|
|
2136
|
+
<% } %>
|
|
1299
2137
|
</div>
|
|
1300
2138
|
|
|
1301
2139
|
<% if (owner.description) { %>
|
|
1302
|
-
<p style="color:var(--text-
|
|
2140
|
+
<p style="color:var(--text-secondary);font-size:0.9rem;margin-bottom:2rem;font-weight:300;max-width:560px;"><%= owner.description %></p>
|
|
1303
2141
|
<% } %>
|
|
1304
2142
|
|
|
1305
2143
|
<% if (governedModels.length > 0) { %>
|
|
1306
|
-
<div class="section
|
|
2144
|
+
<div class="section">
|
|
1307
2145
|
<div class="section-label">Stewardship</div>
|
|
1308
2146
|
<h2 class="section-title">Governed Models</h2>
|
|
1309
|
-
<div class="card-grid
|
|
2147
|
+
<div class="card-grid">
|
|
1310
2148
|
<% for (var gm of governedModels) { %>
|
|
1311
|
-
<a href="<%= basePath %>/models/<%= gm.name %>.html" class="card
|
|
1312
|
-
<div
|
|
1313
|
-
<
|
|
1314
|
-
|
|
2149
|
+
<a href="<%= basePath %>/models/<%= gm.name %>.html" class="card-link">
|
|
2150
|
+
<div class="card">
|
|
2151
|
+
<div style="display:flex;align-items:center;gap:0.4rem;">
|
|
2152
|
+
<span class="mono" style="font-size:0.85rem;color:var(--accent-light);"><%= gm.name %></span>
|
|
2153
|
+
<% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>
|
|
2154
|
+
</div>
|
|
1315
2155
|
</div>
|
|
1316
2156
|
</a>
|
|
1317
2157
|
<% } %>
|
|
1318
2158
|
</div>
|
|
1319
2159
|
</div>
|
|
1320
2160
|
<% } else { %>
|
|
1321
|
-
<p style="color:var(--text-
|
|
2161
|
+
<p style="color:var(--text-secondary);">No models governed by this owner.</p>
|
|
1322
2162
|
<% } %>
|
|
1323
|
-
</
|
|
2163
|
+
</div>
|
|
1324
2164
|
|
|
1325
2165
|
${FOOTER}
|
|
1326
2166
|
${SCRIPTS}
|
|
@@ -1330,18 +2170,22 @@ ${SCRIPTS}
|
|
|
1330
2170
|
// src/templates/search.ts
|
|
1331
2171
|
var searchTemplate = `${HEAD}
|
|
1332
2172
|
<body>
|
|
1333
|
-
${
|
|
2173
|
+
${TOPBAR}
|
|
2174
|
+
${SIDEBAR_DATA}
|
|
2175
|
+
${SIDEBAR}
|
|
1334
2176
|
|
|
1335
|
-
<
|
|
1336
|
-
<
|
|
2177
|
+
<div class="main">
|
|
2178
|
+
<div class="page-header">
|
|
2179
|
+
<h1>Search</h1>
|
|
2180
|
+
</div>
|
|
1337
2181
|
|
|
1338
2182
|
<input type="text" id="search-input"
|
|
1339
|
-
placeholder="Search models, datasets, terms..."
|
|
2183
|
+
placeholder="Search models, datasets, fields, terms..."
|
|
1340
2184
|
class="search-input"
|
|
1341
|
-
style="margin-bottom:
|
|
2185
|
+
style="margin-bottom:1.5rem;" />
|
|
1342
2186
|
|
|
1343
|
-
<div id="search-results" style="display:flex;flex-direction:column;gap:0.
|
|
1344
|
-
</
|
|
2187
|
+
<div id="search-results" style="display:flex;flex-direction:column;gap:0.5rem;"></div>
|
|
2188
|
+
</div>
|
|
1345
2189
|
|
|
1346
2190
|
${FOOTER}
|
|
1347
2191
|
${SCRIPTS}
|
|
@@ -1372,18 +2216,18 @@ ${SCRIPTS}
|
|
|
1372
2216
|
function createResultElement(doc) {
|
|
1373
2217
|
var wrapper = document.createElement('div');
|
|
1374
2218
|
wrapper.className = 'card';
|
|
1375
|
-
wrapper.style.padding = '1rem
|
|
2219
|
+
wrapper.style.padding = '0.75rem 1rem';
|
|
1376
2220
|
|
|
1377
2221
|
var top = document.createElement('div');
|
|
1378
2222
|
top.style.display = 'flex';
|
|
1379
2223
|
top.style.alignItems = 'center';
|
|
1380
|
-
top.style.gap = '0.
|
|
2224
|
+
top.style.gap = '0.4rem';
|
|
1381
2225
|
|
|
1382
2226
|
var link = document.createElement('a');
|
|
1383
2227
|
link.href = doc.url;
|
|
1384
2228
|
link.style.fontFamily = 'var(--mono)';
|
|
1385
|
-
link.style.fontSize = '0.
|
|
1386
|
-
link.style.color = 'var(--
|
|
2229
|
+
link.style.fontSize = '0.82rem';
|
|
2230
|
+
link.style.color = 'var(--accent-light)';
|
|
1387
2231
|
link.textContent = doc.title;
|
|
1388
2232
|
top.appendChild(link);
|
|
1389
2233
|
|
|
@@ -1396,9 +2240,9 @@ ${SCRIPTS}
|
|
|
1396
2240
|
|
|
1397
2241
|
if (doc.description) {
|
|
1398
2242
|
var desc = document.createElement('p');
|
|
1399
|
-
desc.style.fontSize = '0.
|
|
1400
|
-
desc.style.color = 'var(--text-
|
|
1401
|
-
desc.style.marginTop = '0.
|
|
2243
|
+
desc.style.fontSize = '0.78rem';
|
|
2244
|
+
desc.style.color = 'var(--text-secondary)';
|
|
2245
|
+
desc.style.marginTop = '0.2rem';
|
|
1402
2246
|
desc.style.fontWeight = '300';
|
|
1403
2247
|
desc.textContent = doc.description;
|
|
1404
2248
|
wrapper.appendChild(desc);
|
|
@@ -1414,7 +2258,7 @@ ${SCRIPTS}
|
|
|
1414
2258
|
var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
|
|
1415
2259
|
if (hits.length === 0) {
|
|
1416
2260
|
var noResults = document.createElement('p');
|
|
1417
|
-
noResults.style.color = 'var(--text-
|
|
2261
|
+
noResults.style.color = 'var(--text-secondary)';
|
|
1418
2262
|
noResults.textContent = 'No results found.';
|
|
1419
2263
|
resultsContainer.appendChild(noResults);
|
|
1420
2264
|
return;
|
|
@@ -1493,22 +2337,24 @@ function buildSearchIndex(manifest, basePath) {
|
|
|
1493
2337
|
}
|
|
1494
2338
|
|
|
1495
2339
|
// src/generator.ts
|
|
1496
|
-
function generateSite(manifest, config) {
|
|
2340
|
+
function generateSite(manifest, config, options) {
|
|
1497
2341
|
const files = /* @__PURE__ */ new Map();
|
|
1498
2342
|
const siteTitle = config?.title ?? "ContextKit";
|
|
1499
2343
|
const basePath = (config?.base_path ?? "").replace(/\/+$/, "") || ".";
|
|
2344
|
+
const studioMode = options?.studioMode ?? false;
|
|
1500
2345
|
const commonData = {
|
|
1501
2346
|
siteTitle,
|
|
1502
|
-
basePath
|
|
2347
|
+
basePath,
|
|
2348
|
+
studioMode,
|
|
2349
|
+
models: manifest.models,
|
|
2350
|
+
tiers: manifest.tiers
|
|
1503
2351
|
};
|
|
1504
2352
|
files.set(
|
|
1505
2353
|
"index.html",
|
|
1506
2354
|
ejs.render(indexTemplate, {
|
|
1507
2355
|
...commonData,
|
|
1508
2356
|
pageTitle: "Home",
|
|
1509
|
-
models: manifest.models,
|
|
1510
2357
|
governance: manifest.governance,
|
|
1511
|
-
tiers: manifest.tiers,
|
|
1512
2358
|
owners: manifest.owners,
|
|
1513
2359
|
terms: manifest.terms
|
|
1514
2360
|
})
|