Skip to content

Picasso Frontend — Monorepo Documentation

Codebase path: picasso-fe-dev/picasso-fe-dev/ Monorepo tool: TurboRepo with Yarn workspaces Node requirement: >= 20.0.0 Package manager: Yarn 1.22.22

This document covers the full frontend monorepo for the Blinkin platform. The monorepo contains three applications (Houston, Picasso Editor, Widget) and four shared packages (common, eslint-config-custom, tailwind-config, tsconfig).


1. Monorepo Structure

The TurboRepo monorepo organizes code into apps/ (deployable applications) and packages/ (shared internal libraries). TurboRepo handles build orchestration, caching, and dependency ordering across all workspaces.

graph TB
    subgraph root["Root — blinkin-monorepo"]
        turbo["turbo.json<br/><i>pipeline: build, lint, test, coverage</i>"]
    end

    subgraph apps["apps/"]
        houston["houston<br/><i>Public-facing app</i><br/>React 18 + Vite + Express SSR"]
        picasso["picasso-editor<br/><i>Creator dashboard</i><br/>React 18 + Vite + ReactFlow"]
        widget["widget<br/><i>Embeddable script</i><br/>Vanilla TS + SCSS"]
    end

    subgraph packages["packages/"]
        common["common<br/><i>Shared components, hooks, utils</i>"]
        eslint["eslint-config-custom<br/><i>Airbnb + Prettier rules</i>"]
        tailwind["tailwind-config<br/><i>Shared Tailwind preset</i>"]
        tsconfig["tsconfig<br/><i>base, react-app, react-library</i>"]
    end

    root --> apps
    root --> packages

    houston -- "depends on" --> common
    houston -- "depends on" --> eslint
    picasso -- "depends on" --> common
    picasso -- "depends on" --> eslint
    picasso -- "depends on" --> tailwind
    picasso -- "depends on" --> tsconfig

    style root fill:#1a1a2e,stroke:#e94560,color:#fff
    style apps fill:#16213e,stroke:#0f3460,color:#fff
    style packages fill:#16213e,stroke:#0f3460,color:#fff
    style houston fill:#0f3460,stroke:#e94560,color:#fff
    style picasso fill:#0f3460,stroke:#e94560,color:#fff
    style widget fill:#0f3460,stroke:#e94560,color:#fff
    style common fill:#533483,stroke:#e94560,color:#fff
    style eslint fill:#533483,stroke:#e94560,color:#fff
    style tailwind fill:#533483,stroke:#e94560,color:#fff
    style tsconfig fill:#533483,stroke:#e94560,color:#fff

Root-level Turbo pipeline (turbo.json)

Pipeline Dependencies Outputs
build ^build (builds dependencies first) dist/**, build/**
lint none
test ^build
coverage ^test coverage/**

2. Houston App

Path: apps/houston/ URL: houston-app.blinkin.io Purpose: Public-facing application where end-users interact with published flows (called "blinks"). This is what people see when they open a blink link — it renders the interactive flow experience, handles media playback, user responses, and more.

Tech Stack

Technology Version Purpose
React 18.2 UI framework
Vite 6.x Build tool and dev server
Express 4.18 SSR server for meta tags / SEO
TypeScript 4.9 Type safety
Zustand 4.3 Client-side state management
React Query (TanStack) 5.25 Server state / data fetching
apisauce (via hooks) HTTP client wrapper around Axios
i18next 23.x Internationalization
Auth0 (via @auth0/auth0-react) Authentication
PostHog 1.55 Product analytics
Workbox / vite-plugin-pwa 0.20 PWA support (currently commented out)

Route Map

All routes are defined in apps/houston/src/pages/router.tsx using React Router v6. The route enum lives in apps/houston/src/shared/config/navigation.ts.

Route Pattern Enum Key Component Description
/ HOME Home Landing / explore page
/home NEW_HOME / MY_BLINKS MyBlinks (index) User's saved blinks dashboard
/home/viewed VIEWED_BLINKS ViewedBlinks History of viewed blinks
/home/collection/:id BLINKS_COLLECTION CollectionDetails Blink collection detail view
/account/settings ACCOUNT_SETTINGS Settings User account settings
/feedback FEEDBACK FeedbackPage Feedback form
/imprint IMPRINT ImprintPage Legal imprint page
/terms TERMS TermsPage Terms of service page
/:uuid/version/:version ROUTINE_VERSION_VIEW RoutineVersionPage View specific flow version
/u/:user/:slug ROUTINE_VIEW RoutinePage Personal-URL flow view
/b/:slug ROUTINE_SYSTEM_VIEW RoutinePage Default system-URL flow view
/:slug ROUTINE_ORG_ROUTINE_VIEW RoutinePage Organisation-URL flow view

Key Features

  • Audio recording & visualization: react-audio-voice-recorder, react-audio-visualize
  • Image cropping: react-cropper, react-mobile-cropper, react-image-crop
  • QR code generation: qrcode.react
  • Internationalization (i18n): i18next + react-i18next with browser language detection and HTTP backend for translation loading
  • PWA support: Configured via vite-plugin-pwa and Workbox (currently disabled in vite config)
  • Fingerprinting: @fingerprintjs/fingerprintjs for device identification
  • Drag and drop: react-dnd with HTML5 backend
  • Swipe gestures: react-swipeable, swiper
  • Phone input: react-phone-number-input
  • QR/barcode scanning: @zxing/library

State Management: Zustand + React Query Pattern

Houston uses a dual-layer state management pattern:

  1. Zustand stores (*.model.ts files) — manage client-side UI state. These are lightweight stores created with zustand/create. Each store exports the store hook plus individual selector hooks for granular re-rendering.

  2. React Query (@tanstack/react-query) — manages server state (API data fetching, caching, background refetching). Query hooks like useExploreFlowsQuery define queryKey + queryFn pairs.

Key Zustand stores (in apps/houston/src/):

Store File Purpose
entities/flow/models/flows.model.ts Flow listing and search queries
entities/flow/models/collections.model.ts Blink collections
entities/flow/models/favourites.model.ts Favourite blinks
entities/flow/models/viewed.model.ts Viewed blinks history
entities/flow/models/comments.model.ts Flow comments
entities/profile/models/user.model.ts User profile data
entities/organisation/models/organisation.model.ts Organisation data
entities/media/models/media.model.ts Media handling
entities/app/models/app.model.ts Global app state
widgets/houston-routine/model/flow-session.model.ts Active flow session state
widgets/houston-routine/model/page-state.model.ts Current page/step state
widgets/houston-routine/model/page-renderer.model.ts Page rendering logic
widgets/houston-routine/model/statistics.model.ts Session statistics

API Client

Houston connects to the Studio API backend. The client is configured in two places:

Client-side (apps/houston/src/services/api/api.client.ts):

// Uses apisauce (Axios wrapper) pointed at the studio-api /houston namespace
const apiInstance = apisauce.create({
    baseURL: `${import.meta.env.VITE_API_URL}/houston`,
    timeout: 5 * 60 * 1000, // 5 min for large uploads (up to 250MB)
});

Server-side SSR (apps/houston/server/api-client.ts):

// Plain Axios for server-side meta tag rendering
const apiInstance = () => axios.create({
    baseURL: `${process.env.API_URL}/houston`,
    timeout: 5 * 60 * 1000,
});

Authenticated requests use the useApiClientWithAuth hook (apps/houston/src/shared/hooks/use-api-client-with-auth.ts), which injects an Auth0 Bearer token into each request.

Express SSR Server

Houston includes an Express server (apps/houston/server/main.ts) for server-side rendering of meta tags (Open Graph / SEO). The server: - Serves static files from the Vite dist/ build - Intercepts flow URLs (/u/:user/:slug, /b/:slug, /:slug) to inject meta tags (title, description, image) before returning HTML - Handles health checks at /healthz - Compresses responses with compression

Auth: Auth0 Setup

Houston uses Auth0 with organisation-aware authentication. The auth provider (apps/houston/src/app/providers/with-auth-provider.tsx) resolves the current organisation ID, then wraps the app in Auth0Provider with organization-scoped authorization params.

Providers composed (in apps/houston/src/app/providers/index.ts): withDndProvider > WithQueryClientProvider > withAuthProvider > withPostHogProvider > withThemeProvider > withModalsProvider > withFlowPasswordProvider

Key File Paths

Path (relative to apps/houston/) Purpose
src/app/index.tsx App root component
src/app/providers/ Provider composition (auth, query, theme, etc.)
src/pages/router.tsx Route definitions
src/shared/config/navigation.ts Route enum constants
src/services/api/api.client.ts Client-side API client
src/shared/hooks/use-api-client-with-auth.ts Authenticated API hook
src/entities/ Domain entities (flow, media, profile, organisation)
src/widgets/houston-routine/ Core flow/blink rendering engine
src/i18n.ts i18next configuration
server/main.ts Express SSR server
server/api-client.ts Server-side API client
server/replace-meta.ts HTML meta tag injection
vite.config.ts Vite + legacy browser config

3. Picasso Editor App

Path: apps/picasso-editor/ URL: picasso-app.blinkin.io Purpose: The creator dashboard where authenticated users BUILD flows (blinks) using a visual node-based editor. This is the "studio" side of the platform — think of it as the authoring tool.

Tech Stack

Technology Version Purpose
React 18.2 UI framework
Vite 6.x Build tool and dev server
TypeScript 4.9 Type safety
ReactFlow 11.10 Visual node-based flow editor
Lexical 0.16 Rich text editor (for step content)
Yjs 13.5 CRDT for real-time collaborative editing
y-websocket 1.4 WebSocket transport for Yjs
Socket.io 4.8 Real-time collaboration (cursors, presence)
Zustand 4.5 Client-side state management (with immer)
React Query 5.25 Server state / data fetching
apisauce 3.0 HTTP client
Auth0 2.2 Authentication (org-aware)
Framer Motion 12.x Animations
D3 3.x Force-directed layouts
Three.js 0.169 3D rendering
PostHog (via provider) Product analytics
Module Federation 1.4 Remote module loading (Zweistein SDK)

Route Map

All routes are defined in apps/picasso-editor/src/pages/router.tsx. The route enum lives in apps/picasso-editor/src/shared/config/navigation.ts.

Route Pattern Enum Key Component Auth Description
/ HOME HomePage Yes Dashboard — list of flows
/accept-invite ACCEPT_INVITE AcceptInvite No Accept org invitation
/auth-callback AuthCallback No Auth0 callback handler
/integrations INTEGRATIONS IntegrationsPage Yes Third-party integrations
/zweistein ZWEISTEIN Zweistein Yes AI assistant page
/admin-insights ADMIN_INSIGHTS AdminInsights Yes Admin analytics dashboard
/houston-insights HOUSTON_INSIGHTS HoustonInsights Yes Houston usage analytics
/blinks/:folderId ORGANIZATION_FOLDER_BLINKS OrgWrapper Yes Folder view for blinks
/profile/settings PROFILE_SETTINGS ProfileSettings (layout) Yes Profile settings layout
/profile/settings (index) AccountSettings Yes Account settings
/profile/settings/organisation ORG_SETTINGS OrganisationSettings Yes Organisation settings
/profile/settings/organisation-invites ORG_INVITES OrganisationInvites Yes Manage org invites
/profile/settings/user-invites USER_INVITES UserInvites Yes User invite management
/flow/:id FLOW FlowPageLayout (layout) Yes Flow detail layout
/flow/:id/editor FLOW_EDITOR FlowEditorPage Yes Visual flow editor
/flow/:id/settings FLOW_SETTINGS FlowSettings (layout) Yes Flow settings layout
/flow/:id/settings (index) FlowGeneralSettings Yes General flow settings
/flow/:id/settings/integrations FLOW_INTEGRATIONS_SETTINGS FlowIntegrations Yes Flow-level integrations
/flow/:id/settings/webhooks FLOW_WEBHOOKS_SETTINGS FlowWebhooks Yes Webhook configuration
/flow/:id/settings/webhooks/:webhook FLOW_WEBHOOK_DETAILS FlowWebhookDetails Yes Individual webhook detail
/flow/:id/settings/notifications FLOW_NOTIFICATIONS_SETTINGS FlowNotifications Yes Notification settings
/flow/:id/results FLOW_RESULTS FlowResults Yes Flow submission results
/flow/:id/share FLOW_SHARE FlowShare (layout) Yes Share settings layout
/flow/:id/share (index) FlowShareBlink Yes Share as blink link
/flow/:id/share/embedding FLOW_SHARE_EMBEDDING FlowShareEmbeddings Yes Embed code generator

Editor Architecture

The flow editor is the core feature of Picasso Editor. It combines several technologies for visual editing, rich text, and real-time collaboration:

graph TB
    subgraph editor["Flow Editor Page"]
        rf["ReactFlow<br/><i>Visual node graph</i><br/>Drag nodes, connect edges"]
        lex["Lexical Editor<br/><i>Rich text editing</i><br/>Per-node content"]
        toolbar["Editor Top Bar<br/><i>Undo/redo, zoom, mode</i>"]
        sidebar["Creation Bar<br/><i>Add nodes, components</i>"]
        contextmenu["Context Menu<br/><i>Right-click actions</i>"]
    end

    subgraph collab["Real-Time Collaboration"]
        socket["Socket.io Client<br/><i>Singleton SocketManager</i><br/>Presence, cursors, selections"]
        yjs["Yjs (CRDT)<br/><i>Conflict-free sync</i><br/>Document state merging"]
        yws["y-websocket<br/><i>WebSocket transport</i><br/>Syncs Yjs doc to server"]
    end

    subgraph state["State Management"]
        zs["Zustand Stores<br/><i>editor, flow, nodes,</i><br/><i>edges, components, media</i>"]
        rq["React Query<br/><i>API data fetching</i><br/><i>webhooks, results, etc.</i>"]
    end

    subgraph api["API Layer"]
        studio["Studio API<br/><b>/picasso/*</b>"]
        zweis["Zweistein API<br/><b>/api/*</b>"]
    end

    rf --> zs
    lex --> zs
    toolbar --> zs
    sidebar --> rf
    contextmenu --> rf

    socket --> yjs
    socket --> yws

    zs --> studio
    rq --> studio
    rq --> zweis

    socket -.->|"cursor positions,<br/>selections, presence"| rf
    yjs -.->|"document sync"| lex

    style editor fill:#1a1a2e,stroke:#e94560,color:#fff
    style collab fill:#16213e,stroke:#0f3460,color:#fff
    style state fill:#16213e,stroke:#0f3460,color:#fff
    style api fill:#533483,stroke:#e94560,color:#fff

Key Features

  • Flow Builder (ReactFlow): Visual drag-and-drop editor for creating multi-step flows. Nodes represent steps/pages; edges represent transitions between them. Supports @reactflow/node-resizer for resizable nodes.
  • Rich Text Editor (Lexical): Each flow node/step contains rich text content edited with Lexical. Located at plugins/text-editor/. Includes custom nodes, plugins, toolbar actions, and HTML serialization.
  • Real-Time Collaboration: Multiple users can edit a flow simultaneously. Socket.io handles presence (who is online, cursor positions, node selections). Yjs (CRDT) handles conflict-free document state merging.
  • Form Management: Build forms within flow steps, view submission results (/flow/:id/results).
  • Integrations: Connect flows to external services (Google, Typeform, etc.). Managed at both global level (/integrations) and per-flow (/flow/:id/settings/integrations).
  • Webhooks: Configure webhook endpoints that fire on flow events (/flow/:id/settings/webhooks).
  • Analytics & Insights: Admin-level analytics (/admin-insights), Houston usage analytics (/houston-insights), and per-flow result data.
  • AI Features (Zweistein): AI assistant integrated via Module Federation. The Zweistein SDK is loaded as a remote module (/ai/assets/sdk.js). Features include: AI-powered flow creation, image generation for blinks, image analysis, search, and AI spaces/knowledge bases.
  • Media Management: Upload, crop, and manage images/videos. Supports HEIC conversion (heic2any), PDF rendering (react-pdf, pdfmake), and HTML-to-image conversion.
  • Sharing: Generate shareable blink links and embed codes for websites.

State Management Pattern

Picasso Editor uses Zustand with immer middleware for immutable state updates:

// Pattern example from flow.store.ts
export const useFlowStore = create(immer<FlowState>(
    (set) => ({
        flows: [],
        setFlows: (flows) => set((state) => {
            state.flows = flows ?? [];  // immer allows "mutation" syntax
        }),
        // ... more state + actions
    })
));

// Selector hooks for granular subscriptions
export const useSelectedFlow = () => {
    const selectedFlow = useFlowStore(state => state.selectedFlow);
    const setSelectedFlow = useFlowStore(state => state.setSelectedFlow);
    return { selectedFlow, setSelectedFlow };
}

Key Zustand stores (in apps/picasso-editor/src/):

Store File Purpose
entities/flow/models/flow.store.ts Flow list and selected flow
entities/flow/models/layouts.store.ts Saved layout templates
entities/editor/models/editor.store.ts Editor UI state (mode, selection, processing)
entities/editor/models/undo-redo.store.ts Undo/redo history
entities/editor/models/validation.store.ts Flow validation state
entities/component/models/components.store.ts Component library
entities/component/models/fonts.store.ts Font management
entities/component/models/loader.store.ts Component loading states
entities/edge/models/edges.store.ts ReactFlow edge data
entities/cursor/models/cursor.store.ts Cursor state for collaboration
entities/context-menu/models/context-menu.store.ts Right-click context menu
entities/media/models/icons.store.ts Icon library
entities/integration/models/integrations.store.ts Integration connections
entities/form/models/forms-submissions.store.ts Form submission data
entities/livetranscript/transcript.store.ts Live transcription state
widgets/react-flow-instance/models/collaboration.store.ts Collaboration participants and selections

API Clients

Picasso Editor connects to two backend services:

Studio API (apps/picasso-editor/src/services/api/api.client.ts):

const apiInstance = apisauce.create({
    baseURL: `${import.meta.env.VITE_API_URL}/picasso`,
});

// Auto-redirect to sign-in on 401
apiInstance.addMonitor((response) => {
    if (response.status === 401) {
        window.location.replace('/sign-in');
    }
});

Zweistein API (AI service, same file):

export const zsApiInstance = apisauce.create({
    baseURL: `${import.meta.env.VITE_ZWEISTEIN_URL || ''}/api/`,
});

Auth: Organization-Aware Auth0

Picasso Editor uses a more complex auth setup than Houston because it must handle multi-tenant organizations. The flow is:

  1. OrganisationAuthProvider fetches the current org info to get the Auth0 org ID.
  2. Auth0Provider is initialized with organization: info.auth0Id in authorizationParams.
  3. Protected routes use withProtectedRoute HOC which checks isAuthenticated and redirects to Auth0 login if needed, passing organizationId from the window.
  4. PostHog is also initialized with the user's org context for analytics segmentation.

Providers composed (in apps/picasso-editor/src/app/providers/index.ts): WithQueryClientProvider > withAuthProvider > withGoogleAuthProvider > withSocketManagerProvider > withThemeProvider > withToastProvider > withReactFlowProvider > withPostHogProvider > withBrowserCheck > withZweisteinSdkProvider

Socket Manager

The SocketManager is a singleton class (apps/picasso-editor/src/services/socket-manager/socket-manager.ts) that wraps Socket.io:

  • Connects to the server with JWT auth token
  • Manages room joining/leaving (each flow is a "room")
  • Handles event listeners for collaboration (cursor moves, selections, presence)
  • Auto-reconnects (3 attempts, 2-second delay)
  • Detects 401 auth errors and disconnects

Module Federation (Zweistein SDK)

The Vite config uses @originjs/vite-plugin-federation to load the Zweistein AI SDK as a remote module:

federation({
    name: 'picasso-editor',
    remotes: {
        'zweistein-sdk': '/ai/assets/sdk.js',
    },
    shared: ['react', 'react-dom', 'react-router-dom',
             '@ebay/nice-modal-react', 'swr', 'zustand',
             '@auth0/auth0-react'],
})

Key File Paths

Path (relative to apps/picasso-editor/) Purpose
src/app/index.tsx App root component
src/app/providers/ Provider composition
src/app/ui/OrganisationAuthProvider.tsx Org-aware Auth0 wrapper
src/pages/router.tsx Route definitions
src/shared/config/navigation.ts Route enum constants
src/shared/config/auth.ts Auth0 parameter builder
src/services/api/api.client.ts Studio API + Zweistein API clients
src/services/auth/with-auth.tsx Protected route HOC
src/services/socket-manager/ Socket.io singleton manager
src/entities/editor/models/ Editor state stores
src/entities/flow/models/ Flow data stores + React Query hooks
src/entities/component/models/ Component/font stores
src/entities/zweistein/ AI assistant entity (api, hooks, models, UI)
src/features/zweistein-search/ AI search feature
src/features/zweistein-frame/ AI frame/embed feature
src/plugins/text-editor/ Lexical rich text editor setup
src/widgets/react-flow-instance/ ReactFlow editor widget + collaboration
src/widgets/editor-shell/ Editor page shell/layout
src/widgets/editor-top-bar/ Top toolbar (undo, redo, mode)
src/widgets/creation-bar/ Side panel for adding nodes
src/widgets/context-menu/ Right-click context menu
src/widgets/custom-cursor/ Collaborative cursor rendering
src/modals/ Modal dialogs (22+ modals)
vite.config.ts Vite + federation + proxy config

4. Widget App

Path: apps/widget/ Purpose: A lightweight, embeddable JavaScript snippet that external websites can use to display Blinkin flows (blinks) without building any custom integration. Think of it like an "embed SDK."

Tech Stack

Technology Purpose
Vanilla TypeScript No framework — pure DOM manipulation
SCSS Styling (compiled inline)
Vite Build tool

Important: The widget does NOT use React. It is intentionally lightweight so it can be loaded on any website without conflicts.

How It Works

  1. Website owner adds a <script> tag loading the widget bundle.
  2. The widget scans the DOM for elements with data-blinkin-slug (or legacy data-houston-slug) attributes.
  3. When a user clicks one of these trigger elements, the widget creates an iframe pointing to the Houston app (/b/:slug).
  4. The iframe is rendered inside one of three container types: popup, floating, or direct iframe.

Data Attributes

Attribute Required Values Description
data-blinkin-slug Yes Flow slug string Identifies which blink to load
data-blinkin-trigger Yes popup, floating Render mode
data-branding No true / 1 Show Blinkin branding
data-share No true / 1 Show share button
data-navigation No true / 1 Show navigation controls
data-closebtn No true / 1 Show close button

Three Render Modes

graph LR
    subgraph trigger["Trigger Element"]
        btn["&lt;div data-blinkin-slug='my-flow'<br/>data-blinkin-trigger='popup'&gt;<br/>Click me&lt;/div&gt;"]
    end

    subgraph modes["Render Modes"]
        popup["Popup Renderer<br/><i>Full-screen overlay</i><br/>with close button"]
        floating["Floating Renderer<br/><i>Small floating panel</i><br/>anchored to corner,<br/>320x512px default"]
        iframe["Direct Iframe<br/><i>Inline embed</i><br/>No wrapper, raw iframe"]
    end

    subgraph result["Result"]
        iframeEl["&lt;iframe&gt;<br/>src = houston-app.blinkin.io/b/:slug<br/>+ query params for options"]
    end

    btn -->|"click event"| popup
    btn -->|"click event"| floating
    btn -->|"data-blinkin-trigger<br/>determines mode"| iframe

    popup --> iframeEl
    floating --> iframeEl
    iframe --> iframeEl

    style trigger fill:#1a1a2e,stroke:#e94560,color:#fff
    style modes fill:#16213e,stroke:#0f3460,color:#fff
    style result fill:#533483,stroke:#e94560,color:#fff

Embed Flow (Detailed)

sequenceDiagram
    participant Website as External Website
    participant Widget as Widget Script
    participant DOM as Browser DOM
    participant Houston as Houston App (iframe)

    Website->>Widget: Load script bundle
    Widget->>DOM: Inject inline styles
    Widget->>DOM: Find all [data-blinkin-slug] elements
    Widget->>DOM: Attach click listeners

    Note over Website: User clicks trigger element

    DOM->>Widget: Click event fired
    Widget->>Widget: Read data attributes (slug, trigger, options)
    Widget->>Widget: Select renderer (PopupRenderer or FloatingRenderer)

    alt Popup Mode
        Widget->>DOM: Create overlay + popup container
        Widget->>DOM: Create iframe with Houston URL
        Widget->>DOM: Append iframe to popup
        DOM->>Houston: Load blink in iframe
    else Floating Mode
        Widget->>DOM: Create floating container (320x512px)
        Widget->>Widget: Calculate height based on options
        Widget->>DOM: Create iframe with Houston URL
        Widget->>DOM: Append iframe to floating panel
        DOM->>Houston: Load blink in iframe
    end

    Note over Houston: User interacts with blink inside iframe

Iframe URL Construction

The iframe source URL is built as:

{BLINKIN_HOST}/b/{slug}?branding=1&navigation=1&embed_type=popup&autoplay=1&share=1

Parameters like branding, navigation, share, embed_type, and autoplay are derived from the trigger element's data attributes.

Key File Paths

Path (relative to apps/widget/) Purpose
src/main.ts Entry point — DOM scanning, event binding
src/renderers/popup.ts Popup overlay renderer class
src/renderers/floating.ts Floating panel renderer class
src/renderers/iframe.ts Iframe builder class
src/types/renderer.ts TypeScript interfaces for renderers
src/index.scss Widget styles (injected inline)
vite.config.ts Build config with legacy browser support

5. Shared Packages

packages/common/

Shared code used by both Houston and Picasso Editor. Contains reusable components, hooks, utilities, and integration setup.

Directory Contents
src/entities/houston/ Shared Houston entity types, components, hooks, UI, config, context, styles, assets, and library code
src/hooks/ Reusable React hooks: use-is-mounted, use-local-storage-state, use-mutation-observer, use-observe-element-size, use-resize-observer, use-screen-detect, use-window-size
src/utils/ Utility functions: crop, dom, file, format, pubsub, text-transform, throttle, time, video-utils
src/integrations/ setup-sentry.ts — shared Sentry error tracking initialization
src/shared/ui/ Shared UI components organized as atoms/, organisms/, layout/, icons/, config/, lib/
src/config/ Shared configuration

Dependencies include: MUI Material, Mux video player, Vimeo player, YouTube player, Typeform embed, Swiper, Sentry, Video.js, webfontloader, tinycolor2.

packages/eslint-config-custom/

Shared ESLint configuration extending Airbnb TypeScript rules with Prettier formatting.

  • Base: eslint-config-airbnb-typescript
  • Plugins: react, react-hooks, jsx-a11y, import
  • Formatting: eslint-config-prettier

packages/tailwind-config/

Shared Tailwind CSS configuration preset. Exports tailwind.config.ts consumed by apps that use Tailwind (primarily Picasso Editor).

packages/tsconfig/

Shared TypeScript configuration files:

File Purpose
base.json Base TypeScript config
react-app.json Config for React applications
react-library.json Config for React libraries

6. API Integration Architecture

Both Houston and Picasso Editor communicate with the Studio API backend, but through different namespaces. Picasso Editor also connects to the Zweistein AI service.

sequenceDiagram
    participant H as Houston App
    participant PE as Picasso Editor
    participant AC as API Client (apisauce)
    participant Auth as Auth0
    participant SA as Studio API
    participant ZS as Zweistein API

    Note over H,SA: Houston API Flow (public + authenticated)
    H->>Auth: getAccessTokenSilently()
    Auth-->>H: JWT Bearer token
    H->>AC: Create apisauce instance<br/>baseURL: VITE_API_URL/houston
    AC->>SA: GET/POST /houston/*<br/>Authorization: Bearer {token}
    SA-->>AC: JSON response
    AC-->>H: Parsed response data

    Note over PE,ZS: Picasso Editor API Flow (always authenticated)
    PE->>Auth: getAccessTokenSilently()
    Auth-->>PE: JWT Bearer token
    PE->>AC: Create apisauce instance<br/>baseURL: VITE_API_URL/picasso
    AC->>SA: GET/POST /picasso/*<br/>Authorization: Bearer {token}
    SA-->>AC: JSON response
    AC-->>PE: Parsed response data

    Note over PE,ZS: Zweistein (AI) API Flow
    PE->>AC: Create zsApiInstance<br/>baseURL: VITE_ZWEISTEIN_URL/api/
    AC->>ZS: GET/POST /api/*<br/>Authorization: Bearer {token}
    ZS-->>AC: JSON / SSE response
    AC-->>PE: AI-generated content

API Client Details

App Base URL Namespace Auth Timeout File
Houston (client) VITE_API_URL /houston Bearer token (optional) 5 min apps/houston/src/services/api/api.client.ts
Houston (server) API_URL /houston None (server-side) 5 min apps/houston/server/api-client.ts
Picasso Editor VITE_API_URL /picasso Bearer token (required) Default apps/picasso-editor/src/services/api/api.client.ts
Zweistein (AI) VITE_ZWEISTEIN_URL /api/ Bearer token Default apps/picasso-editor/src/services/api/api.client.ts

React Query Usage

Both apps use @tanstack/react-query v5 for server state management. The pattern is:

// Define a query hook (example from Houston)
export const useExploreFlowsQuery = () => {
    const apiClientWithAuth = useApiClientWithAuth();
    return useQuery({
        queryKey: ['flows/explore'],     // Cache key
        queryFn: async () => {           // Fetcher function
            const authApi = await apiClientWithAuth();
            const data = await authApi.get('/flows/listed');
            return data.data;
        },
    });
}

7. Authentication Flow

Houston Authentication

sequenceDiagram
    participant User as User Browser
    participant H as Houston App
    participant OrgAPI as Organisation API
    participant Auth0 as Auth0

    User->>H: Navigate to houston-app.blinkin.io
    H->>OrgAPI: Fetch current org info<br/>(from URL/domain)
    OrgAPI-->>H: Organisation data + auth0Id

    H->>H: Initialize Auth0Provider<br/>with organization: auth0Id

    alt User NOT authenticated
        H->>H: Render public content<br/>(flows are viewable without login)
    else User clicks login / auth required
        H->>Auth0: loginWithRedirect()<br/>organization: auth0Id
        Auth0-->>User: Auth0 login page<br/>(org-scoped)
        User->>Auth0: Enter credentials
        Auth0-->>H: Redirect with auth code
        H->>Auth0: Exchange code for tokens
        Auth0-->>H: Access token + ID token
        H->>H: Store tokens in localStorage
        H->>H: useApiClientWithAuth()<br/>injects Bearer token
    end

Picasso Editor Authentication

sequenceDiagram
    participant User as User Browser
    participant PE as Picasso Editor
    participant OrgInfo as Org Info Hook
    participant Auth0 as Auth0
    participant PostHog as PostHog

    User->>PE: Navigate to picasso-app.blinkin.io
    PE->>OrgInfo: useCurrentOrganisationInfo()
    OrgInfo-->>PE: Organisation data + auth0Id

    PE->>PE: OrganisationAuthProvider<br/>wraps app with Auth0Provider<br/>organization: auth0Id

    alt User NOT authenticated
        PE->>PE: withProtectedRoute() detects<br/>isAuthenticated === false
        PE->>PE: Save current URL to<br/>localStorage('redirectTo')
        PE->>Auth0: loginWithRedirect()<br/>organization: auth0Id<br/>prompt: 'select_account'
        Auth0-->>User: Auth0 login page<br/>(org-scoped, account selector)
        User->>Auth0: Select account / enter credentials
        Auth0-->>PE: Redirect to /auth-callback
        PE->>PE: AuthCallback component<br/>processes redirect
        PE->>Auth0: Exchange code for tokens
        Auth0-->>PE: Access token + ID token
        PE->>PE: Store in localStorage
        PE->>PE: Redirect to saved URL
    end

    PE->>PostHog: posthog.identify(user.email)<br/>orgId, isOrg, id, email
    PE->>PE: Render authenticated dashboard

    Note over PE: All /picasso/* API calls<br/>include Bearer token
    Note over PE: 401 response triggers<br/>redirect to /sign-in

Key Auth Differences Between Apps

Feature Houston Picasso Editor
Auth required Optional (public flows viewable) Always required (all routes protected)
Org resolution useResolveOrgId() from URL useCurrentOrganisationInfo() fetches org data
Auth wrapper Auth0Provider in with-auth-provider.tsx OrganisationAuthProvider component
Protected routes Per-route (some public) withProtectedRoute HOC wraps all authed routes
Login prompt Standard select_account (allows multiple accounts)
Redirect handling Basic Saves URL to localStorage, restores after auth
PostHog identify Yes (in withProtectedRoute) Yes (in withProtectedRoute)
401 handling Per-request Global monitor redirects to /sign-in

8. Key Environment Variables

Houston (apps/houston/.env.example)

Variable Purpose
VITE_API_URL Studio API base URL (client-side)
VITE_SENTRY_DSN Sentry error tracking DSN
VITE_PUBLIC_POSTHOG_KEY PostHog project API key
VITE_PUBLIC_POSTHOG_HOST PostHog instance host URL
VITE_MEDIA_BUCKET_URL Cloud storage URL for media assets
VITE_AUTH0_DOMAIN Auth0 tenant domain
VITE_AUTH0_CLIENT_ID Auth0 application client ID
VITE_AUTH0_AUDIENCE Auth0 API audience identifier
VITE_AUTH0_SCOPE Auth0 requested scopes
ESLINT_NO_DEV_ERRORS Suppress ESLint dev errors
TSC_COMPILE_ON_ERROR Allow TypeScript compilation on error
DISABLE_ESLINT_PLUGIN Disable ESLint Vite plugin

Picasso Editor (apps/picasso-editor/.env.example)

Variable Purpose
VITE_API_URL Studio API base URL
VITE_MEDIA_BUCKET_URL Cloud storage URL for media assets
VITE_AUTH_CLIENT_ID Legacy auth client ID
VITE_AUTH_API_URI Legacy auth API URI
VITE_HOUSTON_URL Houston app URL (for preview links)
VITE_SENTRY_DSN Sentry error tracking DSN
VITE_ZWEISTEIN_URL Zweistein AI service base URL
VITE_AUTH0_DOMAIN Auth0 tenant domain
VITE_AUTH0_CLIENT_ID Auth0 application client ID
VITE_AUTH0_AUDIENCE Auth0 API audience identifier
VITE_AUTH0_SCOPE Auth0 requested scopes
VITE_GOOGLE_API_KEY Google API key (Drive integration)
VITE_GOOGLE_CLIENT_ID Google OAuth client ID
VITE_GOOGLE_DEVELOPER_KEY Google developer/picker key
VITE_ROUTER_BASENAME Base path for router (e.g., /studio on deployed envs)
VITE_PUBLIC_POSTHOG_KEY PostHog project API key
VITE_PUBLIC_POSTHOG_HOST PostHog instance host URL
ESLINT_NO_DEV_ERRORS Suppress ESLint dev errors
TSC_COMPILE_ON_ERROR Allow TypeScript compilation on error
DISABLE_ESLINT_PLUGIN Disable ESLint Vite plugin

Houston Server-Side (in server/main.ts)

Variable Purpose
API_URL Studio API base URL (server-side, no VITE_ prefix)
MEDIA_BUCKET_URL Media bucket URL (server-side)
PORT Express server port (default: 3000)

9. Development Commands

Root-Level Commands

Command Description
yarn build Build all apps and packages (TurboRepo)
yarn build:picasso-editor Build only Picasso Editor
yarn build:houston Build only Houston
yarn test Run tests across all workspaces
yarn coverage Run test coverage across all workspaces
yarn lint Lint all workspaces
yarn prepare Install Husky git hooks

Houston (apps/houston/)

Command Description
yarn client:start Start Vite dev server (port 5175)
yarn client:build Build client with Vite
yarn server:build Compile Express server TypeScript
yarn server:start Start Express SSR server
yarn start Run compiled server (node dist/main.js)
yarn build Full build: tsc && vite build && server:build
yarn serve Preview production build
yarn test Run Vitest tests
yarn test:watch Run tests in watch mode
yarn test:ui Run tests with Vitest UI
yarn coverage Run tests with coverage report
yarn lint Lint src/ with ESLint

Picasso Editor (apps/picasso-editor/)

Command Description
yarn start Start Vite dev server (port 5174)
yarn build Build: tsc && vite build --profile
yarn serve Preview production build
yarn test Run Vitest tests
yarn test:watch Run tests in watch mode
yarn test:ui Run tests with Vitest UI
yarn coverage Run tests with coverage report
yarn lint Lint src/ with ESLint
yarn lint:fix Lint and auto-fix
yarn e2e:run Run Playwright end-to-end tests
yarn e2e:run:ui Run Playwright with UI mode

Widget (apps/widget/)

Command Description
yarn start Start Vite dev server
yarn build Build: tsc && vite build
yarn lint Lint src/ with ESLint
yarn lint:fix Lint and auto-fix

Common Package (packages/common/)

Command Description
yarn build Clean and compile TypeScript (rm -rf build && tsc)
yarn watch Watch mode TypeScript compilation
yarn test Run Vitest tests
yarn test:watch Run tests in watch mode
yarn test:ui Run tests with Vitest UI
yarn coverage Run tests with coverage report
yarn lint Lint src/ with ESLint
yarn lint:fix Lint and auto-fix

Dev Server Ports

App Port Notes
Picasso Editor 5174 Proxies /ai to localhost:5173 (Zweistein SDK)
Houston 5175 Proxies /ai to localhost:5173, /houston to localhost:3001
Widget (default) Opens browser automatically
Houston SSR Server 3000 Express server (configurable via PORT env)