refactor: formalize safety rules, extract shared styles, reconcile docs (P2)
- Add backend/app/safety.py with explicit confirmation policy, multi-intent semantics, and MCP error taxonomy with retry classification - Add 26 unit tests for safety module (confirmation rules, error taxonomy) - Extract repeated inline styles into shared CSS classes in index.css (section-card, stat-label, status-badge, data-table, empty/error-state, pagination-bar) - Refactor DashboardPage, ReplayListPage, ReplayPage to use shared classes - Update README: add missing API endpoints, document safety/confirmation rules - Use proper HTML entities for arrow/dash characters to fix encoding glitches
This commit is contained in:
@@ -28,23 +28,21 @@ export function ReplayPage() {
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="page-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: "2rem" }}>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => navigate("/replay")}
|
||||
style={{ background: "none", border: "none", color: "var(--text-secondary)", fontSize: "0.875rem", cursor: "pointer", padding: "0 0 0.5rem 0", display: "flex", alignItems: "center", gap: "0.25rem" }}
|
||||
>
|
||||
← Back to All Replays
|
||||
</button>
|
||||
<h2>Audit Trail: <span style={{ fontFamily: "monospace", color: "var(--brand-primary)" }}>{threadId}</span></h2>
|
||||
<p>Detailed temporal log of agent reflections, MCP tool calls, and human overrides.</p>
|
||||
</div>
|
||||
<div className="page-header" style={{ marginBottom: "2rem" }}>
|
||||
<button
|
||||
onClick={() => navigate("/replay")}
|
||||
style={{ background: "none", border: "none", color: "var(--text-secondary)", fontSize: "0.875rem", cursor: "pointer", padding: "0 0 0.5rem 0", display: "flex", alignItems: "center", gap: "0.25rem" }}
|
||||
>
|
||||
← Back to All Replays
|
||||
</button>
|
||||
<h2>Audit Trail: <span style={{ fontFamily: "monospace", color: "var(--brand-primary)" }}>{threadId}</span></h2>
|
||||
<p>Detailed temporal log of agent reflections, MCP tool calls, and human overrides.</p>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div style={{ padding: "3rem", textAlign: "center", color: "var(--text-secondary)" }}>
|
||||
<p style={{ fontSize: "1.125rem", fontWeight: 600, color: "var(--brand-accent)" }}>Failed to load replay</p>
|
||||
<p style={{ marginTop: "0.5rem" }}>{error}</p>
|
||||
<div className="error-state">
|
||||
<p className="error-state__title">Failed to load replay</p>
|
||||
<p className="error-state__description">{error}</p>
|
||||
</div>
|
||||
) : isLoading ? (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 3fr", gap: "2rem" }}>
|
||||
@@ -52,29 +50,29 @@ export function ReplayPage() {
|
||||
<div className="skeleton-box" style={{ height: "400px", borderRadius: "var(--radius-xl)", background: "var(--bg-surface)" }}></div>
|
||||
</div>
|
||||
) : steps.length === 0 ? (
|
||||
<div style={{ padding: "3rem", textAlign: "center", color: "var(--text-secondary)" }}>
|
||||
<p style={{ fontSize: "1.125rem", fontWeight: 600 }}>No replay steps found</p>
|
||||
<p style={{ marginTop: "0.5rem" }}>This conversation has no recorded checkpoints.</p>
|
||||
<div className="empty-state">
|
||||
<p className="empty-state__title">No replay steps found</p>
|
||||
<p className="empty-state__description">This conversation has no recorded checkpoints.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 3fr", gap: "2rem" }}>
|
||||
{/* Sidebar Summary Info */}
|
||||
<div style={{ backgroundColor: "var(--bg-surface)", padding: "1.5rem", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-light)", alignSelf: "start" }}>
|
||||
<div className="section-card" style={{ alignSelf: "start" }}>
|
||||
<h3 style={{ fontSize: "1rem", marginBottom: "1.25rem", color: "var(--text-primary)" }}>Session Context</h3>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
||||
<div>
|
||||
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", color: "var(--text-secondary)", fontWeight: 600 }}>Thread ID</div>
|
||||
<div style={{ fontWeight: 600, fontSize: "0.8125rem", fontFamily: "monospace", wordBreak: "break-all" }}>{threadId}</div>
|
||||
<div className="stat-label">Thread ID</div>
|
||||
<div className="stat-value" style={{ fontSize: "0.8125rem", fontFamily: "monospace", wordBreak: "break-all" }}>{threadId}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", color: "var(--text-secondary)", fontWeight: 600 }}>Total Steps</div>
|
||||
<div style={{ fontSize: "0.9375rem" }}>{totalSteps}</div>
|
||||
<div className="stat-label">Total Steps</div>
|
||||
<div className="stat-value">{totalSteps}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: "0.75rem", textTransform: "uppercase", color: "var(--text-secondary)", fontWeight: 600 }}>Time Range</div>
|
||||
<div className="stat-label">Time Range</div>
|
||||
<div style={{ fontSize: "0.8125rem" }}>
|
||||
{steps[0]?.timestamp ? new Date(steps[0].timestamp).toLocaleString() : "N/A"}
|
||||
{" - "}
|
||||
{" \u2013 "}
|
||||
{steps[steps.length - 1]?.timestamp ? new Date(steps[steps.length - 1].timestamp).toLocaleString() : "N/A"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,7 +80,7 @@ export function ReplayPage() {
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div style={{ backgroundColor: "var(--bg-surface)", padding: "2rem", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-light)" }}>
|
||||
<div className="section-card" style={{ padding: "2rem" }}>
|
||||
<ReplayTimeline steps={steps as any} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user