Markdown rendering for skills, refresh button on replay

- react-markdown + remark-gfm for proper skill SKILL.md rendering
- Prose styles matching dark theme (tables, code blocks, blockquotes)
- Strips YAML frontmatter before rendering
- Refresh button in session replay header
This commit is contained in:
Hermes
2026-04-09 21:25:10 -05:00
parent 4ab41d0e38
commit e24c179e89
6 changed files with 1539 additions and 12 deletions
+35
View File
@@ -68,6 +68,41 @@ body {
.btn-accent:active { opacity: 0.7; } .btn-accent:active { opacity: 0.7; }
.btn-accent:disabled { opacity: 0.3; } .btn-accent:disabled { opacity: 0.3; }
/* Markdown prose */
.prose-hermes {
font-size: 13px;
line-height: 1.7;
color: var(--text-1);
}
.prose-hermes h1 { font-size: 18px; font-weight: 700; margin: 20px 0 8px; color: var(--text-1); }
.prose-hermes h2 { font-size: 16px; font-weight: 600; margin: 16px 0 6px; color: var(--text-1); }
.prose-hermes h3 { font-size: 14px; font-weight: 600; margin: 12px 0 4px; color: var(--text-1); }
.prose-hermes p { margin: 6px 0; }
.prose-hermes ul, .prose-hermes ol { margin: 6px 0; padding-left: 20px; }
.prose-hermes li { margin: 2px 0; }
.prose-hermes code {
font-size: 12px;
background: rgba(255,255,255,0.06);
padding: 1px 5px;
border-radius: 4px;
font-family: ui-monospace, "SF Mono", monospace;
}
.prose-hermes pre {
background: rgba(0,0,0,0.3);
border-radius: 8px;
padding: 12px;
overflow-x: auto;
margin: 8px 0;
}
.prose-hermes pre code { background: none; padding: 0; }
.prose-hermes a { color: var(--accent); text-decoration: none; }
.prose-hermes table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 12px; }
.prose-hermes th { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); color: var(--text-2); font-weight: 600; }
.prose-hermes td { padding: 6px 8px; border-bottom: 1px solid var(--border); }
.prose-hermes blockquote { border-left: 3px solid var(--accent); padding-left: 12px; margin: 8px 0; color: var(--text-2); }
.prose-hermes hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
.prose-hermes strong { color: var(--text-1); }
/* Divider */ /* Divider */
.divider { .divider {
height: 1px; height: 1px;
+9 -1
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { use, useState, useRef } from "react"; import { use, useState, useRef } from "react";
import { ArrowLeft, Send, Loader2, Eye } from "lucide-react"; import { ArrowLeft, Send, Loader2, Eye, RefreshCw } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useSession, useMessages } from "@/lib/hooks"; import { useSession, useMessages } from "@/lib/hooks";
import { timeAgo, formatTokens, platformBadgeClass } from "@/lib/utils"; import { timeAgo, formatTokens, platformBadgeClass } from "@/lib/utils";
@@ -96,6 +96,14 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st
</div> </div>
)} )}
</div> </div>
{/* Refresh */}
<button
onClick={() => { qc.invalidateQueries({ queryKey: ["messages", id] }); qc.invalidateQueries({ queryKey: ["session", id] }); }}
className="p-1.5 rounded-lg active:bg-white/[0.04]"
style={{ color: "var(--text-2)" }}
>
<RefreshCw size={16} />
</button>
{/* Verbosity toggle */} {/* Verbosity toggle */}
<div className="relative"> <div className="relative">
<button <button
+10 -4
View File
@@ -4,11 +4,19 @@ import { use } from "react";
import { ArrowLeft, FileText, Folder } from "lucide-react"; import { ArrowLeft, FileText, Folder } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useSkill } from "@/lib/hooks"; import { useSkill } from "@/lib/hooks";
import { Markdown } from "@/components/Markdown";
export default function SkillDetailPage({ params }: { params: Promise<{ name: string }> }) { export default function SkillDetailPage({ params }: { params: Promise<{ name: string }> }) {
const { name } = use(params); const { name } = use(params);
const { data, isLoading } = useSkill(name); const { data, isLoading } = useSkill(name);
// Strip YAML frontmatter for rendering
let content = data?.content || "";
if (content.startsWith("---")) {
const end = content.indexOf("---", 3);
if (end > 0) content = content.slice(end + 3).trim();
}
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -40,10 +48,8 @@ export default function SkillDetailPage({ params }: { params: Promise<{ name: st
)} )}
</div> </div>
)} )}
<div className="card p-4"> <div className="card p-4 overflow-x-hidden">
<pre className="text-[13px] whitespace-pre-wrap leading-relaxed font-sans overflow-x-auto" style={{ color: "var(--text-1)" }}> <Markdown content={content} />
{data.content}
</pre>
</div> </div>
</> </>
) : ( ) : (
+12
View File
@@ -0,0 +1,12 @@
"use client";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
export function Markdown({ content }: { content: string }) {
return (
<div className="prose-hermes">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
</div>
);
}
+1470 -6
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -13,7 +13,9 @@
"lucide-react": "^1.8.0", "lucide-react": "^1.8.0",
"next": "16.2.3", "next": "16.2.3",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4" "react-dom": "19.2.4",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",