Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Changesets

This folder is managed by [changesets](https://github.com/changesets/changesets). Each
pull request that changes a published package (`@intentface/chat`) should add a changeset:
pull request that changes a published package (`@intentface/ui`) should add a changeset:

```bash
bunx changeset
Expand All @@ -24,7 +24,7 @@ merging that PR publishes to npm.

```md
---
"@intentface/chat": minor
"@intentface/ui": minor
---

- [Composer] Fix an Escape in one composer stopping generation in another.
Expand All @@ -37,5 +37,5 @@ If a package change intentionally needs no release, add an empty changeset inste
bunx changeset --empty
```

The app (`intentface-chat`) is private, so changesets skips it — `@intentface/chat` is the
The app (`intentface-ui`) is private, so changesets skips it — `@intentface/ui` is the
only published package.
5 changes: 5 additions & 0 deletions .changeset/rename-to-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@intentface/ui": minor
---

- [Breaking] Renamed from `@intentface/chat`. Swap the dependency for `@intentface/ui` and replace `@intentface/chat/` with `@intentface/ui/` in imports; the rename changes nothing else.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ jobs:

- name: Package tests
run: bun test
working-directory: packages/chat
working-directory: packages/ui

- name: Package build (publint gate)
working-directory: packages/chat
working-directory: packages/ui
run: |
bun run build
bunx --no-install publint
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ jobs:
id: check
if: steps.changesets.outputs.hasChangesets == 'false'
run: |
name=$(node -p "require('./packages/chat/package.json').name")
version=$(node -p "require('./packages/chat/package.json').version")
name=$(node -p "require('./packages/ui/package.json').name")
version=$(node -p "require('./packages/ui/package.json').version")
# Exact version, not the latest dist-tag, and a registry error must not
# read as "unpublished" — that would ask for an approval on every run.
if output=$(npm view "$name@$version" version 2>&1); then
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:

- name: Verify release candidate
run: bun run verify:release
working-directory: packages/chat
working-directory: packages/ui

publish:
needs: [version, verify-release]
Expand Down
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Rules for compound components:
- **Accept `className`** on every sub-component for style overrides
- **Convenience wrappers are fine** — a higher-level component can compose the primitives with default behavior (e.g. `Composer.Attachments` composes `Attachments`, `Attachments.Item`, `Attachments.Remove`)

#### Package layer — `packages/chat/`
#### Package layer — `packages/ui/`

Published primitives use **namespace exports**, not `Object.assign`, and the root is explicit: `<Composer.Root>`, never `<Composer>`. Three modules per primitive:

Expand Down Expand Up @@ -98,15 +98,15 @@ The chat API follows Vercel AI SDK conventions
2. Avoid `useEffect` for syncing/deriving state. Use it only for true side effects (subscriptions, DOM integrations).
3. Use standard size naming: `xs`, `sm`, `md`, `lg`, `xl`.
4. Organize CVA base classes with arrays/comments when classes are long.
5. Every component must use the compound component pattern — via `Object.assign` in `components/ai/` and `components/ui/`, via namespace exports in `packages/chat/` (see Component Architecture above).
5. Every component must use the compound component pattern — via `Object.assign` in `components/ai/` and `components/ui/`, via namespace exports in `packages/ui/` (see Component Architecture above).
6. Use `cn()` from `lib/utils.ts` for className merging.
7. Follow Biome rules and formatting.
8. Use data attributes for styling and state selectors: app components (`components/ai`, `components/ui`) stamp `data-slot` / `data-role`; package primitives (`packages/chat`) emit bespoke part attributes instead (`data-composer-editor`, `data-command-badge`) — `data-slot` belongs to the consumer layer.
8. Use data attributes for styling and state selectors: app components (`components/ai`, `components/ui`) stamp `data-slot` / `data-role`; package primitives (`packages/ui`) emit bespoke part attributes instead (`data-composer-editor`, `data-command-badge`) — `data-slot` belongs to the consumer layer.
9. Leverage Motion for entrance/exit animations.
10. Rich text editing goes through `Composer` from `@intentface/chat/composer` — no editor framework; don't add one.
10. Rich text editing goes through `Composer` from `@intentface/ui/composer` — no editor framework; don't add one.
11. Follow AI SDK patterns (`useChat()`, `streamText()`, `toUIMessageStreamResponse()`).
12. No monolithic components — always decompose into composable sub-components. Consumers compose the pieces; components never hardcode their own layout.
13. Do not use index/barrel files (`index.ts` that re-exports from other files). Import directly from the specific module instead. **Exception:** each `packages/chat/src/<primitive>/` has exactly two barrels — `index.parts.ts` and `index.ts` — which are required for server-component reach and must stay directive-free.
13. Do not use index/barrel files (`index.ts` that re-exports from other files). Import directly from the specific module instead. **Exception:** each `packages/ui/src/<primitive>/` has exactly two barrels — `index.parts.ts` and `index.ts` — which are required for server-component reach and must stay directive-free.

## Environment Variables

Expand Down
6 changes: 3 additions & 3 deletions CHAT_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A reference for the chat surface in this project — the UI primitives (`Compose

- **Vercel AI SDK** (`ai@^6`) and **`@ai-sdk/react@^3`** — `Chat` class, `useChat()`, `DefaultChatTransport`, `UIMessage` parts as the wire format
- **`@ai-sdk/openai@^3`** — the only model provider; chat models are selectable demo ids (`gpt-5.5` / `gpt-5.4` / `gpt-5.4-mini` / `gpt-5.4-nano`, default `gpt-5.4-mini`)
- **No editor framework** — the composer's contenteditable engine (inline chips, prefix-triggered command lists) is hand-rolled in `packages/chat/src/composer/`
- **No editor framework** — the composer's contenteditable engine (inline chips, prefix-triggered command lists) is hand-rolled in `packages/ui/src/composer/`
- **Zustand** (`^5`, with `persist`) — chat-list metadata, model selection, and settings
- **Streamdown** (`^2`) — streaming-safe markdown renderer used by `Message.Markdown`
- **Base UI** (`@base-ui/react`) + **motion** — popovers, collapsibles, transitions
Expand Down Expand Up @@ -248,7 +248,7 @@ Pass a `commands` map to the root:

### Editor engine

The composer runs on a purpose-built contenteditable engine (`packages/chat/src/composer/`), not an editor framework:
The composer runs on a purpose-built contenteditable engine (`packages/ui/src/composer/`), not an editor framework:

- **Document model** (`segments.ts`) — a flat list of text and chip segments. Every mutation reduces to one contiguous range replacement (`TextChange`), which makes position mapping a single arithmetic rule.
- **DOM reconciliation** (`editor-dom.ts`) — renders the canonical child list, reusing chip spans by id so a moved chip keeps its React portal instead of remounting.
Expand All @@ -258,7 +258,7 @@ Chips are atomic inline `contenteditable=false` spans carrying `prefix` / `label

#### Chip wire format

A chip segment serializes to a self-describing markdown-link token (`packages/chat/src/chip-markdown.ts`):
A chip segment serializes to a self-describing markdown-link token (`packages/ui/src/chip-markdown.ts`):

```
[Label](chip:prefix:value?variant=…&icon=…)
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# intentface-chat
# intentface-ui

Monorepo for **[@intentface/chat](https://www.npmjs.com/package/@intentface/chat)** — headless chat UI
primitives for React — and the documentation site and playground at
[ui.intentface.com](https://ui.intentface.com).
Monorepo for **[@intentface/ui](https://www.npmjs.com/package/@intentface/ui)** — headless React UI
primitives for AI chat interfaces and the app shells around them — and the documentation site and
playground at [ui.intentface.com](https://ui.intentface.com).

The package ships behavior, state, and wire formats with no styling of its own: a contenteditable
composer with commands and chips, a thread with scroll auto-follow, message part segmentation, tool-call
timelines, and the app-shell parts around them — a collapsible sidebar, a nav tree, a closable tab
collection. This is the Base UI model applied to chat — the package owns behavior, you own every class.
collection. The package owns behavior; you own every class.

## Layout

| Path | What it is |
| --- | --- |
| `packages/chat` | The published package. Namespace exports per primitive, built per-module by `tsc`. |
| `packages/ui` | The published package. Namespace exports per primitive, built per-module by `tsc`. |
| `content/docs` | Documentation pages (MDX, via fumadocs). |
| `components/docs` | Docs-site chrome: previews, code blocks, tables. |
| `components/ai`, `components/ui` | The playground's own styled layer. **App-private** — it uses design tokens, Motion, and local icons, and is not published or supported for copying. |
Expand All @@ -37,15 +37,15 @@ No environment variables are needed. The playground chat runs on your own OpenAI
Package tests and build:

```bash
cd packages/chat
cd packages/ui
bun test
bun run build
```

## Contributing

Conventions, architecture, and patterns live in [AGENTS.md](./AGENTS.md) — read it before opening a PR.
Changesets gate releases: run `bunx changeset` for any user-facing change to `packages/chat`.
Changesets gate releases: run `bunx changeset` for any user-facing change to `packages/ui`.

## License

Expand Down
6 changes: 3 additions & 3 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { readSourceFile } from "@/tools/read-source-file";
import { renderUi } from "@/tools/render-ui";
import { createWebSearch } from "@/tools/web-search";

const SYSTEM_PROMPT = `You are the assistant in the Intentface Chat playground — a demo built with @intentface/chat, headless React chat primitives. You are knowledgeable, concise, and friendly.
const SYSTEM_PROMPT = `You are the assistant in the playground — a demo built with @intentface/ui, headless React UI primitives. You are knowledgeable, concise, and friendly.

## Response Guidelines
- Be concise and direct. Avoid unnecessary filler or preamble.
Expand All @@ -30,10 +30,10 @@ const SYSTEM_PROMPT = `You are the assistant in the Intentface Chat playground
- Use the current date and time context when answering time-sensitive questions.

## Library Questions
When the user asks about @intentface/chat — its primitives (composer, thread, message, chip, steps, reasoning, attachments), installation, styling, state, or how this playground is built:
When the user asks about @intentface/ui — its primitives (composer, thread, message, chip, steps, reasoning, attachments), installation, styling, state, or how this playground is built:
1. Call listDocsPages to see the documentation index
2. Read the relevant pages with readDocsPage before answering
3. For implementation internals ("how does X work under the hood"), read the code with readSourceFile — each docs page's source field names its component under packages/chat/src
3. For implementation internals ("how does X work under the hood"), read the code with readSourceFile — each docs page's source field names its component under packages/ui/src
- Answer strictly from the documentation and source — never invent props, exports, or APIs
- Link to pages inline using their url from the tool output, e.g. [Composer](/primitives/composer)
- If the documentation doesn't cover something, say so instead of guessing
Expand Down
8 changes: 4 additions & 4 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import localFont from "next/font/local";
import { Providers } from "@/components/providers";
import { SiteShell } from "@/components/site-shell";
import { source } from "@/lib/docs/source";
import chatPackage from "@/packages/chat/package.json";
import uiPackage from "@/packages/ui/package.json";
import "streamdown/styles.css";
import "./globals.css";
const inter = Inter({
Expand All @@ -30,9 +30,9 @@ export const metadata: Metadata = {
// Absolute base for Open Graph and canonical URLs. Without it Next cannot
// resolve them, so link previews fall back to relative paths.
metadataBase: new URL("https://ui.intentface.com"),
title: "@intentface/chat",
title: "@intentface/ui",
description:
"Headless chat UI primitives for React — unstyled compound components, hooks, and wire formats for building AI chat interfaces.",
"Headless UI primitives for React — unstyled compound components, hooks, and wire formats for AI chat interfaces and the app shells around them.",
};

export default function RootLayout({
Expand All @@ -48,7 +48,7 @@ export default function RootLayout({
>
<Providers>
{/* Read here on the server, so only the version reaches the client. */}
<SiteShell tree={source.pageTree} version={chatPackage.version}>
<SiteShell tree={source.pageTree} version={uiPackage.version}>
{children}
</SiteShell>
</Providers>
Expand Down
6 changes: 3 additions & 3 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const SITE = "https://ui.intentface.com";
// that save a round trip: how to fetch a page, and which Tailwind version the
// demos assume.
const SUMMARY = [
"This is the documentation for the `@intentface/chat` package.",
"It provides headless, unstyled React primitives for building AI chat interfaces — a rich-text composer, thread scroll ownership, message segmentation, and streaming disclosure. Styling is entirely the consumer's.",
"This is the documentation for the `@intentface/ui` package.",
"It provides headless, unstyled React primitives for AI chat interfaces and the app shells around them — a rich-text composer, thread scroll ownership, message segmentation, streaming disclosure, and a sidebar, nav tree and tabs. Styling is entirely the consumer's.",
"Every page below is markdown; append `.md` to any docs URL to fetch the same content. Demo source is inlined into those pages.",
"The demos are written for Tailwind CSS v4. If `package.json` uses Tailwind CSS v3, convert unsupported utilities to v3-compatible equivalents.",
].join("\n");
Expand Down Expand Up @@ -65,7 +65,7 @@ export const GET = () => {
const { loose, groups } = sectionsOf(source.pageTree.children as TreeNode[]);

const body = [
"# @intentface/chat",
"# @intentface/ui",
"",
SUMMARY,
"",
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"!build",
"!.source",
"!packages/intentface/registry/registry.json",
"!packages/chat/src/internal/render"
"!packages/ui/src/internal/render"
]
},
"formatter": {
Expand Down
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/ai/ask-user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
AskUser as AskUserPrimitive,
useAskUserOption,
useAskUserOptions,
} from "@intentface/chat/ask-user";
} from "@intentface/ui/ask-user";
import {
Check,
ChevronDown,
Expand Down
4 changes: 2 additions & 2 deletions components/ai/attachments.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import type { AttachmentErrorCode, AttachmentItem } from "@intentface/chat/attachments";
import type { AttachmentErrorCode, AttachmentItem } from "@intentface/ui/attachments";
import { File, Paperclip, X } from "@keyline-icons/react";
import { AnimatePresence } from "motion/react";
import * as m from "motion/react-m";
Expand All @@ -22,7 +22,7 @@ export {
revokeAllAttachmentUrls,
revokeAttachmentUrl,
toAttachmentItem,
} from "@intentface/chat/attachments";
} from "@intentface/ui/attachments";
export {
ATTACHMENT_ACCEPT,
ATTACHMENT_MAX_FILE_SIZE,
Expand Down
2 changes: 1 addition & 1 deletion components/ai/chip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Chip as ChipPrimitive } from "@intentface/chat/chip";
import { Chip as ChipPrimitive } from "@intentface/ui/chip";
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps } from "react";
import HoverCard from "@/components/ui/hover-card";
Expand Down
6 changes: 3 additions & 3 deletions components/ai/composer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { ArrowUp, Stop } from "@keyline-icons/react";

// Styled composer — thin wrappers over @intentface/chat/composer. Behavior
// Styled composer — thin wrappers over @intentface/ui/composer. Behavior
// (editor, store, machines, command plumbing) lives in the package; this file
// carries the Tailwind classes, icons, and motion, and re-exports the public
// surface so consumers keep importing from "@/components/ai/composer".
Expand All @@ -15,7 +15,7 @@ import {
useComposerController,
useComposerStore,
useComposerSubmit,
} from "@intentface/chat/composer";
} from "@intentface/ui/composer";
import { AnimatePresence } from "motion/react";
import * as m from "motion/react-m";
import { Children, type ComponentProps, type ReactNode, useMemo, useRef } from "react";
Expand Down Expand Up @@ -60,7 +60,7 @@ export type {
ComposerSubmitOn,
PrefixOnSelectContext,
TriggerRule,
} from "@intentface/chat/composer";
} from "@intentface/ui/composer";

// ---------------------------------------------------------------------------
// Root
Expand Down
2 changes: 1 addition & 1 deletion components/ai/generated-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { type TreeNode, useTree } from "@intentface/chat/generative";
import { type TreeNode, useTree } from "@intentface/ui/generative";
import * as m from "motion/react-m";
import type { ReactNode } from "react";
import { catalog } from "@/lib/ai/generative-catalog";
Expand Down
4 changes: 2 additions & 2 deletions components/ai/message.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
import type { ChipData } from "@intentface/chat/chip-markdown";
import type { ChipData } from "@intentface/ui/chip-markdown";
import {
Message as MessagePrimitive,
useMessageSelection,
useMessageSelectionScope,
} from "@intentface/chat/message";
} from "@intentface/ui/message";
import { Check, Copy, File, MessageSquare, Paperclip, Stop } from "@keyline-icons/react";
import * as m from "motion/react-m";
import Image from "next/image";
Expand Down
2 changes: 1 addition & 1 deletion components/ai/reasoning.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Reasoning as ReasoningPrimitive, useReasoning } from "@intentface/chat/reasoning";
import { Reasoning as ReasoningPrimitive, useReasoning } from "@intentface/ui/reasoning";
import { Brain, ChevronDown } from "@keyline-icons/react";
import { AnimatePresence } from "motion/react";
import * as m from "motion/react-m";
Expand Down
2 changes: 1 addition & 1 deletion components/ai/steps.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Steps as StepsPrimitive } from "@intentface/chat/steps";
import { Steps as StepsPrimitive } from "@intentface/ui/steps";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";

Expand Down
2 changes: 1 addition & 1 deletion components/ai/thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type ThreadVisibilityState,
useThread,
useThreadVisibility,
} from "@intentface/chat/thread";
} from "@intentface/ui/thread";
import { ArrowDown } from "@keyline-icons/react";
import { AnimatePresence } from "motion/react";
import * as m from "motion/react-m";
Expand Down
Loading
Loading