@tautau/browser
v0.1.10
Published
Private, service-scoped bug reporting and evidence capture for web apps.
Readme
tautau browser reporting
Embed a private bug reporter in a service you manage in tautau. Create a service under Services, add the exact origins for each environment, then copy its installation snippet.
<script defer src="https://tautau.xyz/sdk/latest/tautau.js"
data-tautau-key="tt_pub_YOUR_ENVIRONMENT_KEY"></script>The environment key is publishable. It only permits submissions from the configured origins; it cannot read reports, run fixes or access your repository. Rotate it under the service's Environments tab. Use separate environments for production and localhost.
For module-based apps:
import { createTautau } from 'https://tautau.xyz/sdk/latest/tautau.mjs';
const reporter = createTautau({
ingestKey: 'tt_pub_YOUR_ENVIRONMENT_KEY',
release: { version: '2026.09.27', commit: 'FULL_40_CHARACTER_COMMIT_SHA' },
capture: {
mode: 'reports-only',
blockSelectors: ['.customer-address', '[data-private]'],
},
launcher: false,
onSubmitted: ({ reportId }) => console.log('Report saved', reportId),
});
document.querySelector('#report-bug').onclick = () => reporter.openReport();
// On application teardown:
// reporter.destroy();The latest channel follows each production SDK deployment. Check https://tautau.xyz/sdk/latest/version.json for its package version. Channel URLs revalidate on each load, so an installed embed receives updates on its next page load. The existing /sdk/tautau.js and /sdk/tautau.mjs URLs remain supported.
Open the version disclosure below Send report to see the loaded browser reporter version and draft-recovery setting. Copy version details copies only those values. The label comes from the loaded bundle, so it still identifies an older tab or bundled npm installation correctly. Host app versions remain under App info. For programmatic checks, use reporter.getVersion() or the module’s version export.
For apps with a bundler, install the npm package with TypeScript declarations:
npm install @tautau/browser@latestimport { createTautau } from '@tautau/browser';Initialise the reporter in the browser, using the same options as the module example above. latest selects the current npm release when you install or update; existing apps keep the version recorded in their lockfile until they update it.
Sign in as the reporter
The report drawer shows your connected account or Reporting as a guest. Sign in to tautau saves your draft and evidence, opens account confirmation in the same tab, then returns directly to the restored drawer. The account already signed in to tautau in that Chrome profile is offered first; Google sign-in offers that profile's signed-in Google accounts. A first Google sign-in can still open Google's authentication window. Use another account changes the tautau account before you confirm sharing. Verified names, emails and profile photos appear with reports and linked tickets; this does not change Jira's native reporter account.
The SDK uses a one-use S256 PKCE exchange bound to the environment key, the actual app origin and a server-validated return URL. The return code travels in a URL fragment, is removed immediately and can only be exchanged with the verifier saved in that tab. Firebase ID and refresh tokens never reach the host app. Your reporting-only credential is cached in sessionStorage for at most one hour and checked with the server on reload. Last-used account metadata is remembered for up to 30 days in localStorage, separately from the credential; it never authenticates a report by itself. Both caches are scoped to the API and installation key, within the current browser profile. Sign out revokes the reporting session and keeps the last-used account hint.
Credentials stay out of drafts, diagnostics and public API results. Expired or revoked sessions block sending until you sign in again or explicitly sign out to report as a guest. persistDrafts: false keeps both evidence and reporting sessions in memory. First-party integrations can provide reporterAuth.authorize(requestId, signal) to return a one-use code from the first-party authenticated authorisation endpoint and connect inline; the server still verifies the identity and installation binding.
If draft or tab storage is unavailable, sign-in keeps the draft on the current page and uses the existing window flow. Set reporterAuth: { mode: "popup" } to explicitly keep that flow (it requires same-origin-allow-popups when your app uses Cross-Origin-Opener-Policy). If saving a draft fails after same-tab sign-in begins, navigation stops and your current draft stays visible.
Module-based apps can use these methods. Call signIn() directly from a user click so the browser can open its sign-in window:
connectButton.onclick = async () => {
const identity = await reporter.signIn();
// Safe metadata: uid, displayName, email, emailVerified. No credential.
accountLabel.textContent = identity?.displayName || 'Reporting as a guest';
};
const identity = reporter.getReporter(); // null when disconnected or expired
await reporter.signOut();Evidence and privacy
By default the event buffer begins when the reporter opens. To collect the lead-up to a problem, explicitly choose capture.mode: 'breadcrumbs' and enable breadcrumbs in the service policy. The buffer retains at most 150 events. It records clicks, navigation, error stacks, redacted console messages at log/info/debug/warn/error levels, and request URL/method/status/timing. Console objects are omitted without reading their properties; original arguments still reach the app's console. It does not read request bodies, cookies, storage or form values. General request and response headers are omitted; the diagnostic ID headers described below are the only exception. URLs lose credentials, query strings and fragments; common secrets and email addresses are redacted from diagnostic text. Review the diagnostics before sending.
Error prompts and request IDs
Runtime errors, unhandled promise rejections, visible error boundaries, HTTP 5xx responses and failed network requests can light up the launcher and show a dismissible Report a bug prompt. Cancelled requests stay quiet. The bounce stops after three short pulses and respects reduced motion. Prompts have a 30-second cooldown, and reopening the drawer clears the cue. Set errorPrompt: false to disable prompts. Detection never opens a report or sends one automatically.
While the drawer is closed, the detector keeps only a generic failure signal and up to ten validated request IDs, trace IDs and error references. It does not retain exception text or stacks before event collection starts. Failed responses can contribute x-request-id/request-id, x-trace-id/trace-id, a valid W3C traceparent, and x-error-digest/x-error-reference. The response body is untouched. Cross-origin APIs must expose their diagnostic headers through CORS. Reporter requests are excluded.
The drawer accepts a pasted ID in Request or trace ID. It also recognises explicit labels such as Request ID: …, Trace ID: … and Error reference: … in the report description. These references appear under Review diagnostics and stay with the saved draft, including submission retries. The organisation's configured observability provider can resolve them on the server; provider credentials never reach the browser.
For a caught React boundary, call the reporter with the error and its known IDs:
// In componentDidCatch, or an effect in a framework's error boundary:
reporter.captureError(error, { requestId, traceId, errorDigest: error.digest });Frameworks often catch render errors before a window error event fires. Add an explicit hook to the rendered fallback for reliable detection:
<section role="alert" data-tautau-error-boundary
data-request-id="request-123"
data-trace-id="0123456789abcdef0123456789abcdef"
data-error-digest="error-42">
Something went wrong. Try again, or report the problem.
</section>Existing visible role="alert" fallbacks with labelled IDs are also recognised, including SFH's English request/trace/error-reference rows. DOM detection waits for the service privacy policy and skips private selectors, form controls and hidden content. Explicit hooks work with translated copy. Call destroy() when removing the reporter to disconnect the observer, listeners and network wrappers.
Screenshots and recordings use the browser's screen-sharing permission. When Chromium confirms capture of the current tab, input regions, contenteditable fields, embedded frames and configured private regions are painted over before storage. Arbitrary text and closed shadow trees cannot be classified reliably: mark sensitive host elements data-tautau-private. Other selected surfaces and uploaded files require the reporter's own review and redaction. The reporter says when automatic masking could not verify the selected tab. Invalid privacy selectors discard capture rather than bypass masking.
Choose Draw to hide the drawer and annotate the live page with a floating toolbar. Opening this overlay does not request screen-sharing permission or create an attachment. Switch to Interact to use the app, then back to Draw to add marks. Marks stay at their page coordinates while you scroll. Clear old marks after changing the layout or navigating within the app. The overlay cannot draw on other tabs, browser chrome or desktop windows.
Use Freehand, Arrow, Box, Hide details or Text, with eight colours and Thin, Medium or Bold stroke widths. Drag to place a stroke or shape; repeat to combine marks. Click with Freehand to add a dot. Each mark keeps its original colour and width. Type a label, then click to place it. In the screenshot editor, labels can also be dictated. Undo removes the most recent mark. Hide details always paints an opaque dark mask, independent of the chosen colour.
Choose Screenshot when you are ready to capture actual browser pixels. Grant the browser's permission and select the current app tab to keep page annotations aligned. The saved image includes the annotations and masks; the drawer's screenshot editor remains available for final adjustments. Record steps captures live browser video and allows drawing while recording. Select the current app tab to include its live annotations. The floating toolbar may appear in the video. Open Recording options and select Include my microphone when recording steps beforehand for narration. Review report returns to the drawer, stopping an active recording first. Review every included image and clip before sending. Recording is opt-in, limited to 60 seconds and 50 MB. A report can contain ten attachments, up to 100 MB in total. Screenshot annotations are flattened into the uploaded pixels.
Choose Select area to capture part of a tab, window or screen. After choosing the surface in the browser's picker, drag over the captured image and choose Use selection. Sharing stops before this preview opens. The image keeps its original pixel resolution, including current-tab privacy masks and page annotations. Full screenshot keeps the entire image; Cancel or Escape returns to your draft without adding evidence. Arrow keys create or move a selection, Shift + arrows resize it, and Enter confirms it. The cropped image opens in the screenshot editor for annotation and review.
Add attachments lets you select several files at once, including images, recordings, audio, documents and logs. Supported media can be reviewed in the drawer; other files have a download link for review. Documents and other file attachments are served as downloads. Every file must be non-empty and no larger than 50 MB, with ten attachments and 100 MB total per report. Review all attachments before sending. A file whose format cannot be previewed still retains its original bytes and filename.
Dictate report adds recognised speech to the description. Voice typing uses the browser's speech service when available and stops when the reporter closes, starts capture or submits. Browser support and speech-service availability vary; typed input remains available.
Under Review diagnostics, Include page asset inventory is off by default. When enabled, the diagnostics preview lists up to 100 image, script, stylesheet and media URLs outside private regions. The server sanitises and stores that inventory with the occurrence; the report's Assets tab, JSON manifest and evidence ZIP include it. Inventory collection does not fetch asset files. Attach images or clips separately when their original bytes are needed. Private selectors apply to both snapshots and inventories, and invalid selectors discard their contents.
Report destination and private tautau support
Bug category is optional. Choose Functionality, Visual / layout, Performance, Accessibility, Security or Other, or leave Not specified selected. The category stays with the captured occurrence and its saved draft. Jira previews and new issues include a matching label, such as category-performance, alongside configured labels. The choice stays fixed once submission starts, including retries after a lost response or failed evidence upload.
The drawer keeps capture actions beside the evidence. App info shows the app version at a glance and expands to show the full version and commit supplied in release. Missing values appear as Not provided. The host app's release is only verified when it matches a registered release. Expected behaviour, recording options and diagnostics expand when needed. Uploaded media still requires explicit review before sending.
The Reporter issue button in the drawer header opens a separate draft for an issue with the reporter itself. It stays visible while you scroll. Its destination is visible: tautau admins only. Your customer-app draft remains saved. Host app events, console/network logs, page snapshots, assets and release metadata are excluded from this separate report. Explicitly attached screenshots or clips are shared only after your review. Back to app restores the other draft; each target has its own report identity and pending uploads.
On tautau's own app pages, Report a problem in the top bar uses the same SDK and automatically selects the private platform queue. Public customer artefacts retain their customer's reporting context. SDK hosts can initialise with reportTarget: 'tautau' to open the private support destination directly; the server still validates the source key and origin. This does not grant read access to either queue. Private reports require an advertised server capability and use a distinct start-tautau operation, so an older server or deployment rollback rejects the report instead of sending it to the host app’s team. The separate draft remains available to retry.
When a report's project has a saved Jira connection and epic, completing the capture queues its Jira issue automatically. The destination is frozen for that delivery, including the epic parent. The report shows queued, reconnect/retry or linked-ticket status. Temporary provider failures retain the report. An uncertain create response is reconciled before another write is allowed. GitHub and Jira destinations without an epic retain reviewed manual creation.
Chrome extension
The same reporter can open from the tautau Chrome extension without changing the app's HTML. In extension settings, add the exact app origin and its environment's publishable key. The extension initialises the lightweight detector on that origin's top-level tabs and can show an error prompt before you open the reporter. Other origins and embedded frames are excluded. Removing the destination stops detectors created by the extension; an existing app embed keeps its own instance and privacy policy. A different embedded destination is preserved.
You can also choose Report a bug in this app in the extension popup. The locally bundled SDK runs in the selected page and submits through the same origin-scoped API as the embed. The app's CSP must allow the reporting and upload connections described below. Event capture begins after the reporter opens; the detector's validated IDs are available earlier, but this path does not retrieve earlier logs. Use the embedded breadcrumbs mode when you need the lead-up to a problem. The extension's separate Send tautau feedback action reports problems with tautau itself.
Text is saved before large evidence uploads. A failed upload keeps the same report and attachment identities; retrying does not create another report. Drafts, including pending blobs, stay in this origin's IndexedDB for up to 24 hours. Text edits also save synchronously to localStorage so an immediate refresh retains the latest text; this copy contains no credentials, reporter identity, diagnostics or media. Both stores expire after 24 hours. Draft recovery requires IndexedDB and Web Locks for safe ownership between tabs. If either is unavailable, the reporter warns that the draft is held only in the current tab. Editing waits for draft restoration. Once the report is accepted, its text and captured inventory stay fixed while pending evidence can be retried or removed. Set persistDrafts: false to disable both draft stores. Successful submission deletes the local draft. Removing a pending attachment refunds its reserved storage. Close preserves a draft; destroy stops capture and restores wrapped browser functions.
For signed-in host apps, set draftScope to a stable opaque account ID and recreate the reporter when that identity changes. Draft text, evidence, reporter sessions and sign-in handoffs are separated by that scope. Old unscoped drafts are not imported into an account scope. Never use an email, session token or credential as the scope. Check Tautau.capabilities?.scopedDrafts === true before enabling persistence in an integration that previously used persistDrafts: false; an older cached SDK must stay memory-only until it reloads.
Only one reporter instance is active per document. The widget uses an isolated shadow root. Hosts with a CSP can pass a style nonce, allow https://tautau.xyz in script-src/connect-src, allow the signed GCS upload origin in connect-src, and permit blob: in img-src/media-src. To display Google profile photos, also allow https://lh3.googleusercontent.com in img-src; otherwise the reporter shows initials.
Clippy
When initialising the existing clippy embed, pass ingestKey to its configuration. Its report action lazy-loads this SDK with launcher: false, retaining the same service, privacy policy and evidence flow.
Register a deployment
A manager can rotate a deployment key in the service's Repository tab. Keep this key in CI secrets, never in browser code. POST /api/capture/releases with Authorization: Bearer tt_deploy_… and JSON:
{
"environmentId": "env-…",
"version": "2026.09.27",
"commit": "FULL_40_CHARACTER_COMMIT_SHA",
"url": "https://your-app.example",
"sourceMaps": [
{ "url": "https://your-app.example/assets/app.js", "content": { "version": 3, "sources": ["src/app.ts"], "names": [], "mappings": "AAAA" } }
]
}Only a registered environment/version/commit makes a report's release verified. A release accepts at most 20 source maps totalling 2 MB; the service retains at most 50 release records and expires source-map contents with its retention policy. Chat can resolve bundle frames and inspect the linked repository at this commit.
Development
From the repository root: node browser-sdk/scripts/build.mjs, task test:bugs, task e2e:bugs. The latter uses a fresh database, real Go/Next servers, a local checkout fixture and local provider responses. See docs/BUG_REPORTING.md for management and operations.
To publish, update browser-sdk/package.json, then run npm publish --access public --tag latest from browser-sdk/. The prepack hook rebuilds both bundles and the declarations before packing. Check the tarball with npm pack --dry-run first.
