@runcontext/site 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/astro/astro.config.mjs +13 -0
  2. package/astro/node_modules/.astro/data-store.json +1 -0
  3. package/astro/node_modules/.vite/deps/_metadata.json +31 -0
  4. package/astro/node_modules/.vite/deps/astro___aria-query.js +6776 -0
  5. package/astro/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  6. package/astro/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
  7. package/astro/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  8. package/astro/node_modules/.vite/deps/astro___cssesc.js +99 -0
  9. package/astro/node_modules/.vite/deps/astro___cssesc.js.map +7 -0
  10. package/astro/node_modules/.vite/deps/chunk-BUSYA2B4.js +8 -0
  11. package/astro/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +7 -0
  12. package/astro/node_modules/.vite/deps/package.json +3 -0
  13. package/astro/src/components/Sidebar.astro +71 -0
  14. package/astro/src/components/TierBadge.astro +9 -0
  15. package/astro/src/components/Topbar.astro +42 -0
  16. package/astro/src/data/manifest.json +10839 -0
  17. package/astro/src/data/site-config.json +1 -0
  18. package/astro/src/layouts/Base.astro +461 -0
  19. package/astro/src/pages/glossary.astro +37 -0
  20. package/astro/src/pages/index.astro +98 -0
  21. package/astro/src/pages/models/[name]/rules.astro +87 -0
  22. package/astro/src/pages/models/[name]/schema.astro +82 -0
  23. package/astro/src/pages/models/[name].astro +181 -0
  24. package/astro/src/pages/owners/[id].astro +65 -0
  25. package/astro/src/pages/search.astro +108 -0
  26. package/astro/tsconfig.json +3 -0
  27. package/dist/build-index-MSTAYUC3.cjs +7 -0
  28. package/dist/build-index-MSTAYUC3.cjs.map +1 -0
  29. package/dist/build-index-UCDMZJZP.mjs +7 -0
  30. package/dist/build-index-UCDMZJZP.mjs.map +1 -0
  31. package/dist/chunk-BBC5HGNQ.cjs +65 -0
  32. package/dist/chunk-BBC5HGNQ.cjs.map +1 -0
  33. package/dist/chunk-WM73L4RO.mjs +65 -0
  34. package/dist/chunk-WM73L4RO.mjs.map +1 -0
  35. package/dist/index.cjs +83 -68
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +20 -8
  38. package/dist/index.d.ts +20 -8
  39. package/dist/index.mjs +80 -65
  40. package/dist/index.mjs.map +1 -1
  41. package/package.json +6 -4
@@ -0,0 +1 @@
1
+ {"title":"ContextKit","studioMode":true}
@@ -0,0 +1,461 @@
1
+ ---
2
+ import Topbar from '../components/Topbar.astro';
3
+ import Sidebar from '../components/Sidebar.astro';
4
+ import manifest from '../data/manifest.json';
5
+ import siteConfig from '../data/site-config.json';
6
+
7
+ interface Props {
8
+ title: string;
9
+ activeModel?: string;
10
+ }
11
+
12
+ const { title, activeModel } = Astro.props;
13
+ const siteTitle = siteConfig.title || 'ContextKit';
14
+ const studioMode = (siteConfig as any).studioMode ?? false;
15
+ const models = manifest.models;
16
+ const tiers = manifest.tiers;
17
+ ---
18
+
19
+ <!DOCTYPE html>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charset="UTF-8">
23
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
+ <title>{title} — {siteTitle}</title>
25
+ <link rel="preconnect" href="https://fonts.googleapis.com">
26
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
27
+ <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">
28
+ </head>
29
+ <body>
30
+ <Topbar {siteTitle} {title} />
31
+ <Sidebar {models} {tiers} {title} {activeModel} />
32
+
33
+ <div class="main">
34
+ <slot />
35
+ </div>
36
+
37
+ <footer class="site-footer">
38
+ Generated by <a href="https://github.com/erickittelson/ContextKit">ContextKit</a>
39
+ &nbsp;&middot;&nbsp;
40
+ <a href="https://github.com/erickittelson/ContextKit#readme" target="_blank" rel="noopener">Documentation</a>
41
+ </footer>
42
+
43
+ <script>
44
+ // Sidebar toggle (mobile)
45
+ (window as any).toggleSidebar = function() {
46
+ document.getElementById('sidebar')?.classList.toggle('open');
47
+ document.getElementById('sidebar-overlay')?.classList.toggle('open');
48
+ };
49
+
50
+ // Expandable sections
51
+ (window as any).toggleExpand = function(id: string) {
52
+ const el = document.getElementById(id);
53
+ const icon = document.getElementById(id + '-icon');
54
+ if (!el) return;
55
+ if (el.style.maxHeight && el.style.maxHeight !== '0px') {
56
+ el.style.maxHeight = '0px';
57
+ if (icon) icon.textContent = '+';
58
+ } else {
59
+ el.style.maxHeight = el.scrollHeight + 'px';
60
+ if (icon) icon.textContent = '\u2212';
61
+ }
62
+ };
63
+
64
+ // SQL syntax highlighter — operates on trusted, server-rendered golden query content only
65
+ function highlightSQL() {
66
+ const blocks = document.querySelectorAll('.sql-highlight');
67
+ const kw = /\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|AND|OR|NOT|IN|AS|GROUP|BY|ORDER|HAVING|LIMIT|DISTINCT|CASE|WHEN|THEN|ELSE|END|IS|NULL|DESC|ASC|WITH)\b/gi;
68
+ const fn = /\b(SUM|COUNT|AVG|MIN|MAX|ROUND|COALESCE|CAST|ROW_NUMBER|RANK)\b/gi;
69
+ const str = /('(?:[^'\\]|\\.)*')/g;
70
+ blocks.forEach(function(block) {
71
+ // Safe: content is from trusted template-rendered golden queries, not user input.
72
+ // We escape HTML entities before applying syntax highlighting spans.
73
+ let text = block.textContent || '';
74
+ const div = document.createElement('div');
75
+ div.appendChild(document.createTextNode(text));
76
+ text = div.textContent || '';
77
+ text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
78
+ text = text.replace(str, '<span class="sql-str">$1</span>');
79
+ text = text.replace(kw, '<span class="sql-kw">$&</span>');
80
+ text = text.replace(fn, '<span class="sql-fn">$&</span>');
81
+ block.innerHTML = text;
82
+ });
83
+ }
84
+ highlightSQL();
85
+ </script>
86
+
87
+ {studioMode && (
88
+ <Fragment>
89
+ <div class="staged-bar" id="staged-bar" style="display:none;">
90
+ <span id="staged-count" style="color:#c9a55a;font-weight:500;">0 changes staged</span>
91
+ <div>
92
+ <button onclick="previewAndSave()" class="staged-btn primary">Preview &amp; Save</button>
93
+ <button onclick="discardEdits()" class="staged-btn secondary" style="margin-left:8px;">Discard</button>
94
+ </div>
95
+ </div>
96
+ <div class="diff-modal" id="diff-modal" style="display:none;">
97
+ <div class="diff-modal-content">
98
+ <h2>Review Changes</h2>
99
+ <div id="diff-container"></div>
100
+ <div class="diff-actions">
101
+ <button onclick="confirmSave()" class="staged-btn primary">Save All</button>
102
+ <button onclick="closeDiffModal()" class="staged-btn secondary">Cancel</button>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ <script is:inline>
107
+ window.studioState = { edits: [] };
108
+
109
+ function stageEdit(file, path, value, label) {
110
+ window.studioState.edits = window.studioState.edits.filter(
111
+ function(e) { return !(e.file === file && e.path === path); }
112
+ );
113
+ window.studioState.edits.push({ file: file, path: path, value: value, label: label });
114
+ updateStagedBar();
115
+ }
116
+
117
+ function updateStagedBar() {
118
+ var bar = document.getElementById('staged-bar');
119
+ var count = document.getElementById('staged-count');
120
+ if (!bar || !count) return;
121
+ var n = window.studioState.edits.length;
122
+ bar.style.display = n > 0 ? 'flex' : 'none';
123
+ count.textContent = n + ' change' + (n !== 1 ? 's' : '') + ' staged';
124
+ }
125
+
126
+ function discardEdits() {
127
+ window.studioState.edits = [];
128
+ updateStagedBar();
129
+ document.querySelectorAll('.editable[contenteditable="true"]').forEach(function(el) {
130
+ el.contentEditable = 'false';
131
+ if (el.dataset.original) el.textContent = el.dataset.original;
132
+ });
133
+ }
134
+
135
+ async function previewAndSave() {
136
+ var edits = window.studioState.edits;
137
+ if (edits.length === 0) return;
138
+ var fileGroups = {};
139
+ for (var i = 0; i < edits.length; i++) {
140
+ var edit = edits[i];
141
+ if (!fileGroups[edit.file]) fileGroups[edit.file] = [];
142
+ fileGroups[edit.file].push(edit);
143
+ }
144
+ var previews = [];
145
+ for (var file in fileGroups) {
146
+ var fileEdits = fileGroups[file];
147
+ try {
148
+ var res = await fetch('/api/preview', {
149
+ method: 'POST',
150
+ headers: { 'Content-Type': 'application/json' },
151
+ body: JSON.stringify({ file: fileEdits[0].file, path: fileEdits[0].path, value: fileEdits[0].value }),
152
+ });
153
+ var data = await res.json();
154
+ if (data.error) {
155
+ previews.push({ file: file, edits: fileEdits, error: data.error });
156
+ } else {
157
+ data.file = file;
158
+ data.edits = fileEdits;
159
+ previews.push(data);
160
+ }
161
+ } catch (err) {
162
+ previews.push({ file: file, edits: fileEdits, error: err.message });
163
+ }
164
+ }
165
+ showDiffModal(previews);
166
+ }
167
+
168
+ function showDiffModal(previews) {
169
+ var modal = document.getElementById('diff-modal');
170
+ var container = document.getElementById('diff-container');
171
+ if (!modal || !container) return;
172
+ container.textContent = '';
173
+ for (var i = 0; i < previews.length; i++) {
174
+ var p = previews[i];
175
+ var div = document.createElement('div');
176
+ div.className = 'diff-file';
177
+ var fname = document.createElement('div');
178
+ fname.className = 'diff-file-name';
179
+ fname.textContent = p.file;
180
+ div.appendChild(fname);
181
+ if (p.error) {
182
+ var errEl = document.createElement('span');
183
+ errEl.className = 'diff-del';
184
+ errEl.textContent = 'Error: ' + p.error;
185
+ div.appendChild(errEl);
186
+ } else {
187
+ for (var j = 0; j < p.edits.length; j++) {
188
+ var addEl = document.createElement('div');
189
+ addEl.className = 'diff-add';
190
+ addEl.textContent = ' ' + p.edits[j].label + ': ' + JSON.stringify(p.edits[j].value);
191
+ div.appendChild(addEl);
192
+ }
193
+ }
194
+ container.appendChild(div);
195
+ }
196
+ modal.style.display = 'flex';
197
+ }
198
+
199
+ function closeDiffModal() {
200
+ var modal = document.getElementById('diff-modal');
201
+ if (modal) modal.style.display = 'none';
202
+ }
203
+
204
+ async function confirmSave() {
205
+ var edits = window.studioState.edits;
206
+ if (edits.length === 0) return;
207
+ try {
208
+ var res = await fetch('/api/save', {
209
+ method: 'POST',
210
+ headers: { 'Content-Type': 'application/json' },
211
+ body: JSON.stringify({ edits: edits }),
212
+ });
213
+ var data = await res.json();
214
+ var ok = data.results ? data.results.filter(function(r) { return r.ok; }).length : 0;
215
+ showToast(ok + ' file(s) saved');
216
+ window.studioState.edits = [];
217
+ updateStagedBar();
218
+ closeDiffModal();
219
+ setTimeout(function() { location.reload(); }, 800);
220
+ } catch (err) {
221
+ showToast('Save failed: ' + err.message);
222
+ }
223
+ }
224
+
225
+ function showToast(msg) {
226
+ var existing = document.querySelector('.toast');
227
+ if (existing) existing.remove();
228
+ var toast = document.createElement('div');
229
+ toast.className = 'toast';
230
+ toast.textContent = msg;
231
+ document.body.appendChild(toast);
232
+ setTimeout(function() { toast.remove(); }, 3000);
233
+ }
234
+
235
+ async function renameModel(currentName) {
236
+ var newName = prompt('Rename model "' + currentName + '" to:', currentName);
237
+ if (!newName || newName.trim() === currentName) return;
238
+ try {
239
+ var res = await fetch('/api/rename-model', {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify({ oldName: currentName, newName: newName.trim() }),
243
+ });
244
+ var data = await res.json();
245
+ if (data.ok) {
246
+ showToast('Renamed to "' + data.newName + '" — ' + data.renamed.length + ' file(s) updated');
247
+ setTimeout(function() { window.location.href = '/models/' + data.newName + '.html'; }, 800);
248
+ } else {
249
+ showToast('Rename failed');
250
+ }
251
+ } catch (err) {
252
+ showToast('Rename failed: ' + err.message);
253
+ }
254
+ }
255
+
256
+ function makeEditable(el) {
257
+ el.addEventListener('click', function() {
258
+ if (el.contentEditable === 'true') return;
259
+ el.dataset.original = el.textContent;
260
+ el.contentEditable = 'true';
261
+ el.focus();
262
+ });
263
+ el.addEventListener('blur', function() {
264
+ el.contentEditable = 'false';
265
+ var newVal = el.textContent.trim();
266
+ if (newVal !== el.dataset.original) {
267
+ stageEdit(el.dataset.file, el.dataset.path, newVal, el.dataset.label || el.dataset.path);
268
+ el.style.borderBottomColor = '#4ade80';
269
+ setTimeout(function() { el.style.borderBottomColor = ''; }, 1000);
270
+ }
271
+ });
272
+ el.addEventListener('keydown', function(e) {
273
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); el.blur(); }
274
+ if (e.key === 'Escape') { el.textContent = el.dataset.original; el.contentEditable = 'false'; }
275
+ });
276
+ }
277
+
278
+ document.addEventListener('DOMContentLoaded', function() {
279
+ document.querySelectorAll('.editable').forEach(makeEditable);
280
+ // Wire up rename buttons
281
+ document.querySelectorAll('.rename-btn').forEach(function(btn) {
282
+ btn.addEventListener('click', function() {
283
+ renameModel(btn.dataset.modelName);
284
+ });
285
+ });
286
+ });
287
+ </script>
288
+ </Fragment>
289
+ )}
290
+ </body>
291
+ </html>
292
+
293
+ <style is:global>
294
+ :root {
295
+ --accent: #c9a55a;
296
+ --accent-light: #e0be6a;
297
+ --accent-dim: rgba(201, 165, 90, 0.12);
298
+ --accent-border: rgba(201, 165, 90, 0.25);
299
+ --bg: #0a0908;
300
+ --bg-sidebar: #0f0e0d;
301
+ --bg-card: #141312;
302
+ --bg-hover: #1a1918;
303
+ --bg-code: #111010;
304
+ --text: #e8e6e1;
305
+ --text-secondary: #a09d94;
306
+ --text-dim: #6a675e;
307
+ --border: #252320;
308
+ --border-light: #302d28;
309
+ --green: #4aba6a;
310
+ --green-dim: rgba(74, 186, 106, 0.1);
311
+ --red: #d45050;
312
+ --red-dim: rgba(212, 80, 80, 0.08);
313
+ --blue: #5b9cf5;
314
+ --blue-dim: rgba(91, 156, 245, 0.1);
315
+ --purple: #a78bfa;
316
+ --cyan: #22d3ee;
317
+ --orange: #f59e0b;
318
+ --sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;
319
+ --mono: 'Geist Mono', 'SF Mono', 'Consolas', monospace;
320
+ --sidebar-w: 260px;
321
+ --topbar-h: 48px;
322
+ }
323
+
324
+ * { margin: 0; padding: 0; box-sizing: border-box; }
325
+ html { scroll-behavior: smooth; }
326
+ body { font-family: var(--sans); background: var(--bg); color: var(--text); line-height: 1.6; }
327
+ a { color: var(--accent); text-decoration: none; }
328
+ a:hover { text-decoration: underline; }
329
+
330
+ .main {
331
+ margin-left: var(--sidebar-w);
332
+ margin-top: var(--topbar-h);
333
+ min-height: calc(100vh - var(--topbar-h));
334
+ padding: 2rem 2.5rem 4rem;
335
+ max-width: calc(900px + var(--sidebar-w));
336
+ }
337
+
338
+ .page-header { margin-bottom: 2rem; }
339
+ .page-header h1 { font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 700; letter-spacing: -0.025em; color: var(--text); line-height: 1.2; }
340
+ .page-header .subtitle { font-size: 0.95rem; color: var(--text-secondary); margin-top: 0.4rem; font-weight: 300; max-width: 600px; }
341
+
342
+ .stats-row { display: flex; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; margin-bottom: 2rem; }
343
+ .stat-item { flex: 1; background: var(--bg-card); padding: 1rem 1.25rem; text-align: center; min-width: 80px; }
344
+ .stat-val { font-family: var(--mono); font-size: 1.5rem; font-weight: 600; color: var(--accent); line-height: 1; }
345
+ .stat-lbl { font-size: 0.65rem; color: var(--text-dim); margin-top: 0.3rem; text-transform: uppercase; letter-spacing: 0.1em; }
346
+
347
+ .section { margin-bottom: 2.5rem; }
348
+ .section-label { font-family: var(--mono); font-size: 0.6rem; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 0.5rem; }
349
+ .section-title { font-size: 1.15rem; font-weight: 600; letter-spacing: -0.015em; margin-bottom: 1rem; color: var(--text); }
350
+
351
+ .card { border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.25rem; background: var(--bg-card); transition: border-color 0.15s; }
352
+ .card:hover { border-color: var(--border-light); }
353
+ .card-link { text-decoration: none; display: block; }
354
+ .card-link:hover { text-decoration: none; }
355
+ .card-link:hover .card { border-color: var(--accent-border); }
356
+ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
357
+
358
+ .tag { display: inline-flex; align-items: center; font-family: var(--mono); font-size: 0.6rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; padding: 0.15rem 0.45rem; border-radius: 3px; background: rgba(106, 103, 94, 0.1); color: var(--text-dim); border: 1px solid transparent; }
359
+ .tag-gold { color: var(--accent); background: var(--accent-dim); }
360
+ .tag-silver { color: #a0a8b8; background: rgba(160, 168, 184, 0.08); }
361
+ .tag-bronze { color: #b87a4a; background: rgba(184, 122, 74, 0.1); }
362
+ .tag-green { color: var(--green); background: var(--green-dim); }
363
+ .tag-blue { color: var(--blue); background: var(--blue-dim); }
364
+
365
+ .data-table { width: 100%; border-collapse: collapse; }
366
+ .data-table th { font-family: var(--mono); font-size: 0.6rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); }
367
+ .data-table td { padding: 0.55rem 0.75rem; border-bottom: 1px solid rgba(37, 35, 32, 0.6); font-size: 0.82rem; vertical-align: top; }
368
+ .data-table tr:hover td { background: rgba(201, 165, 90, 0.02); }
369
+ .mono { font-family: var(--mono); }
370
+
371
+ .role-identifier { color: var(--accent); background: var(--accent-dim); }
372
+ .role-metric { color: var(--cyan); background: rgba(34, 211, 238, 0.08); }
373
+ .role-dimension { color: var(--purple); background: rgba(167, 139, 250, 0.08); }
374
+ .role-date { color: var(--green); background: var(--green-dim); }
375
+
376
+ .gov-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
377
+ .gov-cell { background: var(--bg-card); padding: 0.75rem 1rem; }
378
+ .gov-label { font-family: var(--mono); font-size: 0.55rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 0.2rem; }
379
+ .gov-value { font-size: 0.85rem; color: var(--text); }
380
+
381
+ .query-card { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--bg-card); margin-bottom: 1rem; }
382
+ .query-q { padding: 1rem 1.25rem; font-size: 0.95rem; font-weight: 500; font-style: italic; color: var(--text); border-bottom: 1px solid var(--border); }
383
+ .query-sql { padding: 0.85rem 1.25rem; font-family: var(--mono); font-size: 0.75rem; color: var(--text-secondary); line-height: 1.7; background: var(--bg-code); overflow-x: auto; white-space: pre-wrap; }
384
+
385
+ .guardrail { border: 1px solid rgba(212, 80, 80, 0.15); border-radius: 8px; padding: 1rem 1.25rem; background: var(--red-dim); margin-bottom: 0.75rem; }
386
+ .guardrail-name { font-family: var(--mono); font-size: 0.8rem; color: var(--red); margin-bottom: 0.35rem; font-weight: 500; }
387
+ .guardrail-filter { font-family: var(--mono); font-size: 0.75rem; color: var(--text); background: var(--bg-code); padding: 0.35rem 0.6rem; border-radius: 4px; border: 1px solid var(--border); display: inline-block; margin-bottom: 0.4rem; }
388
+ .guardrail-reason { font-size: 0.8rem; color: var(--text-secondary); font-weight: 300; }
389
+
390
+ .scorecard { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
391
+ .sc-tier { background: var(--bg-card); padding: 1.25rem; }
392
+ .sc-tier-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
393
+ .sc-tier-name { font-size: 0.9rem; font-weight: 600; text-transform: capitalize; }
394
+ .sc-tier-name.bronze { color: #b87a4a; }
395
+ .sc-tier-name.silver { color: #a0a8b8; }
396
+ .sc-tier-name.gold { color: var(--accent); }
397
+ .sc-status { font-family: var(--mono); font-size: 0.55rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.12rem 0.4rem; border-radius: 3px; }
398
+ .sc-status.pass { color: var(--green); background: var(--green-dim); }
399
+ .sc-status.fail { color: var(--red); background: var(--red-dim); }
400
+ .check-list { list-style: none; }
401
+ .check-item { display: flex; align-items: flex-start; gap: 0.35rem; padding: 0.2rem 0; font-size: 0.72rem; color: var(--text-secondary); }
402
+ .check-icon { flex-shrink: 0; margin-top: 1px; font-size: 0.75rem; }
403
+ .check-icon.pass { color: var(--green); }
404
+ .check-icon.fail { color: var(--red); }
405
+
406
+ .glossary-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
407
+ .glossary-card { position: relative; overflow: hidden; }
408
+ .glossary-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, var(--accent-border), transparent); }
409
+ .glossary-term { font-size: 1rem; font-weight: 600; margin-bottom: 0.35rem; color: var(--text); }
410
+ .glossary-def { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.6; font-weight: 300; }
411
+
412
+ .sql-kw { color: var(--purple); }
413
+ .sql-fn { color: var(--green); }
414
+ .sql-str { color: var(--orange); }
415
+
416
+ .metric-name { font-family: var(--mono); font-size: 0.85rem; font-weight: 500; color: var(--accent-light); margin-bottom: 0.35rem; }
417
+ .metric-desc { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.6; margin-bottom: 0.75rem; }
418
+ .metric-formula { font-family: var(--mono); font-size: 0.7rem; color: var(--text-dim); background: var(--bg-code); border: 1px solid var(--border); border-radius: 5px; padding: 0.5rem 0.7rem; overflow-x: auto; }
419
+
420
+ .site-footer { margin-left: var(--sidebar-w); text-align: center; padding: 2rem 1.5rem; color: var(--text-dim); font-size: 0.72rem; border-top: 1px solid var(--border); }
421
+ .site-footer a { color: var(--text-dim); }
422
+ .site-footer a:hover { color: var(--accent); }
423
+
424
+ .breadcrumb { display: flex; align-items: center; gap: 0.35rem; font-size: 0.78rem; color: var(--text-dim); margin-bottom: 1.5rem; }
425
+ .breadcrumb a { color: var(--text-secondary); text-decoration: none; }
426
+ .breadcrumb a:hover { color: var(--text); }
427
+ .breadcrumb-sep { color: var(--border-light); }
428
+
429
+ /* Studio mode styles */
430
+ .editable { cursor: text; border-bottom: 1px dashed rgba(201, 165, 90, 0.25); transition: border-color 0.2s; }
431
+ .editable:hover { border-bottom-color: var(--accent); }
432
+ .editable:focus { outline: none; border-bottom: 2px solid var(--accent); background: rgba(201, 165, 90, 0.06); }
433
+ .rename-btn { background: none; border: 1px solid var(--accent-border); color: var(--accent); border-radius: 5px; padding: 0.2rem 0.6rem; font-size: 0.72rem; cursor: pointer; opacity: 0.7; transition: opacity 0.2s, background 0.2s; font-family: var(--sans); }
434
+ .rename-btn:hover { opacity: 1; background: var(--accent-dim); }
435
+ .staged-bar { position: fixed; bottom: 0; left: 0; right: 0; background: #1a1a2e; border-top: 2px solid var(--accent); 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); }
436
+ .staged-btn { border: none; border-radius: 6px; padding: 8px 20px; font-size: 14px; cursor: pointer; font-weight: 500; }
437
+ .staged-btn.primary { background: var(--accent); color: #0a0a0f; }
438
+ .staged-btn.primary:hover { background: #d4b06a; }
439
+ .staged-btn.secondary { background: transparent; border: 1px solid #666; color: #999; }
440
+ .staged-btn.secondary:hover { border-color: #999; color: #fff; }
441
+ .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; }
442
+ .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; }
443
+ .diff-modal-content h2 { margin: 0 0 16px; color: var(--accent); }
444
+ .diff-file { margin: 12px 0; padding: 12px; background: var(--bg); border-radius: 8px; font-family: var(--mono); font-size: 13px; white-space: pre-wrap; }
445
+ .diff-file-name { color: #888; font-size: 12px; margin-bottom: 8px; }
446
+ .diff-add { color: var(--green); }
447
+ .diff-del { color: var(--red); }
448
+ .diff-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px; }
449
+ .toast { position: fixed; top: 20px; right: 20px; background: #1a1a2e; border: 1px solid var(--accent); color: #e0e0e0; padding: 12px 20px; border-radius: 8px; z-index: 3000; animation: toast-in 0.3s ease-out; }
450
+ @keyframes toast-in { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
451
+
452
+ @media (max-width: 860px) {
453
+ .sidebar { transform: translateX(-100%); transition: transform 0.25s ease; z-index: 300; }
454
+ .sidebar.open { transform: translateX(0); }
455
+ .main { margin-left: 0; padding: 1.5rem 1.25rem 3rem; }
456
+ .site-footer { margin-left: 0; }
457
+ .menu-toggle { display: block !important; }
458
+ .stats-row { flex-wrap: wrap; }
459
+ .scorecard { grid-template-columns: 1fr; }
460
+ }
461
+ </style>
@@ -0,0 +1,37 @@
1
+ ---
2
+ import Base from '../layouts/Base.astro';
3
+ import manifest from '../data/manifest.json';
4
+
5
+ const terms = Object.entries(manifest.terms)
6
+ .sort(([a], [b]) => a.localeCompare(b));
7
+ ---
8
+
9
+ <Base title="Glossary">
10
+ <div class="page-header">
11
+ <h1>Glossary</h1>
12
+ <p class="subtitle">{terms.length} business terms defined</p>
13
+ </div>
14
+
15
+ {terms.length === 0 ? (
16
+ <p style="color:var(--text-secondary);">No glossary terms defined yet.</p>
17
+ ) : (
18
+ <div class="glossary-grid">
19
+ {terms.map(([id, term]: [string, any]) => (
20
+ <div class="card glossary-card" id={`term-${id}`}>
21
+ <div class="glossary-term">{term.id || id}</div>
22
+ {term.definition && <div class="glossary-def">{term.definition}</div>}
23
+ {term.synonyms && term.synonyms.length > 0 && (
24
+ <div style="margin-top:0.5rem;display:flex;gap:0.25rem;flex-wrap:wrap;">
25
+ {term.synonyms.map((s: string) => <span class="tag">{s}</span>)}
26
+ </div>
27
+ )}
28
+ {term.maps_to && term.maps_to.length > 0 && (
29
+ <div style="margin-top:0.35rem;font-size:0.7rem;color:var(--text-dim);">
30
+ Maps to: {term.maps_to.map((m: string) => <code class="mono" style="margin-right:0.25rem;">{m}</code>)}
31
+ </div>
32
+ )}
33
+ </div>
34
+ ))}
35
+ </div>
36
+ )}
37
+ </Base>
@@ -0,0 +1,98 @@
1
+ ---
2
+ import Base from '../layouts/Base.astro';
3
+ import TierBadge from '../components/TierBadge.astro';
4
+ import manifest from '../data/manifest.json';
5
+
6
+ const modelNames = Object.keys(manifest.models);
7
+ let totalDatasets = 0;
8
+ let totalFields = 0;
9
+ for (const mn of modelNames) {
10
+ const m = manifest.models[mn];
11
+ if (m?.datasets) {
12
+ totalDatasets += m.datasets.length;
13
+ for (const ds of m.datasets) {
14
+ if (ds.fields) totalFields += ds.fields.length;
15
+ }
16
+ }
17
+ }
18
+ const totalTerms = Object.keys(manifest.terms).length;
19
+ const totalOwners = Object.keys(manifest.owners).length;
20
+ ---
21
+
22
+ <Base title="Home">
23
+ <div class="page-header">
24
+ <h1>Metadata Catalog</h1>
25
+ <p class="subtitle">Explore semantic models, governed datasets, business glossary, and data quality tiers.</p>
26
+ </div>
27
+
28
+ <div class="stats-row">
29
+ <div class="stat-item">
30
+ <div class="stat-val">{modelNames.length}</div>
31
+ <div class="stat-lbl">Models</div>
32
+ </div>
33
+ <div class="stat-item">
34
+ <div class="stat-val">{totalDatasets}</div>
35
+ <div class="stat-lbl">Datasets</div>
36
+ </div>
37
+ <div class="stat-item">
38
+ <div class="stat-val">{totalFields}</div>
39
+ <div class="stat-lbl">Fields</div>
40
+ </div>
41
+ <div class="stat-item">
42
+ <div class="stat-val">{totalTerms}</div>
43
+ <div class="stat-lbl">Terms</div>
44
+ </div>
45
+ <div class="stat-item">
46
+ <div class="stat-val">{totalOwners}</div>
47
+ <div class="stat-lbl">Owners</div>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="section">
52
+ <div class="section-label">Semantic Models</div>
53
+ <h2 class="section-title">Models</h2>
54
+ {modelNames.length === 0 ? (
55
+ <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>
56
+ ) : (
57
+ <div class="card-grid">
58
+ {modelNames.map((name) => (
59
+ <a href={`/models/${name}.html`} class="card-link">
60
+ <div class="card">
61
+ <div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.4rem;">
62
+ <span class="mono" style="font-size:0.88rem;color:var(--accent-light);">{name}</span>
63
+ {manifest.tiers[name] && <TierBadge tier={manifest.tiers[name]?.tier} />}
64
+ </div>
65
+ {manifest.models[name]?.description && (
66
+ <p style="color:var(--text-secondary);font-size:0.82rem;margin-bottom:0.5rem;font-weight:300;">{manifest.models[name].description}</p>
67
+ )}
68
+ {manifest.governance[name] && (
69
+ <div style="display:flex;gap:0.35rem;flex-wrap:wrap;align-items:center;">
70
+ {manifest.governance[name]?.owner && <span class="tag">{manifest.governance[name].owner}</span>}
71
+ {manifest.governance[name]?.trust && <span class="tag tag-green">{manifest.governance[name].trust}</span>}
72
+ {manifest.governance[name]?.tags?.map((t: string) => <span class="tag">{t}</span>)}
73
+ </div>
74
+ )}
75
+ </div>
76
+ </a>
77
+ ))}
78
+ </div>
79
+ )}
80
+ </div>
81
+
82
+ {Object.keys(manifest.owners).length > 0 && (
83
+ <div class="section">
84
+ <div class="section-label">Data Stewardship</div>
85
+ <h2 class="section-title">Owners</h2>
86
+ <div class="card-grid">
87
+ {Object.entries(manifest.owners).map(([oid, owner]: [string, any]) => (
88
+ <a href={`/owners/${oid}.html`} class="card-link">
89
+ <div class="card">
90
+ <div style="font-size:0.9rem;font-weight:500;color:var(--text);margin-bottom:0.15rem;">{owner.display_name}</div>
91
+ {owner.team && <div style="font-size:0.75rem;color:var(--text-dim);">{owner.team}</div>}
92
+ </div>
93
+ </a>
94
+ ))}
95
+ </div>
96
+ </div>
97
+ )}
98
+ </Base>