mostard-qwik-environment
v0.1.0
Published
Where am I running and what can I do here: build, request, client, live and derived environment flags for Mostard on Qwik
Readme
mostard-qwik-environment
"Where am I running, and what can I do here?" as config flags. Product code branches on
them; unused flags are tree-shaken away. Private package, sibling of mostard-qwik-virtual.
Spec: mostard-v2/docs/plans/mostard-qwik-environment.md. Vocabulary: mostard-v2/CONTEXT.md § Runtime modes.
Tiers
| Tier | Entry | Known when | Server value |
| ------- | ---------------------------------- | ------------------------ | ------------------------------------ |
| Build | mostard-qwik-environment/build | vite build | literal (isServer, isOriginless) |
| Request | mostard-qwik-environment/request | SSR receives a Request | plain JSON, per request |
| Client | mostard-qwik-environment/client | first browser call | undefined (unknown) |
| Live | mostard-qwik-environment/live | any time, can change | undefined (unknown) |
Client flags are tri-state: true, false (probed and absent) or undefined (not in a browser).
Request vs live
Some questions exist in two tiers because the server and the browser see different things.
colorScheme and prefersReducedMotion are read from Sec-CH-Prefers-* headers on the request
tier, which is the only source during SSR and on Plain-HTML, and from matchMedia on the live tier,
which follows changes after load. The headers arrive only once the origin opts in with Accept-CH
(not wired yet), so until then the request value is undefined. Neither tier falls back to the
other. On Plain-HTML pages CSS @media (prefers-reduced-motion: reduce) needs neither.
What counts as a signal
Only what the device itself reports: JavaScript probes on the client, and the headers and host the
browser sends on the request (Accept-Language, client hints, the .onion hostname). Nothing comes
from the network path. There is no IP geolocation, no Tor exit list, and no VPN or proxy lookup, so
the library cannot tell whether a clearnet visitor uses Tor or a VPN, and does not try to.
- Each flag answers one question about one feature. No flag guesses the browser's identity (no
isTorBrowser). Fingerprinting protection shows up in the feature flags it affects: Wasm or WebGL off, OPFS denied in private browsing, 2 reported cores inhardwareTier. - Anything inferred about the person (locale, region) goes through a plausible-deniability rule: it is used only when every signal agrees, otherwise the default applies.
Usage
Build tier, namespace import so bundlers drop the dead branch:
import * as MostardEnvironment from "mostard-qwik-environment/build";
if (MostardEnvironment.isServer) {
/* ... */
}Request tier, in the origin's request handler (src/routes/[email protected]):
import { detectRequestEnvironment } from "mostard-qwik-environment/request";
export const onRequest: RequestHandler = ({ request, sharedMap }) => {
sharedMap.set("environment", detectRequestEnvironment(request));
};Root and components:
// root.tsx
<EnvironmentProvider value={useServerData("environment")}>{/* ... */}</EnvironmentProvider>;
// any component
const env = useEnvironment();
env.isOnion; // request tier, safe in render
env.runtimeMode; // "no-js-mpa" | "resumable-ssr"
const ls = useClientFlag("hasLocalStorage"); // Signal<boolean | undefined>, set on the client
const online = useLiveFlag("isOnline"); // Signal<boolean | undefined>, follows eventsLazy client probes outside components (in a task or handler):
import * as client from "mostard-qwik-environment/client";
if (await client.hasIndexedDB()) {
/* ... */
}Capabilities, for login UI and relay selection:
import { deriveCapabilities } from "mostard-qwik-environment/derived";
const caps = deriveCapabilities({
runtimeMode: env.runtimeMode,
platform: env.platform,
hasWebSocket: client.hasWebSocket(),
hasNip07: nip07Signal.value,
});
caps.canUseNip55Signer;Locale with plausible deniability. Every signal must agree, otherwise the fallback is used:
import { resolveLocale } from "mostard-qwik-environment/locale";
const locale = resolveLocale(
{
requestLocales: env.locales,
clientLocales: client.locales(),
timeZone: client.timeZone(),
},
{ fallback: "en", zones: { de: ["Europe/Berlin", "Europe/Busingen"] } },
savedChoice, // an explicit pick always wins
);Tests and dev overrides (process-wide, never set them in production):
import {
setEnvironmentOverride,
clearEnvironmentOverride,
} from "mostard-qwik-environment/overrides";
setEnvironmentOverride({ isOnion: true, hasWasm: false });
clearEnvironmentOverride();CSS tier: containerAttributes(env) from mostard-qwik-environment/css gives the <html> data
attributes for entry.ssr. JS_BOOTSTRAP is the inline script for the Resumable host only.
Rules
- Request-tier values never live in a module global on the server. They go on the request, then into context.
- Render reads only the build and request tiers. Client and live values are read in tasks, handlers, or signals.
- Client probes never throw. Each one catches its own errors.
- Request code never imports client code, and client code never imports request code.
Storage selection and cross-origin isolation
isCrossOriginIsolated is a lazy client flag, available from /client and through useClientFlag("isCrossOriginIsolated"). It is unknown during SSR and observes globalThis.crossOriginIsolated in the browser. Like other client probes it supports overrides and resetClientProbes().
Consumers such as browserdb combine this flag with hasWasm, hasWorker, hasOPFS and hasWebLocks to decide whether to attempt durable Turso WASM. A positive probe is not proof that a named database can open or commit. Environment detection supplies facts; adapters establish availability, and applications define their data-type policies.
