Skip to content

Releases: EmberlyOSS/Emberly

[2.5.0] - Storage & Queue Overhaul

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 25 Jun 09:05
2b8e0ff

Added

  • BullMQ event worker — replaced the custom Postgres+Redis polling event system with BullMQ. All event handlers are now static HandlerMap objects dispatched by a BullMQ worker. In development the worker runs in-process automatically; in production it runs as a separate process via bun run worker. Controlled by the EMBERLY_RUN_EVENT_WORKER environment 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 the emberly-events queue.
  • Standalone worker entry pointscripts/worker.ts can 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. cancelFileExpiration and getFileExpirationInfo use these IDs instead of querying a Postgres EventHandler table.
  • Periodic storage sync via BullMQstorage.sync-buckets is now a recurring BullMQ job (repeat: { pattern: '0 * * * *' }) scheduled at startup rather than through the old event system.
  • Linode Object Storage provider — new LinodeStorageProvider in packages/lib/storage/providers/linode.ts implements the Linode Object Storage management API (cluster listing, bucket creation/deletion, key management). Fully integrated with the ObjectStoragePool model and admin provisioning flow.
  • OVHcloud Object Storage provider — new OVHcloudStorageProvider in packages/lib/storage/providers/ovhcloud.ts implements the OVHcloud Public Cloud Object Storage API (project listing, region listing, S3 credential management). Integrated with ObjectStoragePool and admin provisioning flow.
  • Admin pool manager UI — Linodepackages/components/admin/settings/linode-pool-manager.tsx provides a full CRUD interface for Linode Object Storage pools: list clusters, create/delete pools, view credentials.
  • Admin pool manager UI — OVHcloudpackages/components/admin/settings/ovhcloud-pool-manager.tsx provides a full CRUD interface for OVHcloud Object Storage pools.
  • Linode admin API routesPOST /api/admin/storage/linode (create pool) and DELETE /api/admin/storage/linode/[id] (delete pool).
  • OVHcloud admin API routesPOST /api/admin/storage/ovhcloud (create pool) and DELETE /api/admin/storage/ovhcloud/[id] (delete pool).
  • ObjectStoragePool Prisma migration — unified storage pool model supporting vultr, linode, and ovhcloud providers. Replaces VultrObjectStorage as the single table for all provisioned storage pools. Provider-specific metadata stored as Json (clusterId for Vultr, keyId + linodeClusterId for Linode, projectId + regionName + credentialAccess for 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 HandlerMap static object instead of calling events.on() at registration time. The events.emit() call signature is unchanged across all 31+ call sites — the compat shim in events/index.ts maintains backward compatibility.
  • EventHandler model removed — the EventHandler Prisma model (used by the old polling worker) has been dropped. Apply with bun 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 ObjectStoragePool model.
  • Settings manager — admin settings panel updated to include Linode and OVHcloud pool manager tabs.

What's Changed

Full Changelog: v2.4.7...2.5.0

[2.4.7] - Bucket Routing & Storage Foundations

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 15 Jun 20:27

Added

  • Core Pool Storage BucketsStorageBucket now supports isCore (boolean) and priority (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 is 0 (no preference).
  • File → StorageBucket relation — Files now track which bucket they were stored in via storageBucketId. 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 UserBucketAssignment component 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 / priority UI — Edit dialog now includes a Core Pool Bucket toggle and a Priority number input (visible when isCore is on). List rows show Core Pool badge (violet), Dedicated badge, file count, and priority for core buckets.
  • Bucket connection test — per-bucket providerPOST /api/admin/storage/buckets/[id]/test now uses an explicit select in findUnique (fixes a @prisma/adapter-pg driver adapter issue that caused findUnique without a select to return null for existing records). Test error detail is now surfaced inline in the UI alongside the status message.
  • PUT /api/admin/storage/bucketspriority on create — Create route now reads and persists priority from the request body. Previously new buckets always defaulted to 0 regardless of the value submitted.
  • User bucket page — secret key reveal — The /dashboard/bucket page now shows the actual secret access key with a show/hide toggle (BucketSecretReveal client component). All other credentials are hidden behind a "coming soon" notice while direct S3 credential access is being developed.
  • reconcileDeprovisionedBuckets — atomic Subscription.status update — Both cancellation paths (cancelled subscription, subscription not found in Stripe) now use prisma.$transaction to 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 in PUT /api/files/[id]/suggestions, OCR processing, file expiry handler, and single/bulk delete in the file service. All now use getProviderForStoredFile(file.storageBucketId), which routes to the correct bucket and falls back to global for legacy files.
  • S3 key normalization — Windows backslash pathspath.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 causing NoSuchKey on every read. Added toS3Key() helper that normalizes backslashes to forward slashes before stripping the uploads/ prefix; applied to all five S3 provider methods (uploadFile, getFile, getFileStream, deleteFile, getFileUrl, getFileSize).
  • Upload path generation — forward slashes enforcedapp/api/files/route.ts and app/api/files/chunks/route.ts used path.join() to build filePath, producing backslash paths on Windows that were stored in the database and used as S3 keys. Both now use path.posix.join() so paths are always forward-slash regardless of platform.
  • OCR — wrong bucketprocessImageOCRTask called getStorageProvider() unconditionally. It now looks up file.storageBucketId and routes to getProviderForStoredFile so OCR reads from the same bucket the file was uploaded to.
  • Bucket list — priority missing from selectGET /api/admin/storage/buckets did not include priority in its field select. The edit dialog received priority: undefined, causing the priority input to be uncontrolled (React warning) and saving to silently leave the existing value unchanged.
  • Edit dialog — uncontrolled priority inputopenEdit now uses bucket.priority ?? 0 when populating the form, preventing the React "uncontrolled → controlled" warning when priority was absent from the list response.
  • Dev logger — error detail suppressed — The development pino transport only printed obj.msg, silently dropping obj.error. Error message and stack trace are now appended to the console output.
  • Chunks route — Prisma XOR type errortx.file.create mixed storageBucketId (unchecked input) with user: { connect: { id } } (checked input), which Prisma's XOR type rejects. Replaced with userId scalar field.

Changed

  • getStorageProvider() scope clarified — The global singleton is now the fallback only for legacy files (storageBucketId = null). All new file operations route through getProviderForStoredFile or getUploadBucketForUser/getUploadBucketForSquad.
  • selectCoreBucket ordering — Core bucket selection now orders by fileCount ASC, priority DESC so higher-priority buckets win when file counts are equal.

What's Changed

Full Changelog: v2.4.6...v2.4.7

[2.4.5] - Streamlined Domains

Choose a tag to compare

@NodeByteLTD NodeByteLTD released this 02 Jun 22:09

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

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 25 May 05:52
53d18d1

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_MAP with 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.
  • 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 (pendingactive), 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 like user.bucket-provisioned, user.bucket-deprovisioned with 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.
    • AdminBroadcastEmail template 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-border to animated gradient-border-animated class.
    • Provides cohesive visual language across the entire site header and footer.
  • 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.
  • 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.ts for better maintainability and error handling.
    • Improved instance provisioning flow with better status tracking.
  • 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]/player now 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 Componentpackages/components/layout/dynamic-background.tsx replaced by enhanced animated-background system.
    • Existing animated background effects consolidated into unified system for better maintainability.

[2.4.3] - Provisioning & Validation Fixes

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 18 May 15:06
a1db9cb

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_succeeded can provision if checkout.session.completed is 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

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 14 Apr 23:59
6c6ef76

Added

  • User API Key Management System — Users can now create, name, copy, and revoke multiple personal API keys from /me → API tab.
    • New UserApiKey Prisma model (ebk_ prefix, SHA-256 hashed, max 10 keys per user, lastUsedAt tracking).
    • packages/lib/api-keys/index.tsgenerateApiKey(), 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 via userId filter.
    • 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 in getAuthenticatedUser — SHA-256 hash lookup with non-blocking lastUsedAt update; falls through cleanly if prefix matches but no record is found.
  • Admin System Key Auth Integration — The existing esk_ system key can now authenticate any admin or superadmin route.
    • requireAdmin(), requireSuperAdmin(), requireRole(), and requirePermission() all accept an optional req parameter; when provided, they fall back to system key auth if no session is present.
    • Synthetic SYSTEM_KEY_USER constant (SUPERADMIN role) returned by new getSystemKeyUser(req?) helper, which reuses the existing isSystemKeyAuth() check.
    • All existing callers (no req argument) 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-image gradient) soften the left/right boundaries.
    • animate-marquee keyframe added to tailwind.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 VultrObjectStorage Prisma model tracking vultrId, region, clusterId, s3Hostname, credentials, tier, status, cfHostname, and cfDnsRecordId.
    • 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 stale status and 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 includes imported, updated, and skipped counts.
    • 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).
  • 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() and deleteDnsRecord() added to packages/lib/cloudflare/client.ts; record ID stored as cfDnsRecordId on 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.completed webhook reads metadata.type === 'storage-bucket' and metadata.location to select the correct Vultr pool and call createObjectStorageBucket().
    • On subscription cancellation, the webhook calls deleteObjectStorageBucket(), clears the StorageBucket record, 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-provisioned and user.bucket-deprovisioned events wired into email and Discord notification preference maps.
  • 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 VultrObjectStorage for active instances, groups by tier keyword, and passes per-tier availableRegions to S3Section.
    • StorageTier type exported from S3Section.tsx carries slug, name, priceId, priceCents, and availableRegions.
    • EXCLUDED_ADDON_KEYS in AddOnsSection.tsx expanded to cover all storage-bucket-* slugs so tier products never appear in the generic add-ons list.
  • Vultr API Key in Integrations Configvultr.apiKey added to the config schema (packages/lib/config/index.ts) and DEFAULT_CONFIG; the Vultr client reads from this field with a fallback to the VULTR_API_KEY environment 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.
    • KeyRound icon; 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.tsx extracted as a standalone component and restored to ProfileTools.
  • S3 Pricing Section RedesignedS3Section.tsx completely 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, and metadata.tier to the Stripe session so the webhook can auto-provision the correct bucket.
  • 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 the File table, 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.size and User.storageUsed are stored in MB; the analytics API was naming those values with bytes-suffixed fields, causing consumers to double-convert.
    • GET /api/analytics/storage — response fields renamed: totalBytestotalMB, daily[].bytesdaily[].mb, breakdown[].bytesbreakdown[].mb.
    • StorageMetrics.tsx — removed manual / 1024 / 1024 division; now reads data.totalMB and calls formatFileSize(mb) directly.
    • AnalyticsOverview.tsxstorageTotalBytesstorageTotalMB throughout; storageDay?.bytesstorageDay?.mb; item.bytesitem.mb in breakdown render.
    • POST /api/files — squad storageUsed was being incremented with raw bytes (uploadedFile.size) instead of MB; fixed to bytesToMB(uploadedFile.size).
  • Storage Quota Matching All Tiered Bucket SlugsgetPlanLimits() was only checking for an active storage-bucket subscription; users who purchased a tiered variant (e.g. storage-bucket-archival) still had quota enforced. Fixed to match any slug that startsWith('storage-bucket').
  • File Privacy: Direct URLs Never Expose Storage Hostnames — The /direct endpoint for raw file access and both GET/POST variants of the download route were returning pre-signed Vultr URLs or redirect responses that exposed *.vultrobjects.com hostnames to clients.
    • All paths now route through Emberly's own proxy (buildRawUrl()); storage provider URLs are never returned to the browser.
  • 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 pendingactive, or credentials populated after instance activation) were skipped silently.
    • Sync endpoint now splits results into toImport (new) and toUpdate (existing with stale status or blank credentials) and runs both in parallel; toast shows imported, updated, and skipped counts.
  • Admin Vultr Status Not Synced Back to DB — Vultr instances provisioned while still pending were never updated to active in 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...
Read more

[2.4.1] - Files, Forms & Flexibility

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 10 Apr 09:27

Added

  • Static Web Standards Filesrobots.txt and manifest.webmanifest moved to public/ 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 (#09090b background, #F97316 accent), app shortcuts (Upload, Shorten URL, Pricing), and maskable SVG icon support.
    • No route handlers (app/robots.ts, app/manifest.ts removed); Next.js serves from public/ 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.ts module provides addCORSHeaders(), addSecurityHeaders(), handleCORSPreflight(), and getCORSHeaders() 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) and Referrer-Policy: no-referrer-when-downgrade (enables safe cross-origin embedding).
    • Content-Disposition: inline (not attachment) 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]/.
  • 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') and smtp (host, port, secure, user, password, from) added to the config schema and DEFAULT_CONFIG in packages/lib/config/index.ts.
    • packages/lib/emails/index.ts — added getSmtpTransport() backed by nodemailer with connection caching; sendEmail() now branches on integrations.emailProvider: SMTP path renders React templates to HTML via @react-email/render and 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 — added testSmtp() using nodemailer.createTransport().verify() and wired 'smtp' into the IntegrationKey union 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 — new GET / POST / PATCH / DELETE endpoints 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).
    • FileSharedEmail template (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/files filters by squadId when a query param is provided, with membership verification.
  • Dashboard Tabbed NavigationDashboardShell (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 a ScrollIndicator.
    • header prop 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 HeroFileGrid (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.
    • ViewMode is 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 Cardsupload-form.tsx and paste-form.tsx content sections wrapped in glass-card panels 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.tsPOST accepts isPrivate: boolean; when true, sets metadata: { private: 'true' } on the Stripe promotion code. GET returns isPrivate derived from the same metadata field.
    • app/api/payments/promo-codes/route.ts — public endpoint now filters out any code whose Stripe metadata.private equals '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 an EyeOff · Private pill badge inline with their code in the table. Create form default state extended with isPrivate: false.

Fixed

  • Admin Broadcast Email Markdown Not Rendering — The compose UI advertised markdown support but the AdminBroadcastEmail template rendered the body as a plain text node, causing **bold**, # headings, - lists, etc. to appear literally in the email.
    • Added a marked renderer 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) }} /> in packages/lib/emails/templates/admin-broadcast.tsx.
  • Admin Product List Redesignpackages/components/admin/products/ProductManager.tsx product list rebuilt to match the v2 glass aesthetic.
    • New TypeBadge component with colour-coded variants per type: plan → blue, addon → violet, nexium-plan → orange, one-time → slate.
    • Product rows use glass-subtle border-divided list (consistent with blog-list and partner-list) replacing the old glass-card wrapper.
    • Action buttons (Stripe, Sync, Edit seed, Delete) are always visible instead of appearing only on hover.
    • Removed scale-75 Switch hack; switches render at full size with proper aria-label.
    • Inline skeleton loading rows replace the old full-page spinner, maintaining layout stability during load.
    • Stats row shows price as $X.XX / interval with the Stripe product ID in dim 10px mono beneath the feature badge row.
  • Legal Article Page Redesignpackages/components/legal/LegalArticle.tsx rebuilt for a more open, professional reading experience.
    • Removed inner max-w-5xl constraint that was double-constraining content inside DashboardWrapper's max-w-7xl.
    • Three-card layout replacing the old heavy border stack: glass-subtle meta strip (type + date), glass-subtle article body with generous sm:p-10 padding, and glass-subtle footer CTA — all spaced via space-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 to glass-subtle rounded-xl, consistent with the rest of the admin/dashboard UI. Sidebar column narrowed from 280 px to 260 px.
  • Collaborators Route req Reference BugPOST and PATCH handlers in the collaborators route incorrectly referenced req instead of request, causing runtime errors on every invocation. All handlers rewritten with consistent request naming 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/Popover fire on pointerup, which coincides with a finger lift ending a scroll gesture.
    • useScrollSafeOpen hook added to file-filters.tsx — touch-triggered opens are deferred by one requestAnimationFrame; if the browser fires pointercancel (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% to 99.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

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 10 Apr 04:23

Added

  • Profile Route Moved to /me — User profile settings previously lived at /dashboard/profile; now a standalone route at /me with its own layout and sidebar.
    • app/(main)/me/layout.tsx — new layout using DashboardWrapper (dashboard nav, no footer).
    • app/(main)/me/page.tsx — profile page powered by ProfileClient which has its own Account / Content / Engagement / Billing tab sidebar.
    • app/(main)/me/logout-button.tsxLogoutButton moved from dashboard/profile/.
    • /dashboard/profile route removed; /profile redirect page updated to point at /me.
    • OAuth callback routes (GitHub and Discord link/unlink) updated to redirect to /me after 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.
    • DashboardShell gains an optional header?: React.ReactNode prop; when provided it renders full-width above the sidebar+content flex row.
    • AdminShell gains the same header prop 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 as header.
    • 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.
    • DashboardShell removed from dashboard/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.
    • NexiumDashboardClient renders two horizontally-scrollable tab strips on mobile (lg:hidden) when not in squad-detail view.
    • Top strip: Talent Profile / Squads — controls the selectedTab state.
    • 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 state bg-primary/10 text-primary border border-primary/20).
  • Discovery Navigation in Dashboard Sidebar — Discovery sub-links integrated directly into DashboardSidebar as a collapsible expandable section, replacing the old discovery-specific internal sidebar.
    • Parent row with ChevronDown toggle; 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.

Fixed

  • Double Navbar on /me RoutesConditionalBaseNav excluded /dashboard and /admin from 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 /me to the exclusion list in conditional-base-nav.tsx.
  • LogoutButton Import Broken After Profile Movediscovery/page.tsx imported LogoutButton from ../../profile/logout-button which no longer exists after the profile route moved to /me. Fixed to ../../me/logout-button.
  • NexiumSquad.urlId / avatarUrl Invalid Field References — Prisma threw PrismaClientValidationError on the squad invite routes because urlId and avatarUrl are not fields on NexiumSquad (the correct fields are slug and logo).
    • GET /api/discovery/invites — squad select updated: urlIdslug, avatarUrllogo.
    • GET /api/discovery/invites/[token]/accept — same correction.
    • SquadIncomingInvite type in dashboard/discovery/client.tsx updated 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

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 09 Apr 06:40
eccb724

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.
    • NexiumDashboard now supports optional controlled activeSection / onSectionChange props so the outer sidebar can drive section navigation without internal state conflicts.
    • Squad detail view rendered inline via SquadDashboardClient in embedded mode; tab state owned by the parent discovery client.
  • Discovery Page Hero Header — Glass-card header on /dashboard/discovery showing the page title, a short description, quick-action links (View Public Profile, Profile Settings, Applications), and a LogoutButton.
  • 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:org OAuth Scope — GitHub account linking now requests read:org in addition to public_repo,repo, so repositories from organizations the user belongs to are accessible in the picker.
    • Organization repos included via affiliation=owner,collaborator,organization_member on the GitHub repos API call.
    • "Grant org access" re-auth link in the picker footer and error state for users needing to re-authorize.
  • Squad Branding FieldsNexiumSquad schema extended with tagline (≤120 chars), logo (URL), banner (URL), website, twitter, github, and discord social/web presence fields.
  • Squad File RelationFile model gains a nullable squadId foreign key and NexiumSquad relation (onDelete: SetNull), enabling squad-owned file uploads alongside per-user files.
  • Discovery Dashboard Nav Link — Dashboard navigation updated to surface /dashboard/discovery directly 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.ts and wired into the event handler in packages/lib/events/handlers/email.ts.
  • Email Event Handler Overhaulpackages/lib/events/handlers/email.ts completely rewritten.
    • Each template now has a dedicated dispatch branch instead of falling through to the generic BasicEmail renderer.
    • 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 typed sendTemplateEmail calls.
    • Eliminates the previous "body string split" fallback that caused inconsistent rendering across templates.
  • 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 children so 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/search with 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]/members for 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 with AlertDialog-gated disband action.
    • Validates with Zod, shows inline field errors, and navigates away on successful disband.
  • User Search API EndpointGET /api/users/search?q=query&limit=10.
    • Returns id, name, email, image, urlId for matching users; searches name and email case-insensitively.
    • Used by AddMemberDialog for live member lookups.
  • Squad Members GET EndpointGET /api/discovery/squads/[id]/members now 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_REPO type signals, and branded cards for all other types.
    • New packages/components/profile/signal-card.tsxGitHubRepoCard renders with GitHub's dark #0d1117 surface, 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.
    • SignalCard dispatches to the correct renderer based on type; exported SignalCardData type consumed by all three display locations.
  • Signal Logo / Banner URL — Non-GitHub signals now accept an optional imageUrl field for custom logos or banners displayed in the signal card header.
    • imageUrl added to SignalInputSchema in packages/types/dto/nexium.ts and exposed as a form field in the Nexium dashboard panel.
    • Field is hidden for GITHUB_REPO signals since the owner avatar is sourced automatically from GitHub API metadata.
  • Automatic GitHub Repo Metadata Fetch — When a GITHUB_REPO signal is created or updated with a github.com URL, the API now automatically fetches live repository metadata and stores it in the signal's metadata field.
    • POST /api/discovery/signals — calls parseGitHubUrl() then getRepo(owner, repo) before writing to the database; stores full_name, description, stargazers_count, forks_count, language, topics, and owner.avatar_url.
    • PUT /api/discovery/signals/[id] — re-fetches metadata when the URL field changes on an existing GITHUB_REPO signal, 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 via apiResponse(squad); the isOwner flag is redundant since page.tsx passes role as a prop from the server.
  • Kick Member Failures — Kick action was broken in two independent ways.
    • Wrong HTTP method: client called DELETE /members which invokes leaveSquad() on the caller (owners can't leave, so always errored). Fixed to POST /members with { userId, kick: true }.
    • Wrong ID field: client passed m.user.urlId (human slug) instead of the UUID. Fixed by adding id: true to SQUAD_INCLUDE's user selects in packages/lib/nexium/squads.ts.
  • ShareX Squad Uploads Returning 401POST /api/files only checked session and personal upload tokens; squad-issued upload tokens and nsk_ 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 storageQuotaMB separately from user quota and increment nexiumSquad.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 CasesPOST /api/discovery/squads/[id]/members was previously only handling the join-self flow.
    • Rewritten to dispatch on...
Read more

[2.2.0] - Infrastructure & Access Control

Choose a tag to compare

@CodeMeAPixel CodeMeAPixel released this 04 Apr 11:00
bf0baed

Added

  • Stripe API Clover Compatibility — Updated promo code creation to use new Stripe v2025-11-17.clover API structure.
    • promotionCodes.create now uses promotion: { type: 'coupon', coupon: string } wrapper instead of direct coupon: string parameter.
    • Applied to three endpoints: POST /api/admin/promo-codes, GET /api/admin/promo-codes, and POST /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.create fails, the orphaned coupon is automatically deleted to prevent dangling resources.
    • Reduces Stripe admin cleanup burden and improves data consistency.
  • Kener Status Page Integration — Real time service status monitoring from Kener instance.
    • Maps Kener v4 monitor state field format to aggregated health statuses: ACTIVEUP, INACTIVEDOWN.
    • GET /api/status returns 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).
  • Graceful Status Fallback — Status endpoint returns UNKNOWN status 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 UserBucket model 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.
  • User Grants & Permissions System — Role-based access control and permission management.
    • New UserGrant model 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.
  • 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, and sentry.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 groupBy aggregation.
    • 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, ClipboardCheck icon, and back navigation.
    • Applicant card uses next/image with 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.
  • Pricing Discounts Section Redesign — Flat card design removing layered nesting.
    • Changed from outer wrapper with inner nested glass-subtle cards to flat glass-card per discount.
    • Matches the design pattern of AddOnSelector component.

Changed

  • Documentation Files Comprehensive Update — Production-ready documentation for open source project.
    • README.md expanded 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.md normalized for consistency:
      • Removed hyphens in compound words (real-timerealtime, longer-formlonger form)
      • Removed emoji formatting from template text
      • Maintained comprehensive developer setup, PR process, and coding standards documentation
  • Kener Status API Response Mapping — Updated aggregation logic to handle Kener v4 format correctly.
    • Recognizes ACTIVE as enabled monitor state (treated as UP health status).
    • Differentiates between workflow state and actual health status properly.
    • Fallback to UNKNOWN status prevents status page crashes during Kener outages.
  • GitHub Module Export Structure — Fixed module export for better client bundling.
    • Removed unused github object export from packages/lib/github/index.ts.
    • Clients now import only required functions (getGitHubUser, getOrgRepos, etc.) instead of entire module.
  • Grant Constants Client Access — Separated client-safe constants from server code.
    • public-profile.tsx now imports GRANT_META and ALL_GRANTS directly from packages/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:181 by adding parentheses.
    • Changed: org ?? integrations.github?.org || process.env.GITHUB_ORG ?? 'EmberlyOSS'
    • To: org ?? (integrations.github?.org || process.env.GITHUB_ORG) ?? 'EmberlyOSS'
  • 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'] to promotionCodes.list calls for complete coupon details in responses.
    • Clients can now access coupon discount amounts directly from promo code API responses.
  • Prisma Bundle in Client Components — Removed Node.js database library from browser bundles.
    • public-profile.tsx client 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.

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/monitors with 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.