Skip to content

Add feedback module to databunny.#111

Merged
izadoesdev merged 1 commit into
databuddy-analytics:stagingfrom
sbansal1999:ai-feedback
Aug 24, 2025
Merged

Add feedback module to databunny.#111
izadoesdev merged 1 commit into
databuddy-analytics:stagingfrom
sbansal1999:ai-feedback

Conversation

@sbansal1999

@sbansal1999 sbansal1999 commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added upvote/downvote actions on assistant messages.
    • Introduced inline feedback flow after a downvote, including comment submission.
    • Enabled copy-to-clipboard for the most recent assistant message.
    • Added success and error notifications for feedback submissions.
    • You can now submit feedback comments without selecting a vote.
    • Improved message actions to only show contextually on the latest message for a cleaner chat experience.

@vercel

vercel Bot commented Aug 24, 2025

Copy link
Copy Markdown

@sbansal1999 is attempting to deploy a commit to the Databuddy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds voting and feedback to assistant messages. Expands useChat to expose handleVote and handleFeedbackComment using a new TRPC mutation. MessageBubble and ChatSection now wire these handlers, show upvote/downvote, copy for last message, and an inline feedback input after downvote. RPC input makes vote type optional.

Changes

Cohort / File(s) Summary
UI: Chat container
apps/.../assistant/components/chat-section.tsx
Destructure handleVote and handleFeedbackComment from useChat(). Pass them and isLastMessage to MessageBubble.
UI: Message bubble + feedback UX
apps/.../assistant/components/message-bubble.tsx
Adds voting (up/down), copy action (last message), and inline feedback input. Extends props: handleVote, handleFeedbackComment, isLastMessage. Manages local vote state and feedback form.
Hook: Chat interactions
apps/.../assistant/hooks/use-chat.ts
Exposes handleVote(messageId, type) and handleFeedbackComment(messageId, comment). Introduces TRPC addFeedback mutation and toast notifications. Adds exported `type Vote = 'upvote'
RPC: Assistant feedback endpoint
packages/rpc/src/routers/assistant.ts
addFeedback input type becomes optional. Explicitly handles upvote/downvote; no-op on undefined type. Comment handling unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CS as ChatSection
  participant MB as MessageBubble
  participant HC as useChat (hook)
  participant RPC as TRPC addFeedback
  participant S as Server/DB

  U->>MB: Click 👍 / 👎
  MB->>HC: handleVote(messageId, type)
  HC->>RPC: mutate({ messageId, type })
  RPC->>S: upsert vote
  S-->>RPC: result
  RPC-->>HC: success/error
  HC-->>MB: resolve (UI may update)
  MB-->>U: Vote state reflected

  Note over U,MB: If 👎, feedback textarea appears

  U->>MB: Submit feedback comment
  MB->>HC: handleFeedbackComment(messageId, comment)
  HC->>RPC: mutate({ messageId, comment }) 
  RPC->>S: store comment (no vote change)
  S-->>RPC: result
  RPC-->>HC: success (toast) / error
  HC-->>MB: resolve
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A nibble of code, a carrot of cheer,
I upvote the chats that hop crystal-clear.
Downvote? I’ll pen a thoughtful note—
burrowed feedback keeps bugs afloat.
With twitchy whiskers I toast this feat,
little votes and comments—so sweet! 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sbansal1999

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (10)
packages/rpc/src/routers/assistant.ts (1)

189-191: Explicit else-if for 'downvote' is fine; consider idempotency and per-user vote constraints

The explicit else if (input.type === 'downvote') is clearer than a generic else. However, the endpoint currently allows unlimited voting by the same user on the same message. Consider a assistantMessageVotes table keyed by (messageId, userId) to enforce one vote per user and support toggling.

I can sketch the table and router changes if helpful.

apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (3)

47-53: Centralize feedback UX by using mutateAsync; keep error handling consistent

You already surface errors here. To make callers (MessageBubble) able to react (e.g., only disable UI on success), prefer mutateAsync and return a boolean from the handlers below.

No diff here; see concrete diffs in the handler comments next.


319-325: Make vote submission awaitable and resilient; return success boolean

Currently, UI disables the vote immediately and cannot recover on failure. Return a promise so the component can update state only on success.

Apply this diff:

-  const handleVote = useCallback(
-    (messageId: string, type: Vote) => {
-      addFeedback.mutate({ messageId, type });
-    },
-    [addFeedback]
-  );
+  const handleVote = useCallback(
+    async (messageId: string, type: Vote): Promise<boolean> => {
+      try {
+        await addFeedback.mutateAsync({ messageId, type });
+        return true;
+      } catch {
+        toast.error('Failed to submit vote');
+        return false;
+      }
+    },
+    [addFeedback]
+  );

326-341: Keep feedback input open on failure; return awaitable result

Right now the input closes immediately in the component. Returning a promise lets the component close only on success and preserve text on failures.

Apply this diff:

-  const handleFeedbackComment = useCallback(
-    (messageId: string, comment: string) => {
-      addFeedback.mutate(
-        { messageId, comment },
-        {
-          onSuccess: () => {
-            toast.success('Feedback submitted');
-          },
-          onError: () => {
-            toast.error('Failed to submit feedback');
-          },
-        }
-      );
-    },
-    [addFeedback]
-  );
+  const handleFeedbackComment = useCallback(
+    async (messageId: string, comment: string): Promise<boolean> => {
+      try {
+        await addFeedback.mutateAsync({ messageId, comment });
+        toast.success('Feedback submitted');
+        return true;
+      } catch {
+        toast.error('Failed to submit feedback');
+        return false;
+      }
+    },
+    [addFeedback]
+  );
apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx (1)

195-203: Passing isLastMessage via index works; consider robustness if system messages are appended

Index-based last-message detection is fine today. If system/service messages get appended later, consider a helper that finds the last assistant/user message by type to keep the UX consistent.

No change required now.

apps/dashboard/app/(main)/websites/[id]/assistant/components/message-bubble.tsx (5)

28-36: Reduce type coupling with useChat; declare minimal function types for props

Referencing ReturnType<typeof useChat> creates unnecessary coupling. Prefer explicit function signatures for better stability and compile perf.

Apply this diff:

-import type { useChat, Vote } from '../hooks/use-chat';
+import type { Vote } from '../hooks/use-chat';
@@
-interface MessageBubbleProps {
+type HandleVote = (messageId: string, type: Vote) => Promise<boolean> | void;
+type HandleFeedbackComment = (
+  messageId: string,
+  comment: string
+) => Promise<boolean> | void;
+
+interface MessageBubbleProps {
   message: Message;
-  handleVote: ReturnType<typeof useChat>['handleVote'];
-  handleFeedbackComment: ReturnType<typeof useChat>['handleFeedbackComment'];
+  handleVote: HandleVote;
+  handleFeedbackComment: HandleFeedbackComment;
   isLastMessage: boolean;
 }

This pairs with the optional mutateAsync refactor suggested in the hook.


104-161: Don’t close the feedback box until the comment is saved

Currently onSubmit immediately calls onCancel(), so failures discard the user’s text. Await the submission result and close only on success.

Apply this diff:

-function FeedbackInput({
-  onSubmit,
-  onCancel,
-}: {
-  onSubmit: (feedbackText: string) => void;
-  onCancel: () => void;
-}) {
+function FeedbackInput({
+  onSubmit,
+  onCancel,
+}: {
+  onSubmit: (feedbackText: string) => Promise<boolean> | boolean | void;
+  onCancel: () => void;
+}) {
   const [feedbackText, setFeedbackText] = useState('');
 
-  const handleSubmit = () => {
+  const handleSubmit = async () => {
     if (!feedbackText.trim()) {
       return;
     }
 
-    onSubmit(feedbackText.trim());
-    onCancel();
+    const ok = await Promise.resolve(onSubmit(feedbackText.trim()));
+    if (ok !== false) {
+      onCancel();
+      setFeedbackText('');
+    }
   };

Optionally disable the submit button while awaiting to prevent double submits.


171-199: Set vote state only after a successful mutation; handle failure gracefully

If the network fails, the UI remains in a “voted” state with no way to retry. Await the handler and only set voteType upon success.

Apply this diff (pairs with the mutateAsync change in the hook):

-const handleFeedbackButtonClick = (type: Vote) => {
-  if (type === 'downvote') {
-    setShowFeedbackInput(true);
-  }
-
-  setVoteType(type);
-  handleVote(message.id, type);
-};
+const handleFeedbackButtonClick = async (type: Vote) => {
+  if (type === 'downvote') {
+    setShowFeedbackInput(true);
+  }
+  const ok = await Promise.resolve(handleVote(message.id, type));
+  if (ok !== false) {
+    setVoteType(type);
+  }
+};

186-190: Close feedback input only on success

Same rationale as above—avoid discarding the user’s text on failure.

Apply this diff:

-const handleSubmitFeedback = (feedbackText: string) => {
-  handleFeedbackComment(message.id, feedbackText);
-  setShowFeedbackInput(false);
-};
+const handleSubmitFeedback = async (feedbackText: string) => {
+  const ok = await Promise.resolve(
+    handleFeedbackComment(message.id, feedbackText)
+  );
+  if (ok !== false) {
+    setShowFeedbackInput(false);
+  }
+};

248-257: Optional UX: show a toast on “Copy message” and handle clipboard errors

navigator.clipboard.writeText is async and can fail in some contexts. Consider try/catch with a toast to confirm or report errors.

Example:

try {
  await navigator.clipboard.writeText(message.content);
  // toast.success('Copied to clipboard');
} catch {
  // toast.error('Copy failed');
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cec210 and 8843a02.

📒 Files selected for processing (4)
  • apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx (2 hunks)
  • apps/dashboard/app/(main)/websites/[id]/assistant/components/message-bubble.tsx (3 hunks)
  • apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (5 hunks)
  • packages/rpc/src/routers/assistant.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T10:37:28.419Z
Learnt from: CR
PR: databuddy-analytics/Databuddy#0
File: .cursor/rules/01-MUST-DO.mdc:0-0
Timestamp: 2025-08-11T10:37:28.419Z
Learning: Applies to **/*.{tsx,jsx,ts,js} : When using Phosphor React icons, append 'Icon' to component names (e.g., CaretIcon, not Caret)

Applied to files:

  • apps/dashboard/app/(main)/websites/[id]/assistant/components/message-bubble.tsx
🔇 Additional comments (5)
apps/dashboard/app/(main)/websites/[id]/assistant/hooks/use-chat.ts (3)

4-6: LGTM on wiring trpc + toast

Imports look correct and localized. No action needed.


30-31: Vote type export is clear and narrow

Good use of a discriminated union for votes.


353-355: Good: expose handlers in the hook’s public API

This keeps ChatSection and MessageBubble lean.

apps/dashboard/app/(main)/websites/[id]/assistant/components/chat-section.tsx (1)

95-97: Wiring new handlers is correct

Destructuring handleVote and handleFeedbackComment from useChat() is straightforward and keeps props localized.

apps/dashboard/app/(main)/websites/[id]/assistant/components/message-bubble.tsx (1)

1-14: Icon naming conforms to our convention

Thanks for following the “append ‘Icon’” rule for Phosphor React icons. This matches our repo guideline.

Comment thread packages/rpc/src/routers/assistant.ts Outdated
Comment on lines 223 to 232
const updates: Partial<typeof assistantMessages.$inferInsert> = {};
if (input.type === 'upvote') {
updates.upvotes = message.upvotes + 1;
} else {
} else if (input.type === 'downvote') {
updates.downvotes = message.downvotes + 1;
}

@coderabbitai coderabbitai Bot Aug 24, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Defend against null counters and empty updates; add minimal guards

  • If upvotes/downvotes can be null in existing rows, message.upvotes + 1 yields NaN. Use ?? 0.
  • If neither type nor comment is provided (client bug or tampering), short-circuit with BAD_REQUEST before executing the update.

Apply this diff:

-      if (input.type === 'upvote') {
-        updates.upvotes = message.upvotes + 1;
-      } else if (input.type === 'downvote') {
-        updates.downvotes = message.downvotes + 1;
-      }
+      if (input.type === 'upvote') {
+        updates.upvotes = (message.upvotes ?? 0) + 1;
+      } else if (input.type === 'downvote') {
+        updates.downvotes = (message.downvotes ?? 0) + 1;
+      }
       // Add comment if provided
       if (input.comment) {
@@
       }
 
+      if (Object.keys(updates).length === 0) {
+        throw new TRPCError({
+          code: 'BAD_REQUEST',
+          message: 'No feedback payload provided (vote or comment required).',
+        });
+      }
+
       await db
         .update(assistantMessages)
         .set(updates)
         .where(eq(assistantMessages.id, input.messageId));

Also applies to: 248-251


💡 Verification agent

🧩 Analysis chain

Avoid lost updates on vote counters (race condition) by using atomic SQL increments

Two clients upvoting simultaneously can read the same message.upvotes and overwrite each other, undercounting votes. Prefer an atomic SET upvotes = upvotes + 1/SET downvotes = downvotes + 1 in a single UPDATE. This eliminates the read-modify-write race.

Example (requires import { sql } from 'drizzle-orm'):

// inside addFeedback, instead of computing `updates` from a prior read
if (input.type === 'upvote') {
  await db
    .update(assistantMessages)
    .set({ upvotes: sql`${assistantMessages.upvotes} + 1` })
    .where(eq(assistantMessages.id, input.messageId));
} else if (input.type === 'downvote') {
  await db
    .update(assistantMessages)
    .set({ downvotes: sql`${assistantMessages.downvotes} + 1` })
    .where(eq(assistantMessages.id, input.messageId));
}

If you keep the comment append in the same request, wrap both statements in a transaction to ensure consistency.

Run this to confirm schema defaults (ideally upvotes/downvotes default to 0 and feedbackComments defaults to []):


🏁 Script executed:

#!/bin/bash
# Verify assistantMessages schema has safe defaults for counters & comments
rg -nP -C3 '(assistantMessages|assistant_messages)\s*=\s*(pgTable|mysqlTable|sqliteTable)\('
rg -nP -C3 '\b(upvotes|downvotes|feedbackComments)\b'

Length of output: 3437


Refactor vote updates to use atomic SQL increments

The current implementation in packages/rpc/src/routers/assistant.ts reads message.upvotes/message.downvotes, increments in JavaScript, then writes back—introducing a race condition when multiple clients vote concurrently and potentially overwrite each other’s updates.

Suggested changes:

  • Replace the read-modify-write pattern with an atomic SQL increment via Drizzle’s sql helper:
import { sql } from 'drizzle-orm'
import { eq } from 'drizzle-orm'
import { assistantMessages } from 'packages/db/src/drizzle/schema'

if (input.type === 'upvote') {
  await db
    .update(assistantMessages)
    .set({ upvotes: sql`${assistantMessages.upvotes} + 1` })
    .where(eq(assistantMessages.id, input.messageId))
} else if (input.type === 'downvote') {
  await db
    .update(assistantMessages)
    .set({ downvotes: sql`${assistantMessages.downvotes} + 1` })
    .where(eq(assistantMessages.id, input.messageId))
}
  • If you’re also appending to feedbackComments in the same request, wrap both operations in a transaction to ensure consistency:
await db.transaction(async (tx) => {
  // atomic vote increment (as above) using tx instead of db…
  // then comment append via tx.update(...)
})

Schema verification confirms:

  • upvotes and downvotes are defined as integer(...).default(0).notNull(), preventing nulls and initializing counts at 0.
  • feedbackComments is a nullable jsonb column (no default). Your code’s || [] fallback correctly handles this. If you prefer a DB-level default of [], consider chaining .default([]).notNull() on that column in schema.ts, though it’s optional given the current fallback.

This refactor is essential to eliminate lost updates under concurrent load and ensure vote counters remain accurate.

🤖 Prompt for AI Agents
In packages/rpc/src/routers/assistant.ts around lines 223-228 the code does a
read-modify-write by reading message.upvotes/message.downvotes and incrementing
in JS which causes race conditions; replace that with an atomic SQL increment
using Drizzle's sql helper and eq predicate (import sql and eq and
assistantMessages), e.g. perform db.update(assistantMessages).set({ upvotes:
sql`${assistantMessages.upvotes} + 1` }).where(eq(assistantMessages.id,
input.messageId)) (or downvotes similarly) instead of writing back computed
counts, and if you also append to feedbackComments in the same request wrap both
operations in db.transaction and use the transaction object (tx) for both
updates to ensure consistency.

@sbansal1999 sbansal1999 Aug 24, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If upvotes/downvotes can be null in existing rows, message.upvotes + 1 yields NaN. Use ?? 0.

the DB has default value of upVotes and downVotes as 0, so the null thing won't be an issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sbansal1999, you're absolutely right about the database defaults—I can see from the schema that upvotes and downvotes are defined as integer(...).default(0).notNull(), so null values won't be an issue.

However, the more critical concern remains: race conditions during concurrent voting. When multiple users vote simultaneously, they can read the same counter value, increment it in JavaScript, and overwrite each other's updates, leading to lost votes.

The atomic SQL increment approach I suggested eliminates this race condition entirely:

if (input.type === 'upvote') {
  await db
    .update(assistantMessages)
    .set({ upvotes: sql`${assistantMessages.upvotes} + 1` })
    .where(eq(assistantMessages.id, input.messageId));
}

This ensures vote counts remain accurate under concurrent load, which is essential for a feedback system.


✏️ Learnings added
Learnt from: sbansal1999
PR: databuddy-analytics/Databuddy#111
File: packages/rpc/src/routers/assistant.ts:228-233
Timestamp: 2025-08-24T08:00:03.800Z
Learning: In the Databuddy assistant messages schema, upvotes and downvotes columns are defined with integer(...).default(0).notNull(), so they cannot be null and will always default to 0.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If neither type nor comment is provided (client bug or tampering), short-circuit with BAD_REQUEST before executing the update.

Added zod validation to prevent that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sbansal1999, you're absolutely correct! The Zod validation with .refine((v) => v.type || v.comment, { message: 'Either type or comment must be provided' }) already prevents empty updates by ensuring at least one field is provided before the mutation logic executes. This makes my suggestion about the runtime Object.keys(updates).length === 0 check redundant.

However, the race condition concern with concurrent voting still remains the primary issue—when multiple users vote simultaneously on the same message, the current read-modify-write pattern can lead to lost votes. The atomic SQL increment approach would still be valuable to ensure vote accuracy under concurrent load.


✏️ Learnings added
Learnt from: sbansal1999
PR: databuddy-analytics/Databuddy#111
File: packages/rpc/src/routers/assistant.ts:228-233
Timestamp: 2025-08-24T08:00:27.729Z
Learning: The addFeedback mutation in packages/rpc/src/routers/assistant.ts uses Zod validation with .refine((v) => v.type || v.comment) to ensure either a vote type or comment is provided, preventing empty updates from reaching the database operation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: sbansal1999
PR: databuddy-analytics/Databuddy#111
File: packages/rpc/src/routers/assistant.ts:228-233
Timestamp: 2025-08-24T08:00:03.800Z
Learning: In the Databuddy assistant messages schema, upvotes and downvotes columns are defined with integer(...).default(0).notNull(), so they cannot be null and will always default to 0.

@izadoesdev
izadoesdev merged commit 6e74b09 into databuddy-analytics:staging Aug 24, 2025
2 of 4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 24, 2025
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.

2 participants