qID Connect changelog
Current version: 1.6.6 · download · pinned: /connect/qid-connect-1.6.6.zip. This page is generated from the CHANGELOG.md that ships inside every pack.
All notable changes to the qID Connect integration surface. Versions apply to @qid/connect-server and @qid/connect-widget together.
1.6.6 — 2026-07-27
A docs-and-tooling release driven by a 78-agent review of the whole web estate. No SDK code change; the frozen protocol is untouched.
verify-all.shno longer fails every pack user. The dashboardWIDGET_LATESTprobe reads a sibling repo that exists only on the lab machine, so from an unzipped pack the script always exited 1 with "VERSION MISMATCH" on a perfectly healthy release. The probe is now skipped (and says so) when the path is absent, and the mismatch message explains the chain inline instead of pointing at a file the pack does not ship.- The stated check count is pinned to the checker. The guide and
INSTALL.mdsaid "Ten checks" and the skill README "Eleven" while the 1.6.5 checker printed 12: docs are edited by hand, the checker by code, and they had drifted twice already. All corrected to twelve, and a new regression test runs the checker, reads the real total, and fails the suite if any doc states a different number. node:sqliteneeds Node 22.5+, and the docs now say so.INTEGRATION.mdand the static-rewrite demo said "Node 20+", which crashes on module-not-found for anyone on 20-22.4. Corrected with the better-sqlite3 escape hatch named.- Every "reach out through qid.dev" dead end now names a real channel. The integrate guide's git-access and wallet-builder notes and
INSTALL.md's feedback line all pointed at a site with no contact surface; they now link the public qid-connect-skill repo's issues. - One time-to-integrate claim everywhere. Guide said 15 to 30 minutes,
INSTALL.mdabout an hour; it is 15 to 30 minutes in both places now.
1.6.5 — 2026-07-27
- The checker now verifies
style-src, not justscript-src. A live site shippedstyle-src 'self'plus a hash of its own inline style. Every check passed, and the sign-in dialog rendered as raw unstyled HTML on mobile and desktop, because the widget injects its stylesheet at runtime and nothing looked at that directive. The check understands the real cascade (style-src-elem, thenstyle-src, thendefault-src) and knows that a nonce or hash source makes browsers ignore'unsafe-inline', so the two cannot be combined. Verified against the exact policy that caused the incident. verify-all.sh: one command that re-confirms every live surface and that all versions agree. A handoff claiming "all green" is only true the day it was written; this makes the claim checkable in about forty seconds.- The skill documents the
style-srcrule next to thescript-srcone, so an integrator with a strict policy gets both halves before they debug.
1.6.4 — 2026-07-27
Pack contents only; no code change.
- The private-preview Challenge page no longer ships.
site/challenge/index.htmlcloses with "This is a private preview. What you see and what you find stays between us until release", and the pack is a public download, so every downloader was receiving it. Its source (docs/AUDIT-BRIEF.md) was already on the forbidden list, so the allowlist and the forbidden list disagreed about the same content. The page is now forbidden too, which means a rename cannot leak it either. Neither it norsite/introis deployed on qid.dev, so the pack was the only place either was public. site/README.mdno longer ships. It is an internal deploy note whose build-gate instructions the publish script strips, so they could not be followed from inside an unzipped pack. Kept in the lab and corrected: the__HERO__placeholder it described is long inlined,how/was undocumented, and it now says plainly that it is lab-only.site/introlinked to the Challenge page, which would have 404'd inside the pack once that page stopped shipping. It points at the integrate guide now.
1.6.3 — 2026-07-27
The checker gets a test suite, which is the fix for why it kept needing fixes.
It was rewritten in 1.6.0, 1.6.1 and 1.6.2, every serious audit finding landed in it, and it was the only code here without tests: 476 lines verified by hand each round, with the verification thrown away afterwards. That is the whole explanation for the churn, and it is now closed.
packages/server/test/check-integration.test.js: 13 tests, one per scenario that an audit found broken or that a fix was written for. Real servers on ephemeral ports, the checker run as a subprocess, asserting on its output and exit code. No new dependencies.- Proven to have teeth, not just to be green. Each of the five behaviours that previously shipped broken was reintroduced as a deliberate mutation, and each was caught by exactly one failing test: the WAF-403 acceptance, the dropped
--api=form, the redirect misclassification, the round-trip count printed from a constant, and the CSP directive check. - One test was toothless and was rewritten. The first WAF test asserted only that the run failed. Reintroducing the bug still left it green, because a different check happened to fail. Tests now assert that a *named* check failed, so passing for the wrong reason is not possible.
No behaviour change to the checker or the SDK in this release.
1.6.2 — 2026-07-27
A multi-agent adversarial audit of 1.6.1, with every finding put to a skeptic whose job was to refute it. Twelve survived. The SDK came back clean; the damage was in the checker and in guidance written from memory rather than from source.
- The checker could pass a deployment where sign-in is dead. Checks asserted on the absence of a bad answer instead of the presence of the SDK's. A WAF answering
403 text/htmlon/verifyscored 10/10 "the surface is wired correctly" while browser sign-in was impossible. Every check now asserts on the SDK's own reason vocabulary. Reproduced before and after. --api=/api/qidwas silently ignored, so the checker probed/qid, then failed a correct Next.js integration and blamed a missingexpress.json()on an app with no Express. Both flag forms now parse and unknown flags are fatal.- The store check could report
4/4 round trips kept the noncehaving made zero successful round trips. Rounds that never got a challenge were skipped silently and the total was printed from a constant. - A correct app whose
/redirects to SSO was failed, with a hint that, if followed, breaks every browser sign-in. Only same-site 301/308 canonicalization is a failure now; anything else is reported as a note. - A CSP check, so the claim is true. The site said the checker finds a blocking Content-Security-Policy; it had no such check. It does now, and only when the page actually imports the hosted widget, so vendored setups are not false-flagged.
- Transport errors no longer swallow the run. Each check is isolated, an aborted check is reported as unknown rather than failed, every request has a timeout, and an unreachable host exits 2 (could not run) instead of 1 (a check failed), so CI can tell a blip from a defect.
- Failures are now split into "sign-in cannot work until you fix this" and "worth tightening", because three of the checks do not stop sign-in.
- Docs corrected against the source.
express.json()is *not* required and its position does not matter (the middleware parses the body itself);sessionSecretis mandatory and throws rather than being auto-derived;node:sqliteneeds Node 22.5+, not 20; Edge runtime fails onnode:cryptoregardless of store;address_mismatchis unreachable through/verify; and thepollstep callsgetOrCreatewith no context, which silently blanks a proof-derived column for mobile users only. - Website: vendoring one file produced a dead button. The widget statically imports
./vendor/qrcode.mjs, so the guide's "vendor the single file" left the module graph unresolved and nothing rendered. It now says copy the directory.
1.6.1 — 2026-07-27
Audit of yesterday's skill against the actual source, which caught two pieces of guidance that would have failed on contact.
- The SQLite store docs were wrong twice. They imported from
@qid/connect-server/sqlite, which is not in the exports map and throwsERR_MODULE_NOT_FOUND, and they constructed the stores with{ path }when both take a database handle. Corrected to the patternexamples/static-rewrite-demoactually uses, and verified by running two instances behind a round-robin proxy against one shared SQLite file. - The skill never said where to get the SDK.
@qid/connect-serveris not on npm, so anyone installing the skill standalone hit a dead end at the first import. Added a step 0 pointing at the public pack download. - CSP was undocumented. The hosted widget is a cross-origin module, so a site with a strict policy gets no button and only a console warning. Both of qID's own sites run
script-src 'self' https://qid.dev; now the skill says so. - Checker hardening.
--apiwith a missing or relative value printed a stack trace; a runtime withoutfetchfailed obscurely. Both now exit with a clear message. The shared-store check samples four issue-then-use round trips instead of one, since a single round trip can land back on the issuing instance by luck and pass while the deployment is broken. Verified against a deliberately unshared two-instance cluster: 0/4 kept, correctly failed; with the documented shared store, 4/4 kept, 10/10 passed.
1.6.0 — 2026-07-27
- A skill ships with the pack:
skills/qid-connect/. An Agent Skill that teaches an AI coding assistant to wire qID sign-in properly: the four steps, the framework-specific traps, the account model, and how to verify the result.INSTALL.mdused to say "paste this file into your assistant", which left the assistant to rediscover the same handful of mistakes every time. Also published standalone and open source so it can be installed without the pack. scripts/check-integration.mjs: a live checker, zero dependencies. Ten checks against a running integration, no wallet and no keys. It covers the failures that produce no error in the browser at all, where the dialog simply never completes: anoriginthat is not where users land, aproof_urlthat phones would 404 against (the Next.jsapiPathmistake, which leaves desktop paste working so it reaches production unnoticed), per-process stores on a multi-instance deployment, a stripped login-CSRF guard, and a/sessionroute answering 200 to anonymous callers. Each failure prints the fix.
1.5.9 — 2026-07-26
recovery_leaf_hashis not an identity key, and the guide no longer says it is.INTEGRATION.mdcalled it "already authenticated by the verifier" and "the right identity key for one-per-wallet rules";core.jssaid "a wrong hash cannot have verified". All three overstate what a v1 proof establishes, which is control of the login key only. The leaf hash is signer-chosen, not signer-proven: anyone can pair a leaf hash they saw with a login key they generated and produce a valid proof for a different address carrying that same hash. It is public by design too, since a P2MR spend reveals the sibling hash in its control block. An RP that looked accounts up by it, merged on it, or gated one-per-wallet rules on it was exposed to account takeover. Corrected inINTEGRATION.md,core.jsandstores.js, and pinned bypackages/server/test/identity-key.test.js, which reproduces the takeover through the fullcreateQidConnectpath. No behavior change: the field is still passed togetOrCreateand both built-in adapters still ignore it.- In-memory stores warn in production.
createQidConnectsilently defaulted toMemoryNonceStore/MemoryAccounts. They are per-process, so a multi-instance or serverless deployment loses shared replay protection and drops accounts on restart, and it presents as flaky sign-ins rather than as a security problem. It now warns loudly underNODE_ENV=production, naming which store is in memory and what to pass instead.QID_ALLOW_MEMORY_STORES=1silences it for a deliberate single-process deployment. bun.lockis committed. Installs now resolve to pinned transitive versions instead of re-resolving^ranges on every run, which is what makes the--frozen-lockfileCI step meaningful. Matches qid, qid-dashboard and qid-build.
1.5.8 — 2026-07-24
- Release pack: the static-rewrite example now boots. The pack's root
package.jsonworkspaces list was missingexamples/static-rewrite-demo, sobun installnever linked@qid/connect-serverfor it and the documented static-site path failed with "Cannot find module". The pack's roottestscript now also runs the fullbun test packagessuite (it previously skipped the widget tests). - Dialog copy polish. Four status strings reworded without em-dash asides ("A sign-in only proves ownership. It can never move funds.", "Trouble reaching the server, still trying", "Can't reach the site. Tap to retry", and the cookies-blocked notice). No behavior change.
- Guide + docs. The integrate guide's mount step now leads with the hosted one-line import (auto-updating) and demotes pin/vendor to the follow-up; the quickstart
cdworks in zsh (cd qid-connect-*/); bun is named as a prerequisite with an install link; the test signer prints usage on--help. Server SDK remains byte-identical to 1.5.5.
1.5.7 — 2026-07-23
- Device-appropriate deep-link button. The "Open in wallet" tab now shows only the wallet that can resolve on the current device: on a phone (coarse pointer) the bonuz Wallet button; on desktop the PQ Wallet for BTX button. Previously both buttons appeared on both device types, so tapping the desktop wallet on a phone dead-ended at the OS "cannot open page / invalid address" dialog (and vice versa). Each wallet is single-platform — PQ Wallet is desktop, bonuz is mobile — so only one scheme can ever launch on a given device; the not-installed fallback and Desktop-wallet paste tab remain the escape hatch. QR and paste transports, the verifier, and the server SDK are byte-identical to 1.5.6.
1.5.6 — 2026-07-23
- Same-device "Open in bonuz Wallet". On a phone, qid.dev and the wallet are on one screen, so the QR is unscannable. The "Open in wallet" tab now offers a one-tap bonuz Wallet button alongside PQ Wallet for BTX: it launches the installed bonuz app via
bonuzapp://qid?req=<url-encoded envelope>, carrying the exact same sign-in envelope the QR encodes — the app re-runs its full scan-path validation (bare httpsrp_origin, same-originproof_url, typesignin) and opens the approval sheet; completion flows through the same poll loop. On touch devices bonuz leads; on desktop PQ Wallet leads. Same not-installed fallback (per-wallet download + copy request) as the desktop button. QR and paste transports unchanged. Server SDK byte-identical to 1.5.5.
1.5.5 — 2026-07-22
- Works-with rows can link to the wallet. A wallet entry can now carry a
url; the row becomes a link — clicking the name or the status badge opens the wallet's page in a new tab, the intuitive move for someone who does not have it yet. The defaults now link PQ Wallet for BTX → pq-wallet.com and bonuz Wallet → bonuz.xyz/btx-wallet. Server SDK byte-identical.
1.5.4 — 2026-07-22
Naming: the desktop wallet is now shown as PQ Wallet for BTX everywhere in the dialog, docs, and site (was "BTX PQ Wallet"). The works-with list, the deep-link button, the paste instructions, and the captions all updated; server SDK byte-identical to 1.5.2, carries all the 1.5.3 fixes.
1.5.3 — 2026-07-22
Widget-only (server SDK byte-identical to 1.5.2). Two dead-feeling controls in the "Open in wallet" transport now react, surfaced by a live btc2btx integration.
- The deep-link button gave no feedback on click. It fired the
btxqid://scheme and then changed nothing for ~5s — and when the OS has no handler registered (the common "nothing happened" case) nothing ever changed, so the button read as broken. It now acknowledges the click immediately (briefly disabled with an "Opening…" label, then re-enabled with an "…again" label), and the download/copy fallback affordances now appear in ~2.5s instead of 5s so there is always a fast next step. - The "i" help affordance did nothing in embedded webviews. It was a bare
target="_blank"link — it navigated away from the sign-in flow in a browser and was a complete no-op inside in-app webviews (the bonuz app, embedded browsers), where the help is most needed. It now toggles a one-line inline hint in-flow (a guaranteed reaction everywhere) that still links out to the full guide.
1.5.2 — 2026-07-21
- bonuz Wallet is live. The dialog's "works with" list now shows bonuz Wallet — Available now (mobile): scan the qID QR with the bonuz app, pick your wallet, and you are in. Hosted-widget integrators get the change automatically; vendored copies pick it up on the next pull.
A fresh-eyes review from a different model family (8 lenses over the whole surface, every finding independently refuted) found five real medium defects the prior rounds missed — all in async-race discipline and adapter unhappy-path parity, none in the crypto core. All fixed and live-verified; the frozen protocol is untouched.
- Rotation could orphan an accepted QR sign-in (medium). A proof accepted in the ~1.5s before a ~105s rotation was parked under the old nonce, but after rotating the widget only ever polled the new one: the wallet said "signed in" while the browser showed a fresh QR forever.
renewSessionnow does one last-chance poll of the old nonce (with its poll secret) before discarding it, and finishes the sign-in if a proof landed. Covered by a new server test. - Log out reported success on failure (medium).
doLogoutswallowed a failed request and never checkedres.ok, then cleared the chip and firedonSignedOut— a shared-machine hazard, since the server session was still valid. It now keeps the chip and shows an inline "Couldn't log out — retry" on failure, only signs out on a confirmed 200, and bounds the request with a 15s timeout so a hung POST can't freeze the account widget. - A close-and-reopen could let a stale rotation hijack the new dialog (medium).
renewSession, the paste-verify continuation,startPolling, and the soft-retry timer now all guard on theopen()generation, so an in-flight rotation from a closed dialog can't clear the live dialog's poll timer or poll the wrong nonce. requireQidSessioncould crash the process (medium). A throwing accounts adapter (a transient DB outage) escaped as an unhandled rejection — a hung request plus a process crash on Express 4 / Node ≥15. It now mirrors the middleware's try/catch and returns 500server_error. New test.- The Next adapter had no body-size cap (medium). Express rejects bodies over 64 KB; Next buffered
request.json()unbounded, a memory-exhaustion DoS on the cross-origin-reachable/prooffor any self-hosted runtime without a fronting proxy limit. Next now enforces the same 64 KB cap (and an empty POST body now matches Express's 401, not a parse 400). New test.
Also hardened (non-blocking, from the same review):
- Origin is canonicalized (lowercase host, default port dropped), so a mixed-case
originconfig no longer 403s every browser sign-in while QR keeps working — the worst kind of asymmetric failure. - Nonce retirement is secret-bound. The rotating widget sends the retired nonce's poll secret; a bystander who only saw the QR can no longer burn a victim's in-flight sign-in.
- A lost
doneresponse is survivable. A poll claim is now idempotent for the matching secret within a 30s grace, so a dropped response on a flaky mobile hop can be re-claimed instead of losing the sign-in. - Cookies-blocked browsers get an honest "allow cookies and try again" instead of a "Signed in" screen the next page load contradicts;
verify/pollfetches time out likechallenge; the poll cap is anchored to open time so an abandoned tab stops polling; the account chip survives a no-op repaint without dropping keyboard focus; a deeplink handoff gets a rotation grace; an unknown poll status surfaces viaonError; a trailing slash inapiis normalized; SQLite_rowcan't be shadowed by a profile key andpatchstrips reserved keys;buttonText/pollIntervalMsand the SQLite TTL rule are documented; the no-wallet dev flow in INSTALL.md now says to use the Desktop-wallet tab first.
1.5.1 — 2026-07-21
Fixes from an exhaustive adversarial audit of the whole v1.5.0 surface (7 lenses, every finding independently verified). All confirmed items fixed.
- Next.js:
apiPathwas missing (HIGH, builder-breaking). The next-demo route and the INTEGRATION.md Next snippet omittedapiPath: "/api/qid", so the proof URL baked into the QR and deep link pointed at/qid/proofand 404'd — QR and deep-link sign-in (the default transports) silently failed on Next; only copy-paste worked. Added everywhere, with a loud comment. - The account-chip doc snippet threw (MEDIUM). The chip block in INTEGRATION.md and INSTALL.md referenced
#signinbut only showed#account, so a verbatim copy threw. Both now include both divs. - Forced-logout CSRF (LOW).
POST /qid/logoutnow carries the same login-CSRF (Origin) guard as/verifyand/poll, so a cross-site page can't force-log-out a visitor. Covered by a new test. - The signed-in chip's status dot no longer borrows the "connecting" pulse (it's a settled state);
bad_originnow has a helpful user-facing message (was falling back to "server had a problem"); the logout race guard gains a belt-and-suspendersloggingOutcheck inrefresh(); Escape no longer double-dismisses when both widgets are open;onSignedInon the account chip documents that it fires on restore. - /poll CSRF hardening. The cookie-minting poll now also checks Origin as a fallback (mirroring /verify), so the guard no longer fails open if a proxy strips
Sec-Fetch-Site. - Account dropdown a11y: focus moves into the menu on open and returns to the chip on close; the chip survives a transient
/sessionblip (only a real 401 clears it) instead of flapping. - Doc/version tidy: DESIGN.md updated for three transports + the account chip; next-demo README endpoint count; conformance-suite path; the integrate page's "Get the code" uses the release-pack download (not a git clone of a preview-private repo); CHANGELOG bonuz wording kept to "coming soon"; root package.json version aligned to 1.5.1.
1.5.0 — 2026-07-21
- New: the signed-in account chip + dropdown (
mountQidAccount). The counterpart to the "Sign in" button — a compact chip (green dot + short address) that opens a dropdown with the full address (click to copy), an optional explorer link, extensible integrator menu items, and Log out. Log out is now a deliberate two-step (open the menu, then Log out) instead of a single accidental click. Same--qidc-*theming as the dialog, so the signed-in state looks identical on every BTX app. Renders nothing when signed out; callrefresh()after sign-in to show it without a reload. ThemenuItemsslot is the extension point for future Bonuz ID / social data. - Hardened via an adversarial review before release: a request-generation guard so an in-flight/sessionresponse can't resurrect the chip after Log out,destroyedre-checked after every await, explorer link closes the menu, logout is double-click-guarded, and keyboard-focusable copy.
1.4.2 — 2026-07-17
Fast-follows from a second adversarial review (of the v1.4.1 fixes). Verdict was SHIP CLEAN; these close the two residual low/nit edges so the release has none.
fetchChallengenow has a 20s AbortController timeout, so a hung request (black-holed TCP, captive portal) rejects instead of hanging — a never-settling fetch can no longer latch the renew re-entrancy guard and block further renews.- Focus restore on a re-open (the "Try again" retry path) now returns focus to the original trigger instead of falling to
<body>.
1.4.1 — 2026-07-17
Fixes from an adversarial multi-agent code review of v1.4.0 (3 blocking + polish).
- Orphaned retry timer could burn a live nonce (HIGH regression). After a failed background rotation armed a soft-retry, a subsequent successful renew did not cancel it, so ~15s later it retired the *current* nonce — silently breaking an in-flight sign-in. renewSession now cancels the pending retry on entry and is re-entrancy guarded.
- Focus trap could escape the modal (HIGH regression). The focusable set included disabled and hidden controls (the disabled Verify button, the hidden deep-link fallback), so Tab could walk out of an aria-modal dialog. The set is now filtered to genuinely tabbable elements.
- onSignedIn could fire for a superseded dialog (HIGH). The open() generation token is now also checked in the poll loop, so an abandoned generation's in-flight poll can't drive success after the user moved on.
- Re-opening the dialog no longer emits a spurious onClose; unmount() is final (open() no-ops after it); the deep-link fallback timer is cleared on tab switch; the signed-in address renders via textContent.
1.4.0 — 2026-07-17
Field-driven release: everything below came from taking qID Connect all the way to production on a real static site (btc2btx.com) plus a UX review.
### Widget — reliability
- Paste flow is exempt from proactive rotation. Copy → wallet → sign → paste realistically outlasts the ~2-minute display life; rotation used to wipe the textarea and retire the request the user had just signed. The paste tab no longer rotates (and drops the confusing "Refreshes in Ns" line); the server TTL still bounds it. Switching to a QR/deeplink tab with a stale request renews first.
- A failed background rotation keeps the dialog open and soft-retries with backoff (plus a manual "tap to retry") instead of closing the dialog out from under the user.
- An unreachable backend at open shows an in-dialog error ("Sign-in is temporarily unavailable" + Try again) instead of flash-opening then vanishing.
- A real server outage no longer looks like "waiting for wallet" — after a few failed polls the wait line says "Trouble reaching the server — still trying" and recovers on success.
- Terminal-state guards: rotation can't wipe the "Signed in" screen; a double-clicked open can't corrupt timer state (generation token).
### Widget — API
- Returns
{ open, close, unmount };trigger: falserenders no button so you can open the dialog from your own UI. onClose(reason)—'backdrop' | 'escape' | 'x' | 'signedin' | 'programmatic'.onError(err)is now structured:{ stage: 'challenge'|'verify'|'poll', code?, httpStatus?, cause? }, and verify verdicts (bad_signature,nonce_used, …) reach it too — real telemetry on why sign-ins fail.
### Widget — UX & a11y
- The Copy button is framed and gently pulses so it reads as the action.
- A round ⓘ info button on every transport links to the new https://qid.dev/connect/how (per-transport how-to, incl. the honest notarization note for "Open in wallet").
- The QR center mark is now a small qID wordmark, not a lens.
- Focus is moved into the modal on open, Tab is trapped, focus is restored to the trigger on close; arrow-key tab navigation;
prefers- reduced-motiondisables the entrance/pulse animations;--qidc-anim- durationhook; per-mount--qidc-*theming now reaches the portaled dialog too.
### Docs & examples
- New
examples/static-rewrite-demo/: a standalonenode:http/bun server (durable SQLite, boot-time origin sanity check, hardened systemd unit) for the "CDN page + backend on another host + same-origin rewrite" pattern, with rewrite recipes for Vercel / Netlify / Caddy / nginx. - INTEGRATION.md: the apex/www (and http↔https) origin trap callout; the "do not proxy" rule reworded into ❌ BAD / ✅ GOOD (a same-origin
/qid/*rewrite is fine and is required for the first-party session cookie when your server is on another host);unzip/Node-SQLite prerequisite note. - DESIGN.md: theming and the portaled dialog explained.
1.3.3 — 2026-07-17
- Field hardening from the first real integrations. - The QR no longer dies on "server amnesia": if the server reports a request expired seconds after issuing it (a restart, or multiple instances without a shared store), the widget auto-recovers once with a fresh challenge and reports the exact diagnosis through
onError. - A failed rotation no longer closes the dialog — it offers "Can't reach the site — tap to retry". - The deep-link tab reveals its fallback options after 5s regardless of focus changes (a failedbtxqid://launch also blurs the page, so blur never proved the wallet opened). - The QR itself is now click-to-copy. - Production checklist: explicit multi-instance / shared-store requirement.
1.3.2 — 2026-07-17
- The dialog's "works with" list carries bonuz Wallet (coming soon) for the mobile signer. When it ships, hosted-widget integrators flip to "available now" automatically on our deploy — no dApp changes needed.
1.3.1 — 2026-07-16
- Fix: the 1.3.0 copy trim dropped
appNamefrom the dialog — the option did nothing. The subtitle now reads "Sign in to <appName> with your BTX address. No email, no password." Also corrects the hosted build's provenance header (it cited a commit that predated its content).
1.3.0 — 2026-07-16
- QR-first dialog. The default transports are now
["qr", "paste", "deeplink"]: the dialog opens on a visible QR code, WalletConnect style, with desktop copy-paste and one-click "Open in wallet" one tap away. - Hosted widget channel. The widget is now also served from https://qid.dev/connect/widget.js (latest) and
widget-<version>.js(pinned). Importing the hosted file means wallet-list and UX updates reach your users automatically on our deploys; the signed protocol stays frozen v1 either way. - Calmer dialog copy: shorter subtitle, captions, and safety line.
- Brand casing: bonuz Wallet in the "works with" list.
1.2.0 — 2026-07-16
- Dynamic connector. Every sign-in request now has a short display lifetime with a visible countdown, rotates itself proactively before it goes stale, and on rotation the server burns the superseded nonce (
challenge()accepts{ retire }; the Express and Next adapters parse the optional body). A screenshotted or stale QR, paste request, or deep link stops being acceptable the moment it leaves the screen. btxqid://deep-link transport. One-click connect into the installed desktop PQ Wallet for BTX (v0.27+):transports: ["paste", "qr", "deeplink"]. New exportedbuildBtxqidSignUrlwith unit tests.- Unified "expired — get a new one" affordance on every transport (previously the paste tab had none); poll jitter plus a hard max-duration cap so an abandoned dialog stops polling.
SECURITY.md— private vulnerability reporting policy.- Integration guide published: https://qid.dev/connect/integrate
- Tests: rotation covered at the core and over both HTTP adapters.
1.1.0 — 2026-07-15
- Login-CSRF guards on
/verifyand/poll(a cross-site page cannot plant a session in a visitor's browser). - Atomic async replay consume in the nonce store; widget timer cleanup.
- Dialog wallet list ("works with"), signing animation, accounts context for adapters.
- Live browser demo (the qid.dev/connect page) and OG cards.
1.0.0 — 2026-07-11
- Phase 1: connect, prove, sign in. Server SDK (challenge, verify with the frozen qID Sign-In v1 verifier, account by address, sessions, SQLite stores), the "Sign in with qID" widget (paste + QR transports, no relay server), Express and Next.js adapters, two example apps, the reference test signer CLI, integration/migration/design docs.