Meowland docs
Workbench

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.

packages/meowtail/stories/detail-panel.stories.tsxline 14tsx
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.

usage.tsxtsx
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.

packages/meowtail/stories/link.stories.tsxline 14tsx
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.

packages/meowtail/stories/agent-workflow.stories.tsxline 335tsx
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

PropTypeDefault
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.