Skip to content

feat(chat): json-render unser info card #100

Merged
gaboesquivel merged 3 commits into
mainfrom
json-render
Feb 18, 2026
Merged

feat(chat): json-render unser info card #100
gaboesquivel merged 3 commits into
mainfrom
json-render

Conversation

@gaboesquivel
Copy link
Copy Markdown
Member

@gaboesquivel gaboesquivel commented Feb 18, 2026

  • Add @json-render/core and @json-render/react to apps/next
  • Backend: getAccountInfo returns { __render, spec, summary } with flat spec
  • Frontend: UserInfo catalog/registry, Renderer for getAccountInfo tool output
  • E2E: strict assertions for user-info-card, chat-error
  • Backend unit test: Who am I? returns 200

Summary by CodeRabbit

  • New Features

    • AI chat assistant now displays user info as a formatted card with name, email, join date, and avatar; assistant will render structured user-info outputs.
  • Tests

    • Added an end-to-end test and a backend test validating user-info responses from the AI chat endpoint.
  • UX / Error Messages

    • Magic-link verification failures now surface a standardized "Failed to verify magic link" message.
  • Dependencies

    • Added JSON rendering libraries to support formatted data display.

- Add @json-render/core and @json-render/react to apps/next
- Backend: getAccountInfo returns { __render, spec, summary } with flat spec
- Frontend: UserInfo catalog/registry, Renderer for getAccountInfo tool output
- E2E: strict assertions for user-info-card, chat-error
- Backend unit test: Who am I? returns 200
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
basilic-docs Ready Ready Preview, Comment Feb 18, 2026 3:04am
basilic-fastify Ready Ready Preview, Comment Feb 18, 2026 3:04am
basilic-next Ready Ready Preview, Comment Feb 18, 2026 3:04am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 18, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Adds structured user-info rendering: backend produces a user-info spec for chat tools, frontend registers a UserInfo component and renders spec outputs via a JSON renderer, tests and E2E flows updated, and related test/CI/config and dependency adjustments applied.

Changes

Cohort / File(s) Summary
Backend: AI chat and tests
apps/fastify/src/routes/ai/chat.ts, apps/fastify/src/routes/ai/chat.test.ts
Adds buildUserInfoSpec and USER_INFO_SPEC_ROOT; createAccountInfoTool now returns { __render: 'user-info', spec, summary }. Adds test for POST /ai/chat "Who am I?" asserting ChatResponseSchema (60000ms timeout).
Frontend: UserInfo catalog & renderer
apps/next/components/ai-elements/user-info-catalog.tsx, apps/next/components/dashboard/chat-assistant.tsx
New userInfoCatalog and userInfoRegistry with UserInfoComponent; chat-assistant detects __render: 'user-info' outputs and renders them via JSON renderer (StateProvider/VisibilityProvider) instead of stringifying.
E2E / Playwright / scripts
apps/next/e2e/chat-assistant.spec.ts, apps/next/playwright.config.ts, apps/next/scripts/run-e2e-local.mjs
E2E test updated to assert absence of chat-error and visibility of assistant/user-info-card (60000ms). Playwright config and run script remove wallet projects and update default project list and testIgnore.
Deps & package overrides
apps/next/package.json, package.json
Adds @json-render/core and @json-render/react to apps/next dependencies. Root package override for tar tightened to tar@<7.5.8": "7.5.8".
Auth error codes & UI mapping
apps/next/app/api/auth/[...path]/handlers/magic-link.ts, apps/next/app/login/page.tsx
Replaced literal magic-link error messages with error codes INVALID_TOKEN / FAILED_VERIFY and updated login page mapping to include FAILED_VERIFY.
Security scanner config
osv-scanner.toml
Adds IgnoredVulns entry for GHSA-83g3-92jg-28cx with rationale about overriding tar transitive dependency.

Sequence Diagram

sequenceDiagram
    participant User
    participant Backend as Chat Backend
    participant Frontend as Chat Assistant
    participant Renderer as JSON Renderer
    participant Component as UserInfo Component

    User->>Backend: POST /ai/chat "Who am I?"
    Backend->>Backend: createAccountInfoTool() -> buildUserInfoSpec(user)
    Backend-->>Frontend: Response includes { __render: "user-info", spec, summary }
    Frontend->>Frontend: isUserInfoSpecOutput() detects spec
    Frontend->>Renderer: Render spec (StateProvider / VisibilityProvider)
    Renderer->>Component: Resolve via userInfoRegistry
    Component->>User: Display user info card (avatar, name, email, joinedAt)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A spec I stitched with care and cheer,
Cards of names and dates appear!
From backend burrow to frontend tree,
Data hops, displayed for all to see,
Hip-hop hooray — user-info is here!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title contains a typo ('unser' instead of 'user') and is partially related to the changeset. It refers to the json-render and user info card feature, but the typo makes it unprofessional and the phrasing is awkward. Correct the typo: change 'unser' to 'user' for clarity. Consider improving phrasing to 'feat(chat): add json-render user-info card' or similar for better readability.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch json-render

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
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/fastify/src/routes/ai/chat.ts (1)

243-244: ⚠️ Potential issue | 🟠 Major

generateText without stopWhen returns empty text when the model calls a tool.

By default, generateText runs with stopWhen: stepCountIs(1) — a single model generation step. When the model calls a tool in that step, the generation ends and text is empty because no second step occurs to generate text after the tool executes.

The non-streaming path at lines 243–244 hits this case when users ask "Who am I?" — the model calls getAccountInfo, but the route returns { text: '' }.

The streaming path works around this by rendering tool outputs directly via UIMessageStream events, independent of result.text.

Fix by adding stopWhen with stepCountIs() to enable multi-step tool-calling:

🐛 Proposed fix
- import { generateText, ... } from 'ai'
+ import { generateText, stepCountIs, ... } from 'ai'

  const result = await generateText({
    ...baseOptions,
+   stopWhen: stepCountIs(5),
  })
  return reply.code(200).send({ text: result.text })

(Adjust step count as needed for your use case. Higher step count allows more tool-calling loops before the model generates final text.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/fastify/src/routes/ai/chat.ts` around lines 243 - 244, The non-streaming
branch calls generateText(baseOptions) which uses the default stopWhen
(stepCountIs(1)), so when the model invokes a tool in the first step no
subsequent generation happens and result.text is empty; update the call to
generateText to pass an explicit stopWhen using stepCountIs(n) (e.g.,
stepCountIs(2) or higher) so the agent can perform tool-calls and then produce
final text—modify the invocation around generateText/baseOptions to merge in
stopWhen: stepCountIs(...) before sending reply.
🧹 Nitpick comments (2)
apps/fastify/src/routes/ai/chat.test.ts (1)

122-135: Consider adding a non-empty text assertion for consistency.

All other non-streaming 200 tests (e.g., lines 77, 96) include expect(data.text.length).toBeGreaterThan(0), but this one only checks toBeTypeOf('string'). The omission may be intentional (tool-call responses can return empty text in a single-step generateText run — see the related concern in chat.ts), but the inconsistency is worth noting. If the intent is to verify the LLM produced a meaningful reply after tool execution, the assertion should be tightened.

♻️ Suggested assertion
  expect(data.text).toBeTypeOf('string')
+ expect(data.text.length).toBeGreaterThan(0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/fastify/src/routes/ai/chat.test.ts` around lines 122 - 135, The test
"should return 200 when user asks who am I (getAccountInfo tool)" currently only
asserts that data.text is a string; add a non-empty check to match other
non-streaming 200 tests by asserting expect(data.text.length).toBeGreaterThan(0)
after the ChatResponseSchema.parse check (or, if empty text is intentionally
allowed for tool-only responses, add a clarify comment explaining that exception
next to the test and keep the existing assertion). Reference: the test case
name, ChatResponseSchema.parse, and the route POST '/ai/chat' / generateText
behavior in chat.ts.
apps/next/components/dashboard/chat-assistant.tsx (1)

138-157: Redundant output.spec guard after isUserInfoSpecOutput.

isUserInfoSpecOutput already verifies that output.spec is a non-null object, so the additional && output.spec on line 141 is dead code and TypeScript should narrow the type correctly on its own.

♻️ Suggested simplification
  if (
    toolName === 'getAccountInfo' &&
-   isUserInfoSpecOutput(output) &&
-   output.spec
+   isUserInfoSpecOutput(output)
  ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/next/components/dashboard/chat-assistant.tsx` around lines 138 - 157,
Remove the redundant truthy check "&& output.spec" in the conditional that
already calls isUserInfoSpecOutput(output); update the if to just check toolName
=== 'getAccountInfo' && isUserInfoSpecOutput(output) so TypeScript's type guard
can narrow output and the block using output.spec (Renderer with
userInfoRegistry inside StateProvider/VisibilityProvider) remains unchanged;
ensure hasContent and elements.push logic is left intact and only the extra "&&
output.spec" is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/next/e2e/chat-assistant.spec.ts`:
- Around line 22-26: The negative assertion on sheet.getByTestId('chat-error')
runs too early; move the await
expect(sheet.getByTestId('chat-error')).not.toBeVisible() so it executes after
the assistant appears (await
expect(sheet.locator('[data-role="assistant"]')).toBeVisible(...)) and after the
success check (await
expect(sheet.getByTestId('user-info-card')).toBeVisible(...)) to ensure any late
errors are caught; update the test ordering in chat-assistant.spec.ts
accordingly so the error check runs after those visible assertions.

---

Outside diff comments:
In `@apps/fastify/src/routes/ai/chat.ts`:
- Around line 243-244: The non-streaming branch calls generateText(baseOptions)
which uses the default stopWhen (stepCountIs(1)), so when the model invokes a
tool in the first step no subsequent generation happens and result.text is
empty; update the call to generateText to pass an explicit stopWhen using
stepCountIs(n) (e.g., stepCountIs(2) or higher) so the agent can perform
tool-calls and then produce final text—modify the invocation around
generateText/baseOptions to merge in stopWhen: stepCountIs(...) before sending
reply.

---

Nitpick comments:
In `@apps/fastify/src/routes/ai/chat.test.ts`:
- Around line 122-135: The test "should return 200 when user asks who am I
(getAccountInfo tool)" currently only asserts that data.text is a string; add a
non-empty check to match other non-streaming 200 tests by asserting
expect(data.text.length).toBeGreaterThan(0) after the ChatResponseSchema.parse
check (or, if empty text is intentionally allowed for tool-only responses, add a
clarify comment explaining that exception next to the test and keep the existing
assertion). Reference: the test case name, ChatResponseSchema.parse, and the
route POST '/ai/chat' / generateText behavior in chat.ts.

In `@apps/next/components/dashboard/chat-assistant.tsx`:
- Around line 138-157: Remove the redundant truthy check "&& output.spec" in the
conditional that already calls isUserInfoSpecOutput(output); update the if to
just check toolName === 'getAccountInfo' && isUserInfoSpecOutput(output) so
TypeScript's type guard can narrow output and the block using output.spec
(Renderer with userInfoRegistry inside StateProvider/VisibilityProvider) remains
unchanged; ensure hasContent and elements.push logic is left intact and only the
extra "&& output.spec" is removed.

Comment on lines +22 to +26
await expect(sheet.getByTestId('chat-error')).not.toBeVisible()
await expect(sheet.locator('[data-role="assistant"]')).toBeVisible({
timeout: 60000,
})
const errorEl = sheet.getByTestId('chat-error')
if (await errorEl.isVisible()) {
throw new Error(`Chat API error: ${await errorEl.textContent()}`)
}

const assistantEl = sheet.locator('[data-role="assistant"]')
await expect(assistantEl).toBeVisible({ timeout: 60000 })
await expect(assistantEl).not.toBeEmpty()
await expect(sheet.getByTestId('user-info-card')).toBeVisible({ timeout: 60000 })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

chat-error negative assertion fires before the AI response arrives — fix ordering before un-skipping.

The not.toBeVisible() check on line 22 runs immediately after the user message appears (line 21), before the AI has had time to call the tool, receive a result, or encounter an error. An error that materialises 5–30 seconds later will never be caught here — the assertion silently passes.

Move the error check to after the success assertions, or at minimum after the assistant message is visible:

🛠️ Suggested order
  await expect(
    sheet.locator('[data-role="user"]').filter({ hasText: 'Who am I?' }),
  ).toBeVisible({ timeout: 10000 })
- await expect(sheet.getByTestId('chat-error')).not.toBeVisible()
  await expect(sheet.locator('[data-role="assistant"]')).toBeVisible({
    timeout: 60000,
  })
  await expect(sheet.getByTestId('user-info-card')).toBeVisible({ timeout: 60000 })
+ await expect(sheet.getByTestId('chat-error')).not.toBeVisible()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/next/e2e/chat-assistant.spec.ts` around lines 22 - 26, The negative
assertion on sheet.getByTestId('chat-error') runs too early; move the await
expect(sheet.getByTestId('chat-error')).not.toBeVisible() so it executes after
the assistant appears (await
expect(sheet.locator('[data-role="assistant"]')).toBeVisible(...)) and after the
success check (await
expect(sheet.getByTestId('user-info-card')).toBeVisible(...)) to ensure any late
errors are caught; update the test ordering in chat-assistant.spec.ts
accordingly so the error check runs after those visible assertions.

- Use INVALID_TOKEN/FAILED_VERIFY codes for magic link redirect
- Add FAILED_VERIFY mapping on login page
- Disable wallet-solana and wallet-metamask E2E (require extension setup)
- Ignore tar GHSA in osv-scanner (override in package.json, @vercel/fun transitive)
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