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 bootstrap in root.tsx reads window.ENV.POSTHOG_KEY instead of the build-time import.meta.env value
Changing POSTHOG_KEY in Coolify now takes effect on the next page load — no image rebuild needed
Same pattern used for POSTHOG_HOST so both the key and the ingest URL can be changed at runtime
Non-production environments (staging, local) can use a different PostHog project by setting a different key in their env file
New
Mobile-first redesign and PWA support
Sreniq is now a Progressive Web App. Add it to your home screen for a native-like experience with safe-area support, haptic feedback, a bottom navigation bar, and an install prompt on both iOS and Android.
Bottom navigation bar on mobile with 4 tabs: Chat, Explore, Library, Profile — collapses into a sidebar drawer on desktop
Notch and Dynamic Island safe-area padding (env(safe-area-inset-*)) for iPhone 14 Pro, 15 Pro, and comparable Android cutout devices
Web App Manifest (manifest.webmanifest) with theme colour, icons at 192 × 192 and 512 × 512, and display: standalone
Install prompt for iOS (Safari Share → Add to Home Screen) and Android (Chrome mini-infobar) — dismissed state persisted in localStorage
Optimistic nav selection via useNavigation hook from Remix — the active tab switches instantly without waiting for the loader
Pull-to-refresh gesture on mobile refreshes the current route data without a full page reload
viewport meta includes viewport-fit=cover so content reaches the true screen edges behind the notch
February 2026
New
Multi-tool AI orchestrator
The underlying AI engine now coordinates multiple specialised tools in a single conversation: SQL queries, live web search, and human-in-the-loop confirmations. The orchestrator decides which tools to call and in what order, then streams results back incrementally.
DuckDB WebAssembly for client-side SQL — uploaded CSV and JSON files are queried in-browser; no data is sent to any server
Ask Tool: the AI can pause mid-flow and request clarification from the user before executing potentially destructive or ambiguous queries
Web Search Tool: real-time web results can be injected as context for questions about current events or external benchmarks
Streaming block parser renders charts and tables as they arrive — you see results before the full response is complete
Each tool call and its result is shown inline in the chat as a collapsible ToolCallBlock
The orchestrator uses Anthropic Claude via Vercel AI SDK with a maxSteps: 10 limit to prevent infinite loops
Tool schemas are defined with Zod and validated before execution — malformed AI tool calls are rejected with a clear error message
New
Generative UI — AI renders components directly
Instead of returning plain text, Sreniq's AI emits structured React components directly in the response stream. Charts, tables, KPI cards, and callout blocks appear as you ask questions — no copy-paste into a charting tool needed.
14+ block types: bar, line, pie, area, and scatter charts; sortable data tables; KPI stat cards; callout and insight blocks
uiRegistry in ~/app/lib/ui-registry/ is the central component registry — each block type has a Zod schema, a React renderer, and an icon
Zod-validated schemas reject malformed AI output at the parser level, so a bad AI generation never crashes the UI
The stream parser in ~/app/lib/orchestrator/ reads [ui:chart]{...}[/ui:chart] tags from the LLM stream and hydrates them in real time
Download any chart as PNG with one click — uses html2canvas on the rendered chart DOM node
Block renders are memoised — re-renders only happen when the block's data changes, not on every keystroke
parseMessageBlocks() is the canonical function for converting raw message strings into typed MessageBlock[] arrays
December 2025
v1.0.0
New
Credit-based AI system
Sreniq uses a credit system to power its AI queries — every new account starts with 300 free credits (approximately 30 queries), and you can top up anytime from the credits page. Credits give you a predictable cost model and prevent runaway API spend.
300 free credits on every new account — approximately 30 AI queries at the default credit cost of 10 per query
Credit cost varies by query complexity: simple questions cost fewer credits, multi-tool orchestration with web search costs more
Top-up packs are available on the /credits page via DodoPayments — one-time purchases, no subscription required
Credit balance is checked before each query is processed — requests that would overdraft are rejected with a clear 'out of credits' message
The credits column lives on the users table; deductions are atomic and use a database-level update to prevent race conditions
Credit history (planned): a credit_transactions table is designed to record every deduction and top-up for auditing
Admins can manually adjust credit balances from the /admin panel for support cases or promotional grants