Picasso Frontend — Monorepo Documentation¶
Codebase path:
picasso-fe-dev/picasso-fe-dev/Monorepo tool: TurboRepo with Yarn workspaces Node requirement:>= 20.0.0Package 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.ioPurpose: 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-i18nextwith browser language detection and HTTP backend for translation loading - PWA support: Configured via
vite-plugin-pwaand Workbox (currently disabled in vite config) - Fingerprinting:
@fingerprintjs/fingerprintjsfor device identification - Drag and drop:
react-dndwith 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:
-
Zustand stores (
*.model.tsfiles) — manage client-side UI state. These are lightweight stores created withzustand/create. Each store exports the store hook plus individual selector hooks for granular re-rendering. -
React Query (
@tanstack/react-query) — manages server state (API data fetching, caching, background refetching). Query hooks likeuseExploreFlowsQuerydefinequeryKey+queryFnpairs.
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.ioPurpose: 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-resizerfor 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.iohandles 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:
OrganisationAuthProviderfetches the current org info to get the Auth0 org ID.Auth0Provideris initialized withorganization: info.auth0IdinauthorizationParams.- Protected routes use
withProtectedRouteHOC which checksisAuthenticatedand redirects to Auth0 login if needed, passingorganizationIdfrom the window. - 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¶
- Website owner adds a
<script>tag loading the widget bundle. - The widget scans the DOM for elements with
data-blinkin-slug(or legacydata-houston-slug) attributes. - When a user clicks one of these trigger elements, the widget creates an iframe pointing to the Houston app (
/b/:slug). - 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["<div data-blinkin-slug='my-flow'<br/>data-blinkin-trigger='popup'><br/>Click me</div>"]
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["<iframe><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:
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) |