59 / Agent UI
Memory Chips
Everything an assistant has been told to remember about someone, each one removable on its own.
Remembered about you4
- Prefers pnpm
- Works in TypeScriptfrom a PR review
- Uses merge commits, never squash
- Timezone is IST
"use client"
import * as React from "react"
import { MemoryChips } from "mischief-ui/memory-chips"
const remembered = [
{ id: "1", text: "Prefers pnpm" },
{ id: "2", text: "Works in TypeScript", source: "from a PR review" },
{ id: "3", text: "Uses merge commits, never squash" },
{ id: "4", text: "Timezone is IST" },
]
export function MemoryChipsDemo() {
const [memories, setMemories] = React.useState(remembered)
return (
<div className="w-full max-w-md space-y-3">
<MemoryChips
memories={memories}
onForget={(id) => setMemories((all) => all.filter((m) => m.id !== id))}
onForgetAll={() => setMemories([])}
/>
{memories.length < remembered.length ? (
<button
type="button"
onClick={() => setMemories(remembered)}
className="text-muted-foreground hover:text-foreground text-xs underline underline-offset-4"
>
Put them back
</button>
) : null}
</div>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/memory-chipsimport { MemoryChips } from "mischief-ui/memory-chips"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
"use client" import * as React from "react"import { X } from "lucide-react" import { cn } from "@/lib/utils" export type Memory = { id: string text: string /** Where it came from, such as the conversation that produced it. */ source?: string} Usage
export function Settings({ memories }) {
return <MemoryChips memories={memories} onForget={forget} />
}Memory you cannot see cannot be corrected
An assistant that quietly keeps notes on someone is holding a profile they have never read. The first thing this component does is show it, in the words it was stored in, not a summary of them.
The second is to let one entry go without taking the rest. Where forgetting everything is the only control, a single wrong note turns into a choice between living with it and starting over, so most people live with it.
It removes nothing by itself
onForget is called with an id and that is all that happens here. Deleting the record, telling the server, and deciding whether the assistant may write it again are yours, because they are decisions about someone's data and they do not belong in a component you copied in.
<MemoryChips
memories={memories}
onForget={(id) => forget(id)}
onForgetAll={() => forgetEverything()}
/>API
memoriesreadonly Memory[]What is being remembered.onForget(id: string) => voidOne entry was dismissed.onForgetAll() => voidClear everything. Omit to withhold it.labelstringNames the group.emptyMessagestringShown when there is nothing stored....rootPropsHTMLAttributes<HTMLDivElement>Native root attributes.Memory
idstringUnique within the set.textstringThe note, as it was stored.sourcestringWhere it came from, shown after the note.Accessibility
Each remove button names the entry it will remove, so a screen reader hears "Forget: prefers pnpm" rather than a row of identical Remove buttons. A removal is confirmed through a polite live region, which matters because the chip it was announced from has gone by the time the message lands. The list is a real list, so its length is announced, and the empty state is a sentence rather than an absence.