Releases: EmberlyOSS/Emberly
Release list
[2.5.0] - Storage & Queue Overhaul
Added
- BullMQ event worker — replaced the custom Postgres+Redis polling event system with BullMQ. All event handlers are now static
HandlerMapobjects dispatched by a BullMQ worker. In development the worker runs in-process automatically; in production it runs as a separate process viabun run worker. Controlled by theEMBERLY_RUN_EVENT_WORKERenvironment variable (true/false/ unset). - Bull Board queue monitor — admin queue dashboard available at
/api/admin/queues. Requires an active admin session. Built with a custom Next.js App Router adapter for@bull-board/api— no custom server or external process needed. Displays live job counts, retry/promote/clean controls, job payloads, logs, and metrics for theemberly-eventsqueue. - Standalone worker entry point —
scripts/worker.tscan be run as a dedicated process (bun run worker) for production deployments that want a separate worker process, Docker sidecar, or systemd service. - Deterministic file-expiry job IDs — file expiration jobs use the stable ID
file-expiry-<fileId>so they can be cancelled or inspected directly via BullMQ's job store.cancelFileExpirationandgetFileExpirationInfouse these IDs instead of querying a PostgresEventHandlertable. - Periodic storage sync via BullMQ —
storage.sync-bucketsis now a recurring BullMQ job (repeat: { pattern: '0 * * * *' }) scheduled at startup rather than through the old event system. - Linode Object Storage provider — new
LinodeStorageProviderinpackages/lib/storage/providers/linode.tsimplements the Linode Object Storage management API (cluster listing, bucket creation/deletion, key management). Fully integrated with theObjectStoragePoolmodel and admin provisioning flow. - OVHcloud Object Storage provider — new
OVHcloudStorageProviderinpackages/lib/storage/providers/ovhcloud.tsimplements the OVHcloud Public Cloud Object Storage API (project listing, region listing, S3 credential management). Integrated withObjectStoragePooland admin provisioning flow. - Admin pool manager UI — Linode —
packages/components/admin/settings/linode-pool-manager.tsxprovides a full CRUD interface for Linode Object Storage pools: list clusters, create/delete pools, view credentials. - Admin pool manager UI — OVHcloud —
packages/components/admin/settings/ovhcloud-pool-manager.tsxprovides a full CRUD interface for OVHcloud Object Storage pools. - Linode admin API routes —
POST /api/admin/storage/linode(create pool) andDELETE /api/admin/storage/linode/[id](delete pool). - OVHcloud admin API routes —
POST /api/admin/storage/ovhcloud(create pool) andDELETE /api/admin/storage/ovhcloud/[id](delete pool). ObjectStoragePoolPrisma migration — unified storage pool model supportingvultr,linode, andovhcloudproviders. ReplacesVultrObjectStorageas the single table for all provisioned storage pools. Provider-specific metadata stored asJson(clusterIdfor Vultr,keyId + linodeClusterIdfor Linode,projectId + regionName + credentialAccessfor OVHcloud).
Fixed
- Bucket provisioning — corrected key mapping and credential lookup issues that caused provisioning to fail for newly created pools.
- Storage key issues — fixed S3 key derivation errors that caused reads and deletes to target the wrong object on certain pool configurations.
Changed
- Event handler pattern — all handler files now export a
HandlerMapstatic object instead of callingevents.on()at registration time. Theevents.emit()call signature is unchanged across all 31+ call sites — the compat shim inevents/index.tsmaintains backward compatibility. EventHandlermodel removed — theEventHandlerPrisma model (used by the old polling worker) has been dropped. Apply withbun run db:migrate.- Redis is now required — previously optional for some configurations; BullMQ and rate limiting both require a live Redis connection. See README Event Worker for setup.
- Vultr instance manager — updated to work with the unified
ObjectStoragePoolmodel. - Settings manager — admin settings panel updated to include Linode and OVHcloud pool manager tabs.
What's Changed
- Dev by @CodeMeAPixel in #108
- Dev by @CodeMeAPixel in #109
- Dev by @CodeMeAPixel in #110
- Dev by @CodeMeAPixel in #111
- Dev by @CodeMeAPixel in #112
Full Changelog: v2.4.7...2.5.0
[2.4.7] - Bucket Routing & Storage Foundations
Added
- Core Pool Storage Buckets —
StorageBucketnow supportsisCore(boolean) andpriority(integer) fields. Core buckets are shared across all users via least-filled load balancing (fileCount ASC, priority DESC). Dedicated buckets are assigned per-user or per-squad. Priority breaks ties when two core buckets have equal file counts — higher number wins. Default is0(no preference). File → StorageBucketrelation — Files now track which bucket they were stored in viastorageBucketId. This is used at serve time to route reads, streams, deletes, OCR, and thumbnail generation to the correct bucket rather than the global default.- User bucket assignment UI — New
UserBucketAssignmentcomponent in the admin settings Storage tab. Paginated user list with per-user bucket dropdown and save button. Clears assignment (falls back to core pool) or assigns a dedicated bucket; triggers credentials email on assignment. - Admin storage bucket manager —
isCore/priorityUI — Edit dialog now includes a Core Pool Bucket toggle and a Priority number input (visible whenisCoreis on). List rows show Core Pool badge (violet), Dedicated badge, file count, and priority for core buckets. - Bucket connection test — per-bucket provider —
POST /api/admin/storage/buckets/[id]/testnow uses an explicitselectinfindUnique(fixes a@prisma/adapter-pgdriver adapter issue that causedfindUniquewithout a select to returnnullfor existing records). Test error detail is now surfaced inline in the UI alongside the status message. PUT /api/admin/storage/buckets—priorityon create — Create route now reads and persistspriorityfrom the request body. Previously new buckets always defaulted to0regardless of the value submitted.- User bucket page — secret key reveal — The
/dashboard/bucketpage now shows the actual secret access key with a show/hide toggle (BucketSecretRevealclient component). All other credentials are hidden behind a "coming soon" notice while direct S3 credential access is being developed. reconcileDeprovisionedBuckets— atomicSubscription.statusupdate — Both cancellation paths (cancelled subscription, subscription not found in Stripe) now useprisma.$transactionto atomically update the bucket (provisionStatus → deprovisioning), subscription (status → canceled), and clear user assignments in a single operation.
Fixed
- File serving — wrong storage provider for bucket-routed files — All file read/stream/delete paths used
getStorageProvider()(global config bucket singleton) even when the file was stored in a core or dedicated bucket. Affected routes:GET /api/files/[...path],GET /api/files/[id]/thumbnail, suggestion approval inPUT /api/files/[id]/suggestions, OCR processing, file expiry handler, and single/bulk delete in the file service. All now usegetProviderForStoredFile(file.storageBucketId), which routes to the correct bucket and falls back to global for legacy files. - S3 key normalization — Windows backslash paths —
path.join()produces backslash-separated paths on Windows (e.g.uploads\userUrlId\filename.png). The S3 provider's key derivation regex/^uploads\//never matched these, leaving the full backslash path as the S3 key and causingNoSuchKeyon every read. AddedtoS3Key()helper that normalizes backslashes to forward slashes before stripping theuploads/prefix; applied to all five S3 provider methods (uploadFile,getFile,getFileStream,deleteFile,getFileUrl,getFileSize). - Upload path generation — forward slashes enforced —
app/api/files/route.tsandapp/api/files/chunks/route.tsusedpath.join()to buildfilePath, producing backslash paths on Windows that were stored in the database and used as S3 keys. Both now usepath.posix.join()so paths are always forward-slash regardless of platform. - OCR — wrong bucket —
processImageOCRTaskcalledgetStorageProvider()unconditionally. It now looks upfile.storageBucketIdand routes togetProviderForStoredFileso OCR reads from the same bucket the file was uploaded to. - Bucket list —
prioritymissing from select —GET /api/admin/storage/bucketsdid not includepriorityin its field select. The edit dialog receivedpriority: undefined, causing the priority input to be uncontrolled (React warning) and saving to silently leave the existing value unchanged. - Edit dialog — uncontrolled
priorityinput —openEditnow usesbucket.priority ?? 0when populating the form, preventing the React "uncontrolled → controlled" warning whenprioritywas absent from the list response. - Dev logger — error detail suppressed — The development pino transport only printed
obj.msg, silently droppingobj.error. Error message and stack trace are now appended to the console output. - Chunks route — Prisma XOR type error —
tx.file.createmixedstorageBucketId(unchecked input) withuser: { connect: { id } }(checked input), which Prisma's XOR type rejects. Replaced withuserIdscalar field.
Changed
getStorageProvider()scope clarified — The global singleton is now the fallback only for legacy files (storageBucketId = null). All new file operations route throughgetProviderForStoredFileorgetUploadBucketForUser/getUploadBucketForSquad.selectCoreBucketordering — Core bucket selection now orders byfileCount ASC, priority DESCso higher-priority buckets win when file counts are equal.
What's Changed
- Dev (v2.4.7) by @CodeMeAPixel in #107
Full Changelog: v2.4.6...v2.4.7
[2.4.5] - Streamlined Domains
Added
- Domain Verification Real-time Validation
- Simplified domain verification to use Cloudflare for SaaS real-time validation flow.
- Removed manual DNS validation checks and zone validation logic.
- Verification now relies entirely on Cloudflare's built-in CNAME validation.
- Endpoint returns 202 status when verification is pending; frontend polls for activation.
- Clearer error messages when CNAME is not properly configured.
Changed
- Removed Ownership Verification TXT Record
- TXT record display removed from domain setup UI as it's not required for real-time validation.
- Real-time validation only requires CNAME record configuration.
- Simplifies domain verification setup flow for end users.
- Dynamic CNAME Display — Domain setup instructions now show the user's actual subdomain instead of generic "@ or www" placeholder.
- CNAME host field displays correctly based on the configured domain/subdomain.
- Improves clarity when setting up multiple domain variations.
Fixed
- Domain Verification 409 Conflict Errors
- Fixed recurring 409 (Conflict) errors users encountered during domain verification.
- Root cause was incorrect manual DNS validation bypassing Cloudflare's comprehensive checks.
- Now delegates all validation to Cloudflare, which automatically validates CNAME configuration in real-time.
- Users no longer see spurious 409s; errors now only occur when CNAME is genuinely misconfigured.
- File URLs for Discord Embeds and Media Detection
- Added trailing slashes to all file URLs for proper media embed detection in Discord and other services.
- Modified file upload completion, retrieval, and sharing endpoints to append trailing slashes.
- Updated UI copy functions and file card components to preserve trailing slash formatting.
- Files:
app/api/files/route.ts,app/api/files/chunks/[uploadId]/complete/route.ts,app/api/files/[...path]/route.ts,packages/components/dashboard/file-card/index.tsx,packages/components/dashboard/shared-file-card.tsx,packages/components/file/file-actions.tsx.
- Domain Error Handling Improvements
- Enhanced error responses and logging for domain verification failures.
- Better user-facing messages when domain configuration is incorrect.
- Detailed error logging for troubleshooting verification issues.
[2.4.4] - Palette & Storage Sync
Added
- Expanded Theme System — User theming options significantly expanded with new preset themes and custom hues.
- New Theme Presets (7 additional themes): 🌅 Sunset Glow (gradient-shift animated), 🌲 Forest Deep (particle effects), 🌊 Ocean Depth (wave animation), 🌙 Midnight Purple (glitch gaming), 🏔️ Slate Stone (grid gaming), 🏝️ Tropical Breeze (particle effects), 💜 Lavender Dream (aurora effects).
- Each theme includes full ColorConfig with 18 HSL-based color properties, background effects, and animation speeds.
- Metadata added to
THEME_METADATA_MAPwith descriptions, emoji identifiers, and effect configurations.
- Expanded Custom Hues (12 new hues added for 22 total): Lime, Spring, Mint, Teal, Turquoise, Sky, Purple, Violet, Magenta, Crimson, and additional saturation/lightness variations.
- Hues now span the full color spectrum from warm tones (Coral, Amber, Marigold) through cool tones (Teal, Azure, Ocean) to purples and reds.
- Each hue preset includes saturation and lightness values optimized for visual consistency.
- Users can now mix-and-match theme presets with any custom hue for greater personalization.
- New Theme Presets (7 additional themes): 🌅 Sunset Glow (gradient-shift animated), 🌲 Forest Deep (particle effects), 🌊 Ocean Depth (wave animation), 🌙 Midnight Purple (glitch gaming), 🏔️ Slate Stone (grid gaming), 🏝️ Tropical Breeze (particle effects), 💜 Lavender Dream (aurora effects).
- Storage Bucket Sync Service — New centralized synchronization service for Vultr Object Storage bucket management.
packages/lib/storage/sync-buckets.ts— Unified bucket sync logic handling provisioning, status updates, and credential management.POST /api/admin/storage/sync-buckets— Admin endpoint to trigger manual sync of all Vultr buckets, with operation counts (imported, updated, synced).- Automatic detection and sync of Vultr instance status changes (
pending→active), credential population, and database state reconciliation.
- Storage Event Handler — New dedicated event handler for storage-related events.
packages/lib/events/handlers/storage.ts— Dispatches events likeuser.bucket-provisioned,user.bucket-deprovisionedwith proper error handling and logging.- Integrated into the main event handler index for consistent event routing.
Changed
- Email Template Branding — All transactional email templates updated with consistent NodeByte LTD branding.
- 32 email templates across account lifecycle (welcome, verification, password reset, subscription updates, storage events, nexium events, etc.) now display NodeByte LTD copyright and branding.
- Company name consolidated for consistent brand presentation across all customer communications.
- Admin Broadcast Email Markdown — Admin broadcast emails now properly render markdown formatting.
AdminBroadcastEmailtemplate rewritten to support inline markdown rendering with email-safe HTML styling.- Markdown support includes: headings (h1-h3), bold, italic, lists, links, blockquotes, code blocks, and horizontal rules.
- Footer Visual Design — Footer animated border now matches navbar styling for consistency.
- Changed from static
gradient-borderto animatedgradient-border-animatedclass. - Provides cohesive visual language across the entire site header and footer.
- Changed from static
- Animated Background System Refinement — Animation functions improved for smoother, more visually appealing effects.
- Parallax scrolling now implements horizontal wrap-around layers for seamless infinite scrolling.
- Glitch effect with better frequency control and visual artifact distribution.
- Aurora effect improved for better smoothness and glow quality.
- Gradient shift enhanced with secondary radial gradient layer for depth.
- Scanlines animation now includes flicker effects for authenticity.
- Overall performance optimizations for the particle and effect systems.
Fixed
- OAuth Link/Unlink Flow Refactoring — Discord and GitHub authentication link/unlink endpoints refactored for improved code clarity and consistency.
- Simplified callback handling in
/api/auth/link/[provider]/callback/routes. - Improved error handling and response consistency across all OAuth flows.
- Type safety enhancements to prevent edge cases in credential validation.
- Simplified callback handling in
- Promo Code Admin Management — Improvements to promo code creation and management API.
- Fixed edge cases in code validation and uniqueness checking.
- Enhanced response handling for duplicate code detection.
- Vultr Storage API Integration — Code quality improvements to the Vultr Object Storage management endpoint.
- Refactored
POST /api/admin/storage/vultr/route.tsfor better maintainability and error handling. - Improved instance provisioning flow with better status tracking.
- Refactored
- Legal Content API Improvements — Legal routes refactored for better code structure and consistency.
- Improved parameter validation and response handling.
- Enhanced error messages for debugging.
- Video Player Meta Tags — Fixed video rendering when URLs contain trailing slashes.
POST /api/files/[id]/playernow correctly handles URLs with trailing slashes without breaking playback metadata.
- File Service Updates — Improvements to file upload and management service layer.
- Better error handling and validation logic in file operations.
- API Response Handling — CORS and response utilities improved for better cross-origin support.
- Enhanced CORS header handling for consistent behavior across all endpoints.
- Type Safety Improvements — Across-the-board type refinements and fixes.
- Fixed TypeScript validation errors to ensure strict type safety.
- Improved inference in generic functions and API responses.
- Linting and Code Quality — Resolved linting issues and code quality recommendations from automated analysis.
- Removed unused imports and variables.
- Fixed inconsistent naming and spacing issues.
- License Information — Updated copyright and license information in LICENSE file.
- Reflects current organizational changes and branding updates.
Deprecated
- Dynamic Background Component —
packages/components/layout/dynamic-background.tsxreplaced by enhanced animated-background system.- Existing animated background effects consolidated into unified system for better maintainability.
[2.4.3] - Provisioning & Validation Fixes
Highlights
This patch release improves account handle safety, hardens bucket provisioning reliability after Stripe payments, and reduces event-system log noise when the database is temporarily unreachable.
Added
-
Admin username repair endpoint for existing whitespace handles
POST /api/admin/users/repair-usernames- Supports dry-run and apply modes
- Replacement options: hyphen, underscore, or remove spaces
- Includes conflict checks to safely skip duplicate collisions
-
Shared storage provisioning helper
- Centralized idempotent bucket provisioning flow
- Reuses existing subscription-linked bucket records when present
- Handles already-exists bucket responses gracefully
- Emits provision events and triggers credentials email on success
Changed
-
Stripe checkout metadata persistence — now preserves subscription metadata for recovery scenarios
- Ensures downstream events retain provisioning context (type, location, tier)
-
Bucket dashboard auto-healing — now attempts automatic recovery when user has an active storage-bucket subscription but no assigned bucket
- Updated messaging reflects automatic provisioning and retry behavior
Fixed
-
Username whitespace validation gaps — setup/admin validation now consistently blocks handles containing spaces
-
Storage bucket purchase reliability — webhook flow now includes recovery path
- Merges checkout and subscription metadata
invoice.payment_succeededcan provision ifcheckout.session.completedis missed- Idempotency checks prevent duplicate provisioning
-
Event system DB outage spam — now fails soft when database is unreachable
- Centralized DB-connection error detection
- Handler sync defers/requeues during outages instead of warning per handler
- Worker retry logging is quieter during DB-unreachable periods
Notes
- No breaking API changes
- Focuses on reliability and operational safety
[2.4.2] - API Keys and Bucket Partner
Added
- User API Key Management System — Users can now create, name, copy, and revoke multiple personal API keys from
/me→ API tab.- New
UserApiKeyPrisma model (ebk_prefix, SHA-256 hashed, max 10 keys per user,lastUsedAttracking). packages/lib/api-keys/index.ts—generateApiKey(),listUserApiKeys(),createUserApiKey()(enforces 10-key cap),revokeUserApiKey().GET /api/profile/api-keys— lists keys (prefix and metadata only, never the hash).POST— creates a key and returns the full raw value exactly once.DELETE /api/profile/api-keys/[keyId]— revokes a key; ownership enforced viauserIdfilter.packages/components/profile/api-keys-panel.tsx— combined panel: legacy upload token (show/hide/refresh) + named key CRUD (create with name, one-time copy flash, revoke per key).ebk_prefix recognized ingetAuthenticatedUser— SHA-256 hash lookup with non-blockinglastUsedAtupdate; falls through cleanly if prefix matches but no record is found.
- New
- Admin System Key Auth Integration — The existing
esk_system key can now authenticate any admin or superadmin route.requireAdmin(),requireSuperAdmin(),requireRole(), andrequirePermission()all accept an optionalreqparameter; when provided, they fall back to system key auth if no session is present.- Synthetic
SYSTEM_KEY_USERconstant (SUPERADMIN role) returned by newgetSystemKeyUser(req?)helper, which reuses the existingisSystemKeyAuth()check. - All existing callers (no
reqargument) are unaffected.
- Partners Carousel — Marketing partner logos rebuilt from a static grid to an infinite auto-scrolling marquee.
- Duplicated partner list drives a seamless CSS loop; pauses on hover.
- Edge fade masks (
mask-imagegradient) soften the left/right boundaries. animate-marqueekeyframe added totailwind.config.ts(translateX(-50%), 30 s linear infinite).
- Vultr Object Storage Admin Panel — Admins can now provision, manage, and sync Vultr Object Storage instances directly from Admin → Storage.
- New
VultrObjectStoragePrisma model trackingvultrId,region,clusterId,s3Hostname, credentials,tier,status,cfHostname, andcfDnsRecordId. packages/lib/vultr/index.ts— new typed Vultr API client:listObjectStorages(),createObjectStorage(),getObjectStorage(),deleteObjectStorage(),listClusters(),listTiers(),createObjectStorageBucket(),deleteObjectStorageBucket().POST /api/admin/storage/vultr— provisions a new instance via Vultr API, saves to DB, auto-creates a Cloudflare CNAME record.GET /api/admin/storage/vultr— lists all tracked instances; syncs stalestatusand credentials back to DB on every fetch.GET/DELETE /api/admin/storage/vultr/[id]— fetches/deletes a single instance; DELETE removes the Vultr instance, CF DNS record, and DB row.POST /api/admin/storage/vultr/sync— imports new Vultr instances not yet in the DB and updates status + credentials for existing stale records; response includesimported,updated, andskippedcounts.packages/components/admin/settings/vultr-instance-manager.tsx— full admin UI: cluster/tier picker, region selector with SVG country flags, instance list with live status badges, delete with toast confirmation.- Prisma migrations:
20260414071251_add_block_storage_management(VultrObjectStorage table, StorageBucket Vultr fields),20260414084547_add_cf_hostname_to_vultr_storage(cfHostname, cfDnsRecordId columns).
- New
- Cloudflare DNS Auto-Provisioning — When an admin provisions a Vultr Object Storage instance, a DNS-only CNAME record is automatically created in Cloudflare pointing at the Vultr S3 hostname.
createDnsRecord()anddeleteDnsRecord()added topackages/lib/cloudflare/client.ts; record ID stored ascfDnsRecordIdon the instance for reliable cleanup on delete.- Record is DNS-only (
proxied: false) since Vultr TLS certificates only cover*.vultrobjects.com; Cloudflare proxying would break S3 signature authentication.
- Automatic Storage Bucket Provisioning — Purchasing a storage-bucket subscription now automatically creates a dedicated Vultr bucket for the user with no manual admin intervention required.
- Stripe
checkout.session.completedwebhook readsmetadata.type === 'storage-bucket'andmetadata.locationto select the correct Vultr pool and callcreateObjectStorageBucket(). - On subscription cancellation, the webhook calls
deleteObjectStorageBucket(), clears theStorageBucketrecord, and unlinks the user — keeping costs proportional to active subscribers. - Per-user bucket name is DNS-safe and scoped to the user ID (
emberly-{userId slice}). user.bucket-provisionedanduser.bucket-deprovisionedevents wired into email and Discord notification preference maps.
- Stripe
- Tiered Storage Bucket Products — The S3/Object Storage tab on the pricing page now supports five tiers: Standard, Archival, Premium, Performance, and Accelerated.
- Pricing page queries
VultrObjectStoragefor active instances, groups bytierkeyword, and passes per-tieravailableRegionstoS3Section. StorageTiertype exported fromS3Section.tsxcarriesslug,name,priceId,priceCents, andavailableRegions.EXCLUDED_ADDON_KEYSinAddOnsSection.tsxexpanded to cover allstorage-bucket-*slugs so tier products never appear in the generic add-ons list.
- Pricing page queries
- Vultr API Key in Integrations Config —
vultr.apiKeyadded to the config schema (packages/lib/config/index.ts) andDEFAULT_CONFIG; the Vultr client reads from this field with a fallback to theVULTR_API_KEYenvironment variable.
Changed
- Profile API Tab — Upload token and API key management moved out of the Uploads tab into a new dedicated API tab in
/me.KeyRoundicon; tab inserted between Uploads and Applications in the Content group.- Upload Host domain selector remains in the Uploads tab (upload config, not key management).
packages/components/profile/tools/upload-host.tsxextracted as a standalone component and restored toProfileTools.
- S3 Pricing Section Redesigned —
S3Section.tsxcompletely rebuilt around the new tier + region model.- Location picker shows SVG country flags (
country-flag-icons/react/3x2) instead of emoji for consistent cross-platform rendering. - Tier cards replace the old single-product layout; each tier displays its price, region availability, and a "Choose location" picker that is only shown when the admin has provisioned that tier in at least one region.
- Checkout flow passes
metadata.type,metadata.location, andmetadata.tierto the Stripe session so the webhook can auto-provision the correct bucket.
- Location picker shows SVG country flags (
- Dashboard Storage Tracker Reading Correct Source — The overview storage card now reads
user.storageUsed(the denormalized MB field updated on every upload/delete) instead of aggregating raw bytes from theFiletable, which excluded S3-backed objects and caused near-zero or incorrect totals.
Fixed
- Storage Usage Inconsistencies — Several parts of the site were displaying incorrect or near-zero storage values due to unit mismatches across the storage pipeline.
File.sizeandUser.storageUsedare stored in MB; the analytics API was naming those values withbytes-suffixed fields, causing consumers to double-convert.GET /api/analytics/storage— response fields renamed:totalBytes→totalMB,daily[].bytes→daily[].mb,breakdown[].bytes→breakdown[].mb.StorageMetrics.tsx— removed manual/ 1024 / 1024division; now readsdata.totalMBand callsformatFileSize(mb)directly.AnalyticsOverview.tsx—storageTotalBytes→storageTotalMBthroughout;storageDay?.bytes→storageDay?.mb;item.bytes→item.mbin breakdown render.POST /api/files— squadstorageUsedwas being incremented with raw bytes (uploadedFile.size) instead of MB; fixed tobytesToMB(uploadedFile.size).
- Storage Quota Matching All Tiered Bucket Slugs —
getPlanLimits()was only checking for an activestorage-bucketsubscription; users who purchased a tiered variant (e.g.storage-bucket-archival) still had quota enforced. Fixed to match any slug thatstartsWith('storage-bucket'). - File Privacy: Direct URLs Never Expose Storage Hostnames — The
/directendpoint for raw file access and bothGET/POSTvariants of the download route were returning pre-signed Vultr URLs or redirect responses that exposed*.vultrobjects.comhostnames to clients.- All paths now route through Emberly's own proxy (
buildRawUrl()); storage provider URLs are never returned to the browser.
- All paths now route through Emberly's own proxy (
- Admin Vultr Sync Updates Existing Instances — The "Sync Vultr" button previously only imported instances not yet tracked in the database; instances that had drifted (status changed from
pending→active, or credentials populated after instance activation) were skipped silently.- Sync endpoint now splits results into
toImport(new) andtoUpdate(existing with stale status or blank credentials) and runs both in parallel; toast showsimported,updated, andskippedcounts.
- Sync endpoint now splits results into
- Admin Vultr Status Not Synced Back to DB — Vultr instances provisioned while still
pendingwere never updated toactivein the database, causing the pricing page to show no available regions.- Both the list and single-instance GET handlers now detect drift (live status ≠ DB status, or blank credentials now populated) and write the updated values back to the database; list endpoint fires-and-forgets, single-instance endpoint awaits before responding.
- Destructive Confirmations Use Toast Actions — All destructive confirmation dialogs across the admin and profile UI previously used the browser's native
confirm(), which blocks t...
[2.4.1] - Files, Forms & Flexibility
Added
- Static Web Standards Files —
robots.txtandmanifest.webmanifestmoved topublic/for faster serving and simpler deployment.public/robots.txt— Static robot exclusion file for search engines and web crawlers; disallows/api/,/dashboard/,/admin/paths globally and blocks AI crawlers from user content (/u/).public/manifest.webmanifest— Static PWA manifest with standalone display, dark theme (#09090bbackground,#F97316accent), app shortcuts (Upload, Shorten URL, Pricing), and maskable SVG icon support.- No route handlers (
app/robots.ts,app/manifest.tsremoved); Next.js serves frompublic/directly and links manifest from metadata.
- CORS Headers for File Embedding — Emberly-hosted files and images can now be embedded and used externally on other websites.
- New
packages/lib/api/cors.tsmodule providesaddCORSHeaders(),addSecurityHeaders(),handleCORSPreflight(), andgetCORSHeaders()utilities. - File serving routes now respond with
Access-Control-Allow-Origin: *on both requests and preflight OPTIONS, enabling cross-origin file access from any domain. - Response headers include
X-Content-Type-Options: nosniff(prevents MIME-type sniffing) andReferrer-Policy: no-referrer-when-downgrade(enables safe cross-origin embedding). Content-Disposition: inline(notattachment) allows browsers to display files/images inline instead of forcing downloads, enabling embedding in<img>,<video>,<canvas>,<iframe>, and XMLHttpRequest contexts.- Files remain correctly permission-checked server-side; CORS headers simply allow the browser to fetch them cross-origin once access is confirmed.
- Updated routes:
/api/files/[...path]/(raw file serving),/api/files/[id]/download/,/api/files/[id]/thumbnail/,/api/avatars/[filename]/.
- New
- SMTP Email Provider Support — The email system now supports SMTP as an alternative to Resend, selectable via Admin → Settings → Integrations.
emailProvider('resend'|'smtp', default'resend') andsmtp(host,port,secure,user,password,from) added to the config schema andDEFAULT_CONFIGinpackages/lib/config/index.ts.packages/lib/emails/index.ts— addedgetSmtpTransport()backed by nodemailer with connection caching;sendEmail()now branches onintegrations.emailProvider: SMTP path renders React templates to HTML via@react-email/renderand sends via nodemailer; Resend path unchanged.- Admin settings UI (
packages/components/admin/settings/settings-manager.tsx) — Email Provider selector (Resend / SMTP) added to the Resend section; new SMTP section with Host, Port, Secure (TLS) toggle, Username, Password, and From Address fields plus a Test Connection button. app/api/admin/integrations/test/route.ts— addedtestSmtp()usingnodemailer.createTransport().verify()and wired'smtp'into theIntegrationKeyunion and switch.
- File Sharing & Collaborators — Files can now be shared with other users directly from the file manager.
app/api/files/[id]/collaborators/route.ts— newGET/POST/PATCH/DELETEendpoints for managing per-file collaborators with role support (viewer/editor).- Share button added to every
FileCard; opens a dialog showing current collaborators (with role badges and remove controls) and an invite form (email + role). FileSharedEmailtemplate (packages/lib/emails/templates/file-shared.tsx) — notifies the recipient with file name, sharer name, and a direct link.- Squad files are now correctly scoped:
GET /api/filesfilters bysquadIdwhen a query param is provided, with membership verification.
- Dashboard Tabbed Navigation —
DashboardShell(packages/components/dashboard/dashboard-shell.tsx) rewritten from a sidebar layout to a horizontal pill tab strip.- Tabs: Overview, Files, Upload, Paste, Links, Domains, Analytics, Buckets, Discovery.
- Active tab highlighted with
bg-primary/10 text-primary border border-primary/20; strip is horizontally scrollable on mobile inside aScrollIndicator. headerprop still accepted and rendered full-width above the tab strip.
- Squads Dashboard Migrated to
/dashboard/squads— The squads section previously embedded in the Discovery dashboard is now a standalone route at/dashboard/squads. - File Grid Owned Hero —
FileGrid(packages/components/dashboard/file-grid/index.tsx) now owns the full glass-card hero — title, description, and view switcher dropdown — eliminating the need for a separate header prop on the files page.ViewModeis a discriminated union:{ type: 'my-files' } | { type: 'shared' } | { type: 'squad'; squadId: string; squadName: string }.- Squad selector dropdown lets users switch between My Files, Shared with Me, and per-squad views.
- Upload & Paste Forms in Glass Cards —
upload-form.tsxandpaste-form.tsxcontent sections wrapped inglass-cardpanels for visual consistency with the rest of the dashboard. - Private Promo Codes — Admins can now mark a Stripe promotion code as private so it is hidden from the public Discounts tab on the pricing page while still being redeemable at checkout.
app/api/admin/promo-codes/route.ts—POSTacceptsisPrivate: boolean; whentrue, setsmetadata: { private: 'true' }on the Stripe promotion code.GETreturnsisPrivatederived from the same metadata field.app/api/payments/promo-codes/route.ts— public endpoint now filters out any code whose Stripemetadata.privateequals'true'before returning results.packages/components/admin/payments/promo-codes-manager.tsx— "Private code" toggle (Switch + description) added to the create dialog; private codes display anEyeOff · Privatepill badge inline with their code in the table. Create form default state extended withisPrivate: false.
Fixed
- Admin Broadcast Email Markdown Not Rendering — The compose UI advertised markdown support but the
AdminBroadcastEmailtemplate rendered the body as a plain text node, causing**bold**,# headings,- lists, etc. to appear literally in the email.- Added a
markedrenderer with email-safe inline styles for all elements (paragraphs, headings h1–h3, bold, italic, lists, links, blockquotes, code blocks, inline code, horizontal rules) since email clients strip<style>tags and don't honour Tailwind class names on dynamically injected HTML. - Replaced
<Text>{body}</Text>with<div dangerouslySetInnerHTML={{ __html: marked.parse(body) }} />inpackages/lib/emails/templates/admin-broadcast.tsx.
- Added a
- Admin Product List Redesign —
packages/components/admin/products/ProductManager.tsxproduct list rebuilt to match the v2 glass aesthetic.- New
TypeBadgecomponent with colour-coded variants per type:plan→ blue,addon→ violet,nexium-plan→ orange,one-time→ slate. - Product rows use
glass-subtleborder-divided list (consistent with blog-list and partner-list) replacing the oldglass-cardwrapper. - Action buttons (Stripe, Sync, Edit seed, Delete) are always visible instead of appearing only on hover.
- Removed
scale-75Switch hack; switches render at full size with properaria-label. - Inline skeleton loading rows replace the old full-page spinner, maintaining layout stability during load.
- Stats row shows price as
$X.XX / intervalwith the Stripe product ID in dim 10px mono beneath the feature badge row.
- New
- Legal Article Page Redesign —
packages/components/legal/LegalArticle.tsxrebuilt for a more open, professional reading experience.- Removed inner
max-w-5xlconstraint that was double-constraining content insideDashboardWrapper'smax-w-7xl. - Three-card layout replacing the old heavy border stack:
glass-subtlemeta strip (type + date),glass-subtlearticle body with generoussm:p-10padding, andglass-subtlefooter CTA — all spaced viaspace-y-4. - Meta strip is a compact inline row replacing the large icon header card.
- Sidebar cards switched from manual
relative rounded-xl bg-background/80 backdrop-blur-lg border+ gradient overlay toglass-subtle rounded-xl, consistent with the rest of the admin/dashboard UI. Sidebar column narrowed from 280 px to 260 px.
- Removed inner
- Collaborators Route
reqReference Bug —POSTandPATCHhandlers in the collaborators route incorrectly referencedreqinstead ofrequest, causing runtime errors on every invocation. All handlers rewritten with consistentrequestnaming and duplicate exports removed. - File Filters Accidentally Triggered During Mobile Scroll — On touch devices, scrolling past the filter buttons opened their dropdowns because Radix
DropdownMenu/Popoverfire onpointerup, which coincides with a finger lift ending a scroll gesture.useScrollSafeOpenhook added tofile-filters.tsx— touch-triggered opens are deferred by onerequestAnimationFrame; if the browser firespointercancel(scroll took over) before the frame runs, the pending open is cancelled. Mouse and keyboard interactions open immediately with no change in behaviour.
Policy
- SLA Uptime Commitment Adjusted — Monthly uptime guarantee updated from
99.9%to99.6%to better reflect a sustainable foundation for Emberly's current stage of growth. - Infrastructure Timezone Alignment — Monitoring, maintenance windows, and status reporting moved from UTC to Mountain Standard Time (MST / GMT-7) to align with Emberly's Canada-based operations.
[2.4.0] - Refined Vistas
Added
- Profile Route Moved to
/me— User profile settings previously lived at/dashboard/profile; now a standalone route at/mewith its own layout and sidebar.app/(main)/me/layout.tsx— new layout usingDashboardWrapper(dashboard nav, no footer).app/(main)/me/page.tsx— profile page powered byProfileClientwhich has its own Account / Content / Engagement / Billing tab sidebar.app/(main)/me/logout-button.tsx—LogoutButtonmoved fromdashboard/profile/./dashboard/profileroute removed;/profileredirect page updated to point at/me.- OAuth callback routes (GitHub and Discord link/unlink) updated to redirect to
/meafter completion.
- Full-Width Hero Cards on All Dashboard & Admin Pages — Every dashboard and admin page now renders a full-width glass hero/title card above the
[sidebar | content]flex row, rather than inside it.DashboardShellgains an optionalheader?: React.ReactNodeprop; when provided it renders full-width above the sidebar+content flex row.AdminShellgains the sameheaderprop with identical behaviour.- All dashboard pages (
/dashboard,/dashboard/files,/dashboard/analytics,/dashboard/upload,/dashboard/urls,/dashboard/domains,/dashboard/paste,/dashboard/verification-codes,/dashboard/bucket,/dashboard/discovery) updated to pass their hero asheader. - All admin pages (
/admin,/admin/users,/admin/blog,/admin/legal,/admin/settings,/admin/logs,/admin/email,/admin/testimonials,/admin/partners,/admin/products,/admin/reports,/admin/applications) updated with the same pattern. - Hero glass-cards extracted from client components (
DashboardIndex,AnalyticsOverview,AdminOverviewContent) — server pages own the hero, client components render content only. DashboardShellremoved fromdashboard/layout.tsx; each page wraps individually so the shell appears exactly once per page.
- New Sidebar Components — Dedicated sidebar components for dashboard and admin panels.
packages/components/dashboard/dashboard-sidebar.tsx— collapsible nav with all dashboard routes; Discovery section expands to show Talent Profile / Squads / talent sub-links; mobile horizontal scroll strip.packages/components/admin/admin-sidebar.tsx— admin panel nav with all admin routes and mobile scroll strip.
- Files Page — New dedicated files page at
/dashboard/files.app/(main)/dashboard/files/page.tsx+client.tsx— full file browser with filtering, sorting, and bulk actions.packages/components/dashboard/file-grid/index.tsx— significant improvements to the file grid component.
- Discovery Mobile Tab Navigation — The Discovery page previously relied on the sidebar for section navigation, leaving mobile users with no way to switch between Talent and Squads or navigate talent sub-sections.
NexiumDashboardClientrenders two horizontally-scrollable tab strips on mobile (lg:hidden) when not in squad-detail view.- Top strip: Talent Profile / Squads — controls the
selectedTabstate. - Sub-strip (Talent only): Profile / Skills / Signals / Opportunities / Applications — controls
talentSection. - Both strips match the existing dashboard sidebar mobile style (
glass-subtle rounded-xl p-1.5, active statebg-primary/10 text-primary border border-primary/20).
- Discovery Navigation in Dashboard Sidebar — Discovery sub-links integrated directly into
DashboardSidebaras a collapsible expandable section, replacing the old discovery-specific internal sidebar.- Parent row with
ChevronDowntoggle; children are Talent Profile and Squads. - Talent sub-links (Profile, Skills, Signals, Opportunities, Applications) render as indented secondary items when on a talent sub-page.
- Expand state initialised to open when already under
/dashboard/discovery.
- Parent row with
Fixed
- Double Navbar on
/meRoutes —ConditionalBaseNavexcluded/dashboardand/adminfrom the base nav but not/me, causing two navbars to render on the profile page (<BaseNav />from the main layout +DashboardWrapper's fixed glass navbar).- Added
/meto the exclusion list inconditional-base-nav.tsx.
- Added
LogoutButtonImport Broken After Profile Move —discovery/page.tsximportedLogoutButtonfrom../../profile/logout-buttonwhich no longer exists after the profile route moved to/me. Fixed to../../me/logout-button.NexiumSquad.urlId/avatarUrlInvalid Field References — Prisma threwPrismaClientValidationErroron the squad invite routes becauseurlIdandavatarUrlare not fields onNexiumSquad(the correct fields areslugandlogo).GET /api/discovery/invites— squad select updated:urlId→slug,avatarUrl→logo.GET /api/discovery/invites/[token]/accept— same correction.SquadIncomingInvitetype indashboard/discovery/client.tsxupdated to match.
- Squad Member Invite Flow — Various issues in the invite accept/decline redirect flow corrected.
- Build Errors and GitHub Links — Miscellaneous build failures and broken GitHub repository links resolved.
[2.3.0] - The Discovery Update
Added
- Discovery Dashboard Redesign — Full sidebar-controlled layout replacing the previous flat tab strip.
- Three sidebar modes: talent (sub-tabs), squad detail (squad tabs), and top-level (squads list).
- Talent sub-sections — Profile, Skills, Signals, Opportunities, Applications — rendered as indented sidebar items below "Talent Profile" on desktop; horizontal strip on mobile.
NexiumDashboardnow supports optional controlledactiveSection/onSectionChangeprops so the outer sidebar can drive section navigation without internal state conflicts.- Squad detail view rendered inline via
SquadDashboardClientin embedded mode; tab state owned by the parent discovery client.
- Discovery Page Hero Header — Glass-card header on
/dashboard/discoveryshowing the page title, a short description, quick-action links (View Public Profile, Profile Settings, Applications), and aLogoutButton. - GitHub Repo Picker Improvements — Signal creation flow is now picker-first when GitHub is linked.
- Repo picker shown immediately on "Add signal"; manual form is an explicit secondary option.
- Org switcher replaced with a compact native
<select>dropdown (user shown first, then orgs alphabetically). - Multi-select checkboxes on every repo row; selection highlighted with
border-primary/40 bg-primary/5. - Footer "Add X signals" bulk-submit button appears when at least one repo is selected; POSTs each repo as a separate signal and shows a count toast on completion.
- Info callout explains why repos are shown and lists other signal types (deployed apps, OSS contributions, shipped products, etc.) with a link to the manual form.
- Manual form redesigned with "Browse repos" back button and "Cancel" header; no longer shows an inline GitHub shortcut inside the form body.
- GitHub
read:orgOAuth Scope — GitHub account linking now requestsread:orgin addition topublic_repo,repo, so repositories from organizations the user belongs to are accessible in the picker.- Organization repos included via
affiliation=owner,collaborator,organization_memberon the GitHub repos API call. - "Grant org access" re-auth link in the picker footer and error state for users needing to re-authorize.
- Organization repos included via
- Squad Branding Fields —
NexiumSquadschema extended withtagline(≤120 chars),logo(URL),banner(URL),website,twitter,github, anddiscordsocial/web presence fields. - Squad File Relation —
Filemodel gains a nullablesquadIdforeign key andNexiumSquadrelation (onDelete: SetNull), enabling squad-owned file uploads alongside per-user files. - Discovery Dashboard Nav Link — Dashboard navigation updated to surface
/dashboard/discoverydirectly under the Dashboard menu. - Email Template Expansion — 13 new transactional email templates covering the full account lifecycle.
EmailChangedOldEmail/EmailChangedNewEmail— dual notifications sent to both old and new address when email is changed, including timestamp and change source.ExportRequestedEmail/ExportCompletedEmail— confirmations for data export request and download-ready notifications.DeletionRequestedEmail/DeletionCancelledEmail/AccountDeletedEmail— full account deletion flow emails with cancellation window and final confirmation.SubscriptionCreatedEmail/SubscriptionUpdatedEmail/SubscriptionCancelledEmail— Stripe subscription lifecycle notifications with plan details.PaymentSucceededEmail/PaymentFailedEmail— payment confirmation and failure alerts with invoice context.RefundIssuedEmail— refund confirmation with amount and reason.- All templates barrel-exported from
packages/lib/emails/index.tsand wired into the event handler inpackages/lib/events/handlers/email.ts.
- Email Event Handler Overhaul —
packages/lib/events/handlers/email.tscompletely rewritten.- Each template now has a dedicated dispatch branch instead of falling through to the generic
BasicEmailrenderer. - Existing templates (
welcome,verify-email,magic-link,password-reset,account-change,perk-gained,quota-reached,storage-assigned,new-login,admin-broadcast, Nexium invite/welcome/opportunity, application status/reply, bucket credentials) all converted to typedsendTemplateEmailcalls. - Eliminates the previous "body string split" fallback that caused inconsistent rendering across templates.
- Each template now has a dedicated dispatch branch instead of falling through to the generic
- Squad Create Dialog — Full-featured squad creation dialog replacing the previous single-field inline form.
packages/components/discovery/squad-create-dialog.tsx— React Hook Form + Zod validated dialog with name, description, max size, public toggle, and comma-separated skills fields.- Accepts any trigger element as
childrenso it can be opened from any context.
- Add Member Dialog — Searchable user picker for squad owner to invite members.
packages/components/discovery/add-member-dialog.tsx— live search via/api/users/searchwith debounced input, avatar display, and one-click add.- Shows user name, handle, and email; highlights already-added members with a checkmark badge.
- Squad Members Manager Component — Standalone member list with role and kick controls.
packages/components/discovery/squad-members-manager.tsx— renders member rows with inline role<select>(MEMBER / OBSERVER) and kick button; owner row is non-editable.- Calls
POST /api/discovery/squads/[id]/membersfor both role changes and kicks.
- Squad Settings Form — Full settings and danger-zone panel for squad owners.
packages/components/discovery/squad-settings-form.tsx— editable name, description, max size, public toggle, and skills; separate danger zone card withAlertDialog-gated disband action.- Validates with Zod, shows inline field errors, and navigates away on successful disband.
- User Search API Endpoint —
GET /api/users/search?q=query&limit=10.- Returns
id,name,email,image,urlIdfor matching users; searchesnameandemailcase-insensitively. - Used by
AddMemberDialogfor live member lookups.
- Returns
- Squad Members GET Endpoint —
GET /api/discovery/squads/[id]/membersnow returns the full member list for authenticated squad members (was previously missing entirely). - GitHub-Style Signal Cards — Nexium proof-of-skill signals now render as rich GitHub embed widgets for
GITHUB_REPOtype signals, and branded cards for all other types.- New
packages/components/profile/signal-card.tsx—GitHubRepoCardrenders with GitHub's dark#0d1117surface, blue repo name link, muted description, topic chips, language color dot (30+ language colors from linguist palette), and live ⭐/🍴 counts pulled from metadata. - Generic signals show the user-supplied logo/banner image as a card header, falling back to a colored type abbreviation chip when no image is provided.
SignalCarddispatches to the correct renderer based ontype; exportedSignalCardDatatype consumed by all three display locations.
- New
- Signal Logo / Banner URL — Non-GitHub signals now accept an optional
imageUrlfield for custom logos or banners displayed in the signal card header.imageUrladded toSignalInputSchemainpackages/types/dto/nexium.tsand exposed as a form field in the Nexium dashboard panel.- Field is hidden for
GITHUB_REPOsignals since the owner avatar is sourced automatically from GitHub API metadata.
- Automatic GitHub Repo Metadata Fetch — When a
GITHUB_REPOsignal is created or updated with agithub.laiyagushi.comURL, the API now automatically fetches live repository metadata and stores it in the signal'smetadatafield.POST /api/discovery/signals— callsparseGitHubUrl()thengetRepo(owner, repo)before writing to the database; storesfull_name,description,stargazers_count,forks_count,language,topics, andowner.avatar_url.PUT /api/discovery/signals/[id]— re-fetches metadata when the URL field changes on an existingGITHUB_REPOsignal, keeping star/fork counts fresh on edit.
Fixed
- Squad Dashboard Blank Screen — Squad detail page rendered nothing after a previous session changed the GET route to return
{ squad, isOwner }while the client still read the response as the squad object directly.- Reverted
GET /api/discovery/squads/[id]to return the squad directly viaapiResponse(squad); theisOwnerflag is redundant sincepage.tsxpassesroleas a prop from the server.
- Reverted
- Kick Member Failures — Kick action was broken in two independent ways.
- Wrong HTTP method: client called
DELETE /memberswhich invokesleaveSquad()on the caller (owners can't leave, so always errored). Fixed toPOST /memberswith{ userId, kick: true }. - Wrong ID field: client passed
m.user.urlId(human slug) instead of the UUID. Fixed by addingid: truetoSQUAD_INCLUDE's user selects inpackages/lib/nexium/squads.ts.
- Wrong HTTP method: client called
- ShareX Squad Uploads Returning 401 —
POST /api/filesonly checked session and personal upload tokens; squad-issued upload tokens andnsk_API keys were never validated.getSquadFromBearerToken()is now called first; on match, the squad owner's session is used as the acting user.- Squad uploads check
storageQuotaMBseparately from user quota and incrementnexiumSquad.storageUsed(in bytes) in the same DB transaction.
- Overview Stat Counts Always
–— API keys and domain counts on the squad overview tab showed dashes because those resources were only fetched when the user switched to their respective tabs.- Both are now pre-fetched on initial mount alongside the squad load so overview cards populate immediately.
- Members Route Now Handles Four POST Cases —
POST /api/discovery/squads/[id]/memberswas previously only handling the join-self flow.- Rewritten to dispatch on...
[2.2.0] - Infrastructure & Access Control
Added
- Stripe API Clover Compatibility — Updated promo code creation to use new Stripe v2025-11-17.clover API structure.
promotionCodes.createnow usespromotion: { type: 'coupon', coupon: string }wrapper instead of directcoupon: stringparameter.- Applied to three endpoints:
POST /api/admin/promo-codes,GET /api/admin/promo-codes, andPOST /api/payments/promo-codes. - Promo code responses properly expand nested coupon data via
expand: ['data.promotion.coupon']for complete pricing details.
- Promo Code Orphan Prevention — Coupon deletion rollback mechanism when promotion code creation fails.
- After creating a coupon, if
promotionCodes.createfails, the orphaned coupon is automatically deleted to prevent dangling resources. - Reduces Stripe admin cleanup burden and improves data consistency.
- After creating a coupon, if
- Kener Status Page Integration — Real time service status monitoring from Kener instance.
- Maps Kener v4 monitor state field format to aggregated health statuses:
ACTIVE→UP,INACTIVE→DOWN. GET /api/statusreturns aggregated system health from all visible monitors with 60 second cache TTL.- Properly differentiates between Kener workflow state (ACTIVE/INACTIVE) and actual health status (UP/DOWN/DEGRADED).
- Maps Kener v4 monitor state field format to aggregated health statuses:
- Graceful Status Fallback — Status endpoint returns
UNKNOWNstatus instead of 503 when Kener is unreachable.- Improves UX for development environments without configured status page.
- Prevents production status page from failing hard when external monitoring is unavailable.
- User Buckets Storage System — Granular file storage organization per user.
- New
UserBucketmodel with storage quota tracking and access control. - Prisma migration:
20260404075543_add_user_buckets— Database schema for bucket management. - Enables team-based storage organization without requiring squads.
- New
- User Grants & Permissions System — Role-based access control and permission management.
- New
UserGrantmodel and permission system for granular authorization. - Prisma migration:
20260404085324_add_user_grants— Permission tracking and enforcement. - Supports delegation of admin functions without full superadmin access.
- New
- Sentry Error Tracking Integration — Client and server error reporting with
@sentry/nextjs.- Automatic error capture from both browser and server environments.
- Environment-specific configuration via
sentry.client.config.ts,sentry.server.config.ts, andsentry.edge.config.ts. - Sentry source map upload and release tracking for production deployments.
- Admin Applications List Redesign — Comprehensive card based redesign replacing plain table layout.
- Individual glass card rows with user avatars (initials fallback), role badges, and action buttons.
- Header card with live stats pills showing PENDING and REVIEWING application counts via Prisma
groupByaggregation. - Proper empty state with centered icon when no applications exist.
- Admin Applications Detail Redesign — Improved detail page layout with icon headers and organized sections.
- Header card with accent gradient top bar,
ClipboardCheckicon, and back navigation. - Applicant card uses
next/imagewith initial fallback instead of bare<img>tag for proper image optimization. - Section headers use small caps muted styling for visual hierarchy.
- Metadata sidebar displays icon prefixed rows instead of plain key/value pairs for better readability.
- Header card with accent gradient top bar,
- Pricing Discounts Section Redesign — Flat card design removing layered nesting.
- Changed from outer wrapper with inner nested
glass-subtlecards to flatglass-cardper discount. - Matches the design pattern of
AddOnSelectorcomponent.
- Changed from outer wrapper with inner nested
Changed
- Documentation Files Comprehensive Update — Production-ready documentation for open source project.
README.mdexpanded from minimal placeholder to complete project overview including:- Feature breakdown (file storage, domains, verification, teams, applications, admin tools)
- Tech stack reference (Next.js 15, React 19, TypeScript, Tailwind, Stripe, Prisma, PostgreSQL, Kener)
- Quick start setup guide with prerequisites and step-by-step instructions
- Directory structure overview and contribution guidelines
- Support channels (Discord, GitHub Discussions, email)
- License and acknowledgments sections
CONTRIBUTING.mdnormalized for consistency:- Removed hyphens in compound words (
real-time→realtime,longer-form→longer form) - Removed emoji formatting from template text
- Maintained comprehensive developer setup, PR process, and coding standards documentation
- Removed hyphens in compound words (
- Kener Status API Response Mapping — Updated aggregation logic to handle Kener v4 format correctly.
- Recognizes
ACTIVEas enabled monitor state (treated as UP health status). - Differentiates between workflow state and actual health status properly.
- Fallback to
UNKNOWNstatus prevents status page crashes during Kener outages.
- Recognizes
- GitHub Module Export Structure — Fixed module export for better client bundling.
- Removed unused
githubobject export frompackages/lib/github/index.ts. - Clients now import only required functions (
getGitHubUser,getOrgRepos, etc.) instead of entire module.
- Removed unused
- Grant Constants Client Access — Separated client-safe constants from server code.
public-profile.tsxnow importsGRANT_METAandALL_GRANTSdirectly frompackages/lib/grants/constants.- Prevents Prisma (server-only database code) from being bundled into browser JavaScript.
Fixed
- TypeScript Nullish Coalescing Operator Syntax — Resolved Turbopack build error in GitHub utility.
- Fixed operator precedence in
packages/lib/github/index.ts:181by adding parentheses. - Changed:
org ?? integrations.github?.org || process.env.GITHUB_ORG ?? 'EmberlyOSS' - To:
org ?? (integrations.github?.org || process.env.GITHUB_ORG) ?? 'EmberlyOSS'
- Fixed operator precedence in
- Admin Applications Pages File Truncation — Resolved file truncation issues from concurrent editing.
app/(main)/admin/applications/page.tsx— truncated old table code block removed, file normalized to 224 lines.app/(main)/admin/applications/[id]/page.tsx— stray old code after return removed, missing closing brace added, file normalized to 243 lines.
- Review Form Hydration Error — Fixed React hydration mismatch in application review form.
packages/components/admin/applications/review-form.tsx— replaced invalid<Badge>render inside<p>tag with<div>flex container.- Text content wrapped in
<span>elements to maintain layout structure without hydration mismatches.
- Stripe Promo Code Expansion Fields — Public promo code endpoint now correctly expands promotion coupon data.
- Added
expand: ['data.promotion.coupon']topromotionCodes.listcalls for complete coupon details in responses. - Clients can now access coupon discount amounts directly from promo code API responses.
- Added
- Prisma Bundle in Client Components — Removed Node.js database library from browser bundles.
public-profile.tsxclient component no longer imports server-only database utilities.- Reduced bundle size and prevented runtime errors from missing Node.js modules (dns, fs, net, tls) in browser context.
- Debug Console Output Cleanup — Removed temporary logging statements from Kener integration.
- Cleaned up
[KENER]prefixed console logs used during debugging phase.
- Cleaned up
Technical
- Prisma Migrations — Three migrations for storage and permissions infrastructure.
20260404075543_add_user_buckets— User bucket management schema with quota tracking.20260404085324_add_user_grants— User grants and permission system models.
- Status Endpoint Architecture — Kener integration in
packages/lib/kener/index.ts.getKenerStatus()fetches from/api/v4/monitorswith Bearer token authorization.aggregateStatus()intelligently maps monitor states to health statuses with proper fallback logic.- 60 second cache TTL reduces API load while maintaining reasonable freshness.
- Sentry Configuration — Multi-environment error tracking setup.
sentry.client.config.ts— Browser client configuration with sourcemap upload and release tracking.sentry.server.config.ts— Backend API server error capture.sentry.edge.config.ts— Edge runtime (middleware) error handling.- Environment-aware initialization with development/production specific settings.