- Published on
devutils: 33 browser-only dev tools and what the README overstated

- Authors

- Name
- Nadim Tuhin
- @nadimtuhin
On this page
Pasting a JWT or a production SQL query into a random website is a bad habit. devutils is my answer: a single-page app where every tool runs in the browser tab.
It is live at devutils-one.vercel.app. The README said it works offline once loaded and that no data leaves your computer. This post checks both against the code.
Note: The numbers below come from reading the source and counting. The test figures are counts of test cases in the files, not a pass report.
The problem
Many online formatters run server-side or load third-party scripts around the textarea. For a JWT, a certificate or a Kubernetes secret, that is the wrong place to paste.
A local tool avoids the question. If the code never makes a request with your input, there is nothing to leak.
How it works
The app is React 18, TypeScript, Vite 6 and Tailwind, deployed on Vercel. vercel.json has one rule: rewrite every path to /index.html, so each tool gets a real URL.

Tools are a typed list
Every tool is an entry in one array in src/App.tsx, built from this type:
// src/App.tsx:114-121
type Tool = {
id: string;
name: string;
icon: React.ReactNode;
component: React.ReactNode;
url: string;
isEnabled: boolean;
};
The router maps over that array to create one Route per tool, and / redirects to /unix-time. The sidebar lists the same array, and drag order is saved in localStorage under toolsOrder.
The other localStorage keys hold UI preferences: theme, the code language in the JSON-to-code tool, and tour progress. None store tool input.
Base64 handles UTF-8
src/utils/base64.ts wraps btoa and atob with encodeURIComponent and escape so non-ASCII text survives the round trip:
// src/utils/base64.ts
export function encodeBase64(input: string): string {
try {
// Encode to UTF-8, then to Base64
return btoa(unescape(encodeURIComponent(input)));
} catch (e) {
throw new Error('Failed to encode to Base64.');
}
}
Failures become a readable message instead of a raw browser exception.
Checking the claims

No data leaves the browser: holds. I searched src/ and index.html for fetch(, XMLHttpRequest, sendBeacon and analytics snippets. The only matches were sample curl strings and generated code inside the cURL-to-code tool, plus GitHub links and Open Graph tags. No tool sends your input anywhere.
Works offline once loaded: holds, with no service worker. There is no service worker, web app manifest or PWA plugin in vite.config.ts. Once the page is loaded, nothing needs the network, so an open tab keeps working. A fresh visit with no connection will not load.
35+ tools: did not hold. src/App.tsx has 39 entries. One is the Credits page, and five set isEnabled: false: the CSS, JavaScript and HTML minifiers, Markdown preview and the color converter. That leaves 33 usable tools. PR #25 corrected the README to 33 (and PR #26 again, after a later rewrite).
Before PR #25, the README also listed a Makefile validator and a Kubernetes secret decoder. Both have components and tests, but neither is referenced in App.tsx, so no route reaches them. The disabled tools and these two components are unfinished work, parked until they work.
The decoder passes decoded values through DOMPurify with ALLOWED_TAGS: [] and ALLOWED_ATTR: [] in src/components/k8s/shared/SecretUtils.ts, which strips the tags from a decoded value instead of escaping them. A secret that contains angle brackets would show up altered, which is a bug for a decoder. Today no user can reach that code.
The results
| Fact | Value | Source |
|---|---|---|
| Commits | 97 | git log |
| First commit | 2025-01-26 | git log |
| Latest commit | 2026-10-14 | git log |
| Usable tools | 33 | isEnabled count in src/App.tsx |
| Test files | 12 | *.test.* under src/ |
| Test cases | 166 | count of it( and test( calls |
| Stars / forks | 14 / 3 | GitHub API |
| License | MIT | LICENSE |
Commits come in bursts: 10 in January 2025, 19 in July 2025, 14 in November 2025, 18 in August 2026 and 17 in October 2026. Twelve commits are authored by Claude and one by claude[bot].
What I fixed after writing this
On 2026-10-14 I merged one PR that fixed these:
- CI swallowed test failures. The workflow ran
pnpm test --passWithNoTests || echo "Skipping broken unit tests", so a red suite still passed. CI no longer swallows test failures. - Saved state could break the shell.
JSON.parseon thetoolsOrdervalue inLayoutand on tour progress had no try/catch, so a corrupted value would break the page on load. ThelocalStorageJSON.parsecalls are now guarded. - README count. PR #25 corrected the README to 33 tools (and PR #26 again, after a later rewrite).
- Label accessibility. Labels in
TextAreaWithCopyandBase64SideBySidenow point at their textareas. - Two tool bugs. The CSS beautifier returned empty output, and the PHP serializer had emoji and object bugs. Both are fixed.
Still open:
- Finish the disabled and unrouted tools. Five components are switched off and two are never referenced, all unfinished work parked until it works.
- Cover more than the helpers. Tests exist for Base64,
Base64SideBySide,readStoredArray, the CSS processor, the PHP serializer,TextAreaWithCopyand the debounce hook. The Makefile validator and K8s decoder are tested too, but unrouted. Most formatters and converters have none. - Use or delete the debounce hook.
src/utils/useDebouncedConversion.tsis a tested 300 ms debounce hook, but no tool imports it yet. - Load tools lazily. The tool components are imported at the top of
App.tsx, so the first load pays for every tool.
Try it at devutils-one.vercel.app, and open an issue if a tool you need is missing.
Resources
Related posts
- opencode-9router: model discovery for OpenCode from a 9Router gateway
An OpenCode plugin that asks a 9Router gateway for its models at startup, fills in limits and capabilities, and caches the list. v1.0.0, 26 tests after a six-fix PR.
- express-simple-proxy: an Express proxy with hooks and typed errors
express-simple-proxy forwards Express routes to an upstream API with error codes, a beforeRequest hook and an onResponse callback. 183 tests, two runtime dependencies, and rough edges I found.
- pmharden: auditing npm, pnpm and yarn config for secrets
pmharden audits package manager config, scans for plaintext tokens and flags risky global packages in one command. A walkthrough of the code, the 45 tests and the rough edges I still see.