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

spaps-issue-reporting-react

v0.6.4

Published

Shared React issue-reporting UI for Sweet Potato platform consumers

Readme

spaps-issue-reporting-react

Shared React issue-reporting UI for SPAPS-compatible apps.

Examples in this README use placeholder IDs and app labels. Replace them with values from your own product, session model, and API client.

Install

npm install spaps-issue-reporting-react react react-dom @tanstack/react-query

This package targets Node.js >=18 and React 18+. Voice input uses the package's @elevenlabs/react dependency and a SPAPS-minted single-use token.

When It Fits

| Need | Package gives you | | --- | --- | | A visible issue-report entry point | Floating button with open/recent state | | A guided reporting flow | Page-first create flow, optional section picking, create/edit/reply modal | | Screenshot evidence | Optional picker and opt-in lazy DOM capture that upload private PNG/JPEG/WebP attachments through the host client | | A lightweight integration contract | Any client that exposes issueReporting.* methods | | App-level control | Eligibility, reporter identity, scope, page policy, and copy stay in your app |

Quick Start

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
  FloatingIssueReportButton,
  IssueReportingPageConfig,
  IssueReportingProvider,
  ReportableSection,
} from "spaps-issue-reporting-react";
import { spaps } from "./spapsClient";

const queryClient = new QueryClient();

export function AppShell() {
  return (
    <QueryClientProvider client={queryClient}>
      <IssueReportingProvider
        client={spaps}
        isEligible={true}
        principalId="user_123"
        reporterRoleHint="staff"
        defaultScope="mine"
        inputModes={["text", "voice"]}
        defaultInputMode="text"
      >
        <IssueReportingPageConfig createMode="surface_preferred" />

        <ReportableSection
          reportableName={{
            componentKey: "patient_chart",
            componentLabel: "Patient Chart",
            metadata: { area: "overview" },
          }}
          className="rounded-xl"
        >
          <PatientChart />
        </ReportableSection>

        <FloatingIssueReportButton />
      </IssueReportingProvider>
    </QueryClientProvider>
  );
}

What Your App Still Owns

  • A client with issueReporting.getStatus, list, get, create, update, and reply.
  • A client with issueReporting.createVoiceToken when inputModes includes voice.
  • A client with issueReporting.uploadAttachment(file, { filename }) when you want screenshot uploads.
  • For automatic screenshot capture, spaps-sdk >=1.10.1 or an equivalent client that implements issueReporting.uploadAttachment(file, { filename }).
  • Optionally, a client with issueReporting.listMessages(issueReportId) and issueReporting.submitMessage(issueReportId, { body, idempotency_key }) to wire the operator-to-reporter message thread (and its needs_response badge). When these are present, the bundled IssueReportMessageThread renders the conversation inside the issue detail modal (see Conversation Thread).
  • Any auth and token refresh behavior needed by that client.
  • Eligibility rules such as feature flags, account state, or role checks.
  • The current principal ID and optional role hint passed into the provider.
  • Whether users can switch between mine and a custom tenant queue scope. The stock SPAPS API supports mine only; use allowTenantScope only with a client that implements tenant reads.
  • Whether a page defaults to general_page, surface_required, or surface_preferred reporting.
  • Whether the app allows ["text"], ["voice"], or ["text", "voice"] report input.
  • User-facing screenshot sensitivity warnings for surfaces that may capture private data.
  • Styling integration if your build strips package utility classes.
  • Any app-specific copy overrides.
  • Origin registration on the owning SPAPS application if the browser calls SPAPS directly with a publishable key.

For direct browser integrations, allowed_origins is stored on the SPAPS applications row that owns the publishable key. It is not configured on this package. If multiple hostnames share one SPAPS application, put all of them on that row. Updating allowed_origins does not require restarting SPAPS.

Voice Input

Voice mode is opt-in per app. Configure the owning SPAPS application with either:

{
  "issue_reporting_input_modes": ["text", "voice"]
}

or:

{
  "issue_reporting": {
    "input_modes": ["voice"]
  }
}

The SPAPS server also needs ELEVENLABS_API_KEY. The browser calls your normal SPAPS client, which mints a short-lived Scribe token through issueReporting.createVoiceToken(). The committed transcript is submitted as the issue note; raw audio is not stored by this package.

<IssueReportingProvider
  client={spaps}
  isEligible={true}
  inputModes={["voice"]}
  defaultInputMode="voice"
  voice={{
    modelId: "scribe_v2_realtime",
    microphone: {
      echoCancellation: true,
      noiseSuppression: true,
      autoGainControl: true,
    },
  }}
>
  <FloatingIssueReportButton />
</IssueReportingProvider>

When text and voice are both enabled, the modal keeps the textarea available and lets the user append a committed transcript into the editable note.

Screenshot Attachments

Screenshot attachments are opt-in through the client contract. If the client passed to IssueReportingProvider exposes issueReporting.uploadAttachment, the modal renders a screenshot picker. If the method is absent, the picker stays hidden and text/voice reporting works unchanged.

const client = {
  issueReporting: {
    ...spaps.issueReporting,
    uploadAttachment: (file: Blob, options?: { filename?: string }) =>
      spaps.issueReporting.uploadAttachment(file, options),
  },
};

The picker accepts PNG, JPEG, and WebP files, enforces the 10 MiB per-file limit and 5-screenshot report limit, previews pending files, and preserves existing attachments while editing. On submit, it uploads pending files first and sends only attachment IDs through attachment_ids, add_attachment_ids, or remove_attachment_ids.

This package does not store screenshots, generate public URLs, or perform pixel/OCR redaction after capture. SPAPS stores screenshots through its shared hosted-asset boundary as private objects, and access URLs are minted by the backend after authorization. The automatic capture path masks common sensitive controls and token-like text in the cloned DOM before rendering, but host apps should still warn users before upload when a screenshot might include PHI, credentials, payment data, or other sensitive content.

Automatic Screenshot Capture

Automatic DOM capture is opt-in and lazy. Apps that omit screenshotCapture or set enabled: false get no behavioral change and do not download html2canvas.

Peer dependency: install html2canvas (>=1.4.0) as an optional peer in the consuming app. The capture module only loads via dynamic import() when a report is submitted with capture enabled.

npm install html2canvas
<IssueReportingProvider
  client={spaps}
  isEligible={true}
  screenshotCapture={{
    enabled: true,
    captureOn: "before_submit",
    scope: "visible_viewport",
    imageType: "image/png",
    excludeSelectors: [
      "[data-spaps-screenshot-exclude]",
      "[data-spaps-private]",
    ],
    failureBehavior: "continue_without_screenshot",
    onCaptureError: (err) => console.warn("Screenshot capture failed:", err),
  }}
>
  <FloatingIssueReportButton />
</IssueReportingProvider>

screenshotCapture maps to IssueReportingScreenshotCaptureConfig in src/types.ts:

| Field | Behavior | | --- | --- | | enabled | Required opt-in. false or an absent prop means no DOM capture, no lazy import, and no behavior change. | | captureOn | Only "before_submit" is supported: capture after the reporter submits and before pending attachments upload. | | scope | "visible_viewport" by default. "selected_reportable_target" records the selected target in capture context; to narrow pixels to that section, pass rootElement or a getter that returns the selected same-origin element. | | imageType | Defaults to "image/png"; must stay within SPAPS-supported PNG/JPEG/WebP uploads. | | filename | Optional static name or callback (context) => string. The upload flows through issueReporting.uploadAttachment(file, { filename }). | | rootElement | Optional same-origin element or getter for host shells that need to exclude app chrome or capture a selected section. If omitted, capture starts from document.documentElement and is cropped to the visible viewport. | | rect | Optional visible-viewport crop rectangle or getter. Custom consumers can use this for click-and-drag/refarea tools while still sharing the package capture implementation. | | excludeSelectors | Optional extra selectors to omit. The implementation always honors [data-spaps-screenshot-exclude] and [data-spaps-private] as privacy markers. Invalid custom selectors are ignored so they cannot block submission. | | maskSelectors | Optional selectors to mask in the cloned DOM before rendering. Defaults mask form controls and editable regions. | | pixelRatio | Optional render scale, clamped to 1..2. Defaults to window.devicePixelRatio within that range. | | quality | Optional image quality passed to canvas encoding for lossy formats. | | maxBytes | Cannot exceed the SPAPS 10 MiB attachment limit. An automatic screenshot counts toward the existing five attachment cap. | | failureBehavior | Only "continue_without_screenshot" is supported. Capture/render/upload failure does not block issue submission. | | onCaptureError | Optional host callback for telemetry or a local warning when capture fails. Receives the raw browser error; raw error text is not stored in issue metadata by this package. |

Minimum SDK capability: spaps-sdk >=1.10.1 or an equivalent client implementing issueReporting.uploadAttachment(file, options) returning IssueReportAttachmentOut.

What is captured:

  • Automatic capture is opt-in and lazy. It never runs on provider mount, page load, history view, edit, reply, or when the upload helper is absent.
  • The capture module dynamically imports html2canvas only during create submit after screenshotCapture.enabled === true.
  • Capture is cropped to the current visible viewport dimensions. It does not capture hidden browser chrome, full-page scrollback, background tabs, or data outside the visible page.
  • html2canvas renders same-origin DOM it can access. Cross-origin iframes and tainted canvases are not a supported capture target.

Custom consumers that do not use the shared modal can import the same primitive without reaching into private build output:

import { captureScreenshot } from "spaps-issue-reporting-react/screenshot-capture";

The subpath also exports maskScreenshotCaptureDocument, SCREENSHOT_CAPTURE_REDACTION_TEXT, and the related screenshot capture types for app-local adapters.

Sensitive-region exclusion:

  • Mark sensitive DOM with data-spaps-screenshot-exclude or data-spaps-private.
  • Add host-specific excludeSelectors for reusable sensitive regions such as account numbers, tokens, patient identifiers, or payment fields.
  • Exclusion is omission during rendering, not image-content redaction after capture. The capture path masks common cloned DOM controls and token-like text, but it does not inspect, blur, OCR, or redact rendered pixels. Host apps own warnings and exclusion markers for sensitive surfaces.

Capture metadata and fallback:

  • On successful automatic capture, create payload metadata includes target.metadata.screenshot_capture = { status: "attached", scope, image_type }.
  • On capture/render/upload failure, create payload metadata includes target.metadata.screenshot_capture = { status: "failed", scope, image_type, failure_reason: "capture_failed" }.
  • Failure metadata intentionally stores a coarse reason only. Use onCaptureError for app-local telemetry if you need the raw error.
  • Capture failures continue issue submission with text/voice metadata and any manual attachments.
  • Preserves the SPAPS hosted-asset constraints: private objects, PNG/JPEG/WebP only, 10 MiB per file, and five attachments per report.
  • If the blob exceeds maxBytes, the module compresses automatically (falling back to JPEG at decreasing quality). If compression still exceeds the limit, the error fires through onCaptureError and submission continues without the screenshot.

Operator-gated release:

Publishing this version requires operator approval. Consumers may opt in by passing screenshotCapture={{ enabled: true }} only after the approved package version is published to npm.

Conversation Thread

Support operators can ask a reporter for clarification and post a final resolution message. Those messages flow into an allowlisted, reporter-visible projection that the reporter reads back as a conversation. This package renders that surface when the client implements the two optional message methods:

const client = {
  issueReporting: {
    ...spaps.issueReporting,
    listMessages: (issueReportId: string) =>
      spaps.issueReporting.listMessages(issueReportId),
    submitMessage: (issueReportId, payload) =>
      spaps.issueReporting.submitMessage(issueReportId, payload),
  },
};

When listMessages is present, FloatingIssueReportButton mounts the exported IssueReportMessageThread inside the issue detail modal (the Edit/Reply views). The thread:

  • Renders only active, reporter_visible projection rows in chronological order. Retracted and superseded operator messages, non-visible rows, and raw support_case_events payloads are never displayed. The backend filters this; selectReporterVisibleMessages re-asserts the same rule in the UI.
  • Distinguishes operator (Support) vs reporter (You) authorship, labels each message kind (clarification_request, reporter_response, final_response), and shows a relative timestamp.
  • Shows a needs-response affordance when the API's needs_response flag is set (an open clarification request awaiting an answer), and surfaces a matching dot badge on the floating entrypoint while that thread is open.
  • Renders a reporter clarification-response composer (shown only when the client implements submitMessage). It calls submitMessage(issueReportId, { body, idempotency_key }) with a client-generated idempotency_key. A key reused with a different body returns 409 ISSUE_REPORT_MESSAGE_CONFLICT; the composer detects this (isReporterMessageConflict), shows the conflict copy, and rotates the key for retry.
  • Exposes loading, empty, and error (with retry) states.

The composer does not reopen a closed case; reopen semantics stay with the Reply flow. You can also mount IssueReportMessageThread directly (outside the modal) inside your own issue detail view:

import { IssueReportMessageThread } from "spaps-issue-reporting-react";

<IssueReportMessageThread issueReportId={issue.id} />;

All thread strings are overridable through the provider copy prop. The thread copy keys are: threadTitle, threadDescription, threadLoading, threadLoadFailed, threadEmpty, threadNeedsResponseBadge, threadAuthorOperator, threadAuthorReporter, threadKindClarificationRequest, threadKindReporterResponse, threadKindFinalResponse, threadResponseLabel, threadResponsePlaceholder, threadResponseSubmitAction, threadResponseSubmittingAction, threadResponseConflict, and threadResponseFailed.

Exported Surface

| Export | Purpose | | --- | --- | | IssueReportingProvider | Owns queries, modal state, copy, scope, and report-mode behavior | | IssueReportingPageConfig | Per-page override for general_page, surface_required, or surface_preferred create policy | | FloatingIssueReportButton | Renders the floating entry point, popover, modal, and (when the client supports messages) the conversation thread | | IssueReportMessageThread | Renders the reporter-visible operator-to-reporter conversation for one issue report, with a clarification-response composer | | ReportableSection | Marks a region as selectable when report mode is active | | useIssueReporting | Full provider state, including startNewIssue(), openPageIssueModal(), and enterReportMode() | | useIssueReportingStatus | Query helper for summary state | | useIssueReportingHistory | Query helper for filtered history lists | | useIssueReportingMutations | Mutation helpers for create, update, and reply flows | | useIssueReportingMessages | Query helper for one issue report's reporter-visible message thread | | useIssueReportingMessageMutation | Mutation helper for submitting a reporter clarification response | | selectReporterVisibleMessages | Filters a message list to active, reporter-visible rows in chronological order | | isReporterMessageConflict | Detects a 409 ISSUE_REPORT_MESSAGE_CONFLICT from the client transport | | useReportMode | Low-level selection state for custom wrappers |

Styling Notes

  • The package ships utility-class based markup for the button, popover, modal, and report-mode highlights.
  • If your CSS build only scans local source files, include this package in the scan path or safelist the relevant classes.
  • FloatingIssueReportButton accepts className and positionClassName for placement tweaks.
  • Provider copy can be overridden with the copy prop.

Development

cd packages/issue-reporting-react
npm ci
npm run build
npm test

Troubleshooting

The button renders but the modal never opens

Make sure FloatingIssueReportButton is rendered inside IssueReportingProvider and that isEligible is true.

I want page-level reporting without wrapping every widget

That is the default. Report This Page opens immediately and captures the current route plus the inventory of visible registered reportable sections on the active page.

Report mode turns on, but sections do not respond

Wrap the target UI in ReportableSection, or call useIssueReporting().selectPanel(...) from a custom control.

This page should force a specific surface selection

Render IssueReportingPageConfig with createMode="surface_required" inside that page's visible provider subtree. Hidden mounted routes, tabs, or panels do not affect the active page's policy.

Tenant scope never appears

The stock SPAPS API supports mine only. Set allowTenantScope={true} only when your client implements tenant-scoped getStatus and list calls.

Styles look unformatted

Include the package in your Tailwind or CSS-content scan, or override the rendered classes in your host app.

Voice controls do not appear

Pass inputModes={["voice"]} or inputModes={["text", "voice"]} to the provider, and enable voice in the owning SPAPS application's issue-reporting settings.

Voice starts with a token error

Confirm that the SPAPS server has ELEVENLABS_API_KEY set and that the current user is authenticated and eligible for issue reporting.

Screenshot controls do not appear

Expose issueReporting.uploadAttachment on the client passed to IssueReportingProvider. The package intentionally hides the picker when the upload helper is absent.

The conversation thread does not appear

Expose issueReporting.listMessages on the client passed to IssueReportingProvider, then open an existing issue's detail (Edit/Reply). The thread is intentionally hidden when the client cannot list messages. The response composer additionally requires issueReporting.submitMessage; without it the thread renders read-only.

Limitations

  • This package assumes React Query is already part of the host app.
  • It focuses on the issue-reporting UI flow; queue policy, triage rules, and backend delivery stay outside the package.
  • Deep visual changes may require wrapping the exported components rather than using props alone.

FAQ

Do I need the full SPAPS SDK?

No. You only need a client object that implements the issueReporting methods expected by the provider.

Can I launch report mode from my own button?

Yes. Use useIssueReporting().enterReportMode() inside the provider tree.

Can I open a page-level report from my own button?

Yes. Use useIssueReporting().openPageIssueModal() or useIssueReporting().startNewIssue().

Can I report plain strings instead of structured descriptors?

Yes. reportableName accepts either a string or a { componentKey, componentLabel, ... } object.

Can I hide the feature for some accounts?

Yes. Drive that from isEligible.

Does this package manage tenant permissions?

No. It only renders scope choices that your app explicitly allows.

Does this package manage CORS or allowed_origins?

No. This package is UI only. If your browser app calls SPAPS directly with a publishable key, the relevant SPAPS application row must include that browser origin in allowed_origins.

Design system

This package is intentionally theme-neutral and uses accessible Radix behavior primitives plus a restrained slate palette so it blends into the host app. The Design System Registry (DSR) adoption decision for this surface is an explicit deferral because no suitable theme-neutral registry item exists yet. The repository-level docs/DSR-DECISION.md captures the reasoning, file-level audit, and revisit trigger.

Metadata

  • package_name: spaps-issue-reporting-react
  • latest_version: 0.6.2
  • minimum_runtime: Node.js >=18.0.0
  • api_base_url: https://api.sweetpotato.dev

About Contributions

About Contributions: Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.

License

UNLICENSED