Skip to content

Conversation

@JoviDeCroock
Copy link

@JoviDeCroock JoviDeCroock commented Dec 23, 2025

🎯 Changes

This adds a preact-integration, this is largely adapted from the react tests and implementation. I was considering adding a signals based one as well but wanted to see whether the appeal is there from a maintenance standpoint.

The minimum version of preact is 10.11 as useId was introduced there https://github.com/preactjs/preact/releases/tag/10.11.0

I will add the ai-preact-ui in a follow up PR.

Note

The PR template calls out to follow https://github.com/TanStack/ai/blob/main/CONTRIBUTING.md but this does not exist.

✅ Checklist

  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.

Summary by CodeRabbit

  • New Features

    • Initial release of @tanstack/ai-preact: a Preact hook for managing AI chats, streaming responses, tool call workflows, and error/loading states.
  • Documentation

    • Added comprehensive README and CHANGELOG with usage examples and integration guidance.
  • Tests

    • Full test suite and test utilities covering hook behavior, streaming, tool workflows, lifecycle, and edge cases.
  • Chores / Build

    • Package manifest, TypeScript and test/build configuration and tooling for the new package.

✏️ Tip: You can customize this high-level summary in your review settings.

@JoviDeCroock JoviDeCroock requested a review from a team December 23, 2025 11:40
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

Warning

Rate limit exceeded

@JoviDeCroock has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e685ee and 07890c7.

📒 Files selected for processing (3)
  • docs/api/ai-preact.md
  • docs/config.json
  • packages/typescript/ai-preact/README.md

Walkthrough

Adds a new @tanstack/ai-preact package: package metadata, TypeScript types, a Preact useChat hook (ChatClient lifecycle and actions), Vite/Vitest configs, documentation, and an extensive test suite.

Changes

Cohort / File(s) Summary
Release & docs
\.changeset/tired-years-stick.md, packages/typescript/ai-preact/CHANGELOG.md, packages/typescript/ai-preact/README.md
Initial changeset, changelog heading, and full README for the new package.
Package config & build
packages/typescript/ai-preact/package.json, packages/typescript/ai-preact/tsconfig.json, packages/typescript/ai-preact/vite.config.ts, packages/typescript/ai-preact/vitest.config.ts
New package manifest, TypeScript project config, and Vite/Vitest configs (including path alias in Vitest).
Public API surface
packages/typescript/ai-preact/src/index.ts
Exports useChat, local types, and re-exports utilities/types from @tanstack/ai-client.
Types
packages/typescript/ai-preact/src/types.ts
New UseChatOptions and UseChatReturn types; re-exports UIMessage and ChatRequestBody.
Hook implementation
packages/typescript/ai-preact/src/use-chat.ts
New useChat hook: manages messages, loading/error, ChatClient creation/cleanup, and provides actions (sendMessage, append, reload, stop, clear, addToolResult, addToolApprovalResponse).
Tests & test utils
packages/typescript/ai-preact/tests/test-utils.ts, packages/typescript/ai-preact/tests/use-chat.test.ts
Test utilities and comprehensive tests covering initialization, state sync, send/append/reload/stop/clear flows, callbacks, tool workflows, and edge cases.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Preact component / UI
  participant Hook as useChat (hook)
  participant Client as ChatClient
  participant Adapter as ConnectionAdapter / Server

  rect `#F3F7FF`
    Note over Hook,Client: Hook creates ChatClient on mount with provided options
    UI->>Hook: mount + options
    Hook->>Client: create/initialize client
    Client->>Adapter: open connection / send request
  end

  rect `#FFF8E1`
    UI->>Hook: sendMessage(content)
    Hook->>Client: sendMessage -> start streaming
    Adapter-->>Client: SSE / chunk events
    Client->>Hook: onChunk/onResponse/onFinish callbacks
    Hook->>UI: update messages (isLoading true → false)
  end

  rect `#FFEFF1`
    UI->>Hook: stop()
    Hook->>Client: stop/cancel
    Client->>Adapter: cancel connection
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • harry-whorlow

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add preact integration' directly and clearly describes the main change: introducing a new Preact integration to the TanStack AI project.
Description check ✅ Passed The description covers the main changes, includes detailed context about the implementation, and addresses all required checklist items, though some template sections differ slightly from the standard format.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
packages/typescript/ai-preact/package.json (1)

46-52: Missing vitest in devDependencies.

The test script uses vitest run, but vitest is not listed in devDependencies. While it may be hoisted from the workspace root, explicitly declaring it ensures the package is self-contained.

🔎 Proposed fix
   "devDependencies": {
     "@testing-library/preact": "^3.2.4",
     "@vitest/coverage-v8": "4.0.14",
     "jsdom": "^27.2.0",
     "preact": "^10.26.9",
-    "vite": "^7.2.7"
+    "vite": "^7.2.7",
+    "vitest": "^4.0.0"
   },
packages/typescript/ai-preact/vite.config.ts (1)

5-27: Clarify the role of this config vs vitest.config.ts.

This file sets environment: 'node', while vitest.config.ts sets environment: 'jsdom'. When running vitest, the dedicated vitest.config.ts takes precedence, making the test configuration here potentially unused or confusing.

Consider either:

  1. Removing the test block from this file (keeping it build-only), or
  2. Consolidating test configuration into a single location.
packages/typescript/ai-preact/tests/test-utils.ts (1)

25-29: Type assertion bypasses type safety for required options.

The default parameter {} as UseChatOptions uses a type assertion to cast an empty object. If UseChatOptions has required properties (like connection), this could lead to runtime errors when renderUseChat() is called without arguments.

Consider making the parameter truly optional or requiring the caller to always provide options:

🔎 Option A: Make parameter required (safer)
 export function renderUseChat(
-  options: UseChatOptions = {} as UseChatOptions,
+  options: UseChatOptions,
 ): RenderHookResult<UseChatReturn, UseChatOptions> {
   return renderHook(() => useChat(options))
 }
🔎 Option B: Use Partial type (if truly optional)
 export function renderUseChat(
-  options: UseChatOptions = {} as UseChatOptions,
+  options: Partial<UseChatOptions> = {},
 ): RenderHookResult<UseChatReturn, UseChatOptions> {
-  return renderHook(() => useChat(options))
+  return renderHook(() => useChat(options as UseChatOptions))
 }
packages/typescript/ai-preact/src/use-chat.ts (3)

48-58: Inconsistent options access pattern.

Most options are accessed via optionsRef.current (lines 49-57), but streamProcessor on line 58 is accessed directly from options. This inconsistency could lead to subtle bugs if the component re-renders with new options while the client is being created.

🔎 Proposed fix
       onError: optionsRef.current.onError,
       tools: optionsRef.current.tools,
-      streamProcessor: options.streamProcessor,
+      streamProcessor: optionsRef.current.streamProcessor,
       onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {

74-78: Consider adding ESLint disable comment for intentional empty dependencies.

The empty dependency array is intentional for mount-only behavior (as documented in lines 71-73), but linters will flag this. Adding an explicit disable comment would clarify the intent and prevent accidental "fixes."

🔎 Proposed change
   // Sync initial messages on mount only
   // Note: initialMessages are passed to ChatClient constructor, but we also
   // set them here to ensure Preact state is in sync
   useEffect(() => {
     if (options.initialMessages && options.initialMessages.length && !messages.length) {
       client.setMessagesManually(options.initialMessages)
     }
+    // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentional mount-only effect
   }, [])

100-105: Missing generic type parameter on UIMessage.

The append callback uses UIMessage without the <TTools> parameter, which could cause type inconsistencies since UIMessage is generic.

🔎 Proposed fix
   const append = useCallback(
-    async (message: ModelMessage | UIMessage) => {
+    async (message: ModelMessage | UIMessage<TTools>) => {
       await client.append(message)
     },
     [client],
   )
packages/typescript/ai-preact/tests/use-chat.test.ts (2)

376-425: Test logic may not be verifying the intended behavior.

The adapter2 created on line 395 is passed to rerender on line 410, but since the clientId doesn't change (no id prop change), the useMemo won't recreate the client. The client will continue using the original adapter. The onConnect callback approach also appears unused since callCount increments but the returned chunks aren't utilized by the mock adapter's typical flow.

Consider simplifying this test or clarifying what reload behavior it's actually validating:

🔎 Simplified reload test
   it('should reload the last assistant message', async () => {
-      const chunks1 = createTextChunks('First response')
-      const chunks2 = createTextChunks('Second response')
-      let callCount = 0
-
-      const adapter = createMockConnectionAdapter({
-        chunks: chunks1,
-        onConnect: () => {
-          callCount++
-          // Return different chunks on second call
-          if (callCount === 2) {
-            return chunks2
-          }
-          return undefined
-        },
-      })
-
-      // Create a new adapter for the second call
-      const adapter2 = createMockConnectionAdapter({ chunks: chunks2 })
-      const { result, rerender } = renderUseChat({ connection: adapter })
+      const chunks = createTextChunks('Response')
+      const adapter = createMockConnectionAdapter({ chunks })
+      const { result } = renderUseChat({ connection: adapter })

       await act(async () => {
         await result.current.sendMessage('Hello')
       })

       await waitFor(() => {
         const assistantMessage = result.current.messages.find(
           (m) => m.role === 'assistant',
         )
         expect(assistantMessage).toBeDefined()
       })

-      // Reload with new adapter
-      rerender({ connection: adapter2 })
+      // Reload should resend the last user message
       await act(async () => {
         await result.current.reload()
       })

       await waitFor(() => {
         const assistantMessage = result.current.messages.find(
           (m) => m.role === 'assistant',
         )
         expect(assistantMessage).toBeDefined()
       })

-      // Should have reloaded (though content might be same if adapter doesn't change)
       const messagesAfterReload = result.current.messages
       expect(messagesAfterReload.length).toBeGreaterThan(0)
     })

1053-1084: Test assertion doesn't verify error is cleared.

The test "should clear error on successful operation" (line 1053) doesn't assert that error becomes undefined after the successful operation. Additionally, since the client isn't recreated on rerender (same clientId), the workingAdapter won't be used.

🔎 Suggested improvement
         // Switch to working adapter
         const workingAdapter = createMockConnectionAdapter({
           chunks: createTextChunks('Success'),
         })
         rerender({ connection: workingAdapter })

         await act(async () => {
           await result.current.sendMessage('Test')
         })

         await waitFor(() => {
           // Error should be cleared on success
-          expect(result.current.messages.length).toBeGreaterThan(0)
+          expect(result.current.error).toBeUndefined()
         })
+        expect(result.current.messages.length).toBeGreaterThan(0)

Note: This test may also need a different approach since rerendering with a new adapter doesn't recreate the ChatClient (the clientId remains unchanged). Consider whether this test accurately reflects the intended behavior.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29466c1 and 9e87162.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • .changeset/tired-years-stick.md
  • packages/typescript/ai-preact/CHANGELOG.md
  • packages/typescript/ai-preact/README.md
  • packages/typescript/ai-preact/package.json
  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
  • packages/typescript/ai-preact/tests/use-chat.test.ts
  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/vite.config.ts
  • packages/typescript/ai-preact/vitest.config.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use tree-shakeable adapter architecture for provider implementations - export specialized adapters (text, embedding, summarize, image) as separate imports from /adapters subpath rather than monolithic adapters
Use Zod for runtime schema validation and type inference, particularly for tool input/output definitions with toolDefinition() and Zod schema inference
Implement isomorphic tool system using toolDefinition() with .server() and .client() implementations for dual-environment execution
Use type-safe per-model configuration with provider options typed based on selected model to ensure compile-time safety
Implement stream processing with StreamProcessor for handling chunked responses and support partial JSON parsing for streaming AI responses

Files:

  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
  • packages/typescript/ai-preact/vitest.config.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-preact/tests/use-chat.test.ts
  • packages/typescript/ai-preact/vite.config.ts
packages/typescript/*/src/index.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Export tree-shakeable adapters with clear subpath exports in package.json (e.g., @tanstack/ai/adapters, @tanstack/ai-openai/adapters) to minimize bundle size

Files:

  • packages/typescript/ai-preact/src/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for function and variable names throughout the codebase

Files:

  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
  • packages/typescript/ai-preact/vitest.config.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-preact/tests/use-chat.test.ts
  • packages/typescript/ai-preact/vite.config.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Write unit tests using Vitest alongside source files with .test.ts naming convention

Files:

  • packages/typescript/ai-preact/tests/use-chat.test.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Implement framework integrations using the headless `tanstack/ai-client` for state management with framework-specific hooks (useChat) on top
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/index.ts : Export tree-shakeable adapters with clear subpath exports in package.json (e.g., `tanstack/ai/adapters`, `tanstack/ai-openai/adapters`) to minimize bundle size

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/README.md
  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/vitest.config.ts
  • packages/typescript/ai-preact/vite.config.ts
  • packages/typescript/ai-preact/CHANGELOG.md
  • packages/typescript/ai-preact/package.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/adapters/*.ts : Create individual adapter implementations for each provider capability (text, embed, summarize, image) with separate exports to enable tree-shaking

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/README.md
  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
  • packages/typescript/ai-preact/package.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Use tree-shakeable adapter architecture for provider implementations - export specialized adapters (text, embedding, summarize, image) as separate imports from `/adapters` subpath rather than monolithic adapters

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/README.md
  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Use Zod for runtime schema validation and type inference, particularly for tool input/output definitions with `toolDefinition()` and Zod schema inference

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/model-meta.ts : Maintain model metadata files that define provider options and capabilities per model for per-model type safety

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/package.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to .eslintrc* : Use ESLint with custom TanStack config for linting all TypeScript and JavaScript files

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/vitest.config.ts
  • packages/typescript/ai-preact/vite.config.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to .prettierrc* : Use Prettier for code formatting with project-configured rules

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.test.ts : Write unit tests using Vitest alongside source files with `.test.ts` naming convention

Applied to files:

  • packages/typescript/ai-preact/tsconfig.json
  • packages/typescript/ai-preact/vitest.config.ts
  • packages/typescript/ai-preact/vite.config.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Implement framework integrations using the headless `tanstack/ai-client` for state management with framework-specific hooks (useChat) on top

Applied to files:

  • packages/typescript/ai-preact/README.md
  • packages/typescript/ai-preact/src/index.ts
  • packages/typescript/ai-preact/tests/test-utils.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-preact/tests/use-chat.test.ts
  • packages/typescript/ai-preact/CHANGELOG.md
  • packages/typescript/ai-preact/package.json
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Create changesets with `pnpm changeset` before making changes to prepare for release version bumping and publishing

Applied to files:

  • .changeset/tired-years-stick.md
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/*/package.json : Use `workspace:*` protocol for internal package dependencies in package.json (e.g., `"tanstack/ai": "workspace:*"`)

Applied to files:

  • packages/typescript/ai-preact/package.json
🧬 Code graph analysis (1)
packages/typescript/ai-preact/src/use-chat.ts (2)
packages/typescript/ai-preact/src/types.ts (3)
  • UseChatOptions (26-30)
  • UseChatReturn (32-98)
  • UIMessage (9-9)
packages/typescript/ai-client/src/chat-client.ts (6)
  • append (265-285)
  • reload (336-354)
  • stop (359-366)
  • setMessagesManually (509-511)
  • addToolResult (380-408)
  • addToolApprovalResponse (413-450)
🪛 markdownlint-cli2 (0.18.1)
packages/typescript/ai-preact/README.md

2-2: Images should have alternate text (alt text)

(MD045, no-alt-text)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


23-23: Hard tabs
Column: 1

(MD010, no-hard-tabs)


24-24: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


49-49: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🔇 Additional comments (8)
packages/typescript/ai-preact/CHANGELOG.md (1)

1-1: LGTM!

Standard changelog placeholder for the new package. Entries will be auto-generated by changesets.

.changeset/tired-years-stick.md (1)

1-5: LGTM!

Changeset is correctly formatted with appropriate minor version bump for the initial release.

packages/typescript/ai-preact/tsconfig.json (1)

1-11: LGTM!

TypeScript configuration is correctly set up for Preact with the automatic JSX runtime (react-jsx + jsxImportSource: "preact").

packages/typescript/ai-preact/src/types.ts (1)

1-98: LGTM!

Well-structured type definitions that correctly extend the headless @tanstack/ai-client while omitting callbacks managed internally by Preact state. The JSDoc documentation clearly explains the design rationale. Based on learnings, this follows the recommended pattern for framework integrations.

packages/typescript/ai-preact/src/index.ts (1)

1-17: LGTM! Clean public API surface.

The exports are well-organized, following the pattern of framework integrations using the headless @tanstack/ai-client for state management with a framework-specific hook on top. The re-exports from @tanstack/ai-client provide convenient access to commonly needed utilities. Based on learnings, this aligns with the expected architecture.

packages/typescript/ai-preact/src/use-chat.ts (1)

14-16: Well-structured hook implementation.

The hook correctly implements the framework integration pattern: using the headless ChatClient from @tanstack/ai-client for state management while providing Preact-specific state bindings. The cleanup on unmount, callback memoization, and ref-based message preservation across client recreations are all properly implemented.

Also applies to: 146-158

packages/typescript/ai-preact/tests/use-chat.test.ts (2)

1-11: Comprehensive test suite with good coverage.

The test suite thoroughly covers initialization, state management, message operations, lifecycle behaviors, error handling, concurrency scenarios, and tool workflows. Uses Vitest with .test.ts naming convention as per coding guidelines.


1216-1321: Good defensive testing for tool operations.

The tool operation tests properly use conditional checks (if (toolCallPart)) to handle cases where tool calls may not be present in the response. This defensive approach prevents false positives while still exercising the API surface.

@@ -0,0 +1,157 @@
<div align="center">
<img src="./media/header_ai.png" >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add alt text to the header image for accessibility.

The header image is missing the alt attribute, which is flagged by static analysis (MD045).

🔎 Proposed fix
-  <img src="./media/header_ai.png" >
+  <img src="./media/header_ai.png" alt="TanStack AI" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img src="./media/header_ai.png" >
<img src="./media/header_ai.png" alt="TanStack AI" />
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

2-2: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around line 2, the header image tag
<img src="./media/header_ai.png"> is missing an alt attribute which triggers
MD045; add a concise, meaningful alt attribute (e.g., alt="AI Preact header" or
a short description of the image) to the img tag so the markup becomes <img
src="./media/header_ai.png" alt="..."> to satisfy accessibility and static
analysis.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/typescript/ai-preact/README.md (1)

2-2: Add alt text to the header image for accessibility.

The image is missing an alt attribute (MD045). This is a duplicate of a previously flagged issue.

🔎 Proposed fix
-  <img src="./media/header_ai.png" >
+  <img src="./media/header_ai.png" alt="TanStack AI Preact" />
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c8fbbd and 3e685ee.

📒 Files selected for processing (1)
  • packages/typescript/ai-preact/README.md
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Implement framework integrations using the headless `tanstack/ai-client` for state management with framework-specific hooks (useChat) on top
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/index.ts : Export tree-shakeable adapters with clear subpath exports in package.json (e.g., `tanstack/ai/adapters`, `tanstack/ai-openai/adapters`) to minimize bundle size

Applied to files:

  • packages/typescript/ai-preact/README.md
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/adapters/*.ts : Create individual adapter implementations for each provider capability (text, embed, summarize, image) with separate exports to enable tree-shaking

Applied to files:

  • packages/typescript/ai-preact/README.md
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Use tree-shakeable adapter architecture for provider implementations - export specialized adapters (text, embedding, summarize, image) as separate imports from `/adapters` subpath rather than monolithic adapters

Applied to files:

  • packages/typescript/ai-preact/README.md
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Implement isomorphic tool system using `toolDefinition()` with `.server()` and `.client()` implementations for dual-environment execution

Applied to files:

  • packages/typescript/ai-preact/README.md
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Implement framework integrations using the headless `tanstack/ai-client` for state management with framework-specific hooks (useChat) on top

Applied to files:

  • packages/typescript/ai-preact/README.md
🪛 markdownlint-cli2 (0.18.1)
packages/typescript/ai-preact/README.md

2-2: Images should have alternate text (alt text)

(MD045, no-alt-text)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


23-23: Hard tabs
Column: 1

(MD010, no-hard-tabs)


24-24: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 1

(MD010, no-hard-tabs)


49-49: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

<img alt="" src="https://img.shields.io/npm/dm/@tanstack/ai.svg" />
</a>
<a href="https://github.com/TanStack/ai" target="\_parent">
<img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace hard tabs with spaces for consistent indentation.

Lines 12, 23, 24, and 25 use hard tabs instead of spaces, which violates MD010. Standardize to spaces.

🔎 Proposed fix
  <a href="https://github.com/TanStack/ai" target="\_parent">
-	  <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
+    <img alt="" src="https://img.shields.io/github/stars/TanStack/ai.svg?style=social&label=Star" alt="GitHub stars" />
  </a>
  <a href="https://bundlephobia.com/result?p=@tanstack/ai@latest" target="\_parent">
   <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/ai@latest" />
  </a>
  </div>

  <div align="center">
  <a href="#badge">
    <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
  </a>
-	<a href="#badge">
-		<img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
-	</a>
+  <a href="#badge">
+    <img src="https://img.shields.io/github/v/release/tanstack/ai" alt="Release"/>
+  </a>
  <a href="https://twitter.com/tan_stack">
   <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
  </a>

Also applies to: 23-25

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around lines 12 and 23-25, several
lines use hard tabs for indentation which violates MD010; replace each hard tab
character with spaces (use the repository/project standard for Markdown
indentation — e.g., two spaces per tab) so indentation is consistent with the
rest of the file and no tabs remain on those lines.

- Isomorphic type-safe tools with server/client execution
- **Enhanced integration with TanStack Start** - Share implementations between AI tools and server functions

### <a href="https://tanstack.com/ai">Read the docs →</b></a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading level hierarchy.

Line 49 uses ### (h3) but should use ## (h2) to maintain proper heading increments per MD001.

🔎 Proposed fix
-### <a href="https://tanstack.com/ai">Read the docs →</b></a>
+## <a href="https://tanstack.com/ai">Read the docs →</b></a>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### <a href="https://tanstack.com/ai">Read the docs →</b></a>
## <a href="https://tanstack.com/ai">Read the docs →</b></a>
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

49-49: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
In packages/typescript/ai-preact/README.md around line 49, the heading currently
uses `###` (h3) but should be `##` (h2) to preserve proper heading hierarchy per
MD001; change the leading hashes from `###` to `##` on that line so the section
increments correctly and re-run the linter to verify the MD001 warning is
resolved.

@JoviDeCroock JoviDeCroock force-pushed the add-preact-integration branch from 3e685ee to f6fe950 Compare December 23, 2025 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant