jenkins b2cfbc94e8 hermes(webui): track the ignored HUX-04 artifacts card models
The artifacts/ directory matched a repo-wide ignore rule and was left
out of the UI card commit; its suites only passed locally because the
files existed on disk. Force-track the complete card so the source
push carries every module the tests import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 04:19:03 -03:00

222 lines
10 KiB
TypeScript

/** Accessible HUX-04 artifact UI. This module never fetches or executes content. */
import {
Code2,
Download,
FileText,
GitCompare,
History,
Image as ImageIcon,
Pencil,
Share2,
Sparkles,
} from "lucide-react";
import { useMemo, useState } from "react";
import { artifactsEnabled, normalizeArtifactWorkspace, normalizePreview } from "./model.ts";
import type {
Artifact,
ArtifactAuthorization,
ArtifactMutationIntent,
ArtifactPreview,
ArtifactScope,
DiffIntent,
HuxIdentity,
PromoteIntent,
RawArtifactEnvelope,
RawPreviewPayload,
} from "./types.ts";
interface ArtifactWorkspaceProps {
flags?: Iterable<string>;
identity: HuxIdentity;
scope: ArtifactScope;
page: RawArtifactEnvelope;
previews?: readonly RawPreviewPayload[];
busy?: boolean;
error?: string | null;
onLoadPreview?: (intent: ArtifactMutationIntent & {version: number}) => void;
onContinueEdit?: (intent: ArtifactMutationIntent) => void;
onDiff?: (intent: DiffIntent) => void;
onAuthorize?: (action: "download" | "share", intent: ArtifactMutationIntent) => void;
onDownload?: (intent: ArtifactMutationIntent & {version: number}) => void;
onShare?: (intent: ArtifactMutationIntent & {version: number}) => void;
onPromote?: (intent: PromoteIntent) => void;
}
function intent(artifact: Artifact, scope: ArtifactScope): ArtifactMutationIntent {
return {artifactId: artifact.id, expectedVersion: artifact.currentVersion, scope};
}
function RendererIcon({artifact}: {artifact: Artifact}) {
if (artifact.renderer === "image") return <ImageIcon aria-hidden />;
if (artifact.renderer === "code" || artifact.renderer === "data") return <Code2 aria-hidden />;
return <FileText aria-hidden />;
}
function Preview({artifact, preview}: {artifact: Artifact; preview: ArtifactPreview | null}) {
if (!preview) return <div className="hux-artifact-preview-empty">
<RendererIcon artifact={artifact} />
<p>Preview not loaded. Content stays behind its verified content reference.</p>
</div>;
if (preview.renderer === "image") {
return <figure className="hux-artifact-image">
<img src={preview.blobUrl!} alt={`Preview of ${artifact.title}`} />
<figcaption>{preview.mime}; local authorized preview</figcaption>
</figure>;
}
if (preview.renderer === "audio") {
return <div className="hux-artifact-audio">
<audio src={preview.blobUrl!} controls preload="metadata">
Audio preview is unavailable in this browser.
</audio>
<span>{preview.mime}; local authorized preview</span>
</div>;
}
const className = `hux-artifact-${preview.renderer}`;
return <div className={className} aria-label={`${preview.renderer} preview`}>
{preview.renderer === "report" && <h3>Report preview</h3>}
<pre tabIndex={0}><code>{preview.text}</code></pre>
</div>;
}
function AuthorizationAction({action, state, busy, onAuthorize, onAction}: {
action: "download" | "share";
state: ArtifactAuthorization["download"];
busy: boolean;
onAuthorize: () => void;
onAction: () => void;
}) {
const label = action === "download" ? "Download" : "Share";
const Icon = action === "download" ? Download : Share2;
if (state === "authorized") return <button type="button" disabled={busy} onClick={onAction}>
<Icon aria-hidden /> {label}
</button>;
if (state === "requires_approval") return <button type="button" disabled={busy} onClick={onAuthorize}>
<Icon aria-hidden /> Request {label.toLowerCase()} approval
</button>;
return <button type="button" disabled aria-label={`${label} unavailable`}>
<Icon aria-hidden /> {label} unavailable
</button>;
}
function VersionHistory({artifact, selectedVersion, setSelectedVersion, busy, scope, onDiff}: {
artifact: Artifact;
selectedVersion: number;
setSelectedVersion: (version: number) => void;
busy: boolean;
scope: ArtifactScope;
onDiff?: ArtifactWorkspaceProps["onDiff"];
}) {
return <section className="hux-artifact-history" aria-labelledby="hux-artifact-history-title">
<h3 id="hux-artifact-history-title"><History aria-hidden /> Immutable version history</h3>
<ol>{[...artifact.versions].reverse().map((version) => <li key={version.version}
data-current={version.version === artifact.currentVersion}>
<button type="button" aria-pressed={selectedVersion === version.version}
onClick={() => setSelectedVersion(version.version)}>
<strong>Version {version.version}</strong>
<span>{version.note || `Created by ${version.createdBy}`}</span>
<time dateTime={version.createdAt}>{new Date(version.createdAt).toLocaleString()}</time>
</button>
{version.version > 1 && onDiff && <button className="hux-artifact-diff" type="button"
disabled={busy} onClick={() => onDiff({
...intent(artifact, scope), fromVersion: version.diffFrom || version.version - 1,
toVersion: version.version,
})} aria-label={`Compare version ${version.version}`}><GitCompare aria-hidden /> Compare</button>}
</li>)}</ol>
</section>;
}
/** Render nothing unless all negotiated HUX-04 dependencies are active. */
export function ArtifactWorkspace(props: ArtifactWorkspaceProps) {
const enabled = artifactsEnabled(props.flags);
const page = useMemo(() => enabled ?
normalizeArtifactWorkspace(props.page, props.identity, props.scope) : null,
[enabled, props.identity, props.page, props.scope]);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [selectedVersion, setSelectedVersion] = useState<number | null>(null);
if (!enabled || !page) return null;
const artifact = page.artifacts.find((item) => item.id === selectedId) || page.artifacts[0] || null;
const version = artifact ? Math.min(selectedVersion || artifact.currentVersion, artifact.currentVersion) : null;
const rawPreview = artifact && version ? props.previews?.find((item) =>
item.artifact_id === artifact.id && item.version === version) : undefined;
const preview = artifact && rawPreview ?
normalizePreview(rawPreview, artifact, props.identity, props.scope) : null;
const attachments = artifact ? page.attachments.find((item) => item.artifactId === artifact.id) : null;
const authorization = artifact ? page.authorizations.find((item) => item.artifactId === artifact.id) : null;
function selectArtifact(item: Artifact) {
setSelectedId(item.id);
setSelectedVersion(item.currentVersion);
props.onLoadPreview?.({...intent(item, props.scope), version: item.currentVersion});
}
function selectVersion(next: number) {
if (!artifact) return;
setSelectedVersion(next);
props.onLoadPreview?.({...intent(artifact, props.scope), version: next});
}
return <section className="hux-artifacts" aria-label="Artifact workspace" aria-busy={props.busy}>
<nav className="hux-artifact-list" aria-label="Artifacts">
<header><h2><Sparkles aria-hidden /> Artifacts</h2>
<p>Durable outputs from this conversation.</p></header>
<ul>{page.artifacts.map((item) => <li key={item.id}>
<button type="button" aria-current={artifact?.id === item.id ? "page" : undefined}
onClick={() => selectArtifact(item)}>
<RendererIcon artifact={item} />
<span><strong>{item.title}</strong><small>{item.renderer} · v{item.currentVersion}</small></span>
</button>
</li>)}</ul>
{!page.artifacts.length && <p className="hux-artifact-empty">No artifacts are attached.</p>}
</nav>
<main className="hux-artifact-main">
{props.error && <p className="hux-artifact-error" role="alert">{props.error}</p>}
{page.rejected > 0 && <p className="hux-artifact-warning" role="status">
Some artifacts were withheld because their owner or workspace binding was invalid.
</p>}
{artifact && version ? <>
<header className="hux-artifact-heading">
<div><span>{artifact.renderer}</span><h2>{artifact.title}</h2>
<p>Version {version} · {artifact.sensitivity} · {artifact.language || artifact.type}</p></div>
<div className="hux-artifact-actions">
<button type="button" disabled={props.busy}
onClick={() => props.onContinueEdit?.(intent(artifact, props.scope))}>
<Pencil aria-hidden /> Continue editing
</button>
<AuthorizationAction action="download" state={authorization?.download || "unavailable"}
busy={Boolean(props.busy)}
onAuthorize={() => props.onAuthorize?.("download", intent(artifact, props.scope))}
onAction={() => props.onDownload?.({...intent(artifact, props.scope), version})} />
<AuthorizationAction action="share" state={authorization?.share || "unavailable"}
busy={Boolean(props.busy)}
onAuthorize={() => props.onAuthorize?.("share", intent(artifact, props.scope))}
onAction={() => props.onShare?.({...intent(artifact, props.scope), version})} />
</div>
</header>
<Preview artifact={artifact} preview={preview} />
<section className="hux-artifact-evidence" aria-label="Attached research evidence">
<div><strong>{attachments?.sourceCount || 0}</strong><span>Sources attached</span></div>
<div><strong>{attachments?.citationCount || 0}</strong><span>Citations attached</span></div>
</section>
<button className="hux-artifact-promote" type="button" disabled={props.busy ||
artifact.promotedVersion === version} onClick={() => props.onPromote?.({
...intent(artifact, props.scope), targetProjectId: props.scope.projectId, version,
})}>
{artifact.promotedVersion === version ? "Promoted to this project" : "Promote version to project"}
</button>
</> : <p className="hux-artifact-empty">Select an artifact to inspect it.</p>}
</main>
<aside className="hux-artifact-side" aria-label="Artifact versions">
{artifact && version ? <VersionHistory artifact={artifact} selectedVersion={version}
setSelectedVersion={selectVersion} busy={Boolean(props.busy)} scope={props.scope}
onDiff={props.onDiff} /> : <p>Version history will appear here.</p>}
</aside>
<p className="sr-only" role="status" aria-live="polite">
{props.busy ? "Artifact workspace is updating" : `${page.artifacts.length} artifacts available`}
</p>
</section>;
}