style(frontend): look&feel v2 refinements — 70% gate modal, structured cards, mono labels
- ArtifactGateWidget: gate modal 90%→70% of viewport, larger heading, more padding. - ArtifactView StructuredValue: depth-aware hierarchy (top-level hairline dividers, warm bg-muted card surfaces + shadow for object-array items). - index.css: .thot-label micro-labels switched to the mono register (SF Mono) with tightened tracking, distinguishing labels/meta from sans body prose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
Marco Pancotti
co-authored by
Claude Opus 4.8
parent
9fe1c9321b
commit
e9b2934ef7
@@ -144,11 +144,14 @@
|
||||
* Scoped so it never leaks into widgets.
|
||||
*/
|
||||
@layer components {
|
||||
/* Standard micro-label: panel headers, nav sections, meta rows. */
|
||||
/* Standard micro-label: panel headers, nav sections, meta rows. Set in the
|
||||
mono register so labels/meta read as a distinct typographic layer from the
|
||||
sans body prose (mono needs less tracking than the sans did). */
|
||||
.thot-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: oklch(var(--muted-foreground));
|
||||
}
|
||||
|
||||
@@ -77,8 +77,10 @@ function humanizeKey(k: string): string {
|
||||
|
||||
/** Renders any artifact payload as readable text — NEVER raw JSON. Objects become
|
||||
* labelled sections, arrays become bullet lists, primitives become paragraphs;
|
||||
* strings render as markdown. Nested structures recurse. */
|
||||
function StructuredValue({ value }: { value: unknown }): ReactElement {
|
||||
* strings render as markdown. Nested structures recurse. `depth` drives the
|
||||
* visual hierarchy: top-level sections are split by hairline rules, nested fields
|
||||
* sit tighter, and object-arrays become grouped cards. */
|
||||
function StructuredValue({ value, depth = 0 }: { value: unknown; depth?: number }): ReactElement {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return <p className="text-sm text-muted-foreground">—</p>;
|
||||
}
|
||||
@@ -98,21 +100,32 @@ function StructuredValue({ value }: { value: unknown }): ReactElement {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{value.map((item, i) => (
|
||||
<div key={i} className="rounded-lg border border-border/60 p-3">
|
||||
<StructuredValue value={item} />
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-xl border border-border/70 bg-muted/50 p-4 shadow-xs"
|
||||
>
|
||||
<StructuredValue value={item} depth={depth + 1} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const entries = Object.entries(value as Record<string, unknown>);
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{Object.entries(value as Record<string, unknown>).map(([k, v]) => (
|
||||
<section key={k}>
|
||||
<h4 className="thot-label mb-1.5">
|
||||
{humanizeKey(k)}
|
||||
</h4>
|
||||
<StructuredValue value={v} />
|
||||
<div className="flex flex-col">
|
||||
{entries.map(([k, v], i) => (
|
||||
<section
|
||||
key={k}
|
||||
className={
|
||||
i === 0
|
||||
? undefined
|
||||
: depth === 0
|
||||
? "mt-5 border-t border-border/50 pt-5"
|
||||
: "mt-3"
|
||||
}
|
||||
>
|
||||
<h4 className="thot-label mb-1.5">{humanizeKey(k)}</h4>
|
||||
<StructuredValue value={v} depth={depth + 1} />
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ReservedControls } from "./ReservedControls";
|
||||
import { LinkageHost } from "./LinkageHost";
|
||||
|
||||
/**
|
||||
* Artifact review gate rendered as a full-screen (90%) modal: the artifact fills
|
||||
* Artifact review gate rendered as a centered (70%) modal: the artifact fills
|
||||
* the top (scrollable) area via ArtifactView; the action bar (options + reserved
|
||||
* controls) sits at the bottom. The gate contract forbids silent dismissal, so the
|
||||
* dialog has no close button and is not closeable by Esc/backdrop — the only way
|
||||
@@ -30,11 +30,13 @@ export function ArtifactGateWidget({ descriptor, onRespond, sessionId }: WidgetP
|
||||
<Dialog open>
|
||||
<DialogContent
|
||||
showCloseButton={false}
|
||||
className="grid h-[90vh] w-[90vw] max-w-[90vw] grid-rows-[auto_1fr_auto] gap-3 sm:max-w-[90vw]"
|
||||
className="grid h-[70vh] w-[70vw] max-w-[70vw] grid-rows-[auto_1fr_auto] gap-3.5 sm:max-w-[70vw]"
|
||||
>
|
||||
<DialogTitle>{descriptor.title ?? "Artifact review"}</DialogTitle>
|
||||
<DialogTitle className="text-lg leading-snug">
|
||||
{descriptor.title ?? "Artifact review"}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="min-h-0 overflow-auto rounded-xl border border-border/70 bg-background p-3">
|
||||
<div className="min-h-0 overflow-auto rounded-xl border border-border/70 bg-background p-5">
|
||||
{descriptor.artifact ? (
|
||||
<GateArtifactBody artifact={descriptor.artifact} sessionId={sessionId} />
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user