fix: refine session report and live activity layout

This commit is contained in:
2026-07-24 00:21:36 +02:00
parent 6ca4275ff2
commit 18233d59af
6 changed files with 40 additions and 6 deletions
@@ -54,6 +54,10 @@ test("uses the activity-only Markdown wrapping renderer for long content", () =>
const renderers = document.querySelectorAll(".thot-activity-prose"); const renderers = document.querySelectorAll(".thot-activity-prose");
expect(renderers).toHaveLength(3); expect(renderers).toHaveLength(3);
expect(renderers[0]).toHaveClass("thot-prose"); expect(renderers[0]).toHaveClass("thot-prose");
for (const renderer of renderers) {
expect(renderer).toHaveClass("min-w-0", "w-full", "max-w-none");
expect(renderer).not.toHaveClass("max-w-[74ch]");
}
expect(renderers[1].querySelector("code")).not.toBeNull(); expect(renderers[1].querySelector("code")).not.toBeNull();
expect(renderers[2].querySelector("td")).not.toBeNull(); expect(renderers[2].querySelector("td")).not.toBeNull();
expect(renderers[2].querySelector("pre")).not.toBeNull(); expect(renderers[2].querySelector("pre")).not.toBeNull();
+5 -5
View File
@@ -39,7 +39,7 @@ export function formatModelActivity(text: string): string {
function MarkdownBody({ entry }: { entry: ActivityEntry }) { function MarkdownBody({ entry }: { entry: ActivityEntry }) {
const tone = entry.kind === "thinking" ? "text-muted-foreground" : "text-foreground"; const tone = entry.kind === "thinking" ? "text-muted-foreground" : "text-foreground";
return ( return (
<div className={`thot-prose thot-activity-prose max-w-[74ch] text-sm ${tone}`}> <div className={`thot-prose thot-activity-prose min-w-0 w-full max-w-none text-sm ${tone}`}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{formatModelActivity(entry.text)}</ReactMarkdown> <ReactMarkdown remarkPlugins={[remarkGfm]}>{formatModelActivity(entry.text)}</ReactMarkdown>
</div> </div>
); );
@@ -47,7 +47,7 @@ function MarkdownBody({ entry }: { entry: ActivityEntry }) {
function ActivityRow({ entry }: { entry: ActivityEntry }) { function ActivityRow({ entry }: { entry: ActivityEntry }) {
return ( return (
<article className="border-b border-border/50 py-2 last:border-b-0"> <article className="w-full min-w-0 border-b border-border/50 py-2 last:border-b-0">
<div className="mb-1 flex flex-wrap items-center gap-1 text-[0.65rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground"> <div className="mb-1 flex flex-wrap items-center gap-1 text-[0.65rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
<span className="text-foreground/80">{ACTIVITY_LABELS[entry.kind]}</span> <span className="text-foreground/80">{ACTIVITY_LABELS[entry.kind]}</span>
{entry.phase && <span aria-label={`Phase ${entry.phase}`}>{entry.phase}</span>} {entry.phase && <span aria-label={`Phase ${entry.phase}`}>{entry.phase}</span>}
@@ -79,8 +79,8 @@ export function ModelActivityPanel({
return ( return (
<aside className={desktopSplit <aside className={desktopSplit
? "static z-auto flex w-[var(--activity-panel-width)] shrink-0 flex-col border-r-0 bg-sidebar" ? "static z-auto flex min-w-0 w-[var(--activity-panel-width)] shrink-0 flex-col border-r-0 bg-sidebar"
: "fixed inset-y-0 left-0 z-30 flex w-[min(90vw,24rem)] shrink-0 flex-col border-r border-border bg-sidebar" : "fixed inset-y-0 left-0 z-30 flex min-w-0 w-[min(90vw,24rem)] shrink-0 flex-col border-r border-border bg-sidebar"
}> }>
<div className="flex items-center justify-between border-b border-border/60 px-3 py-2"> <div className="flex items-center justify-between border-b border-border/60 px-3 py-2">
<h2 className="font-heading text-sm font-semibold text-foreground">Model activity</h2> <h2 className="font-heading text-sm font-semibold text-foreground">Model activity</h2>
@@ -93,7 +93,7 @@ export function ModelActivityPanel({
data-testid="activity-scroll" data-testid="activity-scroll"
tabIndex={0} tabIndex={0}
aria-label="Model activity timeline" aria-label="Model activity timeline"
className="flex-1 overflow-y-auto px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40" className="w-full min-w-0 flex-1 overflow-y-auto px-3 py-2 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40"
onScroll={(event) => { onScroll={(event) => {
followRef.current = isNearBottom(event.currentTarget); followRef.current = isNearBottom(event.currentTarget);
}} }}
@@ -174,6 +174,10 @@ test("never renders technical approval, promotion, or memory decisions", async (
{ type: "table_approved", subject: "fact_a" }, { type: "table_approved", subject: "fact_a" },
{ type: "table_promoted", subject: "fact_a" }, { type: "table_promoted", subject: "fact_a" },
{ type: "column_promoted", subject: "fact_a.id" }, { type: "column_promoted", subject: "fact_a.id" },
{ type: "cte_approved", subject: "eventi_device" },
{ type: "sql_approved", subject: "phase:7" },
{ type: "datamart_requested", subject: "phase:8" },
{ type: "datamart_declined", subject: "phase:8" },
{ type: "memory_promoted", subject: "concetto" }, { type: "memory_promoted", subject: "concetto" },
{ type: "column_excluded", subject: "fact_a.note", detail: "Non **pertinente**" }, { type: "column_excluded", subject: "fact_a.note", detail: "Non **pertinente**" },
].map((decision) => JSON.stringify(decision)).join("\n"); ].map((decision) => JSON.stringify(decision)).join("\n");
@@ -189,7 +193,17 @@ test("never renders technical approval, promotion, or memory decisions", async (
expect(await screen.findByText("column excluded")).toBeInTheDocument(); expect(await screen.findByText("column excluded")).toBeInTheDocument();
expect(screen.getByText("pertinente").tagName).toBe("STRONG"); expect(screen.getByText("pertinente").tagName).toBe("STRONG");
for (const hidden of ["phase approved", "table approved", "table promoted", "column promoted", "memory promoted"]) { for (const hidden of [
"phase approved",
"table approved",
"table promoted",
"column promoted",
"cte approved",
"sql approved",
"datamart requested",
"datamart declined",
"memory promoted",
]) {
expect(screen.queryByText(hidden)).not.toBeInTheDocument(); expect(screen.queryByText(hidden)).not.toBeInTheDocument();
} }
}); });
@@ -38,6 +38,10 @@ const HIDDEN_DECISION_TYPES = new Set([
"table_approved", "table_approved",
"table_promoted", "table_promoted",
"column_promoted", "column_promoted",
"cte_approved",
"sql_approved",
"datamart_requested",
"datamart_declined",
]); ]);
function DocBody({ doc }: { doc: SessionDocument }) { function DocBody({ doc }: { doc: SessionDocument }) {
+8
View File
@@ -85,6 +85,10 @@ def _rich_session(tmp_path):
_decision(6, "table_promoted", "fact_procedure"), _decision(6, "table_promoted", "fact_procedure"),
_decision(7, "column_promoted", "fact_procedure.status"), _decision(7, "column_promoted", "fact_procedure.status"),
_decision(8, "column_excluded", "fact_procedure.note", "Non pertinente"), _decision(8, "column_excluded", "fact_procedure.note", "Non pertinente"),
_decision(9, "cte_approved", "eventi_device"),
_decision(10, "sql_approved", "phase:7"),
_decision(11, "datamart_requested", "phase:8"),
_decision(12, "datamart_declined", "phase:8"),
] ]
(session_dir / "question.md").write_text(question) (session_dir / "question.md").write_text(question)
(session_dir / "sql_final.sql").write_text("SELECT COUNT(*) FROM fact_procedure") (session_dir / "sql_final.sql").write_text("SELECT COUNT(*) FROM fact_procedure")
@@ -160,6 +164,10 @@ def test_build_documents_groups_memories_and_filters_summary_decisions(tmp_path)
"phase_approved", "phase_approved",
"table_promoted", "table_promoted",
"column_promoted", "column_promoted",
"cte_approved",
"sql_approved",
"datamart_requested",
"datamart_declined",
): ):
assert hidden not in decisions assert hidden not in decisions
+4
View File
@@ -162,6 +162,10 @@ _SUMMARY_HIDDEN_TYPES = _MEMORY_TYPES | frozenset(
"table_approved", "table_approved",
"table_promoted", "table_promoted",
"column_promoted", "column_promoted",
"cte_approved",
"sql_approved",
"datamart_requested",
"datamart_declined",
} }
) )