Skip to content

feat(ua): expose low-entropy user-agent as --const-ua-* (plugin + head) - #13

Merged
argyleink merged 1 commit into
mainfrom
feat/ua-plugin
Jul 1, 2026
Merged

feat(ua): expose low-entropy user-agent as --const-ua-* (plugin + head)#13
argyleink merged 1 commit into
mainfrom
feat/ua-plugin

Conversation

@argyleink

Copy link
Copy Markdown
Owner

What

Adds a ua global plugin and matching head constants that surface the device/browser identity JS can read but CSS can't, as five write-once custom properties:

prop value
--const-ua-platform macos / windows / linux / android / ios / chromeos / unknown
--const-ua-browser chrome / edge / firefox / safari / opera / samsung / unknown
--const-ua-engine blink / gecko / webkit / unknown
--const-ua-version browser major version (number, 0 if unknown)
--const-ua-mobile 1 on a phone-class device, else 0

Now CSS can branch on the client with no JS and no media query:

@container style(--const-ua-engine: webkit) { .sticky-fix { position: -webkit-sticky; } }
@container style(--const-ua-platform: ios)  { .cta { padding-block: 0.9rem; } }
.badge { opacity: calc((var(--const-ua-version) - 100) / 20); }

How

  • src/core/ua.ts — shared reader. Prefers structured UA Client Hints (navigator.userAgentData, Chromium), falls back to a best-effort userAgent-string sniff on Firefox/Safari. SSR-safe.
  • src/plugins/ua.ts — global source, const cadence, typed (<custom-ident> strings, <integer> numbers). Registered in plugins/index.ts + loaders.ts so auto lazy-loads it.
  • src/head.ts — writes the same five consts synchronously → FOUC-safe, pre-paint branching (dual-availability, like nav-type). Shared code split into the head and plugin chunks; core index.js stays lean.

Privacy

Low-entropy only — no getHighEntropyValues, device model, full version, or architecture. Everything here is already sent in request headers, so it adds no fingerprinting surface beyond what the server already sees. Off Chromium the string fallback is best-effort — hints, not identity.

Demos & docs

  • Demosite: new ua card at the end of "Every source, at a glance" (string idents rendered via the same style-query trick as nav-type).
  • Docsite: interactive /demos/ua/ page (chips light up for the detected OS / browser / engine), plus reference/plugins, getting-started, README, and llms.txt kept in sync.

Verification

  • npm run typecheck clean · 110 unit tests pass (6 new for the reader) · npm run build succeeds
  • Code-split correctly: UA logic in head.* and the plugin chunks, not in core index.js
  • Real-Chromium DOM check: :root computes platform=macos, browser=chrome, engine=blink, version=148, mobile=0; gallery labels and docs chips highlight the right values

Version bumped 0.7.7 → 0.7.8 (additive → patch) with a CHANGELOG.md entry.

Add a `ua` global plugin and matching `head` constants that surface the
device/browser identity JS can read but CSS can't, as five write-once
custom properties:

- --const-ua-platform  macos/windows/linux/android/ios/chromeos/unknown
- --const-ua-browser   chrome/edge/firefox/safari/opera/samsung/unknown
- --const-ua-engine    blink/gecko/webkit/unknown
- --const-ua-version   browser major (0 if unknown)
- --const-ua-mobile    1/0

A shared reader (src/core/ua.ts) prefers structured UA Client Hints
(navigator.userAgentData, Chromium) and falls back to a best-effort
userAgent-string sniff on Firefox/Safari. Low-entropy only — no
getHighEntropyValues, device model, full version, or architecture — so it
adds no fingerprinting surface beyond what request headers already expose.

The same five consts are also written synchronously by the head entry for
FOUC-safe, pre-paint branching (@container style(--const-ua-engine: webkit)).
Typed mode registers the strings as <custom-ident>, the numbers as <integer>.

Includes demosite gallery card + docsite demo, README/llms.txt/reference
sync, unit tests for the reader, and a patch version bump (0.7.8).
@netlify

netlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Deploy Preview for prop-for-that ready!

Name Link
🔨 Latest commit afd6354
🔍 Latest deploy log https://app.netlify.com/projects/prop-for-that/deploys/6a456f5e013ed10008150246
😎 Deploy Preview https://deploy-preview-13--prop-for-that.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@argyleink
argyleink merged commit 47436fd into main Jul 1, 2026
8 checks passed
@argyleink
argyleink deleted the feat/ua-plugin branch July 1, 2026 20:39
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