add committer info to commit detail view

Shows committer name, email, and date when they differ from the author
(e.g. rebased or cherry-picked commits). Escapes angle brackets in
author/committer identity strings via escapeHtml. Fixes audio controls
attribute type mismatch in FileBlob.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
AuthorKonata <konata@posteo.jp>
Date
Commit75550dfa619c2a81cbc7a6311456169fad3500da
Parent3fd6106
5 files changed, 43 insertions(+), 8 deletions(-)
Msrc/index.tsx
@@ -23,8 +23,7 @@ for (const cmd of ["git", "ssh-keygen"]) {
2323
2424 await syncStartup();
2525 if (!SSH_DISABLED) await startSshServer();
26-
27-const _app = new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } })
26+new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } })
2827 .use(
2928 staticPlugin({
3029 assets: path.resolve("./public"),
@@ -40,5 +39,4 @@ const _app = new Elysia({ serve: { maxRequestBodySize: MAX_UPLOAD_BYTES } })
4039 .use(releasesRoutes)
4140 .use(avatarRoutes)
4241 .listen(PORT);
43-
4442 console.log(`Hearthforge running at http://localhost:${PORT}`);
Msrc/services/git.ts
@@ -115,6 +115,9 @@ export interface CommitMeta {
115115 author: string;
116116 email: string;
117117 date: string;
118+ committer: string;
119+ committerEmail: string;
120+ committerDate: string;
118121 parents: string[];
119122 }
120123
@@ -433,7 +436,7 @@ export const git = {
433436 const p = repoPath(name);
434437 try {
435438 const [metaOut, msgOut] = await Promise.all([
436- $`git -C ${p} show --no-patch --format=%H%x1f%an%x1f%ae%x1f%ai%x1f%P ${sha}`.text(),
439+ $`git -C ${p} show --no-patch --format=%H%x1f%an%x1f%ae%x1f%ai%x1f%cn%x1f%ce%x1f%ci%x1f%P ${sha}`.text(),
437440 $`git -C ${p} log --format=%B -1 ${sha}`.text(),
438441 ]);
439442 const parts = metaOut.trim().split("\x1f");
@@ -454,7 +457,10 @@ export const git = {
454457 author: parts[1] ?? "",
455458 email: parts[2] ?? "",
456459 date: parts[3] ?? "",
457- parents: (parts[4] ?? "").trim().split(/\s+/).filter(Boolean),
460+ committer: parts[4] ?? "",
461+ committerEmail: parts[5] ?? "",
462+ committerDate: parts[6] ?? "",
463+ parents: (parts[7] ?? "").trim().split(/\s+/).filter(Boolean),
458464 };
459465 } catch {
460466 return null;
Msrc/views/patches/PatchDetail.tsx
@@ -178,7 +178,11 @@ export function PatchDetail({
178178 </div>
179179 <div class="patch-author-meta">
180180 <span class="patch-author-label">git author:</span>
181- <span class="patch-author-identity">{escapeHtml(`${patch.author_name} <${patch.author_email}>`)}</span>
181+ <span class="patch-author-identity">
182+ {escapeHtml(
183+ `${patch.author_name} <${patch.author_email}>`,
184+ )}
185+ </span>
182186 </div>
183187 </div>
184188
Msrc/views/repos/CommitDetail.tsx
@@ -1,3 +1,4 @@
1+import { escapeHtml } from "@kitajs/html";
12 import type { RepositoryRow } from "../../db/index.ts";
23 import type { SessionUser } from "../../middleware/session.ts";
34 import type { RenderedDiffFile } from "../../services/diffHighlight.ts";
@@ -66,7 +67,7 @@ export function CommitDetail({
6667 <div class="commit-card-meta-row">
6768 <span class="commit-meta-label">Author</span>
6869 <span class="commit-meta-value">
69- {meta.author} &lt;{meta.email}&gt;
70+ {escapeHtml(`${meta.author} <${meta.email}>`)}
7071 </span>
7172 </div>
7273 <div class="commit-card-meta-row">
@@ -78,6 +79,32 @@ export function CommitDetail({
7879 {formatDate(meta.date)}
7980 </time>
8081 </div>
82+ {(meta.committer !== meta.author ||
83+ meta.committerEmail !== meta.email) && (
84+ <>
85+ <div class="commit-card-meta-row">
86+ <span class="commit-meta-label">
87+ Committer
88+ </span>
89+ <span class="commit-meta-value">
90+ {escapeHtml(
91+ `${meta.committer} <${meta.committerEmail}>`,
92+ )}
93+ </span>
94+ </div>
95+ <div class="commit-card-meta-row">
96+ <span class="commit-meta-label">
97+ Commit date
98+ </span>
99+ <time
100+ class="commit-meta-value"
101+ datetime={meta.committerDate}
102+ >
103+ {formatDate(meta.committerDate)}
104+ </time>
105+ </div>
106+ </>
107+ )}
81108 <div class="commit-card-meta-row">
82109 <span class="commit-meta-label">Commit</span>
83110 <code class="commit-meta-value commit-sha-full mono">
Msrc/views/repos/FileBlob.tsx
@@ -89,7 +89,7 @@ export function FileBlob({
8989 ) : view.mimeType.startsWith("audio/") ? (
9090 // biome-ignore lint/a11y/useMediaCaption: captions unavailable for arbitrary repo files
9191 <audio
92- controls
92+ controls="true"
9393 src={`/${repo.name}/raw/${blobRef}/${filePath}`}
9494 class="file-media-audio"
9595 />