@runcontext/site 0.3.3 → 0.3.5
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/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/index.cjs +1194 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -13
- package/dist/index.d.ts +13 -13
- package/dist/index.mjs +1194 -239
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -9
package/dist/index.mjs
CHANGED
|
@@ -3,416 +3,1348 @@ import ejs from "ejs";
|
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
|
|
6
|
-
// src/templates.ts
|
|
6
|
+
// src/templates/shared.ts
|
|
7
7
|
var HEAD = `<!DOCTYPE html>
|
|
8
8
|
<html lang="en">
|
|
9
9
|
<head>
|
|
10
10
|
<meta charset="UTF-8">
|
|
11
11
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
12
12
|
<title><%= pageTitle %> \u2014 <%= siteTitle %></title>
|
|
13
|
-
<
|
|
13
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
|
+
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600;700&family=DM+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
|
|
16
|
+
<style>
|
|
17
|
+
:root {
|
|
18
|
+
--gold: #D4A855;
|
|
19
|
+
--gold-light: #E8C878;
|
|
20
|
+
--gold-dark: #A07D3A;
|
|
21
|
+
--gold-glow: rgba(212, 168, 85, 0.15);
|
|
22
|
+
--bg: #0A0A0C;
|
|
23
|
+
--bg-card: #111114;
|
|
24
|
+
--bg-elevated: #18181C;
|
|
25
|
+
--text: #E8E6E1;
|
|
26
|
+
--text-muted: #8A8880;
|
|
27
|
+
--text-dim: #5A5850;
|
|
28
|
+
--border: #2A2A2E;
|
|
29
|
+
--green: #4ADE80;
|
|
30
|
+
--green-dim: rgba(74, 222, 128, 0.12);
|
|
31
|
+
--blue: #60A5FA;
|
|
32
|
+
--red: #F87171;
|
|
33
|
+
--serif: 'Cormorant Garamond', Georgia, serif;
|
|
34
|
+
--sans: 'DM Sans', system-ui, sans-serif;
|
|
35
|
+
--mono: 'JetBrains Mono', monospace;
|
|
36
|
+
}
|
|
37
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
38
|
+
html { scroll-behavior: smooth; }
|
|
39
|
+
body {
|
|
40
|
+
font-family: var(--sans);
|
|
41
|
+
background: var(--bg);
|
|
42
|
+
color: var(--text);
|
|
43
|
+
line-height: 1.6;
|
|
44
|
+
overflow-x: hidden;
|
|
45
|
+
}
|
|
46
|
+
a { color: var(--gold); text-decoration: none; }
|
|
47
|
+
a:hover { text-decoration: underline; }
|
|
48
|
+
|
|
49
|
+
/* === GRAIN OVERLAY === */
|
|
50
|
+
body::before {
|
|
51
|
+
content: '';
|
|
52
|
+
position: fixed;
|
|
53
|
+
inset: 0;
|
|
54
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
z-index: 9999;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* === LAYOUT === */
|
|
60
|
+
.page { max-width: 1200px; margin: 0 auto; padding: 3rem 2rem 6rem; }
|
|
61
|
+
.section { margin-bottom: 3rem; }
|
|
62
|
+
.section-label {
|
|
63
|
+
font-family: var(--mono);
|
|
64
|
+
font-size: 0.65rem;
|
|
65
|
+
letter-spacing: 0.3em;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
color: var(--gold-dark);
|
|
68
|
+
margin-bottom: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
.section h2, h2.section-title {
|
|
71
|
+
font-family: var(--serif);
|
|
72
|
+
font-weight: 400;
|
|
73
|
+
font-size: clamp(1.6rem, 3vw, 2.4rem);
|
|
74
|
+
letter-spacing: -0.01em;
|
|
75
|
+
margin-bottom: 1rem;
|
|
76
|
+
color: var(--text);
|
|
77
|
+
}
|
|
78
|
+
.section-intro {
|
|
79
|
+
color: var(--text-muted);
|
|
80
|
+
font-size: 1rem;
|
|
81
|
+
max-width: 600px;
|
|
82
|
+
margin-bottom: 2rem;
|
|
83
|
+
font-weight: 300;
|
|
84
|
+
}
|
|
85
|
+
.divider {
|
|
86
|
+
width: 100%;
|
|
87
|
+
height: 1px;
|
|
88
|
+
background: var(--border);
|
|
89
|
+
margin: 0 auto;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* === HERO (index page) === */
|
|
93
|
+
.hero {
|
|
94
|
+
text-align: center;
|
|
95
|
+
padding: 5rem 2rem 4rem;
|
|
96
|
+
position: relative;
|
|
97
|
+
}
|
|
98
|
+
.hero::before {
|
|
99
|
+
content: '';
|
|
100
|
+
position: absolute;
|
|
101
|
+
top: -30%;
|
|
102
|
+
left: 50%;
|
|
103
|
+
transform: translateX(-50%);
|
|
104
|
+
width: 700px;
|
|
105
|
+
height: 700px;
|
|
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);
|
|
115
|
+
margin-bottom: 1.5rem;
|
|
116
|
+
position: relative;
|
|
117
|
+
}
|
|
118
|
+
.hero h1 {
|
|
119
|
+
font-family: var(--serif);
|
|
120
|
+
font-weight: 300;
|
|
121
|
+
font-size: clamp(2.5rem, 6vw, 5rem);
|
|
122
|
+
line-height: 1.1;
|
|
123
|
+
letter-spacing: -0.02em;
|
|
124
|
+
color: var(--text);
|
|
125
|
+
position: relative;
|
|
126
|
+
}
|
|
127
|
+
.hero h1 em { font-style: italic; color: var(--gold); }
|
|
128
|
+
.hero-sub {
|
|
129
|
+
font-size: 1.05rem;
|
|
130
|
+
color: var(--text-muted);
|
|
131
|
+
max-width: 520px;
|
|
132
|
+
margin: 1.5rem auto 0;
|
|
133
|
+
font-weight: 300;
|
|
134
|
+
position: relative;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* === STATS === */
|
|
138
|
+
.stats {
|
|
139
|
+
display: grid;
|
|
140
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
141
|
+
gap: 1px;
|
|
142
|
+
background: var(--border);
|
|
143
|
+
border: 1px solid var(--border);
|
|
144
|
+
border-radius: 12px;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
margin-bottom: 3rem;
|
|
147
|
+
}
|
|
148
|
+
.stat {
|
|
149
|
+
background: var(--bg-card);
|
|
150
|
+
padding: 1.5rem;
|
|
151
|
+
text-align: center;
|
|
152
|
+
}
|
|
153
|
+
.stat-value {
|
|
154
|
+
font-family: var(--serif);
|
|
155
|
+
font-size: 2.4rem;
|
|
156
|
+
font-weight: 300;
|
|
157
|
+
color: var(--gold);
|
|
158
|
+
line-height: 1;
|
|
159
|
+
}
|
|
160
|
+
.stat-label {
|
|
161
|
+
font-size: 0.7rem;
|
|
162
|
+
color: var(--text-muted);
|
|
163
|
+
margin-top: 0.4rem;
|
|
164
|
+
text-transform: uppercase;
|
|
165
|
+
letter-spacing: 0.1em;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* === CARDS === */
|
|
169
|
+
.card {
|
|
170
|
+
border: 1px solid var(--border);
|
|
171
|
+
border-radius: 12px;
|
|
172
|
+
padding: 1.5rem;
|
|
173
|
+
background: var(--bg-card);
|
|
174
|
+
transition: border-color 0.3s, box-shadow 0.3s;
|
|
175
|
+
}
|
|
176
|
+
.card:hover {
|
|
177
|
+
border-color: var(--gold-dark);
|
|
178
|
+
box-shadow: 0 0 40px rgba(212,168,85,0.06);
|
|
179
|
+
}
|
|
180
|
+
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1.5rem; }
|
|
181
|
+
.card-grid-sm { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
|
|
182
|
+
|
|
183
|
+
/* === TAGS === */
|
|
184
|
+
.tag {
|
|
185
|
+
display: inline-block;
|
|
186
|
+
font-family: var(--mono);
|
|
187
|
+
font-size: 0.6rem;
|
|
188
|
+
letter-spacing: 0.05em;
|
|
189
|
+
text-transform: uppercase;
|
|
190
|
+
padding: 0.2rem 0.55rem;
|
|
191
|
+
border-radius: 4px;
|
|
192
|
+
background: var(--bg);
|
|
193
|
+
border: 1px solid var(--border);
|
|
194
|
+
color: var(--text-muted);
|
|
195
|
+
}
|
|
196
|
+
.tag-gold { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }
|
|
197
|
+
.tag-silver { color: #C0C0C0; border-color: rgba(192,192,192,0.3); background: rgba(192,192,192,0.08); }
|
|
198
|
+
.tag-bronze { color: #CD7F32; border-color: rgba(205,127,50,0.3); background: rgba(205,127,50,0.08); }
|
|
199
|
+
.tag-none { color: var(--text-dim); border-color: var(--border); }
|
|
200
|
+
.tag-green { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }
|
|
201
|
+
.tag-red { color: var(--red); border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.08); }
|
|
202
|
+
.tag-blue { color: var(--blue); border-color: rgba(96,165,250,0.3); background: rgba(96,165,250,0.08); }
|
|
203
|
+
.tag-nav {
|
|
204
|
+
padding: 0.35rem 0.75rem;
|
|
205
|
+
font-size: 0.65rem;
|
|
206
|
+
border-radius: 6px;
|
|
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 {
|
|
215
|
+
font-family: var(--mono);
|
|
216
|
+
font-size: 0.65rem;
|
|
217
|
+
letter-spacing: 0.15em;
|
|
218
|
+
text-transform: uppercase;
|
|
219
|
+
color: var(--text-dim);
|
|
220
|
+
text-align: left;
|
|
221
|
+
padding: 0.75rem 1rem;
|
|
222
|
+
border-bottom: 1px solid var(--border);
|
|
223
|
+
}
|
|
224
|
+
.table-dark td {
|
|
225
|
+
padding: 0.75rem 1rem;
|
|
226
|
+
border-bottom: 1px solid rgba(42,42,46,0.5);
|
|
227
|
+
font-size: 0.85rem;
|
|
228
|
+
vertical-align: top;
|
|
229
|
+
}
|
|
230
|
+
.table-dark tr:hover td { background: rgba(212,168,85,0.02); }
|
|
231
|
+
.mono { font-family: var(--mono); font-size: 0.82rem; }
|
|
232
|
+
|
|
233
|
+
/* === SEMANTIC ROLE PILLS === */
|
|
234
|
+
.role-identifier { color: var(--gold); border-color: rgba(212,168,85,0.3); background: rgba(212,168,85,0.08); }
|
|
235
|
+
.role-metric { color: #06B6D4; border-color: rgba(6,182,212,0.3); background: rgba(6,182,212,0.08); }
|
|
236
|
+
.role-dimension { color: #818CF8; border-color: rgba(129,140,248,0.3); background: rgba(129,140,248,0.08); }
|
|
237
|
+
.role-date { color: var(--green); border-color: rgba(74,222,128,0.3); background: var(--green-dim); }
|
|
238
|
+
.role-attribute { color: var(--text-muted); border-color: var(--border); }
|
|
239
|
+
|
|
240
|
+
/* === DS TYPE PILLS === */
|
|
241
|
+
.ds-type {
|
|
242
|
+
font-family: var(--mono);
|
|
243
|
+
font-size: 0.65rem;
|
|
244
|
+
text-transform: uppercase;
|
|
245
|
+
letter-spacing: 0.08em;
|
|
246
|
+
padding: 0.15rem 0.45rem;
|
|
247
|
+
border-radius: 4px;
|
|
248
|
+
display: inline-block;
|
|
249
|
+
}
|
|
250
|
+
.ds-type-fact { color: #818CF8; background: rgba(129,140,248,0.1); }
|
|
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); }
|
|
254
|
+
|
|
255
|
+
/* === METRIC CARDS === */
|
|
256
|
+
.metric-name {
|
|
257
|
+
font-family: var(--mono);
|
|
258
|
+
font-size: 0.9rem;
|
|
259
|
+
color: var(--gold-light);
|
|
260
|
+
margin-bottom: 0.5rem;
|
|
261
|
+
}
|
|
262
|
+
.metric-desc {
|
|
263
|
+
color: var(--text-muted);
|
|
264
|
+
font-size: 0.85rem;
|
|
265
|
+
line-height: 1.6;
|
|
266
|
+
margin-bottom: 1rem;
|
|
267
|
+
}
|
|
268
|
+
.metric-formula {
|
|
269
|
+
font-family: var(--mono);
|
|
270
|
+
font-size: 0.72rem;
|
|
271
|
+
color: var(--text-dim);
|
|
272
|
+
background: var(--bg);
|
|
273
|
+
border: 1px solid var(--border);
|
|
274
|
+
border-radius: 6px;
|
|
275
|
+
padding: 0.6rem 0.8rem;
|
|
276
|
+
line-height: 1.5;
|
|
277
|
+
overflow-x: auto;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* === QUERY CARDS === */
|
|
281
|
+
.query-card {
|
|
282
|
+
border: 1px solid var(--border);
|
|
283
|
+
border-radius: 12px;
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
margin-bottom: 1.5rem;
|
|
286
|
+
background: var(--bg-card);
|
|
287
|
+
transition: border-color 0.3s;
|
|
288
|
+
}
|
|
289
|
+
.query-card:hover { border-color: var(--gold-dark); }
|
|
290
|
+
.query-question {
|
|
291
|
+
padding: 1.25rem 1.5rem;
|
|
292
|
+
font-family: var(--serif);
|
|
293
|
+
font-size: 1.1rem;
|
|
294
|
+
font-weight: 400;
|
|
295
|
+
font-style: italic;
|
|
296
|
+
color: var(--text);
|
|
297
|
+
border-bottom: 1px solid var(--border);
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: flex-start;
|
|
300
|
+
gap: 0.6rem;
|
|
301
|
+
}
|
|
302
|
+
.query-question::before {
|
|
303
|
+
content: 'Q';
|
|
304
|
+
font-family: var(--mono);
|
|
305
|
+
font-size: 0.6rem;
|
|
306
|
+
font-style: normal;
|
|
307
|
+
letter-spacing: 0.1em;
|
|
308
|
+
color: var(--gold);
|
|
309
|
+
background: rgba(212,168,85,0.1);
|
|
310
|
+
border: 1px solid rgba(212,168,85,0.2);
|
|
311
|
+
padding: 0.15rem 0.35rem;
|
|
312
|
+
border-radius: 3px;
|
|
313
|
+
flex-shrink: 0;
|
|
314
|
+
margin-top: 0.3rem;
|
|
315
|
+
}
|
|
316
|
+
.query-sql {
|
|
317
|
+
padding: 1rem 1.5rem;
|
|
318
|
+
font-family: var(--mono);
|
|
319
|
+
font-size: 0.78rem;
|
|
320
|
+
color: var(--text-muted);
|
|
321
|
+
line-height: 1.6;
|
|
322
|
+
background: var(--bg);
|
|
323
|
+
overflow-x: auto;
|
|
324
|
+
white-space: pre-wrap;
|
|
325
|
+
}
|
|
326
|
+
.query-meta {
|
|
327
|
+
padding: 0.6rem 1.5rem;
|
|
328
|
+
display: flex;
|
|
329
|
+
gap: 1rem;
|
|
330
|
+
font-size: 0.7rem;
|
|
331
|
+
color: var(--text-dim);
|
|
332
|
+
border-top: 1px solid var(--border);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* === GUARDRAILS === */
|
|
336
|
+
.guardrail {
|
|
337
|
+
border: 1px solid rgba(248, 113, 113, 0.2);
|
|
338
|
+
border-radius: 12px;
|
|
339
|
+
padding: 1.5rem;
|
|
340
|
+
background: rgba(248, 113, 113, 0.03);
|
|
341
|
+
margin-bottom: 1rem;
|
|
342
|
+
}
|
|
343
|
+
.guardrail-name {
|
|
344
|
+
font-family: var(--mono);
|
|
345
|
+
font-size: 0.82rem;
|
|
346
|
+
color: var(--red);
|
|
347
|
+
margin-bottom: 0.4rem;
|
|
348
|
+
}
|
|
349
|
+
.guardrail-filter {
|
|
350
|
+
font-family: var(--mono);
|
|
351
|
+
font-size: 0.78rem;
|
|
352
|
+
color: var(--text);
|
|
353
|
+
background: var(--bg);
|
|
354
|
+
padding: 0.4rem 0.65rem;
|
|
355
|
+
border-radius: 4px;
|
|
356
|
+
border: 1px solid var(--border);
|
|
357
|
+
display: inline-block;
|
|
358
|
+
margin-bottom: 0.6rem;
|
|
359
|
+
}
|
|
360
|
+
.guardrail-reason {
|
|
361
|
+
font-size: 0.82rem;
|
|
362
|
+
color: var(--text-muted);
|
|
363
|
+
font-weight: 300;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* === LINEAGE === */
|
|
367
|
+
.lineage-flow {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: stretch;
|
|
370
|
+
gap: 0;
|
|
371
|
+
overflow-x: auto;
|
|
372
|
+
padding: 1.5rem 0;
|
|
373
|
+
}
|
|
374
|
+
.lineage-col {
|
|
375
|
+
display: flex;
|
|
376
|
+
flex-direction: column;
|
|
377
|
+
gap: 0.75rem;
|
|
378
|
+
min-width: 220px;
|
|
379
|
+
}
|
|
380
|
+
.lineage-col-label {
|
|
381
|
+
font-family: var(--mono);
|
|
382
|
+
font-size: 0.6rem;
|
|
383
|
+
letter-spacing: 0.2em;
|
|
384
|
+
text-transform: uppercase;
|
|
385
|
+
color: var(--text-dim);
|
|
386
|
+
margin-bottom: 0.25rem;
|
|
387
|
+
padding-left: 0.5rem;
|
|
388
|
+
}
|
|
389
|
+
.lineage-node {
|
|
390
|
+
border: 1px solid var(--border);
|
|
391
|
+
border-radius: 8px;
|
|
392
|
+
padding: 0.75rem 1rem;
|
|
393
|
+
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
|
+
}
|
|
407
|
+
.lineage-arrow {
|
|
408
|
+
display: flex;
|
|
409
|
+
align-items: center;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
min-width: 50px;
|
|
412
|
+
color: var(--gold-dark);
|
|
413
|
+
font-size: 1.3rem;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* === SCORECARD === */
|
|
417
|
+
.scorecard {
|
|
418
|
+
display: grid;
|
|
419
|
+
grid-template-columns: repeat(3, 1fr);
|
|
420
|
+
gap: 1px;
|
|
421
|
+
background: var(--border);
|
|
422
|
+
border: 1px solid var(--border);
|
|
423
|
+
border-radius: 12px;
|
|
424
|
+
overflow: hidden;
|
|
425
|
+
}
|
|
426
|
+
@media (max-width: 768px) { .scorecard { grid-template-columns: 1fr; } }
|
|
427
|
+
.scorecard-tier {
|
|
428
|
+
background: var(--bg-card);
|
|
429
|
+
padding: 1.5rem;
|
|
430
|
+
}
|
|
431
|
+
.scorecard-tier-header {
|
|
432
|
+
display: flex;
|
|
433
|
+
align-items: center;
|
|
434
|
+
justify-content: space-between;
|
|
435
|
+
margin-bottom: 1rem;
|
|
436
|
+
}
|
|
437
|
+
.scorecard-tier-name {
|
|
438
|
+
font-family: var(--serif);
|
|
439
|
+
font-size: 1.3rem;
|
|
440
|
+
font-weight: 500;
|
|
441
|
+
text-transform: capitalize;
|
|
442
|
+
}
|
|
443
|
+
.scorecard-tier-name.bronze { color: #CD7F32; }
|
|
444
|
+
.scorecard-tier-name.silver { color: #C0C0C0; }
|
|
445
|
+
.scorecard-tier-name.gold { color: var(--gold); }
|
|
446
|
+
.scorecard-pass {
|
|
447
|
+
font-family: var(--mono);
|
|
448
|
+
font-size: 0.6rem;
|
|
449
|
+
letter-spacing: 0.1em;
|
|
450
|
+
text-transform: uppercase;
|
|
451
|
+
padding: 0.15rem 0.5rem;
|
|
452
|
+
border-radius: 4px;
|
|
453
|
+
}
|
|
454
|
+
.scorecard-pass.passed { color: var(--green); background: var(--green-dim); }
|
|
455
|
+
.scorecard-pass.failed { color: var(--red); background: rgba(248,113,113,0.1); }
|
|
456
|
+
.check-list { list-style: none; }
|
|
457
|
+
.check-item {
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: flex-start;
|
|
460
|
+
gap: 0.4rem;
|
|
461
|
+
padding: 0.3rem 0;
|
|
462
|
+
font-size: 0.75rem;
|
|
463
|
+
color: var(--text-muted);
|
|
464
|
+
line-height: 1.4;
|
|
465
|
+
}
|
|
466
|
+
.check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.8rem; }
|
|
467
|
+
.check-icon.pass { color: var(--green); }
|
|
468
|
+
.check-icon.fail { color: var(--red); }
|
|
469
|
+
|
|
470
|
+
/* === GLOSSARY === */
|
|
471
|
+
.glossary-grid {
|
|
472
|
+
display: grid;
|
|
473
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
474
|
+
gap: 1.5rem;
|
|
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;
|
|
484
|
+
}
|
|
485
|
+
.glossary-card:hover { border-color: var(--gold-dark); }
|
|
486
|
+
.glossary-card::before {
|
|
487
|
+
content: '';
|
|
488
|
+
position: absolute;
|
|
489
|
+
top: 0; left: 0; right: 0;
|
|
490
|
+
height: 2px;
|
|
491
|
+
background: linear-gradient(90deg, var(--gold-dark), transparent);
|
|
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;
|
|
522
|
+
}
|
|
523
|
+
.expandable-content {
|
|
524
|
+
max-height: 0;
|
|
525
|
+
overflow: hidden;
|
|
526
|
+
transition: max-height 0.3s ease;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/* === SEARCH === */
|
|
530
|
+
.search-input {
|
|
531
|
+
width: 100%;
|
|
532
|
+
background: var(--bg-card);
|
|
533
|
+
border: 1px solid var(--border);
|
|
534
|
+
border-radius: 8px;
|
|
535
|
+
padding: 0.8rem 1.2rem;
|
|
536
|
+
font-family: var(--sans);
|
|
537
|
+
font-size: 1rem;
|
|
538
|
+
color: var(--text);
|
|
539
|
+
outline: none;
|
|
540
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
541
|
+
}
|
|
542
|
+
.search-input::placeholder { color: var(--text-dim); }
|
|
543
|
+
.search-input:focus {
|
|
544
|
+
border-color: var(--gold-dark);
|
|
545
|
+
box-shadow: 0 0 0 3px var(--gold-glow);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/* === BACK LINK === */
|
|
549
|
+
.back-link {
|
|
550
|
+
font-size: 0.85rem;
|
|
551
|
+
color: var(--text-muted);
|
|
552
|
+
display: inline-flex;
|
|
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; }
|
|
559
|
+
|
|
560
|
+
/* === GOVERNANCE GRID === */
|
|
561
|
+
.gov-grid {
|
|
562
|
+
display: grid;
|
|
563
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
564
|
+
gap: 1px;
|
|
565
|
+
background: var(--border);
|
|
566
|
+
border: 1px solid var(--border);
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
overflow: hidden;
|
|
569
|
+
}
|
|
570
|
+
.gov-item {
|
|
571
|
+
background: var(--bg-card);
|
|
572
|
+
padding: 1rem 1.25rem;
|
|
573
|
+
}
|
|
574
|
+
.gov-label {
|
|
575
|
+
font-family: var(--mono);
|
|
576
|
+
font-size: 0.6rem;
|
|
577
|
+
letter-spacing: 0.15em;
|
|
578
|
+
text-transform: uppercase;
|
|
579
|
+
color: var(--text-dim);
|
|
580
|
+
margin-bottom: 0.3rem;
|
|
581
|
+
}
|
|
582
|
+
.gov-value {
|
|
583
|
+
font-size: 0.9rem;
|
|
584
|
+
color: var(--text);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/* === ANIMATIONS === */
|
|
588
|
+
@keyframes fadeUp {
|
|
589
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
590
|
+
to { opacity: 1; transform: translateY(0); }
|
|
591
|
+
}
|
|
592
|
+
@keyframes pulse {
|
|
593
|
+
0%, 100% { opacity: 1; }
|
|
594
|
+
50% { opacity: 0.4; }
|
|
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);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/* === SQL HIGHLIGHT === */
|
|
607
|
+
.sql-kw { color: #818CF8; }
|
|
608
|
+
.sql-fn { color: #34D399; }
|
|
609
|
+
.sql-str { color: #FCD34D; }
|
|
610
|
+
.sql-num { color: #FB923C; }
|
|
611
|
+
.sql-cm { color: var(--text-dim); font-style: italic; }
|
|
612
|
+
</style>
|
|
14
613
|
</head>`;
|
|
15
|
-
var NAV = `<nav
|
|
16
|
-
<a href="<%= basePath %>/"
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
614
|
+
var NAV = `<nav style="position:sticky;top:0;z-index:100;background:var(--bg-card);border-bottom:1px solid var(--border);padding:0.75rem 2rem;display:flex;align-items:center;gap:2rem;">
|
|
615
|
+
<a href="<%= basePath %>/index.html" style="font-family:var(--serif);font-size:1.25rem;font-weight:600;color:var(--gold);text-decoration:none;"><%- siteTitle %></a>
|
|
616
|
+
<div style="display:flex;gap:1.5rem;font-size:0.82rem;">
|
|
617
|
+
<a href="<%= basePath %>/index.html" style="color:var(--text-muted);text-decoration:none;">Models</a>
|
|
618
|
+
<a href="<%= basePath %>/glossary.html" style="color:var(--text-muted);text-decoration:none;">Glossary</a>
|
|
619
|
+
<a href="<%= basePath %>/search.html" style="color:var(--text-muted);text-decoration:none;">Search</a>
|
|
620
|
+
</div>
|
|
20
621
|
</nav>`;
|
|
21
|
-
var FOOTER = `<footer
|
|
22
|
-
Generated by <a href="https://github.com/erickittelson/ContextKit"
|
|
622
|
+
var FOOTER = `<footer style="text-align:center;padding:3rem 2rem;color:var(--text-dim);font-size:0.78rem;border-top:1px solid var(--border);margin-top:4rem;">
|
|
623
|
+
Generated by <a href="https://github.com/erickittelson/ContextKit" style="color:var(--gold-dark);">ContextKit</a>
|
|
23
624
|
</footer>`;
|
|
24
625
|
var TIER_BADGE = `<% function tierBadge(tier) {
|
|
25
|
-
var
|
|
26
|
-
var
|
|
27
|
-
return '<span class="
|
|
626
|
+
var cls = { none: 'tag-none', bronze: 'tag-bronze', silver: 'tag-silver', gold: 'tag-gold' };
|
|
627
|
+
var c = cls[tier] || cls.none;
|
|
628
|
+
return '<span class="tag ' + c + '">' + tier + '</span>';
|
|
28
629
|
} %>`;
|
|
630
|
+
var SCRIPTS = `<script>
|
|
631
|
+
(function() {
|
|
632
|
+
// Scroll-reveal observer
|
|
633
|
+
var reveals = document.querySelectorAll('.reveal');
|
|
634
|
+
if (reveals.length > 0 && 'IntersectionObserver' in window) {
|
|
635
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
636
|
+
entries.forEach(function(e) {
|
|
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
|
+
}
|
|
647
|
+
|
|
648
|
+
// Expandable sections
|
|
649
|
+
window.toggleExpand = function(id) {
|
|
650
|
+
var el = document.getElementById(id);
|
|
651
|
+
var icon = document.getElementById(id + '-icon');
|
|
652
|
+
if (!el) return;
|
|
653
|
+
if (el.style.maxHeight && el.style.maxHeight !== '0px') {
|
|
654
|
+
el.style.maxHeight = '0px';
|
|
655
|
+
if (icon) icon.textContent = '+';
|
|
656
|
+
} else {
|
|
657
|
+
el.style.maxHeight = el.scrollHeight + 'px';
|
|
658
|
+
if (icon) icon.textContent = '\\u2212';
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// Simple SQL syntax highlighter \u2014 operates on trusted template-rendered content
|
|
663
|
+
window.highlightSQL = function() {
|
|
664
|
+
var blocks = document.querySelectorAll('.sql-highlight');
|
|
665
|
+
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
|
+
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
|
+
var str = /('(?:[^'\\\\]|\\\\.)*')/g;
|
|
668
|
+
var num = /\\b(\\d+\\.?\\d*)\\b/g;
|
|
669
|
+
|
|
670
|
+
blocks.forEach(function(block) {
|
|
671
|
+
var text = block.textContent || '';
|
|
672
|
+
text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
673
|
+
text = text.replace(str, '<span class="sql-str">$1</span>');
|
|
674
|
+
text = text.replace(kw, '<span class="sql-kw">$&</span>');
|
|
675
|
+
text = text.replace(fn, '<span class="sql-fn">$&</span>');
|
|
676
|
+
block.innerHTML = text;
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
if (document.readyState === 'loading') {
|
|
680
|
+
document.addEventListener('DOMContentLoaded', window.highlightSQL);
|
|
681
|
+
} else {
|
|
682
|
+
window.highlightSQL();
|
|
683
|
+
}
|
|
684
|
+
})();
|
|
685
|
+
</script>`;
|
|
686
|
+
|
|
687
|
+
// src/templates/index.ts
|
|
29
688
|
var indexTemplate = `${HEAD}
|
|
30
|
-
<body
|
|
689
|
+
<body>
|
|
31
690
|
${NAV}
|
|
32
691
|
${TIER_BADGE}
|
|
33
|
-
<main class="max-w-5xl mx-auto p-6">
|
|
34
|
-
<h1 class="text-3xl font-bold mb-6"><%- siteTitle %></h1>
|
|
35
692
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
693
|
+
<section class="hero">
|
|
694
|
+
<div class="hero-eyebrow"><%- siteTitle %></div>
|
|
695
|
+
<h1>Metadata<br><em>Catalog</em></h1>
|
|
696
|
+
<p class="hero-sub">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>
|
|
697
|
+
</section>
|
|
698
|
+
|
|
699
|
+
<div class="divider"></div>
|
|
700
|
+
|
|
701
|
+
<main class="page">
|
|
702
|
+
<%
|
|
703
|
+
var modelNames = Object.keys(models);
|
|
704
|
+
var totalDatasets = 0;
|
|
705
|
+
var totalFields = 0;
|
|
706
|
+
for (var mn of modelNames) {
|
|
707
|
+
var m = models[mn];
|
|
708
|
+
if (m.datasets) {
|
|
709
|
+
totalDatasets += m.datasets.length;
|
|
710
|
+
for (var ds of m.datasets) {
|
|
711
|
+
if (ds.fields) totalFields += ds.fields.length;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
var totalTerms = Object.keys(terms).length;
|
|
716
|
+
var totalOwners = Object.keys(owners).length;
|
|
717
|
+
%>
|
|
718
|
+
|
|
719
|
+
<div class="stats reveal">
|
|
720
|
+
<div class="stat">
|
|
721
|
+
<div class="stat-value"><%= modelNames.length %></div>
|
|
722
|
+
<div class="stat-label">Models</div>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="stat">
|
|
725
|
+
<div class="stat-value"><%= totalDatasets %></div>
|
|
726
|
+
<div class="stat-label">Datasets</div>
|
|
727
|
+
</div>
|
|
728
|
+
<div class="stat">
|
|
729
|
+
<div class="stat-value"><%= totalFields %></div>
|
|
730
|
+
<div class="stat-label">Fields</div>
|
|
731
|
+
</div>
|
|
732
|
+
<div class="stat">
|
|
733
|
+
<div class="stat-value"><%= totalTerms %></div>
|
|
734
|
+
<div class="stat-label">Terms</div>
|
|
735
|
+
</div>
|
|
736
|
+
<div class="stat">
|
|
737
|
+
<div class="stat-value"><%= totalOwners %></div>
|
|
738
|
+
<div class="stat-label">Owners</div>
|
|
739
|
+
</div>
|
|
740
|
+
</div>
|
|
741
|
+
|
|
742
|
+
<div class="section reveal">
|
|
743
|
+
<div class="section-label">Semantic Models</div>
|
|
744
|
+
<h2 class="section-title">Models</h2>
|
|
745
|
+
<% if (modelNames.length === 0) { %>
|
|
746
|
+
<p style="color:var(--text-muted);">No models found.</p>
|
|
40
747
|
<% } else { %>
|
|
41
|
-
<div class="grid
|
|
42
|
-
<% for (var name of
|
|
43
|
-
<div class="
|
|
44
|
-
<div
|
|
45
|
-
<a href="<%= basePath %>/models/<%= name %>.html"
|
|
748
|
+
<div class="card-grid">
|
|
749
|
+
<% for (var name of modelNames) { %>
|
|
750
|
+
<div class="card">
|
|
751
|
+
<div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.6rem;">
|
|
752
|
+
<a href="<%= basePath %>/models/<%= name %>.html" style="font-family:var(--mono);font-size:0.95rem;color:var(--gold-light);text-decoration:none;">
|
|
753
|
+
<%= name %>
|
|
754
|
+
</a>
|
|
46
755
|
<% if (tiers[name]) { %><%- tierBadge(tiers[name].tier) %><% } %>
|
|
47
756
|
</div>
|
|
48
757
|
<% if (models[name].description) { %>
|
|
49
|
-
<p
|
|
758
|
+
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:0.75rem;font-weight:300;"><%= models[name].description %></p>
|
|
50
759
|
<% } %>
|
|
51
760
|
<% if (governance[name]) { %>
|
|
52
|
-
<div
|
|
761
|
+
<div style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;">
|
|
53
762
|
<% if (governance[name].owner) { %>
|
|
54
|
-
<
|
|
763
|
+
<a href="<%= basePath %>/owners/<%= governance[name].owner %>.html" class="tag tag-nav" style="text-decoration:none;"><%= governance[name].owner %></a>
|
|
55
764
|
<% } %>
|
|
56
765
|
<% if (governance[name].trust) { %>
|
|
57
|
-
<span
|
|
766
|
+
<span class="tag tag-green"><%= governance[name].trust %></span>
|
|
58
767
|
<% } %>
|
|
768
|
+
<% if (governance[name].tags) { for (var t of governance[name].tags) { %>
|
|
769
|
+
<span class="tag"><%= t %></span>
|
|
770
|
+
<% } } %>
|
|
59
771
|
</div>
|
|
60
772
|
<% } %>
|
|
61
773
|
</div>
|
|
62
774
|
<% } %>
|
|
63
775
|
</div>
|
|
64
776
|
<% } %>
|
|
65
|
-
</
|
|
777
|
+
</div>
|
|
66
778
|
|
|
67
779
|
<% if (Object.keys(owners).length > 0) { %>
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
<
|
|
780
|
+
<div class="section reveal">
|
|
781
|
+
<div class="section-label">Data Stewardship</div>
|
|
782
|
+
<h2 class="section-title">Owners</h2>
|
|
783
|
+
<div class="card-grid-sm">
|
|
71
784
|
<% for (var oid of Object.keys(owners)) { %>
|
|
72
|
-
<
|
|
785
|
+
<a href="<%= basePath %>/owners/<%= oid %>.html" class="card" style="text-decoration:none;">
|
|
786
|
+
<div style="font-size:1rem;font-weight:500;color:var(--text);margin-bottom:0.25rem;"><%= owners[oid].display_name %></div>
|
|
787
|
+
<% if (owners[oid].team) { %>
|
|
788
|
+
<div style="font-size:0.78rem;color:var(--text-dim);"><%= owners[oid].team %></div>
|
|
789
|
+
<% } %>
|
|
790
|
+
</a>
|
|
73
791
|
<% } %>
|
|
74
|
-
</
|
|
75
|
-
</
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
76
794
|
<% } %>
|
|
77
795
|
</main>
|
|
796
|
+
|
|
78
797
|
${FOOTER}
|
|
798
|
+
${SCRIPTS}
|
|
79
799
|
</body>
|
|
80
800
|
</html>`;
|
|
801
|
+
|
|
802
|
+
// src/templates/model.ts
|
|
81
803
|
var modelTemplate = `${HEAD}
|
|
82
|
-
<body
|
|
804
|
+
<body>
|
|
83
805
|
${NAV}
|
|
84
806
|
${TIER_BADGE}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
807
|
+
|
|
808
|
+
<main class="page">
|
|
809
|
+
<a href="<%= basePath %>/" class="back-link">← All Models</a>
|
|
810
|
+
|
|
811
|
+
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem;">
|
|
812
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);"><%= model.name %></h1>
|
|
88
813
|
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
89
814
|
</div>
|
|
90
815
|
<% if (model.description) { %>
|
|
91
|
-
<p
|
|
816
|
+
<p style="color:var(--text-muted);font-size:1rem;margin-bottom:1.5rem;max-width:700px;font-weight:300;"><%= model.description %></p>
|
|
92
817
|
<% } %>
|
|
93
818
|
|
|
94
|
-
<div
|
|
95
|
-
<a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="
|
|
96
|
-
<a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="
|
|
819
|
+
<div style="display:flex;gap:0.6rem;margin-bottom:2.5rem;">
|
|
820
|
+
<a href="<%= basePath %>/models/<%= model.name %>/schema.html" class="tag tag-nav tag-blue">Schema Browser</a>
|
|
821
|
+
<a href="<%= basePath %>/models/<%= model.name %>/rules.html" class="tag tag-nav tag-gold">Rules & Queries</a>
|
|
97
822
|
</div>
|
|
98
823
|
|
|
99
824
|
<% if (gov) { %>
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
825
|
+
<div class="section reveal">
|
|
826
|
+
<div class="section-label">Governance</div>
|
|
827
|
+
<div class="gov-grid">
|
|
828
|
+
<div class="gov-item">
|
|
829
|
+
<div class="gov-label">Owner</div>
|
|
830
|
+
<div class="gov-value"><a href="<%= basePath %>/owners/<%= gov.owner %>.html"><%= gov.owner %></a></div>
|
|
831
|
+
</div>
|
|
832
|
+
<% if (gov.trust) { %>
|
|
833
|
+
<div class="gov-item">
|
|
834
|
+
<div class="gov-label">Trust</div>
|
|
835
|
+
<div class="gov-value"><%= gov.trust %></div>
|
|
836
|
+
</div>
|
|
837
|
+
<% } %>
|
|
838
|
+
<% if (gov.security) { %>
|
|
839
|
+
<div class="gov-item">
|
|
840
|
+
<div class="gov-label">Security</div>
|
|
841
|
+
<div class="gov-value"><%= gov.security %></div>
|
|
842
|
+
</div>
|
|
843
|
+
<% } %>
|
|
844
|
+
<% if (gov.tags && gov.tags.length > 0) { %>
|
|
845
|
+
<div class="gov-item">
|
|
846
|
+
<div class="gov-label">Tags</div>
|
|
847
|
+
<div class="gov-value" style="display:flex;gap:0.4rem;flex-wrap:wrap;">
|
|
848
|
+
<% for (var t of gov.tags) { %><span class="tag"><%= t %></span><% } %>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
<% } %>
|
|
852
|
+
</div>
|
|
853
|
+
</div>
|
|
111
854
|
<% } %>
|
|
112
855
|
|
|
113
856
|
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
114
|
-
<
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<div
|
|
124
|
-
|
|
857
|
+
<div class="section reveal">
|
|
858
|
+
<div class="section-label">Data Explorer</div>
|
|
859
|
+
<h2 class="section-title">Datasets</h2>
|
|
860
|
+
<p style="color:var(--text-muted);font-size:0.85rem;margin-bottom:1.5rem;font-weight:300;">Click a dataset to explore its fields, governance, and metadata.</p>
|
|
861
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
862
|
+
<% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
|
|
863
|
+
<% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
|
|
864
|
+
<div class="card">
|
|
865
|
+
<div class="expandable-header" onclick="toggleExpand('ds-<%= i %>')">
|
|
866
|
+
<div>
|
|
867
|
+
<div style="display:flex;align-items:center;gap:0.6rem;margin-bottom:0.25rem;">
|
|
868
|
+
<span class="mono" style="color:var(--text);"><%= ds.name %></span>
|
|
869
|
+
<% if (dsGov && dsGov.table_type) { %>
|
|
870
|
+
<span class="ds-type ds-type-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
|
|
871
|
+
<% } %>
|
|
872
|
+
<% if (ds.fields) { %><span class="tag"><%= ds.fields.length %> fields</span><% } %>
|
|
873
|
+
</div>
|
|
874
|
+
<div style="font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);">
|
|
875
|
+
<%= ds.source %>
|
|
876
|
+
<% if (dsGov && dsGov.grain) { %> · <span style="color:var(--text-muted);font-family:var(--sans);"><%= dsGov.grain %></span><% } %>
|
|
877
|
+
<% if (dsGov && dsGov.refresh) { %> · <span style="color:var(--text-muted);font-family:var(--sans);"><%= dsGov.refresh %></span><% } %>
|
|
878
|
+
</div>
|
|
879
|
+
<% if (ds.description) { %>
|
|
880
|
+
<p style="font-size:0.82rem;color:var(--text-muted);margin-top:0.4rem;font-weight:300;"><%= ds.description %></p>
|
|
881
|
+
<% } %>
|
|
882
|
+
</div>
|
|
883
|
+
<span class="expand-icon" id="ds-<%= i %>-icon">+</span>
|
|
884
|
+
</div>
|
|
885
|
+
<div class="expandable-content" id="ds-<%= i %>">
|
|
886
|
+
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
887
|
+
<table class="table-dark" style="margin-top:1rem;">
|
|
888
|
+
<thead>
|
|
889
|
+
<tr>
|
|
890
|
+
<th>Field</th>
|
|
891
|
+
<th>Description</th>
|
|
892
|
+
<th>Role</th>
|
|
893
|
+
<th>Aggregation</th>
|
|
894
|
+
</tr>
|
|
895
|
+
</thead>
|
|
896
|
+
<tbody>
|
|
897
|
+
<% for (var field of ds.fields) { %>
|
|
898
|
+
<% var fKey = ds.name + '.' + field.name; %>
|
|
899
|
+
<% var fGov = gov && gov.fields && gov.fields[fKey]; %>
|
|
900
|
+
<% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
|
|
901
|
+
<tr>
|
|
902
|
+
<td class="mono" style="font-size:0.78rem;"><%= field.name %></td>
|
|
903
|
+
<td style="color:var(--text-muted);font-size:0.82rem;"><%= field.description || '' %></td>
|
|
904
|
+
<td><% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %></td>
|
|
905
|
+
<td style="font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
|
|
906
|
+
</tr>
|
|
907
|
+
<% } %>
|
|
908
|
+
</tbody>
|
|
909
|
+
</table>
|
|
910
|
+
<% } %>
|
|
911
|
+
</div>
|
|
125
912
|
</div>
|
|
126
913
|
<% } %>
|
|
127
914
|
</div>
|
|
128
|
-
</
|
|
915
|
+
</div>
|
|
129
916
|
<% } %>
|
|
130
917
|
|
|
131
918
|
<% if (model.relationships && model.relationships.length > 0) { %>
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
919
|
+
<div class="section reveal">
|
|
920
|
+
<div class="section-label">Data Model</div>
|
|
921
|
+
<h2 class="section-title">Relationships</h2>
|
|
922
|
+
<table class="table-dark">
|
|
135
923
|
<thead>
|
|
136
|
-
<tr
|
|
924
|
+
<tr><th>Name</th><th>From</th><th></th><th>To</th></tr>
|
|
137
925
|
</thead>
|
|
138
926
|
<tbody>
|
|
139
927
|
<% for (var rel of model.relationships) { %>
|
|
140
|
-
<tr
|
|
928
|
+
<tr>
|
|
929
|
+
<td style="color:var(--text-muted);font-size:0.82rem;"><%= rel.name %></td>
|
|
930
|
+
<td class="mono" style="font-size:0.78rem;"><%= rel.from %></td>
|
|
931
|
+
<td style="color:var(--gold-dark);text-align:center;">→</td>
|
|
932
|
+
<td class="mono" style="font-size:0.78rem;"><%= rel.to %></td>
|
|
933
|
+
</tr>
|
|
141
934
|
<% } %>
|
|
142
935
|
</tbody>
|
|
143
936
|
</table>
|
|
144
|
-
</
|
|
937
|
+
</div>
|
|
145
938
|
<% } %>
|
|
146
939
|
|
|
147
940
|
<% if (model.metrics && model.metrics.length > 0) { %>
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
<
|
|
941
|
+
<div class="section reveal">
|
|
942
|
+
<div class="section-label">Computed Metrics</div>
|
|
943
|
+
<h2 class="section-title">Metrics</h2>
|
|
944
|
+
<div class="card-grid">
|
|
151
945
|
<% for (var metric of model.metrics) { %>
|
|
152
|
-
<div class="
|
|
153
|
-
<div class="
|
|
154
|
-
<% if (metric.description) { %><
|
|
946
|
+
<div class="card">
|
|
947
|
+
<div class="metric-name"><%= metric.name %></div>
|
|
948
|
+
<% if (metric.description) { %><div class="metric-desc"><%= metric.description %></div><% } %>
|
|
949
|
+
<% if (metric.expression && metric.expression.dialects && metric.expression.dialects.length > 0) { %>
|
|
950
|
+
<div class="metric-formula"><%= metric.expression.dialects[0].expression %></div>
|
|
951
|
+
<% } %>
|
|
155
952
|
</div>
|
|
156
953
|
<% } %>
|
|
157
954
|
</div>
|
|
158
|
-
</
|
|
955
|
+
</div>
|
|
159
956
|
<% } %>
|
|
160
957
|
|
|
161
|
-
<% if (
|
|
162
|
-
<
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
</h3>
|
|
175
|
-
<% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
|
|
176
|
-
<ul class="text-sm ml-4 list-disc">
|
|
177
|
-
<% for (var chk of tier[lvl].checks) { %>
|
|
178
|
-
<li class="<%= chk.passed ? 'text-green-700' : 'text-red-600' %>"><%= chk.label %><% if (chk.detail) { %> \u2014 <%= chk.detail %><% } %></li>
|
|
179
|
-
<% } %>
|
|
180
|
-
</ul>
|
|
958
|
+
<% if (lineage && (lineage.upstream && lineage.upstream.length > 0 || lineage.downstream && lineage.downstream.length > 0)) { %>
|
|
959
|
+
<div class="section reveal">
|
|
960
|
+
<div class="section-label">Data Lineage</div>
|
|
961
|
+
<h2 class="section-title">Lineage</h2>
|
|
962
|
+
<div class="lineage-flow">
|
|
963
|
+
<% if (lineage.upstream && lineage.upstream.length > 0) { %>
|
|
964
|
+
<div class="lineage-col">
|
|
965
|
+
<div class="lineage-col-label">Upstream</div>
|
|
966
|
+
<% for (var u of lineage.upstream) { %>
|
|
967
|
+
<div class="lineage-node">
|
|
968
|
+
<div class="lineage-node-name"><%= u.source %></div>
|
|
969
|
+
<div class="lineage-node-detail"><%= u.type || '' %><% if (u.tool) { %> via <%= u.tool %><% } %></div>
|
|
970
|
+
</div>
|
|
181
971
|
<% } %>
|
|
182
972
|
</div>
|
|
183
|
-
|
|
184
|
-
|
|
973
|
+
<div class="lineage-arrow">→</div>
|
|
974
|
+
<% } %>
|
|
975
|
+
<div class="lineage-col">
|
|
976
|
+
<div class="lineage-col-label">This Model</div>
|
|
977
|
+
<div class="lineage-node" style="border-color:var(--gold-dark);">
|
|
978
|
+
<div class="lineage-node-name" style="color:var(--gold);"><%= model.name %></div>
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
<% if (lineage.downstream && lineage.downstream.length > 0) { %>
|
|
982
|
+
<div class="lineage-arrow">→</div>
|
|
983
|
+
<div class="lineage-col">
|
|
984
|
+
<div class="lineage-col-label">Downstream</div>
|
|
985
|
+
<% for (var d of lineage.downstream) { %>
|
|
986
|
+
<div class="lineage-node">
|
|
987
|
+
<div class="lineage-node-name"><%= d.target %></div>
|
|
988
|
+
<div class="lineage-node-detail"><%= d.type || '' %><% if (d.tool) { %> via <%= d.tool %><% } %></div>
|
|
989
|
+
</div>
|
|
990
|
+
<% } %>
|
|
991
|
+
</div>
|
|
992
|
+
<% } %>
|
|
993
|
+
</div>
|
|
994
|
+
</div>
|
|
995
|
+
<% } %>
|
|
996
|
+
|
|
997
|
+
<% if (tier) { %>
|
|
998
|
+
<div class="section reveal">
|
|
999
|
+
<div class="section-label">Data Quality</div>
|
|
1000
|
+
<h2 class="section-title">Tier Scorecard</h2>
|
|
1001
|
+
<div class="scorecard">
|
|
1002
|
+
<% var tierLevels = ['bronze', 'silver', 'gold']; %>
|
|
1003
|
+
<% for (var lvl of tierLevels) { %>
|
|
1004
|
+
<div class="scorecard-tier">
|
|
1005
|
+
<div class="scorecard-tier-header">
|
|
1006
|
+
<span class="scorecard-tier-name <%= lvl %>"><%= lvl %></span>
|
|
1007
|
+
<% if (tier[lvl].passed) { %>
|
|
1008
|
+
<span class="scorecard-pass passed">Passed</span>
|
|
1009
|
+
<% } else { %>
|
|
1010
|
+
<span class="scorecard-pass failed">Not passed</span>
|
|
1011
|
+
<% } %>
|
|
1012
|
+
</div>
|
|
1013
|
+
<% if (tier[lvl].checks && tier[lvl].checks.length > 0) { %>
|
|
1014
|
+
<ul class="check-list">
|
|
1015
|
+
<% for (var chk of tier[lvl].checks) { %>
|
|
1016
|
+
<li class="check-item">
|
|
1017
|
+
<span class="check-icon <%= chk.passed ? 'pass' : 'fail' %>"><%= chk.passed ? '\\u2713' : '\\u2717' %></span>
|
|
1018
|
+
<span><%= chk.label %><% if (chk.detail) { %> — <span style="color:var(--text-dim);"><%= chk.detail %></span><% } %></span>
|
|
1019
|
+
</li>
|
|
1020
|
+
<% } %>
|
|
1021
|
+
</ul>
|
|
1022
|
+
<% } %>
|
|
1023
|
+
</div>
|
|
1024
|
+
<% } %>
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
185
1027
|
<% } %>
|
|
186
1028
|
</main>
|
|
1029
|
+
|
|
187
1030
|
${FOOTER}
|
|
1031
|
+
${SCRIPTS}
|
|
188
1032
|
</body>
|
|
189
1033
|
</html>`;
|
|
1034
|
+
|
|
1035
|
+
// src/templates/schema.ts
|
|
190
1036
|
var schemaTemplate = `${HEAD}
|
|
191
|
-
<body
|
|
1037
|
+
<body>
|
|
192
1038
|
${NAV}
|
|
193
1039
|
${TIER_BADGE}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
1040
|
+
|
|
1041
|
+
<main class="page">
|
|
1042
|
+
<a href="<%= basePath %>/models/<%= model.name %>.html" class="back-link">← Back to <%= model.name %></a>
|
|
1043
|
+
|
|
1044
|
+
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:2rem;">
|
|
1045
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);"><%= model.name %></h1>
|
|
197
1046
|
<% if (tier) { %><%- tierBadge(tier.tier) %><% } %>
|
|
198
|
-
|
|
199
|
-
<div class="mb-4 text-sm">
|
|
200
|
-
<a href="<%= basePath %>/models/<%= model.name %>.html" class="text-blue-600 hover:underline">← Back to model</a>
|
|
1047
|
+
<span style="font-size:0.85rem;color:var(--text-dim);">Schema Browser</span>
|
|
201
1048
|
</div>
|
|
202
1049
|
|
|
203
1050
|
<% if (model.datasets && model.datasets.length > 0) { %>
|
|
204
|
-
<% for (var
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
<div class="
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
1051
|
+
<% for (var i = 0; i < model.datasets.length; i++) { var ds = model.datasets[i]; %>
|
|
1052
|
+
<% var dsGov = gov && gov.datasets && gov.datasets[ds.name]; %>
|
|
1053
|
+
<div class="section reveal">
|
|
1054
|
+
<div class="card" style="padding:0;overflow:hidden;">
|
|
1055
|
+
<div style="padding:1.25rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.5rem;">
|
|
1056
|
+
<div style="display:flex;align-items:center;gap:0.6rem;">
|
|
1057
|
+
<span class="mono" style="font-size:1rem;color:var(--text);"><%= ds.name %></span>
|
|
1058
|
+
<% if (dsGov && dsGov.table_type) { %>
|
|
1059
|
+
<span class="ds-type ds-type-<%= dsGov.table_type %>"><%= dsGov.table_type %></span>
|
|
1060
|
+
<% } %>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;">
|
|
1063
|
+
<% if (dsGov && dsGov.grain) { %><span class="tag"><%= dsGov.grain %></span><% } %>
|
|
1064
|
+
<% if (dsGov && dsGov.refresh) { %><span class="tag"><%= dsGov.refresh %></span><% } %>
|
|
1065
|
+
<% if (dsGov && dsGov.security) { %><span class="tag"><%= dsGov.security %></span><% } %>
|
|
1066
|
+
</div>
|
|
217
1067
|
</div>
|
|
218
|
-
|
|
1068
|
+
<div style="padding:0 1.5rem 0.5rem;font-size:0.75rem;color:var(--text-dim);font-family:var(--mono);padding-top:0.75rem;">
|
|
1069
|
+
Source: <%= ds.source %>
|
|
1070
|
+
</div>
|
|
1071
|
+
<% if (ds.description) { %>
|
|
1072
|
+
<div style="padding:0 1.5rem 1rem;font-size:0.85rem;color:var(--text-muted);font-weight:300;"><%= ds.description %></div>
|
|
1073
|
+
<% } %>
|
|
219
1074
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
</tr>
|
|
229
|
-
</thead>
|
|
230
|
-
<tbody>
|
|
231
|
-
<% for (var field of ds.fields) { %>
|
|
232
|
-
<% var fieldKey = ds.name + '.' + field.name; %>
|
|
233
|
-
<% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
|
|
234
|
-
<tr class="border-b">
|
|
235
|
-
<td class="py-1 px-2 font-mono text-xs"><%= field.name %></td>
|
|
236
|
-
<td class="py-1 px-2"><%= field.description || '' %></td>
|
|
237
|
-
<td class="py-1 px-2"><%= fGov && fGov.semantic_role ? fGov.semantic_role : '' %></td>
|
|
238
|
-
<td class="py-1 px-2"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
|
|
1075
|
+
<% if (ds.fields && ds.fields.length > 0) { %>
|
|
1076
|
+
<table class="table-dark">
|
|
1077
|
+
<thead>
|
|
1078
|
+
<tr>
|
|
1079
|
+
<th>Field</th>
|
|
1080
|
+
<th>Description</th>
|
|
1081
|
+
<th>Semantic Role</th>
|
|
1082
|
+
<th>Aggregation</th>
|
|
239
1083
|
</tr>
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1084
|
+
</thead>
|
|
1085
|
+
<tbody>
|
|
1086
|
+
<% for (var field of ds.fields) { %>
|
|
1087
|
+
<% var fieldKey = ds.name + '.' + field.name; %>
|
|
1088
|
+
<% var fGov = gov && gov.fields && gov.fields[fieldKey]; %>
|
|
1089
|
+
<% var role = fGov && fGov.semantic_role ? fGov.semantic_role : ''; %>
|
|
1090
|
+
<tr>
|
|
1091
|
+
<td class="mono" style="font-size:0.78rem;"><%= field.name %></td>
|
|
1092
|
+
<td style="color:var(--text-muted);font-size:0.82rem;"><%= field.description || '' %></td>
|
|
1093
|
+
<td><% if (role) { %><span class="tag role-<%= role %>"><%= role %></span><% } %></td>
|
|
1094
|
+
<td style="font-family:var(--mono);font-size:0.78rem;color:var(--text-dim);"><%= fGov && fGov.default_aggregation ? fGov.default_aggregation : '' %></td>
|
|
1095
|
+
</tr>
|
|
1096
|
+
<% } %>
|
|
1097
|
+
</tbody>
|
|
1098
|
+
</table>
|
|
1099
|
+
<% } else { %>
|
|
1100
|
+
<p style="padding:1.5rem;color:var(--text-dim);font-size:0.85rem;">No fields defined.</p>
|
|
1101
|
+
<% } %>
|
|
1102
|
+
</div>
|
|
1103
|
+
</div>
|
|
247
1104
|
<% } %>
|
|
248
1105
|
<% } else { %>
|
|
249
|
-
<p
|
|
1106
|
+
<p style="color:var(--text-muted);">No datasets found.</p>
|
|
250
1107
|
<% } %>
|
|
251
1108
|
</main>
|
|
1109
|
+
|
|
252
1110
|
${FOOTER}
|
|
1111
|
+
${SCRIPTS}
|
|
253
1112
|
</body>
|
|
254
1113
|
</html>`;
|
|
1114
|
+
|
|
1115
|
+
// src/templates/rules.ts
|
|
255
1116
|
var rulesTemplate = `${HEAD}
|
|
256
|
-
<body
|
|
1117
|
+
<body>
|
|
257
1118
|
${NAV}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
1119
|
+
|
|
1120
|
+
<main class="page">
|
|
1121
|
+
<a href="<%= basePath %>/models/<%= modelName %>.html" class="back-link">← Back to <%= modelName %></a>
|
|
1122
|
+
|
|
1123
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(1.8rem,4vw,2.8rem);color:var(--text);margin-bottom:2rem;">
|
|
1124
|
+
<%= modelName %> <span style="color:var(--text-dim);font-weight:300;">— Rules & Queries</span>
|
|
1125
|
+
</h1>
|
|
263
1126
|
|
|
264
1127
|
<% if (rules && rules.golden_queries && rules.golden_queries.length > 0) { %>
|
|
265
|
-
<
|
|
266
|
-
<
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
1128
|
+
<div class="section reveal">
|
|
1129
|
+
<div class="section-label">Golden Queries</div>
|
|
1130
|
+
<h2 class="section-title">Pre-validated questions</h2>
|
|
1131
|
+
<% for (var gq of rules.golden_queries) { %>
|
|
1132
|
+
<div class="query-card">
|
|
1133
|
+
<div class="query-question"><%= gq.question %></div>
|
|
1134
|
+
<div class="query-sql sql-highlight"><%= gq.sql %></div>
|
|
1135
|
+
<% if (gq.dialect || (gq.tags && gq.tags.length > 0)) { %>
|
|
1136
|
+
<div class="query-meta">
|
|
1137
|
+
<% if (gq.dialect) { %><span>Dialect: <%= gq.dialect %></span><% } %>
|
|
1138
|
+
<% if (gq.tags && gq.tags.length > 0) { %><span>Tags: <%= gq.tags.join(', ') %></span><% } %>
|
|
274
1139
|
</div>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
1140
|
+
<% } %>
|
|
1141
|
+
</div>
|
|
1142
|
+
<% } %>
|
|
1143
|
+
</div>
|
|
278
1144
|
<% } %>
|
|
279
1145
|
|
|
280
1146
|
<% if (rules && rules.business_rules && rules.business_rules.length > 0) { %>
|
|
281
|
-
<
|
|
282
|
-
<
|
|
283
|
-
<
|
|
1147
|
+
<div class="section reveal">
|
|
1148
|
+
<div class="section-label">Business Rules</div>
|
|
1149
|
+
<h2 class="section-title">Business Rules</h2>
|
|
1150
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
284
1151
|
<% for (var br of rules.business_rules) { %>
|
|
285
|
-
<div class="
|
|
286
|
-
<div
|
|
287
|
-
<p
|
|
1152
|
+
<div class="card">
|
|
1153
|
+
<div style="font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;"><%= br.name %></div>
|
|
1154
|
+
<p style="font-size:0.85rem;color:var(--text-muted);line-height:1.6;font-weight:300;"><%= br.definition %></p>
|
|
288
1155
|
<% if (br.enforcement && br.enforcement.length > 0) { %>
|
|
289
|
-
<div
|
|
1156
|
+
<div style="display:flex;gap:0.4rem;margin-top:0.6rem;flex-wrap:wrap;">
|
|
1157
|
+
<% for (var e of br.enforcement) { %><span class="tag tag-green"><%= e %></span><% } %>
|
|
1158
|
+
</div>
|
|
290
1159
|
<% } %>
|
|
291
1160
|
<% if (br.avoid && br.avoid.length > 0) { %>
|
|
292
|
-
<div
|
|
1161
|
+
<div style="display:flex;gap:0.4rem;margin-top:0.4rem;flex-wrap:wrap;">
|
|
1162
|
+
<% for (var a of br.avoid) { %><span class="tag tag-red"><%= a %></span><% } %>
|
|
1163
|
+
</div>
|
|
293
1164
|
<% } %>
|
|
294
1165
|
</div>
|
|
295
1166
|
<% } %>
|
|
296
1167
|
</div>
|
|
297
|
-
</
|
|
1168
|
+
</div>
|
|
298
1169
|
<% } %>
|
|
299
1170
|
|
|
300
1171
|
<% if (rules && rules.guardrail_filters && rules.guardrail_filters.length > 0) { %>
|
|
301
|
-
<
|
|
302
|
-
<
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
1172
|
+
<div class="section reveal">
|
|
1173
|
+
<div class="section-label">Safety</div>
|
|
1174
|
+
<h2 class="section-title">Guardrail Filters</h2>
|
|
1175
|
+
<% for (var gf of rules.guardrail_filters) { %>
|
|
1176
|
+
<div class="guardrail">
|
|
1177
|
+
<div class="guardrail-name"><%= gf.name %></div>
|
|
1178
|
+
<div class="guardrail-filter"><%= gf.filter %></div>
|
|
1179
|
+
<div class="guardrail-reason"><%= gf.reason %></div>
|
|
1180
|
+
<% if (gf.tables && gf.tables.length > 0) { %>
|
|
1181
|
+
<div style="display:flex;gap:0.4rem;margin-top:0.6rem;">
|
|
1182
|
+
<% for (var tb of gf.tables) { %><span class="tag"><%= tb %></span><% } %>
|
|
1183
|
+
</div>
|
|
1184
|
+
<% } %>
|
|
1185
|
+
</div>
|
|
1186
|
+
<% } %>
|
|
1187
|
+
</div>
|
|
313
1188
|
<% } %>
|
|
314
1189
|
|
|
315
1190
|
<% if (rules && rules.hierarchies && rules.hierarchies.length > 0) { %>
|
|
316
|
-
<
|
|
317
|
-
<
|
|
318
|
-
<
|
|
1191
|
+
<div class="section reveal">
|
|
1192
|
+
<div class="section-label">Drill Paths</div>
|
|
1193
|
+
<h2 class="section-title">Hierarchies</h2>
|
|
1194
|
+
<div style="display:flex;flex-direction:column;gap:1rem;">
|
|
319
1195
|
<% for (var h of rules.hierarchies) { %>
|
|
320
|
-
<div class="
|
|
321
|
-
<div
|
|
322
|
-
<div
|
|
323
|
-
<div
|
|
1196
|
+
<div class="card">
|
|
1197
|
+
<div style="font-family:var(--mono);font-size:0.88rem;color:var(--gold-light);margin-bottom:0.4rem;"><%= h.name %></div>
|
|
1198
|
+
<div style="font-size:0.82rem;color:var(--text-muted);margin-bottom:0.4rem;">Dataset: <span class="mono"><%= h.dataset %></span></div>
|
|
1199
|
+
<div style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
|
|
1200
|
+
<% for (var li = 0; li < h.levels.length; li++) { %>
|
|
1201
|
+
<span class="tag tag-blue"><%= h.levels[li] %></span>
|
|
1202
|
+
<% if (li < h.levels.length - 1) { %><span style="color:var(--gold-dark);">→</span><% } %>
|
|
1203
|
+
<% } %>
|
|
1204
|
+
</div>
|
|
324
1205
|
</div>
|
|
325
1206
|
<% } %>
|
|
326
1207
|
</div>
|
|
327
|
-
</
|
|
1208
|
+
</div>
|
|
328
1209
|
<% } %>
|
|
329
1210
|
|
|
330
1211
|
<% 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))) { %>
|
|
331
|
-
<p
|
|
1212
|
+
<p style="color:var(--text-muted);">No rules or queries defined for this model.</p>
|
|
332
1213
|
<% } %>
|
|
333
1214
|
</main>
|
|
1215
|
+
|
|
334
1216
|
${FOOTER}
|
|
1217
|
+
${SCRIPTS}
|
|
335
1218
|
</body>
|
|
336
1219
|
</html>`;
|
|
1220
|
+
|
|
1221
|
+
// src/templates/glossary.ts
|
|
337
1222
|
var glossaryTemplate = `${HEAD}
|
|
338
|
-
<body
|
|
1223
|
+
<body>
|
|
339
1224
|
${NAV}
|
|
340
|
-
|
|
341
|
-
|
|
1225
|
+
|
|
1226
|
+
<main class="page">
|
|
1227
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;">Glossary</h1>
|
|
1228
|
+
<p style="color:var(--text-muted);font-size:1rem;margin-bottom:2rem;font-weight:300;">Business term definitions and mappings.</p>
|
|
342
1229
|
|
|
343
1230
|
<% var termIds = Object.keys(terms).sort(); %>
|
|
344
1231
|
<% if (termIds.length === 0) { %>
|
|
345
|
-
<p
|
|
1232
|
+
<p style="color:var(--text-muted);">No terms defined.</p>
|
|
346
1233
|
<% } else { %>
|
|
347
|
-
<
|
|
1234
|
+
<input type="text" id="glossary-filter"
|
|
1235
|
+
placeholder="Filter terms..."
|
|
1236
|
+
class="search-input"
|
|
1237
|
+
style="margin-bottom:2rem;max-width:400px;" />
|
|
1238
|
+
|
|
1239
|
+
<div class="glossary-grid" id="glossary-grid">
|
|
348
1240
|
<% for (var tid of termIds) { %>
|
|
349
1241
|
<% var term = terms[tid]; %>
|
|
350
|
-
<div class="
|
|
351
|
-
<
|
|
352
|
-
<
|
|
1242
|
+
<div class="glossary-card" id="term-<%= tid %>" data-term="<%= tid %>">
|
|
1243
|
+
<div class="glossary-term"><%= tid %></div>
|
|
1244
|
+
<div class="glossary-def"><%= term.definition %></div>
|
|
353
1245
|
<% if (term.synonyms && term.synonyms.length > 0) { %>
|
|
354
|
-
<div
|
|
1246
|
+
<div style="display:flex;gap:0.4rem;margin-top:0.75rem;flex-wrap:wrap;">
|
|
1247
|
+
<% for (var s of term.synonyms) { %><span class="tag"><%= s %></span><% } %>
|
|
1248
|
+
</div>
|
|
355
1249
|
<% } %>
|
|
356
1250
|
<% if (term.maps_to && term.maps_to.length > 0) { %>
|
|
357
|
-
<div
|
|
1251
|
+
<div style="display:flex;gap:0.4rem;margin-top:0.5rem;flex-wrap:wrap;">
|
|
1252
|
+
<% for (var m of term.maps_to) { %><span class="tag tag-blue"><%= m %></span><% } %>
|
|
1253
|
+
</div>
|
|
358
1254
|
<% } %>
|
|
359
1255
|
<% if (term.owner) { %>
|
|
360
|
-
<div
|
|
1256
|
+
<div style="font-size:0.78rem;color:var(--text-dim);margin-top:0.6rem;">
|
|
1257
|
+
Owner: <a href="<%= basePath %>/owners/<%= term.owner %>.html"><%= term.owner %></a>
|
|
1258
|
+
</div>
|
|
361
1259
|
<% } %>
|
|
362
1260
|
</div>
|
|
363
1261
|
<% } %>
|
|
364
1262
|
</div>
|
|
365
1263
|
<% } %>
|
|
366
1264
|
</main>
|
|
1265
|
+
|
|
367
1266
|
${FOOTER}
|
|
1267
|
+
${SCRIPTS}
|
|
1268
|
+
<script>
|
|
1269
|
+
(function() {
|
|
1270
|
+
var input = document.getElementById('glossary-filter');
|
|
1271
|
+
if (!input) return;
|
|
1272
|
+
var cards = document.querySelectorAll('.glossary-card');
|
|
1273
|
+
input.addEventListener('input', function() {
|
|
1274
|
+
var q = input.value.toLowerCase();
|
|
1275
|
+
cards.forEach(function(card) {
|
|
1276
|
+
var text = card.textContent.toLowerCase();
|
|
1277
|
+
card.style.display = text.indexOf(q) !== -1 ? '' : 'none';
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
})();
|
|
1281
|
+
</script>
|
|
368
1282
|
</body>
|
|
369
1283
|
</html>`;
|
|
1284
|
+
|
|
1285
|
+
// src/templates/owner.ts
|
|
370
1286
|
var ownerTemplate = `${HEAD}
|
|
371
|
-
<body
|
|
1287
|
+
<body>
|
|
372
1288
|
${NAV}
|
|
373
1289
|
${TIER_BADGE}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
1290
|
+
|
|
1291
|
+
<main class="page">
|
|
1292
|
+
<a href="<%= basePath %>/" class="back-link">← All Models</a>
|
|
1293
|
+
|
|
1294
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:0.5rem;"><%= owner.display_name %></h1>
|
|
1295
|
+
|
|
1296
|
+
<div style="display:flex;gap:1.5rem;font-size:0.85rem;color:var(--text-dim);margin-bottom:1.5rem;">
|
|
1297
|
+
<% if (owner.email) { %><span>Email: <span style="color:var(--text-muted);"><%= owner.email %></span></span><% } %>
|
|
1298
|
+
<% if (owner.team) { %><span>Team: <span style="color:var(--text-muted);"><%= owner.team %></span></span><% } %>
|
|
379
1299
|
</div>
|
|
1300
|
+
|
|
380
1301
|
<% if (owner.description) { %>
|
|
381
|
-
<p
|
|
1302
|
+
<p style="color:var(--text-muted);font-size:0.95rem;margin-bottom:2rem;font-weight:300;max-width:600px;"><%= owner.description %></p>
|
|
382
1303
|
<% } %>
|
|
383
1304
|
|
|
384
1305
|
<% if (governedModels.length > 0) { %>
|
|
385
|
-
<section>
|
|
386
|
-
<
|
|
387
|
-
<
|
|
1306
|
+
<div class="section reveal">
|
|
1307
|
+
<div class="section-label">Stewardship</div>
|
|
1308
|
+
<h2 class="section-title">Governed Models</h2>
|
|
1309
|
+
<div class="card-grid-sm">
|
|
388
1310
|
<% for (var gm of governedModels) { %>
|
|
389
|
-
<
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
1311
|
+
<a href="<%= basePath %>/models/<%= gm.name %>.html" class="card" style="text-decoration:none;">
|
|
1312
|
+
<div style="display:flex;align-items:center;gap:0.5rem;">
|
|
1313
|
+
<span class="mono" style="color:var(--gold-light);"><%= gm.name %></span>
|
|
1314
|
+
<% if (gm.tier) { %><%- tierBadge(gm.tier) %><% } %>
|
|
1315
|
+
</div>
|
|
1316
|
+
</a>
|
|
393
1317
|
<% } %>
|
|
394
1318
|
</div>
|
|
395
|
-
</
|
|
1319
|
+
</div>
|
|
396
1320
|
<% } else { %>
|
|
397
|
-
<p
|
|
1321
|
+
<p style="color:var(--text-muted);">No models governed by this owner.</p>
|
|
398
1322
|
<% } %>
|
|
399
1323
|
</main>
|
|
1324
|
+
|
|
400
1325
|
${FOOTER}
|
|
1326
|
+
${SCRIPTS}
|
|
401
1327
|
</body>
|
|
402
1328
|
</html>`;
|
|
1329
|
+
|
|
1330
|
+
// src/templates/search.ts
|
|
403
1331
|
var searchTemplate = `${HEAD}
|
|
404
|
-
<body
|
|
1332
|
+
<body>
|
|
405
1333
|
${NAV}
|
|
406
|
-
|
|
407
|
-
|
|
1334
|
+
|
|
1335
|
+
<main class="page">
|
|
1336
|
+
<h1 style="font-family:var(--serif);font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--text);margin-bottom:2rem;">Search</h1>
|
|
408
1337
|
|
|
409
1338
|
<input type="text" id="search-input"
|
|
410
1339
|
placeholder="Search models, datasets, terms..."
|
|
411
|
-
class="
|
|
1340
|
+
class="search-input"
|
|
1341
|
+
style="margin-bottom:2rem;" />
|
|
412
1342
|
|
|
413
|
-
<div id="search-results"
|
|
1343
|
+
<div id="search-results" style="display:flex;flex-direction:column;gap:0.75rem;"></div>
|
|
414
1344
|
</main>
|
|
1345
|
+
|
|
415
1346
|
${FOOTER}
|
|
1347
|
+
${SCRIPTS}
|
|
416
1348
|
|
|
417
1349
|
<script src="https://cdn.jsdelivr.net/npm/minisearch@7.1.0/dist/umd/index.min.js"></script>
|
|
418
1350
|
<script>
|
|
@@ -424,6 +1356,13 @@ ${FOOTER}
|
|
|
424
1356
|
var resultsContainer = document.getElementById('search-results');
|
|
425
1357
|
var docs = indexData.documents;
|
|
426
1358
|
|
|
1359
|
+
var typeColors = {
|
|
1360
|
+
model: 'tag-gold',
|
|
1361
|
+
dataset: 'tag-blue',
|
|
1362
|
+
term: 'tag-green',
|
|
1363
|
+
owner: 'tag-bronze'
|
|
1364
|
+
};
|
|
1365
|
+
|
|
427
1366
|
function clearResults() {
|
|
428
1367
|
while (resultsContainer.firstChild) {
|
|
429
1368
|
resultsContainer.removeChild(resultsContainer.firstChild);
|
|
@@ -432,22 +1371,35 @@ ${FOOTER}
|
|
|
432
1371
|
|
|
433
1372
|
function createResultElement(doc) {
|
|
434
1373
|
var wrapper = document.createElement('div');
|
|
435
|
-
wrapper.className = '
|
|
1374
|
+
wrapper.className = 'card';
|
|
1375
|
+
wrapper.style.padding = '1rem 1.25rem';
|
|
1376
|
+
|
|
1377
|
+
var top = document.createElement('div');
|
|
1378
|
+
top.style.display = 'flex';
|
|
1379
|
+
top.style.alignItems = 'center';
|
|
1380
|
+
top.style.gap = '0.5rem';
|
|
436
1381
|
|
|
437
1382
|
var link = document.createElement('a');
|
|
438
1383
|
link.href = doc.url;
|
|
439
|
-
link.
|
|
1384
|
+
link.style.fontFamily = 'var(--mono)';
|
|
1385
|
+
link.style.fontSize = '0.88rem';
|
|
1386
|
+
link.style.color = 'var(--gold-light)';
|
|
440
1387
|
link.textContent = doc.title;
|
|
441
|
-
|
|
1388
|
+
top.appendChild(link);
|
|
442
1389
|
|
|
443
1390
|
var badge = document.createElement('span');
|
|
444
|
-
badge.className = '
|
|
1391
|
+
badge.className = 'tag ' + (typeColors[doc.type] || '');
|
|
445
1392
|
badge.textContent = doc.type;
|
|
446
|
-
|
|
1393
|
+
top.appendChild(badge);
|
|
1394
|
+
|
|
1395
|
+
wrapper.appendChild(top);
|
|
447
1396
|
|
|
448
1397
|
if (doc.description) {
|
|
449
1398
|
var desc = document.createElement('p');
|
|
450
|
-
desc.
|
|
1399
|
+
desc.style.fontSize = '0.82rem';
|
|
1400
|
+
desc.style.color = 'var(--text-muted)';
|
|
1401
|
+
desc.style.marginTop = '0.3rem';
|
|
1402
|
+
desc.style.fontWeight = '300';
|
|
451
1403
|
desc.textContent = doc.description;
|
|
452
1404
|
wrapper.appendChild(desc);
|
|
453
1405
|
}
|
|
@@ -462,7 +1414,7 @@ ${FOOTER}
|
|
|
462
1414
|
var hits = miniSearch.search(query, { prefix: true, fuzzy: 0.2 });
|
|
463
1415
|
if (hits.length === 0) {
|
|
464
1416
|
var noResults = document.createElement('p');
|
|
465
|
-
noResults.
|
|
1417
|
+
noResults.style.color = 'var(--text-muted)';
|
|
466
1418
|
noResults.textContent = 'No results found.';
|
|
467
1419
|
resultsContainer.appendChild(noResults);
|
|
468
1420
|
return;
|
|
@@ -544,7 +1496,7 @@ function buildSearchIndex(manifest, basePath) {
|
|
|
544
1496
|
function generateSite(manifest, config) {
|
|
545
1497
|
const files = /* @__PURE__ */ new Map();
|
|
546
1498
|
const siteTitle = config?.title ?? "ContextKit";
|
|
547
|
-
const basePath = (config?.base_path ?? "").replace(/\/+$/, "");
|
|
1499
|
+
const basePath = (config?.base_path ?? "").replace(/\/+$/, "") || ".";
|
|
548
1500
|
const commonData = {
|
|
549
1501
|
siteTitle,
|
|
550
1502
|
basePath
|
|
@@ -557,13 +1509,15 @@ function generateSite(manifest, config) {
|
|
|
557
1509
|
models: manifest.models,
|
|
558
1510
|
governance: manifest.governance,
|
|
559
1511
|
tiers: manifest.tiers,
|
|
560
|
-
owners: manifest.owners
|
|
1512
|
+
owners: manifest.owners,
|
|
1513
|
+
terms: manifest.terms
|
|
561
1514
|
})
|
|
562
1515
|
);
|
|
563
1516
|
for (const [name, model] of Object.entries(manifest.models)) {
|
|
564
1517
|
const gov = manifest.governance[name] ?? null;
|
|
565
1518
|
const tier = manifest.tiers[name] ?? null;
|
|
566
1519
|
const rules = manifest.rules[name] ?? null;
|
|
1520
|
+
const lineage = manifest.lineage[name] ?? null;
|
|
567
1521
|
files.set(
|
|
568
1522
|
`models/${name}.html`,
|
|
569
1523
|
ejs.render(modelTemplate, {
|
|
@@ -572,7 +1526,8 @@ function generateSite(manifest, config) {
|
|
|
572
1526
|
model,
|
|
573
1527
|
gov,
|
|
574
1528
|
tier,
|
|
575
|
-
rules
|
|
1529
|
+
rules,
|
|
1530
|
+
lineage
|
|
576
1531
|
})
|
|
577
1532
|
);
|
|
578
1533
|
files.set(
|