Component reference
ResourceLink
A reference to one thing that can be opened: a file, a message, a record.
Preview
ResourceLink has no story of its own; this one draws it as part of DetailSection.
Sections, timeline, and resource references
Shows the shared detail components on their own; does not simulate a work detail panel.
render: () => (
<Stack>
<DetailSection title="Section title" aside="Supporting note">
<p>The body is provided by the caller.</p>
</DetailSection>
<Timeline>
<TimelineItem
state="success"
title="Draft submitted"
stateLabel="Done"
description="09:12 · 4 files changed"
/>
<TimelineItem
state="returned"
title="Review returned"
stateLabel="Returned"
description="Two comments need a reply before the next run."
/>
<TimelineItem state="processing" title="Checks running" stateLabel="In progress" />
</Timeline>
<ResourceLink href="#sample" title="Sample link" detail="Link style" />
<ResourceLink
title="A very long sample resource name to observe how caller-supplied long text behaves"
detail="Button style"
/>
</Stack>
)Installation
bun add meowtail
Usage
Taken from the examples on this page.
import { ResourceLink } from "meowtail";
<ResourceLink href="#sample" title="Sample link" detail="Link style" />Examples
Other stories that draw ResourceLink.
Inline text
A themed inline text link inside a sentence. ResourceLink remains the whole-row reference to an openable object.
render: () => (
<Stack align="start">
<p>
Open the{" "}
<Link href="https://github.com/lidessen/meowland" target="_blank" rel="noreferrer">
Meowland repository
</Link>{" "}
for the library source.
</p>
<ResourceLink
href="https://github.com/lidessen/meowland"
title="Meowland repository"
detail="Whole-row reference, not an inline link"
/>
</Stack>
)Complete Assistant composition
The same Assistant renders tools and tasks by default and keeps messages, draft, attachments, activity, questions and safe Markdown.
export function agentAssistantDemo(): Child {
const messages = signal<readonly AssistantMessage[]>([
turn("ask-check", "Check the shared components and tell me what still needs a decision."),
{
id: "agent-run",
role: "assistant",
author: "Assistant",
status: "streaming",
parts: [
{
id: "summary",
kind: "text",
text: "Checking shared components. **Public progress summary**: The file check is in progress, the independent review finished executing, and its public reply is still pending.\n\n`<script>alert(1)</script>` is description text. Raw HTML is not executed either: <img src=x onerror=alert(1)>.",
},
{
id: "inspect-tool",
kind: "tool",
icon: "file-text",
label: "Read component files",
summary: "packages/meowtail/src/chat/long-component-file-name.tsx",
state: "running",
statusLabel: "Receiving output",
input: payload,
output: resultLines,
outputSummary: "Returned 24 records; the full result can be expanded.",
maxLines: 3,
defaultOpen: true,
canCancel: true,
},
{
id: "delegate-task",
kind: "task",
title: "Independent keyboard review",
state: "success",
statusLabel: "Succeeded · Public reply not received",
executionText: "Execution returned; 5 / 5 items complete.",
replyText: "Waiting for the public reply does not mean execution failed.",
progress: { value: 5, max: 5, label: "Execution check 5 / 5 items" },
},
{
id: "retry-tool",
kind: "tool",
icon: "database",
label: "Read index",
state: "error",
statusLabel: "Local simulated read failed",
input: "index.json",
output: "Previous confirmed data is retained.",
canRetry: true,
},
{ id: "files", kind: "attachments", items: [attachment] },
{ id: "references", kind: "text", text: "Artifacts and sources provided by the host" },
],
},
]);
const draft = signal<AssistantDraft>({
text: "Continue checking composition in a narrow container.",
attachments: [],
});
const questions = signal<readonly AssistantQuestion[]>([
{
id: "scope",
title: "Which areas should be checked next?",
state: "open",
description: "Waiting for your answer. No reply does not mean execution failed.",
options: [
{ value: "tools", label: "Tools and tasks" },
{ value: "composer", label: "Input and attachments" },
],
},
]);
let alive = true;
const timers = new Set<ReturnType<typeof setTimeout>>();
const patchTool = (
id: string,
patch: Partial<Extract<AssistantMessage["parts"][number], { kind: "tool" }>>,
) => {
messages.value = messages.value.map((message) => ({
...message,
parts: message.parts.map((part) =>
part.kind === "tool" && part.id === id ? { ...part, ...patch } : part,
),
}));
};
return (
<Stack
style={{ height: "min(1000px, calc(100dvh - 48px))", minHeight: "420px" }}
ref={() => () => {
alive = false;
timers.forEach(clearTimeout);
}}
>
<Inline>
<Button
size="small"
variant="outline"
onClick={() => {
const part = messages.value
.flatMap((message) => message.parts)
.find((item) => item.id === "inspect-tool");
if (part?.kind !== "tool") return;
patchTool("inspect-tool", {
output: `${part.output ?? ""}\nNew output: A text update <img src=x onerror=alert(1)> stays inert.`,
outputSummary: "Appended one local result; expansion and focus are unchanged.",
});
}}
>
Append tool output
</Button>
</Inline>
<AssistantView
title="Agent workflow"
description="Shared default composition · Local data, not connected to an Agent runtime"
messages={messages}
draft={draft}
onDraftChange={(next) => {
draft.value = next;
}}
onSubmit={async (snapshot) => {
messages.value = [
...messages.value,
{
id: `user-${messages.value.length}`,
role: "user",
author: "You",
parts: [{ id: "text", kind: "text", text: snapshot.text }],
},
];
return { accepted: true };
}}
activity={{
title: "Check shared components",
statusText: "File check in progress",
tone: "processing",
}}
work={[
{
id: "running",
title: "File check",
statusText: "Receiving output",
tone: "processing",
executionText: "Running",
},
{
id: "queued",
title: "Organize the report",
statusText: "Queued · Not started yet",
tone: "idle",
executionText: "Waiting for scheduling",
},
{
id: "returned",
title: "Independent keyboard review",
statusText: "Succeeded · Waiting for public reply",
tone: "success",
executionText: "Execution complete",
replyText: "Public reply not received yet",
},
]}
questions={questions}
onAnswer={(id, answer) => {
questions.value = questions.value.map((question) =>
question.id === id
? {
...question,
state: "answered",
feedback: `The host received the scope choice: ${answer}`,
}
: question,
);
}}
onMessageAction={() => {}}
onToolAction={(_messageId, partId, action) => {
if (action === "cancel") {
patchTool(partId, {
state: "cancelled",
statusLabel: "The user canceled further checks",
canCancel: false,
});
return;
}
patchTool(partId, {
state: "running",
statusLabel: "Retrying the local simulation",
retryDisabled: true,
});
const timer = setTimeout(() => {
timers.delete(timer);
if (alive)
patchTool(partId, {
state: "success",
statusLabel: "Local simulation retry succeeded",
canRetry: false,
output: "Index read completed in this local fixture.",
});
}, 650);
timers.add(timer);
}}
renderPart={(part) =>
part.id === "references" ? (
<Stack gap="compact">
<ResourceLink
title="Component review artifact"
detail="Example entry point · Uses a local anchor only"
href="#agent-reference"
/>
<ChatSources
items={[
{
title: "Meowtail source",
url: "https://github.com/lidessen/meowland",
description: "An explicitly provided example source; not retrieved by the UI.",
},
]}
/>
<span id="agent-reference">
Artifacts, references and attachments are all provided by the host.
</span>
</Stack>
) : undefined
}
/>
</Stack>
);
}API reference
Props
| Prop | Type | Default |
|---|---|---|
titleRequired | Child | – |
detailWhere it lives and which version, for example "docs/records · Revision 1". | Child | – |
icon | Child | – |
trailingReplaces the arrow, for a state the reader should see before opening. | Child | – |
hrefA destination renders an anchor; an in-page action renders a button. | string | – |
Also takes className, children and the attributes and events of its root element.
DOM parts
rooticoncopytitledetailtrailing
Registry
The component is registered as resource-link in the meowtail component registry.