* fix: use stable site hash for install telemetry deduplication (#297)
generateSiteHash() used Date.now() as the hash seed, producing a different
hash on every call. Since the installs table uses PRIMARY KEY (plugin_id,
site_hash), the same site could insert unlimited rows, inflating install
counts and making "Most Popular" sorting meaningless.
Fix: use the site's request origin as a stable hash seed. The same origin
always produces the same hash, so the marketplace deduplicates correctly.
Also denormalizes install_count on the plugins table to avoid a COUNT(*)
subquery per row in searchPlugins(). The count is recalculated atomically
on each upsertInstall() call.
Fixes#297
* chore: add changeset for install telemetry fix
* fix: address review feedback on install telemetry
- Replace crypto.subtle fallback with FNV-1a hash to avoid origin
leakage and collisions from truncated seed strings
- Remove duplicate p.install_count from SELECT (p.* already includes it)
- Use explicit p.install_count in ORDER BY clause
- Use db.batch() for atomic upsert + count recomputation instead of
separate statements with misleading meta.changes check
* fix(webhook-notifier): add build step and export built files
The webhook-notifier plugin exported raw TypeScript source from its
package.json exports (./sandbox pointed to src/sandbox-entry.ts).
When the Vite plugin resolved this at site build time, it embedded
unbuilt TypeScript into the sandbox module, causing "Unexpected token
'{'" errors at runtime.
Add a tsdown build step (matching sandboxed-test's pattern) and update
the exports map to point to dist/*.mjs.
Fixes#150
* fix(core): reject unbuilt source in sandbox module generator and bundle validator
Add two validation checks to prevent plugins with misconfigured exports
from silently breaking site builds:
1. generateSandboxedPluginsModule() now throws a clear error if a
sandbox entrypoint resolves to a TypeScript/JSX source file instead
of pre-built JavaScript. This catches the problem at site build time
with an actionable message.
2. The `emdash bundle` command now validates that all package.json
exports point to built files (.js/.mjs), not source (.ts/.tsx/.jsx).
This catches the misconfiguration at plugin publish time, before
consumers are affected.
Fixes#150
* chore: add changeset for sandbox source validation
* fix: use slash syntax for e18e rule override in oxlintrc
The test file override for e18e/prefer-static-regex used parenthesis
syntax ("e18e(prefer-static-regex)") which is the diagnostic display
format, not the config format. Changed to slash syntax to match the
top-level rule declarations so the override actually takes effect.
* test: add tests for sandbox source validation
Add tests for both validation checks:
- generateSandboxedPluginsModule: verifies it embeds pre-built JS,
rejects .ts/.tsx/.mts source files, and includes the plugin ID in
error messages.
- findSourceExports: verifies it flags .ts/.tsx/.mts/.cts/.jsx exports,
accepts .mjs/.js exports, and handles conditional export maps.
Also extracts findSourceExports() from the inline bundle.ts validation
into bundle-utils.ts so it can be tested without the CLI harness.
* fix(atproto, audit-log): add build step and export built files
Same issue as webhook-notifier — both plugins exported raw TypeScript
source from their package.json sandbox exports. Add tsdown build steps
and update exports to point to dist/*.mjs.
* refactor(smoke): replace sequential per-site astro builds with recursive pnpm build
The build verification section was running `astro build` individually
and sequentially for every demo and template (~12 sites). Replace with
a single `pnpm run --recursive --filter {./demos/*} --filter
{./templates/*} build` which pnpm parallelizes automatically.
* fix: prevent media upload OOM on Workers via client thumbnails + server safety net
Large image uploads (5MB+) crash Cloudflare Workers (128MB limit) because
generatePlaceholder() decodes entire images to raw RGBA pixels. A 4000x3000
JPEG becomes ~48MB RGBA, exceeding the isolate memory budget.
Two-layer fix:
- Client-side: browser generates a 64px canvas thumbnail for oversized images
and sends it alongside the upload. Server generates blurhash from the
thumbnail (~16KB RGBA) instead of decoding the full image.
- Server-side: reads dimensions from image headers via image-size and skips
placeholder generation when estimated decoded size exceeds 32MB. This covers
API/CLI uploads that don't provide thumbnails.
* chore: add changeset for media upload OOM fix
* fix: clamp upload thumbnail to 64x64 box for extreme aspect ratios
Naive sizing (thumbW=64, thumbH=(h/w)*64) could produce an enormous canvas
for very tall or very wide images — e.g. a 100x840000 image would allocate
a 64x537600 canvas client-side, reintroducing the memory blowup this feature
exists to prevent.
Extract computeThumbnailSize() that fits the image within a 64x64 box by
scaling against max(width, height), wrap canvas allocation and drawImage
in try/catch with a no-thumbnail fallback, and add unit tests covering
extreme aspect ratios.
---------
Co-authored-by: Matt Kane <mkane@cloudflare.com>
* fix: passkeys behind TLS reverse proxy
Add passkeyPublicOrigin and wire it through passkey routes so origin/rpId match
the browser when dev runs behind nginx. Expose dev-only /_emdash/api/dev/passkey-url,
add admin messaging for insecure WebAuthn contexts, nginx repro under demos/simple,
and direct kysely dependency for the simple demo Node adapter bundle.
Made-with: Cursor
* docs: add passkeyPublicOrigin to configuration reference
Adds the new passkeyPublicOrigin option and reverse proxy guidance
to the public-facing configuration docs as requested in PR review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* update tests and more docs
* fix: add missing refresh-server-pat fixture and restore docs heading
---------
Co-authored-by: Joseph Eftekhari <jdeftekhari@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>