Skip to main content
Skip to content
SreniqChangelog

March 2026

v1.6.0
New

In-app support & feature requests

You can now submit support tickets and feature requests directly inside Sreniq — no more hunting for an email address. Every request is saved to the database and tracked through to resolution.

  • Built-in support dialog replaces all mailto: links — accessible from the help menu and the changelog page
  • Requests are persisted to the support_requests table with type, status, and optional admin notes
  • Admin /admin/support module shows all open tickets with status filtering (open, in_progress, resolved, closed)
  • Admins can update ticket status and add internal notes without leaving the dashboard
  • Feature requests and bug reports share the same flow but are tagged separately for easier triage
  • Unauthenticated users can still submit requests — the userId field is nullable and the email field captures contact info
  • Response time SLA tracking is planned for a future release based on created_at timestamps
v1.6.0
FixImproved

Test accounts always use DodoPayments test mode

Test accounts in production were previously creating real DodoPayments checkout sessions and could trigger live charges. The testMode flag is now threaded all the way through the checkout creation flow so test accounts always land in sandbox mode.

  • Root cause: DODO_TEST_MODE env variable was checked at startup but not forwarded into individual checkout calls
  • The testMode: true flag is now passed to every DodoPayments.payments.create() call when the account is flagged as a test account
  • Test accounts are identified by the is_test_account boolean on the users table
  • No live charges can be created for any user with is_test_account = true, regardless of environment
  • Added a server-side assertion log when test mode is active so it is visible in production logs for auditing
  • Existing live account checkout flow is unchanged — only test-flagged accounts are redirected to sandbox
v1.6.0
Improved

Credits & billing page redesign

The credits and billing page has been tightened up with a cleaner layout and accurate usage indicators. The misleading progress bar that implied a hard cap of 6,000 credits has been removed — credits have no such ceiling.

  • Removed the progress bar that used an arbitrary 6,000-credit cap — this was never a real limit and was confusing users
  • Credit balance is now shown as a simple numeric display with contextual colour (green → amber → red as balance drops)
  • The simulation panel (SimPanel) now horizontal-scrolls on narrow mobile screens instead of wrapping awkwardly
  • Badge wrapping in credit pack cards is fixed — badges no longer overflow outside the card boundary on small screens
  • Layout uses a tighter grid with less wasted vertical space, especially on iPhone SE and compact Android devices
  • Pack card CTA buttons are full-width on mobile for easier tap targets
v1.5.5
Fix

Pull-to-refresh now covers the full app shell

Pull-to-refresh was previously only wrapping the (page content area), so the sticky header stayed pinned and visible during the pull gesture — causing a jarring visual split. The PTR gesture now wraps the entire content column including the header.

  • Root cause: PullToRefresh component was mounted inside the wrapper, leaving the header outside the draggable region
  • The PTR root now wraps the full content column — header, outlet, and footer — so the entire shell pulls and bounces together
  • The header still sticks on scroll; PTR only activates when the page is already scrolled to the top
  • Tested on iOS Safari (momentum scroll) and Chrome Android (overscroll bounce) — both behave correctly
  • No change to the PTR threshold distance or haptic feedback — only the DOM nesting changed
v1.5.0
New

Admin changelog editor with rich text

Admins can now write, edit, and publish changelog entries directly in the Sreniq admin panel using a TipTap rich text editor — no code deploy needed to ship a changelog update. Entries support full HTML output including bold, code blocks, and lists.

  • TipTap rich text editor in /admin/changelog — supports bold, italic, inline code, bulleted lists, and headings
  • Each entry has a title, summary (plain text for OG/meta), content_html (TipTap HTML for display), badges, and optional version string
  • Entries are stored in the changelog_entries Turso/SQLite table and fetched by the public changelog page
  • Publish toggle — entries can be saved as drafts and published when ready; unpublished entries are not visible to users
  • The public /changelog page prefers DB entries and falls back to the static CHANGELOG data file if the table is empty
  • Admin-only route protected by requireAdminPage — non-admin users are redirected to /
  • Rebuild endpoint at /api/admin/rebuild-changelog can re-seed the DB from static data in one POST request
v1.3.0
NewImproved

Rich social sharing for all major platforms

Every story now generates a beautiful preview card when shared on LinkedIn, Twitter/X, WhatsApp, Slack, Discord, or iMessage — showing your data insights right in the feed. OG images are rendered server-side using Satori and cached at the CDN edge.

  • Dynamic OG images with story title, author name, key stats pulled from the embed, and a mini bar chart preview
  • Full meta tag coverage: og:image:secure_url, article:published_time, twitter:image:alt, and twitter:card: summary_large_image
  • Google JSON-LD Article schema for rich search results — eligible for article carousels in Google Search
  • Generic branded OG card (/og-default.png) used for the home page, explore page, and any page without a story slug
  • OG images served from /api/og/:slug — Satori renders the image, resvg-js converts SVG to PNG in-process
  • 5-second timeout with graceful fallback to the default brand image if font loading or rendering takes too long
  • stale-while-revalidate cache headers added so CDN serves the cached image instantly while regenerating in the background
New

'Made with Sreniq' on shared embeds

Every shared story embed now includes a subtle 'Made with Sreniq' footer that links readers back to create their own stories. This turns every publicly shared data story into a low-friction acquisition funnel.

  • Branded footer appears on all /e/:slug direct embed views — readable even in dark iframes
  • 'Try Sreniq free' call-to-action appears at the end of every public VisualStoryViewer story after the last section
  • The CTA is only visible to unauthenticated visitors — logged-in users see no promotional interstitial
  • Footer link points to the marketing homepage with a utm_source=embed query param for attribution tracking
  • Implemented without any layout shift — the CTA fades in with Framer Motion after the last story section animates in
New

Contextual follow-up suggestions after AI responses

After every chart or insight block, Sreniq now shows smart follow-up prompt pills — so you always know what to ask next. Suggestions are context-aware and update after every AI response based on the block type that was returned.

  • Chart blocks suggest: 'Why does this look like this?', 'Compare to previous period', 'Show me the breakdown by segment'
  • Insight blocks suggest: 'What's driving this?', 'Break this down further', 'Show me the raw data'
  • Table blocks suggest: 'Visualize this as a chart', 'Show top 10 only', 'Sort by highest value'
  • Suggestions are generated by the useFollowUpSuggestions hook in chat-interface.tsx — fires after each streaming response completes
  • Clicking a pill fills the input and immediately submits — no extra keypress needed
  • The suggestion list clears while a new response is streaming and reappears once the stream is complete
  • Implemented as SuggestedActionPill components inside SuggestionsList, both in ~/components/ui/suggested-action-pill
New

Story milestone notifications

Get notified when your stories hit meaningful view milestones — first view, 10, 50, 100, 500, and 1,000 views — rather than a notification for every single visitor. Milestones keep notifications meaningful and actionable.

  • Milestone thresholds: 1, 10, 50, 100, 500, 1,000 views — each fires at most once per story
  • Notifications live at /notifications — UI is fully implemented and reads from the notifications table
  • Built on the view_count integer column on embeds — also powers future story analytics dashboards
  • Milestone check runs server-side after each view increment, so no polling or background job is needed
  • Unread notification count appears as a dot in the bottom nav and sidebar nav items
  • Future: email delivery for milestone notifications is planned — the notification record already stores enough context to generate the email
New

Native OS share sheet on mobile

Sharing a story on your phone now triggers the native iOS or Android share sheet — send directly to Slack, WhatsApp, Twitter, or iMessage without copy-pasting a URL. On desktop, the share button copies the URL to clipboard with a toast confirmation.

  • Uses the Web Share API (navigator.share) when available — iOS Safari 15+ and Chrome Android 61+ are supported
  • Graceful fallback: if navigator.share is unavailable (desktop, Firefox), the URL is written to clipboard via navigator.clipboard.writeText
  • Share payload includes story title, text (first 160 chars of summary), and the full url with UTM params
  • Share button appears in the story header (VisualStoryViewer) and as a floating action button on public story pages
  • The button is hidden for draft/private stories — only published public embeds expose the share action
  • Toast notification (✓ Link copied) shown on clipboard fallback with a 2-second auto-dismiss
New

Upgrade prompt for public story publishing

Free users who try to publish a story publicly now see a clear upgrade modal with pricing and key benefits — instead of silently failing or showing a cryptic error. The modal is dismissible and non-blocking.

  • $49 Lifetime Access plan is the primary offer shown — no subscription upsell, just a one-time purchase
  • Three benefit callouts: unlimited public stories, custom embed domains, and priority support
  • Modal is dismissible — users can close it and continue drafting privately without any friction
  • The publish button becomes the upgrade trigger only for free-tier users; Pro users see the normal publish flow
  • DodoPayments checkout opens in the same tab on click — no popup blockers to fight on mobile
  • Implemented as a portal modal (via createPortal) to avoid z-index stacking issues inside the chat interface
Fix

Explore search no longer flashes blank

Fixed a race condition where typing in the Explore search bar would briefly show an empty grid before results appeared. Results now stay visible while the new search loads, and the transition is seamless.

  • Root cause: lastServerQuery.current was being set inside the debounce timeout, which caused the query-change effect to see a stale value and reset allEmbeds prematurely
  • Fix: lastServerQuery.current is now only updated inside the useEffect that watches initialData.query — never inside the debounce
  • The effect detects a genuine server-side query change and resets allEmbeds to fresh server data only when truly needed
  • Client-side search results (filteredEmbeds) now persist visually until the server responds with a new result set
  • No change to search debounce timing (300 ms) or API call behaviour — only the client state management was fixed
Fix

OG image generation now reliable

Fixed two compounding bugs that prevented story preview images from generating: a Satori flex-layout constraint error and a font loading race condition. All story shares now produce a real image instead of falling back to the default brand card.

  • Font race condition: getFontData() now resets fontDataPromise = null on failure so the next request retries rather than returning a cached rejection
  • Font loaded from Google Fonts CDN with explicit text= subsetting to get a TTF file — Satori requires TTF, not WOFF2
  • 5-second timeout added around the font fetch; on timeout, Satori falls back to system fonts and rendering continues
  • CORS headers and stale-while-revalidate=3600 added to the /api/og/:slug response for CDN edge caching
  • Satori layout constraint: all flex containers now have explicit width instead of relying on implicit stretch, which was triggering a Yoga layout assertion
Improved

Analytics now bypass adblockers

Added a /ingest reverse proxy route so PostHog analytics events route through your own domain instead of app.posthog.com. Events that were previously silently dropped by adblockers now reach PostHog reliably.

  • Reverse proxy at /ingest forwards all PostHog API calls through the Sreniq domain — no third-party origin visible to adblockers
  • PostHog client is initialised with api_host: '/ingest' so all capture, decide, and session replay calls go through the proxy
  • Compatible with uBlock Origin, Privacy Badger, and Brave's built-in tracker blocking — all tested
  • Zero performance impact — the proxy adds ~1 ms of latency versus direct requests to PostHog
  • Session replays and feature flags also route through the proxy, so no PostHog features are degraded
Improved

PostHog key is now runtime-configurable

The PostHog project key is now injected at request time via the root loader (window.ENV) instead of being baked into the JS bundle at build time. You can rotate the key or swap environments without triggering a full rebuild and redeploy.

  • window.ENV.POSTHOG_KEY is populated by the root loader on every request — the value comes from process.env at runtime
  • The PostHog