Back to Projects

Disbug

A bug-reporting tool for developer teams

Year
2021 – 2024
Role
Built core capture pipeline + integrations layer
Status
Live — v2 rebuilt after my time
Visit disbug.io
JavaScript TypeScript Vue.js Python Django Chrome Extension API rrweb html2canvas marker.js2 PostgreSQL Celery Redis

What it is

Disbug was a bug-reporting tool for developer teams. A Chrome extension captured screenshots, video recordings, console logs, network requests, and rrweb session replays from a browser session, then attached the report to an issue tracker — with a 2-way sync so status changes flowed back. A few hundred teams used it between 2022 and 2024.

My role

I was the first engineering hire (joined Sep 2021), working alongside the founder. Over my Trainee → Full Stack Developer years (2021–2023), I owned a few specific subsystems on Disbug: the in-extension capture pipeline (network / console / video timing), the third-party issue-tracker integration layer in the backend, and the embeddable feedback widget. Other parts of Disbug — the original XHR capture, the React rewrite, several later integrations — were built by the founder or other engineers.

What I built

The network-capture pipeline

Disbug needed to attach every network request the user’s page made to the bug report. The original code captured XHR. I added fetch() capture on top, which turned out to be much fussier:

  • The fetch API gives you Request and Response objects with bodies you can only read once. If you read them to log them, the page’s own code breaks (“body already used”). Solution: clone every Request/Response before reading.
  • Reading a response body is async and the response can resolve at any time; if you log it before the body is ready, you get an empty record. Solution: use Promise.all to gate the log on both the response and its body.
  • Logging every response body would balloon payload size and hit content types where the body is binary garbage. Solution: a content-type allow-list (JSON, text/*, form-encoded — that’s it).

I also added a separate path for non-fetch / non-XHR resources via PerformanceObserver (CSS, fonts, images), with deduplication so the same request didn’t appear in both the fetch log and the resource log.

Time-windowed evidence

A bug report includes screenshots, a recording, and the logs from “around” the user’s action. The “around” was vague — without bounds, we’d attach 30 minutes of unrelated console spam. I added 5-minute windowing: drop any network / console / event log older than 5 minutes before the screenshot or the recording start. Smaller payloads, less noise.

If a user refreshed or navigated mid-recording, the recording broke. I hooked chrome.webNavigation.onBeforeNavigate to insert a pause marker before the navigation and a resume marker after the new page loaded — so the recording stayed continuous across page reloads.

The third-party issue-tracker integration layer (backend)

This was the meat of my backend work. I built and maintained the integrations layer in the Django backend — 7 trackers in total (Jira Cloud + Jira Server, ClickUp, Monday, Trello, GitLab, GitHub). For each one:

  • Connection adapter — OAuth flow, credential storage, token refresh.
  • Destination fetching — pull the user’s projects / boards / lists from the tracker so they could pick where bugs go.
  • Submission — post the bug to the tracker (attachments, status, priority, custom fields).
  • 2-way sync via webhooks — when someone closed / updated / deleted the issue in Jira (or wherever), we received a webhook and mirrored the change back onto the Disbug issue. I designed the WebhookListener model and the receiver endpoints; later trackers (Linear, Asana, Notion, Azure DevOps) were built by other engineers on top of this base.

The embeddable feedback widget

A separate sub-product: a JS snippet sites embedded to let their users report bugs (vs. Disbug’s main extension which captured internal team bug reports). I built it largely on my own: a Vue app compiled to a single-file Web Component (so it could be dropped into any site without conflicts), html2canvas for screenshot capture, custom marker.js2 marker subclasses for highlight + blur-to-redact annotation, console / window.onerror interception, browser-metadata collection, and authenticated submission to the Disbug backend.

A session-replay SDK for an analytics vendor

Around the same time I built a session-replay SDK for UserExperior, shipped publicly as the user-experior-web npm package. I wrote essentially all of it: a TypeScript browser SDK with rrweb-based DOM recording (with privacy masking), automatic XHR / fetch / console / error capture, a unique-CSS-selector generator for clicked elements, multi-tab session / idle lifecycle (auto-restart on logout, browser-close, 30-min idle), and batched JSON upload — published with UMD/CJS/ESM builds.

Status

Disbug is still live, but not the version I built. The v1 I worked on ran from 2021 to around 2024. It has since been rebuilt as a v2 with AI features, which is deployed now — I had no part in that rebuild. Everything above is v1.

What I learned

Disbug was where I learned what it means to ship browser-runtime code that has to not break the host page. Every monkey-patch (fetch, XHR, console, errors) is a way to break someone’s app if you get it wrong. Most of the work was defensive. I still write browser instrumentation the same way.