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.
|
* Scoped so it never leaks into widgets.
|
||||||
*/
|
*/
|
||||||
@layer components {
|
@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 {
|
.thot-label {
|
||||||
|
font-family: var(--font-mono);
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.06em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: oklch(var(--muted-foreground));
|
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
|
/** Renders any artifact payload as readable text — NEVER raw JSON. Objects become
|
||||||
* labelled sections, arrays become bullet lists, primitives become paragraphs;
|
* labelled sections, arrays become bullet lists, primitives become paragraphs;
|
||||||
* strings render as markdown. Nested structures recurse. */
|
* strings render as markdown. Nested structures recurse. `depth` drives the
|
||||||
function StructuredValue({ value }: { value: unknown }): ReactElement {
|
* 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 === "") {
|
if (value === null || value === undefined || value === "") {
|
||||||
return <p className="text-sm text-muted-foreground">—</p>;
|
return <p className="text-sm text-muted-foreground">—</p>;
|
||||||
}
|
}
|
||||||
@@ -98,21 +100,32 @@ function StructuredValue({ value }: { value: unknown }): ReactElement {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{value.map((item, i) => (
|
{value.map((item, i) => (
|
||||||
<div key={i} className="rounded-lg border border-border/60 p-3">
|
<div
|
||||||
<StructuredValue value={item} />
|
key={i}
|
||||||
|
className="rounded-xl border border-border/70 bg-muted/50 p-4 shadow-xs"
|
||||||
|
>
|
||||||
|
<StructuredValue value={item} depth={depth + 1} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const entries = Object.entries(value as Record<string, unknown>);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col">
|
||||||
{Object.entries(value as Record<string, unknown>).map(([k, v]) => (
|
{entries.map(([k, v], i) => (
|
||||||
<section key={k}>
|
<section
|
||||||
<h4 className="thot-label mb-1.5">
|
key={k}
|
||||||
{humanizeKey(k)}
|
className={
|
||||||
</h4>
|
i === 0
|
||||||
<StructuredValue value={v} />
|
? 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>
|
</section>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ReservedControls } from "./ReservedControls";
|
|||||||
import { LinkageHost } from "./LinkageHost";
|
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
|
* the top (scrollable) area via ArtifactView; the action bar (options + reserved
|
||||||
* controls) sits at the bottom. The gate contract forbids silent dismissal, so the
|
* 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
|
* 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>
|
<Dialog open>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
showCloseButton={false}
|
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 ? (
|
{descriptor.artifact ? (
|
||||||
<GateArtifactBody artifact={descriptor.artifact} sessionId={sessionId} />
|
<GateArtifactBody artifact={descriptor.artifact} sessionId={sessionId} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user