This page is also available in Italiano.

Continua in italiano

What the release plan couldn't predict: publishing for real

Published on July 25, 2026

Every other post in this series is about a decision made deliberately, ahead of time. This one is about the opposite: three things that went wrong only once the project stopped planning and actually tried to ship — a rejected package name, a CI pipeline that couldn't authenticate, and a handful of docs-app bugs that only a live deploy surfaced. None of these were anticipated by the phase that preceded them; all three were real, and all three got fixed the same day.

The package name npm view couldn't have warned about

npm view tinydi returned a 404 during planning — read, reasonably, as "the name is free." At the actual moment of publishing, npm rejected the unscoped name tinydi anyway: not because it existed, but because it was judged too similar to the pre-existing package tiny-di, under npm's anti-typosquatting policy. npm view only ever checks for an exact name match — it has no way to warn you about a similarity check that only runs at actual publish time.

The fix was a full rename to tinydi-container across every file that referenced the npm package name — both READMEs, both docs-app languages, examples, CONTRIBUTING.md — while deliberately keeping "TinyDI" as the project and brand name everywhere else. Only the npm specifier changed; nobody imports tinydi and nobody ever will.

The 2FA/OTP standoff with CI

The npm account's two-factor mode requires an interactive browser approval on every single publish. That is fundamentally incompatible with a non-interactive CI job — no token, of any scope, satisfies an interactive OTP prompt. tinydi-container@0.1.1 shipped manually, from an authenticated local session, as the only way around it for a first release. Two more issues turned up chasing an automated path for the next one:

  • changesets/action reads its npm token from an env var literally named NPM_TOKEN — a workflow that instead exported it as NODE_AUTH_TOKEN (the convention actions/setup-node itself uses) failed silently rather than loudly, because the action just fell back to whatever auth it could find.
  • npm Trusted Publishing (OIDC) briefly returned a 404 on the actual publish step for a package version being published this way for the first time — a known npm quirk when provenance is requested for a never-before-published version, not a permissions problem.

The durable fix was switching fully to npm Trusted Publishing: removing NPM_TOKEN from the workflow entirely (so there is no token to silently fall back to), bumping the CI job to npm CLI ≥11.5.1 via an explicit self-update step (Trusted Publishing requires it), and configuring a matching Trusted Publisher entry on npmjs.com for this exact repo and workflow file. tinydi-container@0.1.2 published fully automatically through this path — Version Packages PR merged, workflow ran, real publish, no manual OTP step. RELEASE.md now documents the whole runbook for whoever cuts the next release.

The common thread

Neither the naming rejection nor the OTP failures were things a careful reading of the plan could have caught in advance — both only exist at the actual moment of talking to npm's real infrastructure. "It worked in planning" and "it works when you actually publish" turned out to be different claims.

A second wave, after the site went live

A later pass over the deployed docs site turned up its own crop of production-only bugs, on top of the three already covered in the docs-site post:

  • The code-block copy button deleted its own icon. The click handler set button.textContent directly to show "Copied!" — which silently wipes out the icon <svg> sitting next to the label, since an SVG contributes nothing to textContent. The icon never came back after the first click. Fixed by only ever swapping the label <span>'s text, never the button's.
  • Twelve absolute internal links, across six files in both languages. Links written as href="/docs/lifetimes.html" work fine in local dev, where BASE_PATH is empty — and 404 on the real GitHub Pages deploy, where the site is served from /TinyDI. Invisible until the deployed site was actually clicked through.
  • Cross-document View Transitions (@view-transition { navigation: auto; }) were tried for smoother page-to-page navigation and reverted the same day — even the bare at-rule, with zero custom styling, threw a real console exception on every navigation when tested in a browser. An attempted improvement, not a shipped one.

None of these three were caught by npm run lint, npm run build, or the test suite — all green throughout. They were caught by clicking through the actual deployed pages, which is the same lesson the earlier docs-site bugs already taught, reconfirmed by a second, independent round of real bugs.

If there is one thing worth changing about the process rather than the code, it is this: a manual click-through of the live site belongs after every deploy that touches user-facing behavior, not just after the first one. The first launch gets scrutiny by default because everyone is watching it closely; the fifth deploy does not, and that is exactly when a regression like the copy-button one would go unnoticed.

to navigate to select Esc to close