npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@botiverse/hands-feedback-react

v0.4.1

Published

Embeddable reporter-facing Hands feedback inbox components.

Readme

@botiverse/hands-feedback-react

Reporter-facing Hands feedback components. The package renders a ticket inbox, conversation detail, reply composer, and new-feedback form with elegant and brutal themes.

import {
  FeedbackProvider,
  FeedbackWorkspace,
  type HandsFeedbackTransport,
} from "@botiverse/hands-feedback-react";
import "raft-ui/styles.css";
import "@botiverse/hands-feedback-react/styles.css";

// Implement this in your app. It calls your authenticated same-origin proxy;
// the complete adapter is in the integration guide linked below.
const transport: HandsFeedbackTransport =
  createMyAppFeedbackTransport("/api/feedback");

<FeedbackProvider
  transport={transport}
  theme="brutal"
  // Optional. Without this override the SDK maps browser `zh*` to `zh-CN`
  // and falls back to English. Pass the host app's selected locale when set.
  locale="en"
  onUnreadChanged={({ total }) => setFeedbackBadge(total)}
>
  <FeedbackWorkspace />
</FeedbackProvider>;

The workspace owns the reporter interaction state: list filter/scroll/focus restoration, per-ticket reply drafts, IME-safe Enter handling, attachment progress/retry/cancel, recoverable cursor pages, and near-bottom conversation following. Hosts should not duplicate that state machine. They provide only the reporter-scoped transport, route notifications, and (optionally) an attachment opener.

On mobile, hosts can bind those workspace notifications to real browser URLs and enable the package's touch-only pull-to-refresh behavior:

<FeedbackWorkspace
  route={routeFromLocation(location)}
  onRouteChange={(nextRoute, options) =>
    navigate(pathForRoute(nextRoute), { replace: options?.replace })
  }
  enablePullToRefresh={isMobile}
/>

inbox, new, and ticket are the supported workspace routes. The SDK marks the create-success transition from new to ticket with replace: true, so Back does not reopen a submitted form. Pull-to-refresh is attached only to the list and conversation scroll viewports, starts only at scroll-top, ignores editable controls, and preserves existing content while authoritative data is reloaded. The generic usePullToRefresh hook is also exported for other host scroll surfaces.

All visible copy, closed error copy, dates, and accessibility labels share the SDK locale. Supported locales are en and zh-CN; an explicit provider locale takes precedence over browser language detection.

Localization is provider-driven rather than limited to the built-in bundles. messages accepts a typed partial override (missing keys fall back to the resolved locale), including parameterized validation strings. formatDate and formatFileSize let the host apply its own formatting conventions without rebuilding SDK UI. Browser negotiation selects the first supported entry in navigator.languages order, then falls back to English.

<FeedbackProvider
  transport={transport}
  locale={appLocale}
  messages={{
    newFeedback: t("feedback.new"),
    attachmentUnsupported: t("feedback.attachmentUnsupported"), // `{name}`
  }}
  formatDate={(date, { locale }) => appDateFormatter(date, locale)}
  formatFileSize={(bytes, { locale }) => appFileSizeFormatter(bytes, locale)}
>
  <FeedbackWorkspace />
</FeedbackProvider>

Host-staged attachments

A host may put one small file (for example a validated diagnostic snapshot) into the reporter's pending attachments. Prepare the File first (any async fetch/validation happens before this click), then call the handle synchronously from the click handler:

const workspace = useRef<FeedbackWorkspaceHostHandle>(null);

<button
  onClick={() => {
    // No `await` before this call: it needs the live user gesture.
    const result = workspace.current?.openNewFeedbackWithPendingFile({
      file: preparedDiagnosticFile,
    });
    if (result && !result.ok) showReason(result.reason);
  }}
>
  Report issue with diagnostic
</button>
<FeedbackWorkspace
  ref={workspace}
  onOpenPendingAttachment={({ file }) => previewInHost(file)}
/>
  • openNewFeedbackWithPendingFile({ file }) opens the new-feedback form with the file already pending in the same call. If the form is already open, it adds the file there. It is synchronous and returns a typed result.
  • attachPendingFile({ file }) is the low-level variant: it only stages into an already open form and returns composer_closed otherwise.

The contract (both methods):

  • Stage only. They never upload or submit. The reporter sees the file, can open it (via onOpenPendingAttachment, including non-image files), can remove it, and it is sent only when they press Submit.
  • User gesture required. Without transient browser user activation (navigator.userActivation.isActive) they return user_activation_required and change nothing, including the route.
  • Bytes only. Input is exactly { file: File }. No paths, URLs, callbacks, or background fetches.
  • Bounded. At most MAX_FEEDBACK_HOST_ATTACHMENTS (1) host file, MAX_FEEDBACK_HOST_ATTACHMENT_BYTES (1 MiB), types in FEEDBACK_HOST_ATTACHMENT_TYPES (the screenshot image types plus application/json and text/plain), and it counts toward the shared 3-attachment limit. The same file (name, type, size, lastModified) is rejected as duplicate.
  • Typed rejection, zero mutation. Failures return { ok: false, reason } (FeedbackHostAttachmentRejection) and do not navigate, change the pending list, show an error banner, or upload anything. busy means a submission is in flight.

Your Hands app and transport must accept the injected MIME type. The Hands public submit endpoint accepts any type. Reporter replies accept only images, which is why host files go to the new-feedback form only.

Security boundary

  • The package has no appToken, clientSecret, reporterId, or arbitrary owner prop.
  • The host transport binds a short-lived reporter-scoped session.
  • Hands is authoritative for tickets and read/unread state. unreadTotal comes from Hands responses; the component never derives or persists its own read cursor.
  • A detail request reports and clears unread only after its successful Hands response. Failed, aborted, stale, and unmounted reads do not mutate unread.
  • Webhooks are optional server-to-server integrations and are not required for inbox correctness.

Consumers using Tailwind v4 should include the compiled package as a source if their build purges library classes:

@source '../node_modules/raft-ui/dist';
@source '../node_modules/@botiverse/hands-feedback-react/dist';

For a source-pinned integration before an npm release, install this package's Hands monorepo subdirectory with pnpm's path: git selector, then import the explicit source exports:

pnpm add "github:botiverse/hands#<full-commit-sha>&path:packages/feedback-react"
pnpm add react react-dom raft-ui lucide-react
import {
  FeedbackProvider,
  FeedbackWorkspace,
} from "@botiverse/hands-feedback-react/source";
import "@botiverse/hands-feedback-react/source/styles.css";

The host bundler must support TypeScript/TSX dependencies. Published consumers should use the compiled root exports shown above.

The complete server credential, opaque reporter identity, route binding, proxy, transport, webhook, and production-checklist walkthrough is in the React Feedback Inbox guide.