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:
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 "—";
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in new issue
Block a user