Skip to content

Developer tools: JWT, Codes, Codec and Digests; shared UI; deploy fix - #20

Merged
PeterHewat merged 31 commits into
mainfrom
developer-tools
Oct 3, 2026
Merged

PeterHewat merged 31 commits into
mainfrom
developer-tools

Conversation

@PeterHewat

Copy link
Copy Markdown
Owner

What changed

Four new developer tools — JWT (JSON Web Token Debugger), Codes (QR codes and barcodes), Codec (UTF-8, Base64, URL and Hex Converter) and Digests (SHA Hash and HMAC Calculator) — all listed as stable, plus the shared code and repo changes they led to.

  • JWT: two-way editing (edit the token or its parts), algorithm from the header, jwt.io colours, key checks and encoding conversion, key generation.
  • Codes: steps like qr.io: Type → Content (link, text, email, phone, SMS, WhatsApp, Wi-Fi, vCard, event; Code 128, EAN-13, UPC-A) → Design (colours with opacity and radial/linear gradients, module and corner shapes, preset or uploaded logo, frames with caption) → Download. Integer-only SVG; PNG sizes S/M/L in whole pixels per module; redrawn in place without blinking; the index art is generated from the app's own example (scripts/codes-art.ts).
  • Codec: every format stacked and live from whichever field is edited; invisible characters drawn.
  • Digests: SHA-256/384/512 and SHA-1 at once, Hex or Base64, HMAC, files up to 64 MiB, Compare naming the matching digest; laid out like Codec. Codec and Digests open each other on the same input.
  • Shared (@tools/ui): colour picker (from the SVG app, now with opacity), renderPng, .ui-switch with bindSwitch in every app, debounce, versioned per-tab drafts (JSON moved onto them), .ui-note / .ui-data, new icons.
  • Smaller PNG exports: written in the app as a palette PNG (1–8 bits) when ≤ 256 colours, else RGB/RGBA. Lossless: a plain code at XL went 325 KB → 13.5 KB; the gradient example about 55–63% smaller.
  • Repo: build/test tools declared once at the root; bun run links Markdown link check; bun audit in CI; AGENTS.md clean-change and shared-UI rules; @zxing/library 0.23.0.

Why this needs merging soon

The deploy of #19 failed and the live site has been on #18 since. The JS budget check counted the external Cloudflare analytics script as a missing local file, and only the deploy build has the analytics token. Fixed in scripts/bundle-budget.ts (with a test); the site build now also checks that every page carries the beacon when the token is set.

For reviewers

  • No draft migrations: these apps had no users yet, so drafts stay at version 1 and an old JSON draft opens on the sample.
  • JS budgets raised with the change: Codes 20 KiB, Digests 135 KiB (it now uses the shared editor, like Codec).
  • Browser tests decode the downloaded QR PNGs with jsQR, falling back to ZXing for dot and circle styles.

Checks

  • bun run verify passes
  • Tried it in the browser (all 64 Playwright tests pass in Chromium and Firefox)

🤖 Generated with Claude Code

PeterHewat and others added 30 commits October 1, 2026 19:32
- Drop the Decode / Encode switch: the token, header and payload are all
  editable. Editing the token decodes it; editing the JSON rebuilds the
  token and signs it again, or keeps the old signature and says why.
- The algorithm is the header's alg; choosing one rewrites only that
  member and generates a key when there is none (or only a generated one
  that no longer fits). Unsupported algs such as "none" are shown, not hidden.
- Keys by kind: a secret with its encoding for HMAC; separate public
  (verifies) and private (signs) keys for RS/PS/ES/EdDSA, PEM or JWK
  detected from the text. Generate key and Sign token replace the
  generated-public-key panel.
- One status row under the token (valid, signature, expiry) and one quiet
  line per panel instead of badges plus fixed message boxes; JSON errors
  are underlined where they are.
- Payload gets a Claims view: registered claim names, exact values, time
  claims as local dates with relative times.
- Token editor without line numbers at a fixed height; draft version 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Keep the "JSON Web Token (JWT) Debugger" title; drop the Example button.
- Header, payload and signature use jwt.io's #E27133, #9472F7 and #8FC88A
  in the token and as the panel titles' colour (darker shades of the same
  hues in the light theme, for contrast); the dots stay plain.
- Replace the JSON / Claims toggle with notes at the end of each known
  member's line: what the header parameter or claim means, and exp / nbf /
  iat as local dates with how long ago or ahead. Notes come from a new
  setNotes in @tools/editor: widgets, not text, so never copied or signed.
- Under each key: "Valid secret", "Valid public key", "Valid private key",
  or why not (too short to sign, wrong curve, a public key where the
  private one belongs).
- Changing the secret's encoding rewrites the secret as the same bytes,
  so it keeps verifying; generated secrets are random text.
- Typing a payload with the token cleared starts a new token with a
  default header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The token in jwt.io's colours with its Valid JWT / Signature verified
pills, and a payload with each claim explained beside it. Darker shades
of the same hues when the browser prefers a light scheme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e tags

- The header shows "JWT  JSON Web Token Debugger" from a new catalog `title`,
  cut short as the header narrows; the page's own title and intro are gone.
- JWT is stable (no BETA badge) and joins the released apps in AGENTS.md;
  its compact header drops to the measured 217px.
- The verdict ticks and crosses are CSS masks, so the stylesheet is ASCII
  and cannot be misdecoded into mojibake.
- SVG is tagged "design" and Codes "print", beside the "developer" tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Header: a catalog `compactHome` drops "Tools" from the home button at its own
width, keeping the app's name; JWT does so under 390px, so "JSON Web Token
Debugger" fits on a phone.

Review fixes:
- Editing the payload no longer replaces a pasted token whose header could
  not be read with a default header; the header panel says why instead.
- alg none: an empty signature is a valid, unsigned token ("Unsigned token"),
  editing the header to none empties the signature, and the key fields hide.
- A "Bearer " prefix and line breaks are taken out of a pasted token.
- After the secret's encoding changes, a button reads the text as typed in
  the new encoding, for a Base64 secret pasted while UTF-8 was chosen.
- A private key in the Public key field is flagged.
- Expiry and the time notes keep up with the clock.
- A key that names its type is held to it before import (Firefox imported an
  Ed25519 key for ES256 and signed with it); a damaged key says so rather
  than "not an RSA key".
- An edited header or payload is still signed after a key edit or a reload
  that comes before the rebuild (saved with the draft as `resign`).
- Encrypted tokens (JWE) say what they are; strict JSON errors name the
  actual problem.
- Removed the old decoder and verifier only tests used; their checks now
  run against inspectJwt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app was never released, so its draft has no earlier version to tell
apart, and a per-tab draft in session storage needs no migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UTF-8 text, URL component, Base64, Base64url and hex are one field each,
all rewritten from the bytes of the field being edited. While that field
cannot be read, the error shows beside its name and the others keep their
last value, dimmed. URL encoding now works on bytes, so binary data has a
URL form too. The fields are CodeMirror editors with a new `exact` option:
\r\n stays two bytes, and tabs, control and invisible characters are drawn.
The header carries the full name; the in-page title and intro are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le cache

- JWT: a key that cannot be generated is said under the keys and the token
  still follows the new algorithm; a key request no longer cancels the
  pending rebuild of an earlier edit; friendly error for an unreadable JWK
  secret; claim notes in one pass over the text
- Codes: QR quiet zone fixed at 4 modules (setting removed); each barcode
  gets its standard quiet zones (EAN-13 11/7)
- Codes and Digests keep working beside an incompatible draft
- Digests: a file is not read and hashed again when only the comparison changed
- Codec: stale fields cannot be copied; title names UTF-8
- Codes and Digests are stable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Content: link, text, email, call, SMS, WhatsApp, Wi-Fi, contact (vCard),
  event (iCalendar), and barcodes (Code 128, EAN-13, UPC-A)
- Design: code and background colours, transparency, gradients, corner
  colour; module and corner shapes; a preset or uploaded logo (error
  correction H); a frame with a caption
- One scene drawn as SVG and on canvas, so both exports match; warnings
  when colours may not scan
- Layout: type grid, Content and Design steps, Download beside them
- Shared icons for the content types

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Codes before Codec in the catalog
- Steps: 1 Type, 2 Content (with the encoded content), 3 Design, 4 Download
- Size is XS to XL, each a whole number of pixels a module, so the PNG has
  no partial pixels; the SVG has whole numbers only, in relative paths
- Design panels share one height and options open beside their box, so
  nothing below moves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Content and Design each have a clear button beside their heading
- The logo is always as large as error correction H allows; no size slider
- The frame's caption, text colour and frame colour share one line; picking
  a frame swaps a text colour that would vanish on it
- Each design tab is as tall as what it holds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The preview is patched in place after an edit, never dimmed or emptied,
  so the code and its logo do not blink; downloads draw what is pending
- Gradients shared by layers are defined once in the SVG
- The index card art is a styled code drawn by the app's renderer
  (scripts/codes-art.ts), 8 KB instead of 46 KB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The SVG app's colour picker (and its swatch, styles and dropper icon)
  moves to @tools/ui, with colour helpers (color.ts) and renderPng
- Codes uses them: every colour has an opacity, a background at 0% is
  transparent (no more Transparent checkbox), and the PNG is the SVG drawn
  at its size, so there is one renderer
- Index art encodes https://peterhewat.github.io/Tools/ and fills more of
  the card

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every on/off setting is the shared switch (Codes, Digests, the SVG app's
  Fill and Closed, JSON through the shared isOn/setOn/bindSwitch)
- JSON keeps its draft with the shared readDraft/writeDraft, which now marks
  a draft dropped for being too large
- A shared debounce replaces hand-written timers in Codes, Digests, JWT,
  JSON (validation, find) and the SVG source editor
- Codes starts on EXAMPLE, the same code as the index art: the site's
  address, radial gradient, link logo, caption; the art is generated from it
- Preset logos are vector markup in the code, so a colour change cannot
  make them flash; an error dims the preview only once it lasts
- Removing a logo gives back the error correction chosen before it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renderPng writes the pixels itself: a palette PNG at 1-8 bits a pixel for 256
colours or fewer, otherwise RGB or RGBA, unfiltered or up-filtered, whichever
is smaller. The bundle budget skipped no external script, so a build with the
analytics token failed; it now ignores another site's script, and the site
build checks every page carries the beacon when a token is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TypeScript, Vite, happy-dom and jsqr came from each workspace; every workspace
resolves them from the root now. Workspaces list only the @tools packages they
import; unused publish fields gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Input with the shared editor (invisible characters drawn), Text | File and a
clear button beside its heading; every SHA at once with copy buttons, Hex |
Base64, and Compare naming the digest it matches. A new tab starts on the
quick brown fox; lines never move between encodings. Unused tool styles gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Digests is one column: the input, a card per digest (HMAC- in the title when
keyed, Matches or Legacy beside it) and Compare. Each app opens the other on
the same text in the same tab by writing its draft. Card notes and the data
editor's look move to tool.css as .ui-note and .ui-data; a link styled as a
button loses its underline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From Extractora: bun run links (in check and CI) fails on a relative Markdown
link to a missing path; CI audits dependencies for high or critical
vulnerabilities; AGENTS.md gains the clean-change rules (no unsolicited
legacy, iterate in place, stop on breaking changes, shared code, budgets, docs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On CI, Node could not find the named exports of 0.23's CommonJS build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PeterHewat
PeterHewat merged commit c772071 into main Oct 3, 2026
4 checks passed
@PeterHewat
PeterHewat deleted the developer-tools branch October 3, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant