Take the audit log and file checksums out of the chat surface

The thread panel rendered an "Audit" feed — "Chat message sent", "Task
run completed", "Approval requested" rows with monospace timestamps —
a server log sitting inside a conversation. Attachments showed an eight-
character SHA-256 fragment next to the file type. Neither belongs in a
surface a non-technical operator reads as people talking.

Remove the audit replay from the chat thread (and its now-dead
replayEventsForThread/auditPayload plumbing) and drop the checksum from
the attachment card. The audit trail itself is unchanged — it is still
recorded and still shown on the task activity tab. The e2e thread check
now asserts the reply box, not the log.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-26 17:56:15 -04:00
1 parent e80c8487f3
commit 287c072c9f
5 files changed
+3 -77

No files matched your search

@@ -8,7 +8,6 @@ import {
collectParticipants,
filterApprovalsForSpace,
isRootMessage,
replayEventsForThread,
} from "./chat/chat-helpers";
import { ROLE_KEY } from "../../../shared/domain";
import type { PageProps } from "./types";
@@ -47,10 +46,6 @@ export function ChatPage({ snapshot, actions }: PageProps) {
() => snapshot.approvals.filter((a) => a.orgId === orgId && a.state === "pending"),
[snapshot.approvals, orgId],
);
const auditEventsAll = useMemo(
() => snapshot.auditEvents.filter((event) => event.orgId === orgId),
[snapshot.auditEvents, orgId],
);
const channels = useMemo(() => spaces.filter((space) => space.spaceType !== "direct"), [spaces]);
const directs = useMemo(() => spaces.filter((space) => space.spaceType === "direct"), [spaces]);
const canBoardIntervene = useMemo(
@@ -106,10 +101,6 @@ export function ChatPage({ snapshot, actions }: PageProps) {
const threadMessages = selectedThread
? messagesInSpace.filter((m) => m.threadId === selectedThread.id && m.id !== selectedThread.rootMessageId)
: [];
const threadAuditEvents = useMemo(
() => (selectedThread ? replayEventsForThread(auditEventsAll, selectedThread, messagesInSpace) : []),
[auditEventsAll, selectedThread, messagesInSpace],
);
const selectedBoardInterventions = useMemo(
() => (selectedThread ? boardInterventionsAll.filter((item) => item.threadId === selectedThread.id) : []),
[boardInterventionsAll, selectedThread],
@@ -309,7 +300,6 @@ export function ChatPage({ snapshot, actions }: PageProps) {
slashInvocations={slashInvocationsAll}
boardInterventions={selectedBoardInterventions}
files={files}
auditEvents={threadAuditEvents}
context={{ employees, agents }}
canIntervene={canBoardIntervene}
attachmentFileId={threadAttachmentFileId}
@@ -212,7 +212,7 @@ function AttachmentCard({ file, handoff = false, onOpen }: { file: FileObjectRec
</span>
)}
</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{file.kind.toUpperCase()} · {file.sha256.slice(0, 8)}</div>
<div style={{ fontSize: 12, color: "var(--text-3)" }}>{file.kind.toUpperCase()}</div>
</div>
{canOpen && <IconButton icon="arrowUpRight" label={`Open ${file.name}`} onClick={onOpen ?? undefined} />}
</div>
@@ -1,7 +1,6 @@
import { useState, type FormEvent } from "react";
import { Button, Icon, IconButton, Pill } from "../../shared/ui";
import type {
AuditEventRecord,
BoardInterventionRecord,
ChatAttachmentRecord,
ChatMentionRecord,
@@ -10,7 +9,7 @@ import type {
ChatThreadRecord,
FileObjectRecord,
} from "../../../../shared/domain";
import { auditEventLabel, formatTime } from "../types";
import { formatTime } from "../types";
import { ChatMessageRow } from "./ChatMessageRow";
import { MentionTextarea } from "./MentionTextarea";
import { senderName, type MessageContext } from "./chat-helpers";
@@ -24,7 +23,6 @@ export function ChatThreadPanel({
slashInvocations,
boardInterventions,
files,
auditEvents,
context,
canIntervene,
attachmentFileId,
@@ -43,7 +41,6 @@ export function ChatThreadPanel({
slashInvocations: ChatSlashInvocationRecord[];
boardInterventions: BoardInterventionRecord[];
files: FileObjectRecord[];
auditEvents: AuditEventRecord[];
context: MessageContext;
canIntervene: boolean;
attachmentFileId: string;
@@ -118,7 +115,6 @@ export function ChatThreadPanel({
compact
/>
))}
{auditEvents.length > 0 && <AuditReplay events={auditEvents} />}
</div>
<form onSubmit={onSubmit} style={{ padding: 12, flexShrink: 0 }}>
<div style={{ border: "1px solid var(--border-strong)", borderRadius: 10, background: "var(--panel)", padding: "8px 10px" }}>
@@ -244,33 +240,3 @@ function boardStateLabel(state: string): string {
function formatBoardAction(action: string): string {
return action.replace(/_/g, " ");
}
function AuditReplay({ events }: { events: AuditEventRecord[] }) {
return (
<section style={{ borderTop: "1px solid var(--border-soft)", marginTop: 18, paddingTop: 14 }}>
<div className="section-label" style={{ marginBottom: 10 }}>Audit</div>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{events.map((event) => (
<div
key={event.id}
className="row"
style={{
alignItems: "baseline",
gap: 8,
border: "1px solid var(--border)",
borderRadius: 8,
padding: "8px 10px",
background: "var(--panel)",
minWidth: 0,
}}
>
<span className="mono" style={{ fontSize: 12, color: "var(--text-3)", flexShrink: 0 }}>{formatTime(event.createdAt)}</span>
<span style={{ fontSize: 12, color: "var(--text-1)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{auditEventLabel(event.entityType, event.action)}
</span>
</div>
))}
</div>
</section>
);
}
@@ -1,7 +1,6 @@
import type {
AgentRecord,
ApprovalRecord,
AuditEventRecord,
ChatAttachmentRecord,
ChatMessageRecord,
ChatSpaceRecord,
@@ -81,33 +80,6 @@ export function countMessagesIn(spaceId: string, messages: ChatMessageRecord[]):
return messages.filter((message) => message.spaceId === spaceId).length;
}
export function replayEventsForThread(
events: AuditEventRecord[],
thread: ChatThreadRecord,
messages: ChatMessageRecord[],
): AuditEventRecord[] {
const messageIds = new Set(
messages
.filter((message) => message.threadId === thread.id || message.id === thread.rootMessageId)
.map((message) => message.id),
);
return events
.filter((event) => {
if (event.entityType === "chat_message" && messageIds.has(event.entityId)) return true;
const payload = auditPayload(event);
return payload.threadId === thread.id || payload.sourceThreadId === thread.id;
})
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
}
function auditPayload(event: AuditEventRecord): Record<string, unknown> {
const parsed = JSON.parse(event.payloadJson) as unknown;
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Audit event payload is invalid.");
}
return parsed as Record<string, unknown>;
}
export function formatDay(iso: string): string {
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return "—";
+1 -3
View File
@@ -102,9 +102,7 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await expect(page.getByText("@Board").first()).toBeVisible();
await expect(page.getByText("/escalate").first()).toBeVisible();
await page.getByRole("button", { name: "Open thread" }).first().click();
await expect(page.getByText("Audit")).toBeVisible();
await expect(page.getByText("Chat message sent").first()).toBeVisible();
await expect(page.getByText("Approval requested").first()).toBeVisible();
await expect(page.getByRole("textbox", { name: "Thread reply" })).toBeVisible();
await page.getByRole("button", { name: "Approvals" }).click();
await expect(page.getByText("Escalate release").first()).toBeVisible();