Waleed e96b150bc9 refactor(frontend-arch): migrate server state to React Query, collapse duplicate workflow-state cache, granular error boundaries (#5168)
* refactor(session): migrate SessionProvider to React Query useSessionQuery

Replace the hand-rolled useState/useEffect/loadSession session loading in
SessionProvider with a useSessionQuery() React Query hook. The SessionContext
shape is unchanged ({ data, isPending, error, refetch }) so no consumer changes.

The 'upgraded' path still forces a fresh DB read via
client.getSession({ query: { disableCookieCache: true } }) (refetch() cannot
pass disableCookieCache) and writes the result via queryClient.setQueryData,
then invalidates ['organizations']/['subscription'] as before.

* refactor(workflows): collapse duplicate workflow-state cache

The registry store fetched the GET /api/workflows/[id] envelope inline via
requestJson while useWorkflowState cached the same endpoint's mapped state
under workflowKeys.state(id) — two requests, two cache shapes, never
reconciled.

Collapse to one request + one cache entry keyed by workflowKeys.state(id):

- Add hooks/queries/utils/fetch-workflow-envelope.ts: a standalone
  fetchWorkflowEnvelope(id, signal) returning the full GetWorkflowResponseData.
  Standalone (not in workflows.ts) to avoid a store -> query-hook import cycle.
- useWorkflowState/useWorkflowStates now query the envelope and derive the
  mapped WorkflowState via select (mapWorkflowState), so consumers see the
  identical mapped shape from the shared entry.
- The store's loadWorkflowState reads via getQueryClient().fetchQuery({
  staleTime: 0 }) instead of raw requestJson — always-fresh (preserving the
  prior always-fetch boot/refresh semantics, incl. the socket
  handle-resource-event refresh path that has no separate state
  invalidation), in-flight deduped, writing into the same cache entry the
  hooks read.

Request-id staleness guard, deployment-cache priming, cross-store projection,
and the active-workflow-changed event are all preserved unchanged.

* fix(workspace): add granular error boundaries to logs, knowledge, and files panels

Scope a crash in one workspace panel to that panel instead of the whole
workspace shell. Each boundary reuses the shared ErrorState component and
mirrors the existing tables/settings error.tsx convention.

* refactor(unsubscribe): migrate page to React Query

Replace the hand-rolled useState+useEffect+requestJson server-state in the
unsubscribe page with React Query hooks. Add useUnsubscribe (validation/load
query, keyed by email+token, auto-runs on mount via enabled) and
useUnsubscribeMutation (unsubscribe action, reconciles cached preferences on
success) in hooks/queries/unsubscribe.ts with a hierarchical key factory.

Export UnsubscribeData/UnsubscribeActionResponse/UnsubscribeType type aliases
from the existing user contract; loading/error/success now derive from the
query and mutation objects with no local server-state mirror.

* test(frontend-arch): cover session race fix, workflow-state cache collapse, unsubscribe, error boundary

Add targeted tests for the four frontend-architecture refactors:
- session-provider: upgrade-path ordering — fresh disableCookieCache read wins
  over a late-resolving stale mount query (proves the cancelQueries guard)
- fetch-workflow-envelope + registry store: single shared state(id) cache entry,
  always-refetch (staleTime 0), request-id staleness guard
- unsubscribe: query enable-gating + mutation cache reconcile
- logs error boundary: renders ErrorState + reset wiring (also first ErrorState coverage)

* fix(session): harden upgrade path + address review feedback

- Reconcile plan surfaces after upgrade even when the fresh disableCookieCache
  read fails: invalidate ['organizations']/['subscription'] regardless of the
  bypass-read outcome (they read server truth, not the cookie cache). The valid
  cookie-cached session is still served, so a transient failure no longer signs
  the user out or leaves the just-upgraded plan looking stale. Org-activate
  fallback stays gated on having a session.
- Use a bare return in the cancelled branch of refreshAfterUpgrade (the caller
  discards the value) for clearer intent; caller coerces with ?? null.
- Make the upgrade tests deterministic: the mount mock honors the abort signal
  like the real fetch-backed client, and assertions read the query cache (the
  state cancelQueries/setQueryData/invalidation actually govern) instead of the
  async-rendered context value.

* refactor(session): break provider<->hook type cycle, fail-fast session query

Address review feedback:
- Move the AppSession type to lib/auth/session-response.ts (the module that
  produces it) so useSessionQuery and SessionProvider both import it from there,
  eliminating the provider <-> query-hook import cycle.
- Add retry: false to useSessionQuery, restoring the prior fail-fast contract
  (the global QueryClient default is retry: 1; an auth failure should surface
  immediately rather than retry a request that won't succeed).
- Return null (not the fetched value) from refreshAfterUpgrade's cancelled
  branch to make the cancellation contract explicit.
2026-06-22 12:44:07 -07:00
2026-06-11 18:13:21 -07:00

Sim Logo

The open-source AI workspace where teams build, deploy, and manage AI agents. Build conversationally, visually, or with code. Connect 1,000+ integrations and every major LLM to automate real work.

Sim.ai Discord Twitter Documentation

Ask DeepWiki Set Up with Cursor

Build everything in Chat

Your AI command center. Describe what you want in plain language. Sim knows your entire workspace and takes action: building agents, running them, querying data, and more.

Sim building and running an agent from chat

Create files and documents

Generate documents, reports, and presentations from a single prompt, grounded in your workspace data.

Sim generating a document from a prompt

Ground agents in your knowledge

Upload documents to a knowledge base and let agents answer questions from your own content.

Creating a knowledge base

Structured data with Tables

A database, built in. Store, query, and wire structured data into agent runs.

Tables view with typed columns

Build visually with Workflows

Prefer a canvas? Design agents block by block in the visual builder, and let Sim generate blocks, wire variables, and fix errors from natural language.

Workflow builder demo

Quickstart

Cloud-hosted: sim.ai

Sim.ai

Self-hosted: NPM Package

npx simstudio

→ http://localhost:3000

Note

Docker must be installed and running on your machine.

Options

Flag Description
-p, --port <port> Port to run Sim on (default 3000)
--no-pull Skip pulling latest Docker images

Self-hosted: Docker Compose

git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d

Open http://localhost:3000

Sim also supports local models via Ollama and vLLM. See the Docker self-hosting docs for setup details.

Self-hosted: Manual Setup

Requirements: Bun, Node.js v20+, PostgreSQL 12+ with pgvector

  1. Clone and install:
git clone https://github.com/simstudioai/sim.git
cd sim
bun install
bun run prepare  # Set up pre-commit hooks
  1. Set up PostgreSQL with pgvector:
docker run --name simstudio-db -e POSTGRES_PASSWORD=your_password -e POSTGRES_DB=simstudio -p 5432:5432 -d pgvector/pgvector:pg17

Or install manually via the pgvector guide.

  1. Configure environment:
cp apps/sim/.env.example apps/sim/.env
# Create your secrets
perl -i -pe "s/your_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_internal_api_secret/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_api_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
# DB configs for migration
cp packages/db/.env.example packages/db/.env
# Edit both .env files to set DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
  1. Run migrations:
cd packages/db && bun run db:migrate
  1. Start development servers:
bun run dev:full  # Starts Next.js app and realtime socket server

Or run separately: bun run dev (Next.js) and cd apps/sim && bun run dev:sockets (realtime).

Chat API Keys

Chat is a Sim-managed service. To use Chat on a self-hosted instance:

  • Go to https://sim.ai → Settings → Chat keys and generate a Chat API key
  • Set COPILOT_API_KEY environment variable in your self-hosted apps/sim/.env file to that value

Environment Variables

See the environment variables reference for the full list, or apps/sim/.env.example for defaults.

Tech Stack

Contributing

We welcome contributions! Please see our Contributing Guide for details.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Made with ❤️ by the Sim Team

Languages
TypeScript 77%
MDX 20.8%
JavaScript 1.9%
CSS 0.1%