fix(ui): unify Memory and Evidence reading typography

This commit is contained in:
Codex
2026-09-13 17:07:37 +02:00
parent 3535fda958
commit 26c5605ff7
7 changed files with 188 additions and 24 deletions
+43 -1
View File
@@ -349,7 +349,49 @@ body.thot-full-document { margin: 0; }
.thot-prose h2 { font-size: var(--text-section); }
.thot-prose h3 { font-size: var(--text-body); }
.thot-prose :where(p, ul, ol, blockquote) { max-width: 75ch; }
.thot-knowledge-prose { width: 100%; min-width: 0; max-width: none; }
/* Shared reading contract for Memory and Evidence, independent of UI controls. */
.thot-knowledge-reader,
.thot-knowledge-prose {
font-family: var(--font-sans);
font-size: var(--text-body);
font-weight: 400;
line-height: 1.65;
letter-spacing: normal;
color: oklch(var(--foreground));
width: 100%;
min-width: 0;
max-width: none;
overflow-wrap: anywhere;
}
.thot-knowledge-title {
font: 600 var(--text-page)/1.3 var(--font-sans);
letter-spacing: -0.015em;
margin-top: 0.25rem;
}
.thot-knowledge-heading {
font: 600 var(--text-section)/1.4 var(--font-sans);
letter-spacing: normal;
margin: 0 0 0.5rem;
}
.thot-knowledge-meta {
font: 400 var(--text-control)/1.5 var(--font-sans);
letter-spacing: normal;
}
.thot-knowledge-meta dt { font-weight: 600; margin-bottom: 0.25rem; }
.thot-knowledge-reader code,
.thot-knowledge-reader pre,
.thot-prose.thot-knowledge-prose code {
font-family: var(--font-mono);
font-size: var(--text-control);
line-height: 1.65;
}
.thot-prose.thot-knowledge-prose :where(h1, h2, h3, h4, h5, h6) {
font: 600 var(--text-body)/1.5 var(--font-sans);
letter-spacing: normal;
margin: 1.5rem 0 0.5rem;
}
.thot-prose.thot-knowledge-prose > :first-child { margin-top: 0; }
.thot-knowledge-prose strong { font-weight: 600; }
.thot-knowledge-prose :where(p, ul, ol, blockquote) { max-width: none; }
.thot-prose.thot-knowledge-prose p + p { margin-top: 1.25em; }
.thot-knowledge-prose pre { white-space: pre-wrap; overflow-wrap: anywhere; }
@@ -29,7 +29,7 @@ function CopyText({ text, label, showText = true }: { text: string; label: strin
const { t } = useI18n();
const [notice, setNotice] = useState("");
return <div className="flex min-w-0 flex-wrap items-start gap-2">
{showText && <code className="min-w-0 flex-1 [overflow-wrap:anywhere] text-xs select-text">{text}</code>}
{showText && <code className="min-w-0 flex-1 [overflow-wrap:anywhere] text-sm select-text">{text}</code>}
<Button size="icon" variant="ghost" title={t("Copy {label}", { label })} aria-label={t("Copy {label}", { label })} onClick={async () => {
try { await navigator.clipboard.writeText(text); setNotice("Copied"); }
catch { setNotice("Select the text and copy it manually."); }
@@ -131,15 +131,15 @@ function UnitDetail({ item, location }: { item: EvidenceUnit; location: Evidence
const { t } = useI18n();
const source = item.provenance.kind === "manual" ? item.provenance.original : item.provenance;
const file = location.workspace ? `${location.workspace.replace(/\\/g, "/")}/evidence/${item.file}` : null;
return <article className="min-w-0 w-full space-y-5 pb-8 [overflow-wrap:anywhere]">
<header><p className="text-xs text-muted-foreground">{t(states[item.status])} · {item.purposes.map(purpose => t(purpose.replaceAll("_", " "))).join(", ")}</p><h2 className="mt-1 font-sans text-xl font-semibold">{item.title}</h2></header>
return <article className="thot-knowledge-reader space-y-6 pb-8">
<header><p className="thot-knowledge-meta text-muted-foreground">{t(states[item.status])} · {item.purposes.map(purpose => t(purpose.replaceAll("_", " "))).join(", ")}</p><h2 className="thot-knowledge-title">{item.title}</h2></header>
{file && <CopyText text={file} label={t("Evidence file path")} />}
{item.status !== "active" && <p className="text-sm text-muted-foreground">{["invalid", "removed"].includes(item.status) ? t("The file is invalid or removed. Its last active content is shown here.") : t("This is the file version awaiting consolidation.")} {t("The core continues to use its last active version.")}</p>}
{Object.entries(item.applies_to).some(([, values]) => values.length > 0) && <section><h3 className="font-medium">{t("Scope")}</h3>{Object.entries(item.applies_to).map(([name, values]) => values.length > 0 && <p className="mt-2 break-words text-sm" key={name}><span className="capitalize">{t(name)}</span>: {values.join(", ")}</p>)}</section>}
{Object.entries(item.payload).map(([field, value]) => <section key={field}><h3 className="mb-2 text-sm font-semibold capitalize">{t(field.replaceAll("_", " "))}</h3>{typeof value === "string" ? field === "sql" ? <pre className="overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md bg-muted p-3 text-sm">{value}</pre> : <MarkdownView source={value} readable /> : Array.isArray(value) ? <ul className="list-inside list-disc space-y-2 text-sm">{value.map(v => <li key={v}>{v}</li>)}</ul> : <dl className="space-y-2 text-sm">{Object.entries(value).map(([key, text]) => <div key={key}><dt className="font-mono font-medium">{key || t("(empty value)")}</dt><dd><MarkdownView source={text} readable /></dd></div>)}</dl>}</section>)}
<section className="border-t pt-4"><h3 className="font-medium">{t("Provenance")}</h3><p className="mt-2 text-sm">{item.provenance.kind === "manual" ? t("Manual declaration by {author}.", { author: item.provenance.declared_by ?? "" }) : t("Derived from a source document.")}</p>
{source?.source_file && <><p className="mt-2 [overflow-wrap:anywhere] text-xs">{item.provenance.kind === "manual" ? t("Original document (lineage): ") : t("Source: ")}{source.source_file}</p>{source.supporting_excerpts?.map((text, i) => <div key={i} className="mt-4"><MarkdownView source={text} readable /></div>)}</>}
{Object.entries(item.applies_to).some(([, values]) => values.length > 0) && <section><h3 className="thot-knowledge-heading">{t("Scope")}</h3>{Object.entries(item.applies_to).map(([name, values]) => values.length > 0 && <p className="mt-3" key={name}><span className="capitalize">{t(name)}</span>: {values.join(", ")}</p>)}</section>}
{Object.entries(item.payload).map(([field, value]) => <section key={field}><h3 className="thot-knowledge-heading capitalize">{t(field.replaceAll("_", " "))}</h3>{typeof value === "string" ? field === "sql" ? <pre className="overflow-auto whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md bg-muted p-3">{value}</pre> : <MarkdownView source={value} readable /> : Array.isArray(value) ? <ul className="list-outside list-disc space-y-2 pl-5">{value.map(v => <li key={v}>{v}</li>)}</ul> : <dl className="space-y-4">{Object.entries(value).map(([key, text]) => <div key={key}><dt className="mb-2 font-semibold">{key ? <code>{key}</code> : t("(empty value)")}</dt><dd><MarkdownView source={text} readable /></dd></div>)}</dl>}</section>)}
<section className="border-t pt-6"><h3 className="thot-knowledge-heading">{t("Provenance")}</h3><p>{item.provenance.kind === "manual" ? t("Manual declaration by {author}.", { author: item.provenance.declared_by ?? "" }) : t("Derived from a source document.")}</p>
{source?.source_file && <><p className="thot-knowledge-meta mt-3">{item.provenance.kind === "manual" ? t("Original document (lineage): ") : t("Source: ")}<code>{source.source_file}</code></p>{source.supporting_excerpts?.map((text, i) => <div key={i} className="mt-5"><MarkdownView source={text} readable /></div>)}</>}
</section>
{item.review_items.length > 0 && <section><h3 className="font-medium">{t("Review required")}</h3>{item.review_items.map((review, i) => <p key={i} className="mt-2 text-sm">{t(review.message)}</p>)}</section>}
{item.review_items.length > 0 && <section><h3 className="thot-knowledge-heading">{t("Review required")}</h3>{item.review_items.map((review, i) => <p key={i} className="mt-3">{t(review.message)}</p>)}</section>}
</article>;
}
+13 -13
View File
@@ -180,21 +180,21 @@ function MemoryWorkspace({ workspace, onDirtyChange, onBusyChange }: {
</section>
<section aria-label={t("Memory detail")} className="thot-administration-detail">
{!draft && !selected && <p className="py-8 text-sm text-muted-foreground">{t("Select a card to inspect its content, scope and provenance.")}</p>}
{selected && !draft && <div className="min-w-0 w-full space-y-5 [overflow-wrap:anywhere]">
{selected && !draft && <div className="thot-knowledge-reader space-y-6">
{formattingExample && <p role="note" className="rounded-md border bg-muted p-3 text-sm">{t("Simulated cards for visual review only. Not saved, indexed or sent to the model.")}</p>}
<div><p className="text-xs text-muted-foreground">{t(families[selected.family])}</p><h2 className="mt-1 break-words font-sans text-xl font-semibold">{selected.subject}</h2></div>
<dl className="space-y-4 text-sm">
<div><p className="thot-knowledge-meta text-muted-foreground">{t(families[selected.family])}</p><h2 className="thot-knowledge-title">{selected.subject}</h2></div>
<dl className="space-y-6">
{(["detail", "scope", "rationale", "question", "sql"] as const).filter(k => selected[k]).map(key => <div key={key}>
<dt className="mb-1 font-semibold capitalize">{key === "detail" ? t("Content") : key === "sql" ? t("Approved SQL") : t(key)}</dt>
<dd className="min-w-0 w-full">{key === "sql" ? <pre className="whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md bg-muted p-3 font-mono text-sm">{selected[key]}</pre> : <MarkdownView source={selected[key]} readable />}</dd></div>)}
<div><dt className="font-semibold">{t("Provenance")}</dt><dd>{formattingExample ? t("Formatting example, not workspace knowledge.") : selected.origin === "manual" ? t("Created manually") : t("Workflow session {session}", { session: selected.session_id ?? "" })}</dd></div>
{selected.decision_seq !== null && <div><dt className="font-semibold">{t("Source decision")}</dt><dd>{selected.decision_seq}</dd></div>}
<div><dt className="font-semibold">{t("Card ID")}</dt><dd className="break-all font-mono text-xs">{selected.id}</dd></div>
<div><dt className="font-semibold">{t("Updated")}</dt><dd>{new Date(selected.updated_at).toLocaleString(getLocale())}</dd></div>
<div><dt className="font-semibold">{t("Created")}</dt><dd>{new Date(selected.created_at).toLocaleString(getLocale())}</dd></div>
{selected.concepts.length > 0 && <div><dt className="font-semibold">{t("Concepts")}</dt><dd>{selected.concepts.join(", ")}</dd></div>}
{selected.dependencies.length > 0 && <div><dt className="font-semibold">{t("Schema dependencies")}</dt><dd><ul>{selected.dependencies.map((d, i) => <li key={i}>{[d.database, d.schema_name, d.table, d.column].filter(Boolean).join(" / ")}</li>)}</ul></dd></div>}
{selected.links.length > 0 && <div><dt className="font-semibold">{t("Linked cards")}</dt><dd><ul>{selected.links.map(link => <li key={link.target_id} className="my-2"><button className="text-left text-primary underline" onClick={() => void open(link.target_id)}>{link.meaning}</button></li>)}</ul></dd></div>}
<dt className="thot-knowledge-heading capitalize">{key === "detail" ? t("Content") : key === "sql" ? t("Approved SQL") : t(key)}</dt>
<dd className="min-w-0 w-full">{key === "sql" ? <pre className="whitespace-pre-wrap [overflow-wrap:anywhere] rounded-md bg-muted p-3">{selected[key]}</pre> : <MarkdownView source={selected[key]} readable />}</dd></div>)}
<div className="border-t pt-6"><dt className="thot-knowledge-heading">{t("Provenance")}</dt><dd>{formattingExample ? t("Formatting example, not workspace knowledge.") : selected.origin === "manual" ? t("Created manually") : t("Workflow session {session}", { session: selected.session_id ?? "" })}</dd></div>
{selected.decision_seq !== null && <div className="thot-knowledge-meta"><dt>{t("Source decision")}</dt><dd>{selected.decision_seq}</dd></div>}
<div className="thot-knowledge-meta"><dt>{t("Card ID")}</dt><dd><code>{selected.id}</code></dd></div>
<div className="thot-knowledge-meta"><dt>{t("Updated")}</dt><dd>{new Date(selected.updated_at).toLocaleString(getLocale())}</dd></div>
<div className="thot-knowledge-meta"><dt>{t("Created")}</dt><dd>{new Date(selected.created_at).toLocaleString(getLocale())}</dd></div>
{selected.concepts.length > 0 && <div><dt className="thot-knowledge-heading">{t("Concepts")}</dt><dd>{selected.concepts.join(", ")}</dd></div>}
{selected.dependencies.length > 0 && <div><dt className="thot-knowledge-heading">{t("Schema dependencies")}</dt><dd><ul className="list-outside list-disc space-y-2 pl-5">{selected.dependencies.map((d, i) => <li key={i}><code>{[d.database, d.schema_name, d.table, d.column].filter(Boolean).join(" / ")}</code></li>)}</ul></dd></div>}
{selected.links.length > 0 && <div><dt className="thot-knowledge-heading">{t("Linked cards")}</dt><dd><ul>{selected.links.map(link => <li key={link.target_id} className="my-2"><button className="text-left text-primary underline" onClick={() => void open(link.target_id)}>{link.meaning}</button></li>)}</ul></dd></div>}
</dl>
{!formattingExample && <div className="flex gap-2"><Button disabled={busy} onClick={() => setDraft(memoryInput(selected))}>{t("Edit card")}</Button>
<Button variant="outline" disabled={busy} onClick={() => setDeleteConfirm(true)}>{t("Delete card")}</Button></div>}