Skip to content

feat: Live stories with data caching and refresh#476

Draft
Bl3f wants to merge 10 commits intomainfrom
cursor/live-story-data-caching-0ecc
Draft

feat: Live stories with data caching and refresh#476
Bl3f wants to merge 10 commits intomainfrom
cursor/live-story-data-caching-0ecc

Conversation

@Bl3f
Copy link
Contributor

@Bl3f Bl3f commented Mar 19, 2026

Summary

Implements live stories (#312) and a data refresh button (#440) in combination. Users can now mark a story as "live" in the story editor, which enables re-running the original SQL queries to refresh the story's chart and table data — either manually via a refresh button or automatically via a configurable cron-based schedule. Optionally, AI can also regenerate the text analysis to match the new data while preserving the story's structure.

Key design decisions

  • Three data modes: Stories support static (default), cached live (cron-scheduled refresh), and no-cache live (always-fresh per-query fetching).
  • No-cache mode: When set to "No cache (always fresh)", each chart/table independently fetches its data via a dedicated getLiveQueryData endpoint on every view. This is the freshest but most expensive option.
  • Cache-first approach: For cached live stories, data is cached in a story_data_cache table and served from cache until the cron schedule triggers or a manual refresh occurs.
  • Cron-based scheduling: Cache expiration uses cron expressions (via cron-parser), supporting presets (5min, 1h, 24h, weekly, monthly), custom cron input, and natural language to cron conversion via LLM.
  • Per-story settings: isLive, cacheSchedule, and refreshText are stored on story_version rows. New versions inherit live settings from the previous version so settings aren't lost when the agent updates a story.
  • Re-execution mechanism: The backend finds the original SQL queries from message_part tool inputs (matching by query_id), then re-executes them against the same FastAPI SQL endpoint used by the agent.
  • Structure-preserving text regeneration: When refreshText is enabled, the story is parsed into a template with numbered text slots and structural blocks (charts/tables/grids). The LLM receives the template + new query data and regenerates only the text sections, preserving the exact structure, heading levels, and formatting style.
  • Graceful fallback: If query re-execution fails (e.g. warehouse is unavailable), the system falls back to stale cache, then to the original static data from message_part. Text regeneration failures are also handled gracefully (keeps original text).

Changes

Database

  • Added is_live (boolean), cache_schedule (nullable text for cron expression), and refresh_text (boolean) columns to story_version
  • Added new story_data_cache table with composite PK (chat_id, story_id) storing cached query results, regenerated story code, and timestamp
  • Migrations 0026_live_stories, 0027_live_story_text_regeneration, and 0028_cron_schedule for both SQLite and PostgreSQL

Backend

  • services/live-story.ts — Service with refreshStoryData() (re-executes all SQL queries, optionally regenerates text), getStoryQueryData() (cache-aware data fetcher with cron-based expiration), and executeLiveQuery() (single-query execution for no-cache mode)
  • services/story-text-regeneration.ts — Parses story into structural template + text sections, calls LLM with new data to regenerate text while preserving structure
  • services/cron-nlp.ts — Converts natural language schedule descriptions to cron expressions using a small LLM, validates output with cron-parser
  • queries/story.queries.ts — Added updateLiveSettings(), getStoryDataCache(), upsertStoryDataCache(), collectSqlQueries(), findSqlQueryById(), resolveLiveSettings()
  • trpc/story.routes.ts — Added updateLiveSettings, refreshData, getLiveQueryData, and parseCronFromText mutations/queries
  • trpc/shared-story.routes.ts — Added refreshData and getLiveQueryData; updated get to return live status, cached data, and regenerated code

Frontend

  • Live settings dialog — Toggle live mode, configure refresh schedule (no-cache / presets / custom cron / natural language), toggle AI text refresh
  • No-cache modeNoCacheChartEmbed and NoCacheTableEmbed components that independently fetch live data per-query with staleTime: 0, showing loading spinners per chart/table
  • Custom cron input — Manual cron expression field + natural language input with AI conversion (Wand2 button)
  • Story viewer — Activity icon (green when live) to open settings, RefreshCw button for manual refresh, three rendering paths (static / cached-live / no-cache)
  • Full-page preview & shared story page — Live badge, refresh button, no-cache support with per-query fetching, regenerated code display

Closes

Open in Web Open in Cursor 

cursoragent and others added 4 commits March 19, 2026 08:06
- Add isLive and cacheTtlMinutes columns to storyVersion (pg + sqlite)
- Add storyDataCache table for caching live story query results
- Add live-story service for re-executing SQL queries
- Add updateLiveSettings and refreshData mutations to story routes
- Update getLatest and storyShare.get to use cached data for live stories
- Add refreshData mutation to shared story routes

Co-authored-by: Christophe Blefari <christophe.blefari@gmail.com>
… data display

- Add live story settings dialog with toggle and cache TTL configuration
- Add useStoryViewerLiveSettings hook for managing live state
- Add refresh button and live indicator badge in story viewer header
- Support live data rendering in side-panel with backend-fetched query data
- Add refresh button and live badge to full-page preview page
- Add refresh button and live badge to shared story page

Co-authored-by: Christophe Blefari <christophe.blefari@gmail.com>
- Add migration 0026_live_stories for both SQLite and PostgreSQL
- Add is_live and cache_ttl_minutes columns to story_version
- Add story_data_cache table with composite PK (chat_id, story_id)
- Add migration snapshots and journal entries
- Fix unused import and formatting
When a new version is created (by agent or user), inherit isLive and
cacheTtlMinutes from the latest existing version. This prevents losing
live settings when the agent updates a story.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

🚀 Preview Deployment

URL https://pr-476-c3b13cf.preview.getnao.io
Commit c3b13cf

⚠️ No LLM API keys configured - you'll see the API key setup flow when trying to chat.


Preview will be automatically removed when this PR is closed.

cursoragent and others added 6 commits March 19, 2026 09:17
Tooltip components require a TooltipProvider ancestor. Wrap each
Tooltip usage in story-viewer, preview page, and shared story page
with TooltipProvider, matching the existing codebase pattern.
When a live story refreshes its data, optionally use AI to regenerate
the text analysis while preserving the story structure (charts, tables,
grids stay identical, only text sections are updated).

Backend:
- Add story-text-regeneration service that parses story into a template
  with numbered text slots, sends to LLM with new query data, and
  reassembles the story with updated text
- Add refreshText column to storyVersion
- Add regeneratedCode column to storyDataCache
- Wire text regeneration into refreshStoryData flow
- Return regeneratedCode from getLatest and shared story routes

Frontend:
- Add 'Refresh text analysis' toggle (with Sparkles icon) in live
  settings dialog
- Display regeneratedCode instead of original code when available
- Migration 0027 for both SQLite and PostgreSQL
Replace the simple cacheTtlMinutes integer with a cacheSchedule text
field storing cron expressions. This enables flexible refresh schedules:
- 5 minutes, hourly, daily, weekly, monthly presets
- Custom cron expression input
- Natural language to cron conversion via LLM (uses extractor model)

Backend:
- Replace cacheTtlMinutes with cacheSchedule in schema (pg + sqlite)
- Use cron-parser to check cache expiration against cron schedule
- Add parseCronFromText mutation using LLM for NL-to-cron
- Add cron-nlp service with validation via cron-parser
- Migration 0028 with data migration from old TTL values to cron

Frontend:
- Redesign schedule picker with preset options + custom cron
- Add cron expression manual input field
- Add natural language input with Wand2 convert button
- Show error feedback when NL conversion fails
Add a 'No cache (always fresh)' schedule option that re-executes each
SQL query independently every time a chart or table is viewed. This is
the most expensive but freshest option.

Backend:
- Add executeLiveQuery() to live-story service for single-query execution
- Add story.getLiveQueryData and storyShare.getLiveQueryData query endpoints
- Extract findSqlQueryById() and shared findSqlQueriesByIds() helper
- Export NO_CACHE_SCHEDULE sentinel constant

Frontend:
- Add 'No cache (always fresh)' as first schedule option after manual
- Add NoCacheStoryPreview with NoCacheChartEmbed/NoCacheTableEmbed that
  each independently call getLiveQueryData with staleTime: 0
- Each chart/table shows a loading spinner while fetching
- Add no-cache support to full-page preview and shared story pages
- Detect no-cache mode via cacheSchedule === 'no-cache' sentinel
- Merge origin/main (0026_add_log_table, 0027_add_shared_chat)
- Consolidate our three migrations (0026, 0027, 0028) into single 0028_live_stories
- Resolve code conflicts: ShareStoryDialog rename, notifySharedItemRecipients rename, isReadonlyMode addition
- Regenerate package-lock.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In stories, add a button to refresh all data Live stories

2 participants