This document shows the specific files and code changes to make for each common customization.
Logo — drop your SVG into frontend/public/logo.svg. The <img src="/logo.svg" /> references
in Login.tsx, Dashboard.tsx, and Admin.tsx will pick it up automatically.
Colors — edit the CSS variables at the top of frontend/src/styles.css:
/* Before */
:root {
--color-primary: #6366f1; /* indigo */
--color-primary-hover: #4f46e5;
}
/* After — e.g. ClinicIQ teal */
:root {
--color-primary: #0d9488; /* teal-600 */
--color-primary-hover: #0f766e; /* teal-700 */
}App name — frontend/index.html line 7:
<!-- Before -->
<title>My App — Powered by MIOSA</title>
<!-- After -->
<title>ClinicIQ Workspace</title>- Install
python-jose[cryptography]inpyproject.toml. - Replace
backend/app/routes/auth.pywith an Auth0 token verifier:
# backend/app/routes/auth.py — Auth0 variant
import httpx
from fastapi import HTTPException
AUTH0_DOMAIN = "your-tenant.auth0.com"
AUTH0_AUDIENCE = "https://api.yourapp.com"
async def verify_auth0_token(token: str) -> dict:
jwks_uri = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
async with httpx.AsyncClient() as client:
jwks = (await client.get(jwks_uri)).json()
# Validate token against JWKS — use python-jose
from jose import jwt as jose_jwt
payload = jose_jwt.decode(token, jwks, algorithms=["RS256"], audience=AUTH0_AUDIENCE)
return payload- In
deps.py, replacejwt.decode(...)with yourverify_auth0_token()call and look up the user bypayload["sub"](the Auth0 user ID). - On the frontend, replace
Login.tsxwith Auth0'sLoginButtonanduseAuth0()from@auth0/auth0-react. Store the Auth0 access token in place of the local JWT.
// frontend/src/auth.tsx — Supabase variant
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
export function useAuth() {
// Use supabase.auth.getSession() and supabase.auth.signInWithPassword()
// Pass supabase's JWT to your FastAPI backend as Bearer token
}On the backend, verify with Supabase's JWKS endpoint (same pattern as Auth0 above).
Use Clerk's @clerk/clerk-react on the frontend. Pass the session token to FastAPI. Verify
with Clerk's JWT public key in deps.py.
The Studio page uses CSS Grid: 240px sidebar | 1fr main | 320px terminal.
To make the terminal full-width at the bottom:
// frontend/src/pages/Studio.tsx
// Change the layout div:
// Before
<div className="studio-layout"> {/* grid: sidebar | main | terminal */}
// After — terminal below
<div style={{ display: "flex", flexDirection: "column", flex: 1, overflow: "hidden" }}>
<div style={{ display: "grid", gridTemplateColumns: "240px 1fr", flex: 1, overflow: "hidden" }}>
<aside><MiosaFileTree sandboxId={sandboxId} token={tok.token} /></aside>
<main><MiosaPreview sandboxId={sandboxId} token={tok.token} /></main>
</div>
<div style={{ height: "280px", borderTop: "1px solid var(--color-border)" }}>
<MiosaTerminal sandboxId={sandboxId} token={tok.token} />
</div>
</div>// frontend/src/pages/Studio.tsx — add MiosaDatabase
import { MiosaDatabase, MiosaFileTree, MiosaPreview, MiosaTerminal } from "@miosa/react";
// Inside the layout:
<aside><MiosaDatabase sandboxId={sandboxId} token={tok.token} /></aside>On the backend, expose a /api/sandboxes/{id}/database-token endpoint analogous to
tokens.py but calling miosa.databases.preview_token(sandbox_id).
- Replace
frontend/with a Next.js 15 app:npx create-next-app@latest frontend. - Move
api.tsandauth.tsxtofrontend/src/lib/. - Convert pages to the App Router layout:
pages/Dashboard.tsx→frontend/src/app/page.tsx(Server Component fetches sandbox list)pages/Studio.tsx→frontend/src/app/studio/[id]/page.tsx
Studio.tsxusesMiosaPreview(a client component), so add"use client"at the top.- For the preview token, fetch it in a Server Component and pass as a prop to the client component — this keeps the backend call on the server, not in the browser.
// app/studio/[id]/page.tsx (Server Component)
import { cookies } from "next/headers";
import { StudioClient } from "./StudioClient";
export default async function StudioPage({ params }: { params: { id: string } }) {
const token = cookies().get("token")?.value;
const res = await fetch(`${process.env.BACKEND_URL}/api/sandboxes/${params.id}/preview-token`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
const { token: previewToken } = await res.json();
return <StudioClient sandboxId={params.id} previewToken={previewToken} />;
}The key contract is: any framework that can:
- Verify a JWT (PyJWT works in all three)
- Make async HTTP calls to MIOSA (httpx works in all three)
- Return JSON
from flask import Blueprint, jsonify, abort, g
import miosa, jwt
bp = Blueprint("tokens", __name__)
client = miosa.Miosa(api_key=MIOSA_API_KEY)
@bp.post("/api/sandboxes/<sandbox_id>/preview-token")
def mint_preview_token(sandbox_id):
sandbox = client.sandboxes.get(sandbox_id) # sync client
if sandbox.external_user_id != g.user.id:
abort(403)
token = client.sandboxes.preview_token(sandbox_id, expires_in=3600)
return jsonify({"token": token.value, "expires_at": token.expires_at.isoformat()})Use miosa.Miosa(api_key=...) in a Django view. The external_user_id pattern and ownership
check are identical — only the request/response API differs.
If you want alice.yourapp.com and bob.yourapp.com as isolated tenants:
- In FastAPI, read the
Hostheader to resolve the tenant. - Store a
tenant_id → miosa_api_keymap in your database. - Instantiate a per-request MIOSA client with the resolved key:
# deps.py — per-tenant client
async def get_miosa_client(request: Request, db = Depends(get_db)) -> miosa.Miosa:
host = request.headers.get("host", "")
subdomain = host.split(".")[0]
tenant = await db.execute(select(Tenant).where(Tenant.subdomain == subdomain))
if not tenant:
raise HTTPException(404, "Unknown tenant")
return miosa.Miosa(api_key=tenant.miosa_api_key)