Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

ai-archaeology.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

ai-archaeology.tsxBlame817 lines · 2 contributors
b1070a5Claude1/**
2 * AI Code Archaeology — /:owner/:repo/archaeology
3 *
4 * A developer can ask "why does this file exist?" and get a synthesized
5 * answer from git history, PR discussions, and the issue tracker.
6 *
7 * GET /:owner/:repo/archaeology — search form
8 * GET /:owner/:repo/archaeology?file=&q= — results page
9 * GET /:owner/:repo/archaeology?file=&q=&deep=1 — force-refresh results
10 */
11
12import { Hono } from "hono";
13import { html } from "hono/html";
14import { and, eq } from "drizzle-orm";
15import { db } from "../db";
16import { repositories, users } from "../db/schema";
17import { Layout } from "../views/layout";
18import { RepoHeader, RepoNav } from "../views/components";
19import { renderMarkdown } from "../lib/markdown";
20import { softAuth } from "../middleware/auth";
21import type { AuthEnv } from "../middleware/auth";
22import { excavate, invalidateCache } from "../lib/ai-archaeology";
23import type { ArchaeologyFinding } from "../lib/ai-archaeology";
24
25export const archaeologyRoutes = new Hono<AuthEnv>();
26
27/* ─────────────────────────────────────────────────────────────────────────
28 * Scoped CSS
29 * ───────────────────────────────────────────────────────────────────────── */
30const styles = `
31 .arch-wrap { max-width: 1100px; margin: 0 auto; padding: var(--space-6) var(--space-4); }
32
33 /* Hero */
34 .arch-hero {
35 position: relative;
36 margin-bottom: var(--space-5);
37 padding: var(--space-5) var(--space-6);
38 background: var(--bg-elevated);
39 border: 1px solid var(--border);
40 border-radius: 16px;
41 overflow: hidden;
42 }
43 .arch-hero::before {
44 content: '';
45 position: absolute;
46 top: 0; left: 0; right: 0;
47 height: 2px;
48 background: linear-gradient(90deg, transparent 0%, #c97b2e 30%, #e8a45a 70%, transparent 100%);
49 opacity: 0.8;
50 pointer-events: none;
51 }
52 .arch-hero-orb {
53 position: absolute;
54 inset: -20% -10% auto auto;
55 width: 380px; height: 380px;
56 background: radial-gradient(circle, rgba(201,123,46,0.18), rgba(232,164,90,0.08) 45%, transparent 70%);
57 filter: blur(70px);
58 pointer-events: none;
59 z-index: 0;
60 }
61 .arch-hero-inner { position: relative; z-index: 1; max-width: 700px; }
62
63 .arch-eyebrow {
64 font-size: 12px;
65 color: var(--text-muted);
66 margin-bottom: var(--space-2);
67 letter-spacing: 0.02em;
68 display: inline-flex;
69 align-items: center;
70 gap: 8px;
71 }
72 .arch-eyebrow .pill {
73 display: inline-flex;
74 align-items: center;
75 justify-content: center;
76 width: 18px; height: 18px;
77 border-radius: 6px;
78 background: rgba(201,123,46,0.14);
79 color: #e8a45a;
80 box-shadow: inset 0 0 0 1px rgba(201,123,46,0.35);
81 font-size: 11px;
82 }
83 .arch-title {
84 font-size: clamp(26px, 4vw, 38px);
85 font-family: var(--font-display);
86 font-weight: 800;
87 letter-spacing: -0.025em;
88 line-height: 1.05;
89 margin: 0 0 var(--space-2);
90 color: var(--text-strong);
91 }
92 .arch-title-grad {
93 background-image: linear-gradient(135deg, #e8a45a 0%, #c97b2e 50%, #e8a45a 100%);
94 -webkit-background-clip: text;
95 background-clip: text;
96 -webkit-text-fill-color: transparent;
97 color: transparent;
98 }
99 .arch-sub {
100 font-size: 14px;
101 color: var(--text-muted);
102 margin: 0;
103 line-height: 1.55;
104 max-width: 580px;
105 }
106
107 /* Search form */
108 .arch-form {
109 margin-bottom: var(--space-5);
110 background: var(--bg-elevated);
111 border: 1px solid var(--border);
112 border-radius: 14px;
113 padding: var(--space-4) var(--space-5);
114 }
115 .arch-form-title {
116 font-family: var(--font-display);
117 font-size: 15px;
118 font-weight: 700;
119 color: var(--text-strong);
120 margin: 0 0 var(--space-3);
121 }
122 .arch-form-row {
123 display: grid;
124 grid-template-columns: 1fr 1fr auto;
125 gap: var(--space-2);
126 align-items: end;
127 }
128 @media (max-width: 640px) {
129 .arch-form-row { grid-template-columns: 1fr; }
130 }
131 .arch-form label {
132 display: block;
133 font-size: 12px;
134 font-weight: 600;
135 color: var(--text-muted);
136 margin-bottom: 4px;
137 letter-spacing: 0.03em;
138 }
139 .arch-form input[type="text"] {
140 width: 100%;
141 padding: 9px 12px;
142 background: var(--bg-tertiary);
143 border: 1px solid var(--border);
144 border-radius: 8px;
145 color: var(--text);
146 font-size: 14px;
147 font-family: inherit;
148 outline: none;
149 box-sizing: border-box;
150 transition: border-color 120ms ease;
151 }
152 .arch-form input[type="text"]:focus {
153 border-color: #c97b2e;
154 box-shadow: 0 0 0 3px rgba(201,123,46,0.12);
155 }
156 .arch-submit {
157 display: inline-flex;
158 align-items: center;
159 gap: 6px;
160 padding: 9px 18px;
161 background: linear-gradient(135deg, #c97b2e 0%, #e8a45a 100%);
162 color: #fff;
163 border: 1px solid transparent;
164 border-radius: 9px;
165 font-size: 13.5px;
166 font-weight: 600;
167 cursor: pointer;
168 font-family: inherit;
169 box-shadow: 0 4px 12px -3px rgba(201,123,46,0.45);
170 transition: transform 120ms ease, box-shadow 120ms ease;
171 white-space: nowrap;
172 }
173 .arch-submit:hover {
174 transform: translateY(-1px);
175 box-shadow: 0 8px 18px -4px rgba(201,123,46,0.55);
176 }
177
178 /* Explanation panel */
179 .arch-panel {
180 position: relative;
181 margin-bottom: var(--space-5);
182 background: #ffffff;
183 color: #0a0a0a;
184 border: 1px solid #e5e7eb;
185 border-radius: 14px;
186 overflow: hidden;
187 box-shadow: 0 1px 0 rgba(255,255,255,0.04), 0 8px 32px rgba(0,0,0,0.16);
188 }
189 .arch-panel-head {
190 display: flex;
191 align-items: center;
192 justify-content: space-between;
193 gap: 12px;
194 padding: 12px 18px;
195 background: #f9fafb;
196 border-bottom: 1px solid #e5e7eb;
197 flex-wrap: wrap;
198 }
199 .arch-panel-title {
200 display: flex;
201 align-items: center;
202 gap: 10px;
203 font-size: 14px;
204 font-weight: 700;
205 color: #111827;
206 margin: 0;
207 font-family: var(--font-display, system-ui, sans-serif);
208 }
209 .arch-panel-dot {
210 width: 8px; height: 8px;
211 border-radius: 9999px;
212 background: linear-gradient(135deg, #c97b2e, #e8a45a);
213 box-shadow: 0 0 0 3px rgba(201,123,46,0.18);
214 }
215 .arch-panel-meta {
216 display: flex;
217 align-items: center;
218 gap: 10px;
219 flex-wrap: wrap;
220 }
221
222 /* Confidence pill */
223 .arch-confidence {
224 display: inline-flex;
225 align-items: center;
226 gap: 5px;
227 padding: 2px 9px;
228 border-radius: 9999px;
229 font-size: 10.5px;
230 font-weight: 700;
231 letter-spacing: 0.05em;
232 text-transform: uppercase;
233 }
234 .arch-confidence-high {
235 background: rgba(52,211,153,0.12);
236 color: #047857;
237 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.4);
238 }
239 .arch-confidence-medium {
240 background: rgba(251,191,36,0.12);
241 color: #92400e;
242 box-shadow: inset 0 0 0 1px rgba(251,191,36,0.4);
243 }
244 .arch-confidence-low {
245 background: rgba(248,113,113,0.12);
246 color: #b91c1c;
247 box-shadow: inset 0 0 0 1px rgba(248,113,113,0.4);
248 }
249 .arch-confidence .dot {
250 width: 5px; height: 5px;
251 border-radius: 9999px;
252 background: currentColor;
253 }
254
255 .arch-panel-body {
256 padding: 22px 24px;
257 }
258 .arch-panel-body .markdown-body {
259 color: #0a0a0a;
260 background: #ffffff;
261 font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif);
262 font-size: 14.5px;
263 line-height: 1.65;
264 }
265 .arch-panel-body .markdown-body h1,
266 .arch-panel-body .markdown-body h2,
267 .arch-panel-body .markdown-body h3 {
268 color: #0a0a0a;
269 border-bottom-color: #e5e7eb;
270 }
271 .arch-panel-body .markdown-body a { color: #92400e; }
272 .arch-panel-body .markdown-body code {
273 background: #fef3c7;
274 color: #92400e;
275 padding: 1px 5px;
276 border-radius: 4px;
277 font-family: var(--font-mono);
278 font-size: 12.5px;
279 }
280 .arch-panel-body .markdown-body pre {
281 background: #0f111a;
282 color: #e6edf3;
283 border: 1px solid #1f2330;
284 border-radius: 8px;
285 padding: 12px 14px;
286 overflow-x: auto;
287 }
288 .arch-panel-body .markdown-body pre code {
289 background: transparent;
290 color: inherit;
291 padding: 0;
292 }
293 .arch-panel-body .markdown-body blockquote {
294 border-left: 3px solid #fcd34d;
295 background: #fffbeb;
296 color: #78350f;
297 padding: 8px 14px;
298 margin: 12px 0;
299 border-radius: 6px;
300 }
301
302 /* Actions row below panel */
303 .arch-actions {
304 display: flex;
305 align-items: center;
306 gap: 12px;
307 margin-bottom: var(--space-5);
308 flex-wrap: wrap;
309 }
310 .arch-dig-deeper {
311 display: inline-flex;
312 align-items: center;
313 gap: 6px;
314 padding: 8px 16px;
315 background: var(--bg-elevated);
316 border: 1px solid var(--border);
317 border-radius: 9px;
318 font-size: 13px;
319 font-weight: 600;
320 color: var(--text);
321 text-decoration: none;
322 transition: background 120ms ease, border-color 120ms ease;
323 }
324 .arch-dig-deeper:hover {
325 background: var(--bg-tertiary);
326 border-color: #c97b2e;
327 color: #e8a45a;
328 }
329 .arch-file-link {
330 font-size: 12.5px;
331 color: var(--text-muted);
332 text-decoration: none;
333 font-family: var(--font-mono);
334 padding: 4px 8px;
335 background: var(--bg-tertiary);
336 border-radius: 6px;
337 border: 1px solid var(--border);
338 }
339 .arch-file-link:hover { color: var(--text); border-color: var(--border-strong, var(--border)); }
340 .arch-analyzed-at {
341 font-size: 11.5px;
342 color: var(--text-muted);
343 margin-left: auto;
344 }
345
346 /* Findings timeline */
347 .arch-timeline-title {
348 font-family: var(--font-display);
349 font-size: 16px;
350 font-weight: 700;
351 color: var(--text-strong);
352 margin: 0 0 var(--space-3);
353 letter-spacing: -0.01em;
354 }
355 .arch-timeline {
356 display: flex;
357 flex-direction: column;
358 gap: 0;
359 border: 1px solid var(--border);
360 border-radius: 12px;
361 overflow: hidden;
362 margin-bottom: var(--space-5);
363 }
364 .arch-finding {
365 display: grid;
366 grid-template-columns: 36px 1fr auto;
367 align-items: start;
368 gap: var(--space-3);
369 padding: 14px 16px;
370 border-bottom: 1px solid var(--border);
371 background: var(--bg-elevated);
372 text-decoration: none;
373 color: inherit;
374 transition: background 100ms ease;
375 }
376 .arch-finding:last-child { border-bottom: none; }
377 .arch-finding:hover { background: var(--bg-tertiary); }
378
379 .arch-finding-icon {
380 display: flex;
381 align-items: center;
382 justify-content: center;
383 width: 28px; height: 28px;
384 border-radius: 8px;
385 font-size: 13px;
386 flex-shrink: 0;
387 margin-top: 1px;
388 }
389 .arch-icon-commit {
390 background: rgba(140,109,255,0.12);
391 color: #a78bfa;
392 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.28);
393 }
394 .arch-icon-pr {
395 background: rgba(52,211,153,0.12);
396 color: #10b981;
397 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.28);
398 }
399 .arch-icon-issue {
400 background: rgba(248,113,113,0.12);
e589f77ccantynz-alt401 color: var(--red);
b1070a5Claude402 box-shadow: inset 0 0 0 1px rgba(248,113,113,0.28);
403 }
404
405 .arch-finding-body {}
406 .arch-finding-title {
407 font-size: 13.5px;
408 font-weight: 600;
409 color: var(--text-strong);
410 margin: 0 0 3px;
411 line-height: 1.35;
412 word-break: break-word;
413 }
414 .arch-finding-summary {
415 font-size: 12.5px;
416 color: var(--text-muted);
417 margin: 0;
418 line-height: 1.45;
419 }
420
421 .arch-finding-meta {
422 display: flex;
423 flex-direction: column;
424 align-items: flex-end;
425 gap: 4px;
426 font-size: 11.5px;
427 color: var(--text-muted);
428 white-space: nowrap;
429 flex-shrink: 0;
430 }
431 .arch-finding-type {
432 font-size: 10px;
433 font-weight: 700;
434 letter-spacing: 0.06em;
435 text-transform: uppercase;
436 padding: 1px 6px;
437 border-radius: 4px;
438 }
439 .arch-type-commit { background: rgba(140,109,255,0.12); color: #a78bfa; }
440 .arch-type-pr { background: rgba(52,211,153,0.12); color: #10b981; }
e589f77ccantynz-alt441 .arch-type-issue { background: rgba(248,113,113,0.12); color: var(--red); }
b1070a5Claude442
443 /* Empty states */
444 .arch-empty {
445 position: relative;
446 margin: var(--space-4) 0;
447 padding: var(--space-6);
448 border: 1px dashed var(--border);
449 border-radius: 14px;
450 background: var(--bg-elevated);
451 text-align: center;
452 overflow: hidden;
453 }
454 .arch-empty-orb {
455 position: absolute;
456 inset: -40% 35% auto 35%;
457 width: 260px; height: 260px;
458 background: radial-gradient(circle, rgba(201,123,46,0.16), rgba(232,164,90,0.06) 45%, transparent 70%);
459 filter: blur(55px);
460 pointer-events: none;
461 z-index: 0;
462 }
463 .arch-empty > * { position: relative; z-index: 1; }
464 .arch-empty h2 {
465 margin: 0 0 6px;
466 font-family: var(--font-display);
467 font-size: 18px;
468 color: var(--text-strong);
469 }
470 .arch-empty p {
471 margin: 0 auto 12px;
472 color: var(--text-muted);
473 font-size: 14px;
474 max-width: 440px;
475 line-height: 1.55;
476 }
477
478 /* Powered-by pill */
479 .arch-poweredby {
480 margin-top: var(--space-5);
481 text-align: center;
482 color: var(--text-muted);
483 font-size: 11.5px;
484 }
485 .arch-poweredby-pill {
486 display: inline-flex;
487 align-items: center;
488 gap: 6px;
489 padding: 4px 10px;
490 border-radius: 9999px;
491 background: rgba(201,123,46,0.08);
492 border: 1px solid rgba(201,123,46,0.22);
493 color: var(--text-muted);
494 font-size: 11px;
495 letter-spacing: 0.04em;
496 text-transform: uppercase;
497 font-weight: 600;
498 }
499 .arch-poweredby-pill .dot {
500 width: 6px; height: 6px;
501 border-radius: 9999px;
502 background: linear-gradient(135deg, #c97b2e, #e8a45a);
503 }
504
505 :root[data-theme='light'] .arch-panel {
506 box-shadow: 0 1px 0 rgba(0,0,0,0.02), 0 8px 28px rgba(15,16,28,0.08);
507 }
508`;
509
510// ---------------------------------------------------------------------------
511// Helpers
512// ---------------------------------------------------------------------------
513
514interface ResolvedRepo {
515 ownerId: string;
516 repoId: string;
517}
518
519async function resolveRepo(
520 ownerName: string,
521 repoName: string
522): Promise<ResolvedRepo | null> {
523 try {
524 const [ownerRow] = await db
525 .select()
526 .from(users)
527 .where(eq(users.username, ownerName))
528 .limit(1);
529 if (!ownerRow) return null;
530
531 const [repoRow] = await db
532 .select()
533 .from(repositories)
534 .where(
535 and(
536 eq(repositories.ownerId, ownerRow.id),
537 eq(repositories.name, repoName)
538 )
539 )
540 .limit(1);
541 if (!repoRow) return null;
542
543 return { ownerId: ownerRow.id, repoId: repoRow.id };
544 } catch {
545 return null;
546 }
547}
548
549function findingIcon(type: ArchaeologyFinding["type"]): string {
550 if (type === "commit") return "◆"; // diamond
551 if (type === "pr") return "↪"; // right arrow hook
552 return "!"; // issue
553}
554
555function confidenceLabel(conf: "high" | "medium" | "low"): string {
556 if (conf === "high") return "High confidence";
557 if (conf === "medium") return "Medium confidence";
558 return "Low confidence";
559}
560
561// ---------------------------------------------------------------------------
562// Route
563// ---------------------------------------------------------------------------
564
565archaeologyRoutes.get("/:owner/:repo/archaeology", softAuth, async (c) => {
566 const { owner, repo } = c.req.param();
567 const user = c.get("user");
568 const filePath = c.req.query("file") ?? "";
569 const query = c.req.query("q") ?? "Why does this code exist?";
570 const deep = c.req.query("deep") === "1";
571
572 const resolved = await resolveRepo(owner, repo);
573 if (!resolved) {
574 return c.html(
575 <Layout title="Not Found" user={user}>
576 <div class="empty-state">
577 <h2>Repository not found</h2>
578 </div>
579 </Layout>,
580 404
581 );
582 }
583
584 // Search form view (no file param)
585 if (!filePath) {
586 return c.html(
587 <Layout title={`Archaeology — ${owner}/${repo}`} user={user}>
588 <RepoHeader owner={owner} repo={repo} />
589 <RepoNav owner={owner} repo={repo} active="archaeology" />
590 <div class="arch-wrap">
591 <section class="arch-hero">
592 <div class="arch-hero-orb" aria-hidden="true" />
593 <div class="arch-hero-inner">
594 <div class="arch-eyebrow">
595 <span class="pill" aria-hidden="true">{"🏛"}</span>
596 AI · gluecron · archaeology
597 </div>
598 <h1 class="arch-title">
599 <span class="arch-title-grad">Archaeology.</span>
600 </h1>
601 <p class="arch-sub">
602 Ask why any file exists. Claude searches git history, pull
603 requests, and issues to reconstruct the original motivation
604 and key decisions.
605 </p>
606 </div>
607 </section>
608
609 <div class="arch-form">
610 <p class="arch-form-title">Excavate a file</p>
611 <form method="get" action={`/${owner}/${repo}/archaeology`}>
612 <div class="arch-form-row">
613 <div>
614 <label for="arch-file">File path</label>
615 <input
616 id="arch-file"
617 type="text"
618 name="file"
619 placeholder="src/lib/auth.ts"
620 required
621 autocomplete="off"
622 spellcheck={false as any}
623 />
624 </div>
625 <div>
626 <label for="arch-q">Question (optional)</label>
627 <input
628 id="arch-q"
629 type="text"
630 name="q"
631 placeholder="Why does this exist?"
632 autocomplete="off"
633 />
634 </div>
635 <div>
636 <button type="submit" class="arch-submit">
637 {"🏛"} Dig
638 </button>
639 </div>
640 </div>
641 </form>
642 </div>
643
644 <div class="arch-poweredby">
645 <span class="arch-poweredby-pill">
646 <span class="dot" aria-hidden="true" />
647 Powered by Claude
648 </span>
649 </div>
650 </div>
651 <style dangerouslySetInnerHTML={{ __html: styles }} />
652 </Layout>
653 );
654 }
655
656 // Results view — excavate
657 if (deep) {
658 invalidateCache(resolved.repoId, filePath);
659 }
660
661 const report = await excavate(owner, repo, resolved.repoId, filePath, query);
662
663 const deepUrl = `/${owner}/${repo}/archaeology?file=${encodeURIComponent(filePath)}&q=${encodeURIComponent(query)}&deep=1`;
664 const fileUrl = `/${owner}/${repo}/blob/HEAD/${filePath}`;
665 const analyzedAt = report.analyzedAt.toUTCString().replace(" GMT", " UTC");
666
667 return c.html(
668 <Layout title={`Archaeology: ${filePath} — ${owner}/${repo}`} user={user}>
669 <RepoHeader owner={owner} repo={repo} />
670 <RepoNav owner={owner} repo={repo} active="archaeology" />
671 <div class="arch-wrap">
672 <section class="arch-hero">
673 <div class="arch-hero-orb" aria-hidden="true" />
674 <div class="arch-hero-inner">
675 <div class="arch-eyebrow">
676 <span class="pill" aria-hidden="true">{"🏛"}</span>
677 AI · gluecron · archaeology
678 </div>
679 <h1 class="arch-title">
680 <span class="arch-title-grad">Archaeology.</span>
681 </h1>
682 <p class="arch-sub">
683 Why does <code style="font-family:var(--font-mono);font-size:13px;background:var(--bg-tertiary);padding:1px 6px;border-radius:4px;color:var(--text)">{filePath}</code> exist?
684 </p>
685 </div>
686 </section>
687
688 {/* Search form (pre-filled) */}
689 <div class="arch-form">
690 <p class="arch-form-title">Refine your question</p>
691 <form method="get" action={`/${owner}/${repo}/archaeology`}>
692 <div class="arch-form-row">
693 <div>
694 <label for="arch-file2">File path</label>
695 <input
696 id="arch-file2"
697 type="text"
698 name="file"
699 value={filePath}
700 required
701 autocomplete="off"
702 spellcheck={false as any}
703 />
704 </div>
705 <div>
706 <label for="arch-q2">Question</label>
707 <input
708 id="arch-q2"
709 type="text"
710 name="q"
711 value={query}
712 autocomplete="off"
713 />
714 </div>
715 <div>
716 <button type="submit" class="arch-submit">
717 {"🏛"} Dig
718 </button>
719 </div>
720 </div>
721 </form>
722 </div>
723
724 {/* Explanation panel */}
725 <section class="arch-panel" aria-label="AI explanation">
726 <header class="arch-panel-head">
727 <p class="arch-panel-title">
728 <span class="arch-panel-dot" aria-hidden="true" />
729 Explanation
730 </p>
731 <div class="arch-panel-meta">
732 <span
733 class={`arch-confidence arch-confidence-${report.confidence}`}
734 >
735 <span class="dot" aria-hidden="true" />
736 {confidenceLabel(report.confidence)}
737 </span>
738 </div>
739 </header>
740 <div class="arch-panel-body">
741 <div class="markdown-body">
742 {html(
743 [renderMarkdown(report.explanation)] as unknown as TemplateStringsArray
744 )}
745 </div>
746 </div>
747 </section>
748
749 {/* Actions */}
750 <div class="arch-actions">
751 <a href={deepUrl} class="arch-dig-deeper">
752 {"🔍"} Dig deeper
753 </a>
754 <a href={fileUrl} class="arch-file-link">
755 {filePath}
756 </a>
757 <span class="arch-analyzed-at">Analyzed {analyzedAt}</span>
758 </div>
759
760 {/* Findings timeline */}
761 {report.findings.length > 0 ? (
762 <>
763 <h2 class="arch-timeline-title">Evidence timeline</h2>
764 <div class="arch-timeline">
765 {report.findings.map((f) => (
766 <a
767 href={f.url}
768 class="arch-finding"
769 key={`${f.type}-${f.id}`}
770 >
771 <span
772 class={`arch-finding-icon arch-icon-${f.type}`}
773 aria-label={f.type}
774 >
775 {findingIcon(f.type)}
776 </span>
777 <div class="arch-finding-body">
778 <p class="arch-finding-title">{f.title}</p>
779 <p class="arch-finding-summary">{f.summary}</p>
780 </div>
781 <div class="arch-finding-meta">
782 <span class={`arch-finding-type arch-type-${f.type}`}>
783 {f.type}
784 </span>
785 <span>{f.date ? f.date.slice(0, 10) : ""}</span>
786 {f.author ? <span>{f.author}</span> : null}
787 </div>
788 </a>
789 ))}
790 </div>
791 </>
792 ) : (
793 <div class="arch-empty">
794 <div class="arch-empty-orb" aria-hidden="true" />
795 <h2>No supporting evidence found</h2>
796 <p>
797 No commits, pull requests, or issues mentioning{" "}
798 <strong>{filePath.split("/").pop()}</strong> were found in this
799 repository. The explanation above was generated from file content
800 alone.
801 </p>
802 </div>
803 )}
804
805 <div class="arch-poweredby">
806 <span class="arch-poweredby-pill">
807 <span class="dot" aria-hidden="true" />
808 Powered by Claude
809 </span>
810 </div>
811 </div>
812 <style dangerouslySetInnerHTML={{ __html: styles }} />
813 </Layout>
814 );
815});
816
817export default archaeologyRoutes;