Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4885b4d
Tiny Sunny: slim the integration files so they read easily
amitbardos Oct 4, 2026
b264f0a
Tiny Sunny: simplify the UI components
amitbardos Oct 4, 2026
87af46a
Tiny Sunny: make client.ts a pure 1:1 Base44 wrapper
amitbardos Oct 4, 2026
6ce286c
Tiny Sunny: re-mint service-user tokens instead of refreshing them
amitbardos Oct 4, 2026
037756b
Tiny Sunny: use the documented single-account model
amitbardos Oct 4, 2026
90ae557
Tiny Sunny: shorter origin check and body read in the API handler
amitbardos Oct 4, 2026
ff5236e
Tiny Sunny: render chat parts with assistant-ui's component API
amitbardos Oct 4, 2026
abe1cc8
Merge remote-tracking branch 'origin/main' into chore/tiny-sunny-slim
amitbardos Oct 4, 2026
2a53c97
Tiny Sunny: say why a live-updates session was refused
amitbardos Oct 4, 2026
d64e570
Tiny Sunny: follow the docs' build turn, add Publish
amitbardos Oct 4, 2026
5f34494
Tiny Sunny: show the latest build through the documented embed API
amitbardos Oct 4, 2026
1de2370
Tiny Sunny: sign the builder into the live preview too
amitbardos Oct 4, 2026
65a213c
Tiny Sunny: every Base44 call in client.ts
amitbardos Oct 4, 2026
798c0f9
Tiny Sunny: one name for live updates, end to end
amitbardos Oct 4, 2026
6206909
Tiny Sunny: a smaller Builder
amitbardos Oct 4, 2026
c7fcd16
Tiny Sunny: client.ts written like a guide
amitbardos Oct 4, 2026
c1bb8bf
Tiny Sunny: one file per docs page in lib/base44
amitbardos Oct 4, 2026
6d64488
Tiny Sunny: the API handler as a plain switch
amitbardos Oct 4, 2026
e7f09b2
Tiny Sunny: the builder reads one chat state
amitbardos Oct 4, 2026
f556ab7
Tiny Sunny: server actions instead of a generic API endpoint
amitbardos Oct 4, 2026
80924ef
Tiny Sunny: each server action shows its checks
amitbardos Oct 4, 2026
3b7353d
Tiny Sunny: remove what server actions left behind
amitbardos Oct 4, 2026
91652d0
Tiny Sunny: one form per kind of question
amitbardos Oct 4, 2026
ac3e2f7
Tiny Sunny: server/ and client/ folders
amitbardos Oct 4, 2026
869f478
Tiny Sunny: the browser's build turn in one hook
amitbardos Oct 4, 2026
7910b8a
Tiny Sunny: a readable Workspace
amitbardos Oct 4, 2026
a03cfda
Tiny Sunny: one chat component
amitbardos Oct 4, 2026
656a8fb
Tiny Sunny: one preview component
amitbardos Oct 4, 2026
ada0ad3
Merge origin/main into chore/tiny-sunny-slim
amitbardos Oct 4, 2026
64b8e10
Tiny Sunny: link the API reference, not the platform host
amitbardos Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions examples/white-label-minimal/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
DATABASE_URL=
DIRECT_URL=
# Workspace key; it also needs the apps:watch scope for live builder updates.
# Personal access token of the integration account, created in your enterprise
# workspace with access to all apps and full permission.
BASE44_ACCESS_TOKEN=
BASE44_WORKSPACE_ID=
# Workspace API key for live updates (apps:watch) and for signing builders into
# their latest build (Provision app users, Mint embed sign-in tokens).
BASE44_SVC_KEY=
BASE44_ORG_ID=
# Set to your Base44 HTTPS origin.
BASE44_PLATFORM_HOST=
# Set NEXTAUTH_URL explicitly, including in local development.
NEXTAUTH_URL=http://127.0.0.1:3001
# Exact HTTPS origin in production; leave empty for localhost.
BUILDER_ORIGIN=
# Optional: use Sunny's existing OAuth callback with a shared NEXTAUTH_SECRET.
AUTH_REDIRECT_PROXY_URL=

# Static preview hosting domain; confirm for your Base44 environment.
BASE44_STATIC_PREVIEW_DOMAIN=
15 changes: 15 additions & 0 deletions examples/white-label-minimal/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# Tiny Sunny

This example is read by developers who just read the
[white label docs](https://docs.base44.com/developers/white-label/overview), and its snippets go
into integration guides. Write it the way a careful developer would write a guide.

- **One Base44 call, one function.** Each function shows its request in the open: path, method,
body. A reader should be able to paste it into a guide as it is.
- **Name things after the docs.** Use the step and field names the docs use.
- **Plain control flow.** `if` statements on one clear state, not chained ternaries or clever helpers.
Prefer a few obvious lines over one dense line.
- **Guard only what matters.** Keep the token on the server, check app ownership, filter what goes to
the browser, keep `X-Request-ID` stable. Skip checks for states that cannot happen.
- **Comments say why, briefly.** No comment that repeats the code.

<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know
Expand Down
165 changes: 83 additions & 82 deletions examples/white-label-minimal/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
# Tiny Sunny

A minimal Base44 integration: sign in, connect a workspace, create an app,
chat with the builder, answer its questions, preview, and publish.
A minimal Base44 integration: sign in, create an app, chat with the builder,
answer its questions, preview, and publish.

This example uses the [service-user tenancy model](https://base44-docs-white-label-rewrite.mintlify.site/white-label/tenancy-and-credentials#service-users).
Each builder gets a Base44 service user that owns their apps. Your backend provisions
that identity using a workspace API key, then uses the service user's access token
to create and manage apps. Both credentials stay on the server.
It follows Base44's [tenancy model](https://docs.base44.com/developers/white-label/tenancy-and-credentials):
one Base44 account owns every app, and your server calls Base44 with that account's
personal access token. Base44 cannot tell your builders apart, so Tiny keeps them
apart itself: it records which builder owns each app and checks that on every call.
The token never reaches the browser.

## From the docs to the code

If you have read the [white label docs](https://docs.base44.com/developers/white-label/overview),
each part of them lives in one place here:

| Docs | Code |
| --- | --- |
| [Tenancy and credentials](https://docs.base44.com/developers/white-label/tenancy-and-credentials) | [server/base44/config.ts](server/base44/config.ts), [server/base44/request.ts](server/base44/request.ts) |
| Keeping builders apart | [server/actions.ts](server/actions.ts), [server/ownership.ts](server/ownership.ts) |
| [The build turn](https://docs.base44.com/developers/white-label/the-build-turn), steps 1–6 | [server/base44/build-turn.ts](server/base44/build-turn.ts), one function per call, numbered by step |
| Watch it build (live, instead of polling) | [server/base44/live-updates.ts](server/base44/live-updates.ts), [client/useBuildTurn.ts](client/useBuildTurn.ts) |
| Answer the agent's questions | [components/Question.tsx](client/components/Question.tsx), one UI per `waiting_on.kind` |
| Publish the app | `publish()` in [components/Builder.tsx](client/components/Builder.tsx) |
| [Embed the app](https://docs.base44.com/developers/white-label/embed-the-app) | [server/base44/embed.ts](server/base44/embed.ts), used for every preview |
| [Custom instructions](https://docs.base44.com/developers/white-label/custom-instructions) | [server/base44/custom-instructions.ts](server/base44/custom-instructions.ts) |

Everything else is Tiny's own product: sign-in, the app list, and the chat UI.

## Run locally

Expand All @@ -18,94 +37,90 @@ cp examples/white-label-minimal/.env.example examples/white-label-minimal/.env.l
```

Fill in `.env.local` with Sunny's Google OAuth credentials, Auth.js secret,
migrated Prisma database, and Base44 workspace service key. Keep `NEXTAUTH_URL`
set to `http://127.0.0.1:3001`, then run:
migrated Prisma database, and the integration account's
[personal access token](https://docs.base44.com/Workspaces/Personal-access-tokens) and workspace ID.
Create the token in your enterprise workspace, with access to all apps and full permission.
Live chat updates also need a workspace API key with the `apps:watch` scope (`BASE44_SVC_KEY`).
Keep `NEXTAUTH_URL` set to `http://127.0.0.1:3001`, then run:

```sh
npm run minimal:dev
```

Open [Tiny Sunny](http://127.0.0.1:3001), sign in, and select **Connect workspace**.
Apps are saved in Sunny's ownership table, so existing apps in the same database
and workspace are available here too.
Open [Tiny Sunny](http://127.0.0.1:3001) and sign in.

## Copy the integration

| Folder | Responsibility |
| Folder | What runs there |
| --- | --- |
| `lib/base44/` | Base44 API calls, identity lifecycle, and custom instructions |
| `lib/server/` | Authentication and request handling |
| `lib/storage/` | Database access and app ownership |
| `lib/chat/` | Browser API calls and conversation helpers |
| `lib/types.ts` | Shared types |

Start with [lib/base44/client.ts](lib/base44/client.ts). It contains the Base44
endpoints and request payloads for creation, conversation, tool answers, preview,
and publishing. Copy it with `lib/base44/config.ts`, `lib/base44/error.ts`,
`lib/base44/custom-instructions.ts`, and `lib/types.ts`. It uses `fetch` and `server-only`, with
| `server/` | The server: Base44 calls ([server/base44/](server/base44)), server actions, sign-in, and app ownership |
| `client/` | The browser: the build turn's state, live updates, and the UI components |
| `app/` | Next.js routes only: the page, its layout, and the sign-in callback |
| `types.ts` | Types both sides share |

Start with [server/base44/build-turn.ts](server/base44/build-turn.ts). It has one function per Base44 call,
numbered by the step of the build turn: create, send a prompt, watch, answer, and publish.
[embed.ts](server/base44/embed.ts) and [live-updates.ts](server/base44/live-updates.ts) cover the other two docs
pages, and [request.ts](server/base44/request.ts) is the `fetch` they share. Copy them with
`server/base44/config.ts`, `server/base44/error.ts`, `server/base44/custom-instructions.ts`, and `types.ts`. Every endpoint it calls is described in
Base44's [Apps API reference](https://docs.base44.com/developers/references/apps-api/get-started/overview), which also links the OpenAPI spec if you prefer to generate a typed client. It uses `fetch` and `server-only`, with
no Sunny imports. Set `BASE44_PLATFORM_HOST` to your Base44 HTTPS origin and
adapt `lib/base44/custom-instructions.ts` to your product. New apps use the first 80
adapt `server/base44/custom-instructions.ts` to your product. New apps use the first 80
characters of the prompt as their initial name.

For a complete browser integration, follow this path:

```text
components/Builder.tsx → lib/chat/builder-api.ts → app/api/base44/route.ts
→ lib/server/api-handler.ts → lib/server/app-service.ts → lib/base44/client.ts
client/components/Builder.tsx → server/actions.ts → server/base44/build-turn.ts
```

[lib/server/app-service.ts](lib/server/app-service.ts) is where your application plugs in:
[server/actions.ts](server/actions.ts) holds Tiny's server actions, and is where your application plugs in:

- [server/auth.ts](lib/server/auth.ts) supplies the verified user from your session.
- [base44/identity.ts](lib/base44/identity.ts) provisions a service user, mints its token,
and refreshes it near expiry.
- [storage/app-repository.ts](lib/storage/app-repository.ts) saves ownership and scopes app access to that user.
- [server/auth.ts](server/auth.ts) supplies the verified user from your session.
- [server/ownership.ts](server/ownership.ts) records which builder created each app, and checks it.

[prisma/schema.prisma](prisma/schema.prisma) defines identity links and app ownership.
[lib/storage/db.ts](lib/storage/db.ts) connects through `DATABASE_URL`. The Prisma client is generated
[prisma/schema.prisma](prisma/schema.prisma) defines app ownership.
[server/db.ts](server/db.ts) connects through `DATABASE_URL`. The Prisma client is generated
on install and build; run `npm run db:generate` from the example after schema edits.

Google login and Base44 connection are separate. `/api/base44/connection`
provisions a service principal using the workspace key. Builder requests use its
stored token; they do not provision identities or send credentials to the browser.
The API handler validates requests and checks app ownership before app operations.
Every Base44 call sends `Authorization: Bearer <token>` and `X-Active-Workspace-Id`, and
new apps are created with `organization_id`, so they use your workspace's design system,
skills, and plan. Each server action checks who is signed in and, for an app, that they own it,
before it calls Base44.

For the chat UI, copy `components/`, `lib/chat/`, and `lib/base44/socket-session.ts`.
The chat uses assistant-ui's external-store runtime with Base44's live updates as its
source of truth (see below). `Question.tsx` handles
In the browser, [client/useBuildTurn.ts](client/useBuildTurn.ts) is the whole build turn: it opens
live updates, sends prompts and answers questions. The components only show it. The chat uses
assistant-ui's external-store runtime with Base44's live updates as its source of truth (see below). `Question.tsx` handles
approvals, choices, and secrets; retries preserve the original answer and request ID.
Preview URLs stay in page memory and remain stable during normal use. A timed-out
creation may still succeed, so the UI asks users to check before creating again.
An app can only be resumed here if its ownership was saved successfully.
A timed-out creation may still succeed, and the error says so.

## Live builder updates

The chat updates live over the Base44 platform socket, through the Platform SDK,
[`@base44/platform`](https://github.com/base44/javascript-sdk/tree/main/packages/platform).

```text
components/useBuilderSocket.ts → lib/chat/live-updates.ts → @base44/platform
lib/chat/builder-api.ts → app/api/base44/route.ts → lib/base44/socket-session.ts
client/useBuildTurn.ts → server/actions.ts → server/base44/live-updates.ts
client/useBuildTurn.ts → @base44/platform
```

1. **Server** ([socket-session.ts](lib/base44/socket-session.ts)): after the usual
1. **Server** ([server/base44/live-updates.ts](server/base44/live-updates.ts)): after the usual
sign-in and app-ownership checks, opens a read-only session for one app with the
workspace key. `BASE44_SVC_KEY` needs the `apps:watch` scope. Only the session
token reaches the browser.
2. **Browser** ([live-updates.ts](lib/chat/live-updates.ts)): connects with that
token and subscribes to the app. A snapshot (status and the last 50 messages)
arrives on every connect; then `message.updated`, `message.removed` and
`app.status_changed` keep the chat current.
3. **React** ([useBuilderSocket.ts](components/useBuilderSocket.ts)): holds the
state and shows one error with **Reconnect live updates** if the connection stops.
2. **Browser** ([useBuildTurn.ts](client/useBuildTurn.ts), step 2): creates the platform
client with that token and subscribes to the app. A snapshot (status and the last 50
messages) arrives on every connect; then `message.updated`, `message.removed` and
`app.status_changed` keep the chat current. If the connection stops, the builder sees
one error with **Reconnect live updates**.

Kept out to stay minimal; add them in production:

- The session is not closed on leave; it expires after an hour. Workspace keys have
a limit on open sessions, so close it (`DELETE /api/service/socket-sessions/{id}`)
when the builder unmounts.
- The chat shows the last 50 messages. Load older ones over HTTP
(`getConversation`) if you need full history.
- The chat shows the last 50 messages. Load older ones with
[Read conversation messages](https://docs.base44.com/api-reference/read-conversation-messages) if you need full history.
- Generated images stay as placeholders until the next reconnect (`image.resolved`).

## Deploy to Netlify
Expand All @@ -115,7 +130,7 @@ as the package directory. Its `netlify.toml` builds the example without running
migrations; use an already migrated Sunny database.

Set the variables from `.env.example` for builds and production functions.
Set `NEXTAUTH_URL` and `BUILDER_ORIGIN` to your deployment's exact HTTPS origin.
Set `NEXTAUTH_URL` to your deployment's exact HTTPS origin.
Register `<origin>/api/auth/callback/google` with your Google OAuth client, or
set `AUTH_REDIRECT_PROXY_URL=https://sunny44.com/api/auth` and use Sunny's Google
client and `NEXTAUTH_SECRET` for its existing redirect proxy.
Expand All @@ -132,33 +147,19 @@ npm run minimal:build
API tests cover session checks, ownership, validation, and upstream failures.
Browser tests use a separate fixture app with mocked Base44 responses.

### Static and live previews

Browsing uses `https://preview--{slug}.{BASE44_STATIC_PREVIEW_DOMAIN}`. Set
`BASE44_STATIC_PREVIEW_DOMAIN` to the preview hosting domain confirmed for your
Base44 environment. Without it, browsing uses screenshots or placeholders. This URL is derived by Tiny, not returned by Base44.
Apps without a slug show a screenshot or placeholder; browsing never starts a sandbox.
Editing requests a live preview and keeps static visible until the live iframe loads.
A load event only controls the visual transition; it does not verify app health.
Edited widgets remain live for the page session instead of silently switching to a
potentially stale static build. Recovery checks the source window and live origin of `preview:requestRefresh` messages, with at most three recovery attempts per preview session and no retries on authorization failures. This observed Base44 message is not a confirmed public contract, so manual refresh remains available. Recovery reloads the iframe using its last parent-supplied path and parameters; cross-origin navigation inside the app cannot be preserved without a supported bridge. No private
build-status, runtime-auth, or heartbeat endpoints are used.

### Reusing the preview component

`components/Base44Preview.tsx` owns static/live rendering, loading, recovery, and manual refresh. It depends only on React. `PreviewFrame` adapts Tiny's app model and backend client to its props:

```tsx
<Base44Preview
appId={app.id}
title="App preview"
staticUrl={app.static_preview_url}
screenshotUrl={app.preview_screenshot_url}
live={editing}
loadPreview={getPreviewUrl}
/>
```
### Latest build and live previews

Every preview is the app signed in as the builder, through
[Embed the app](https://docs.base44.com/developers/white-label/embed-the-app)
([server/base44/embed.ts](server/base44/embed.ts)). The builder is not a Base44 user, so
without it a private app would show its own sign-in page inside the frame.

`loadPreview(appId)` calls your authenticated backend and returns `{ url }`. Reject with an error carrying `status: 401` or `403` to stop automatic recovery on authorization failures. Keep platform credentials on the backend. Changing the app or live mode starts a new preview session; changing the callback does not reload the iframe.
- **Latest build** (`target: "latest_preview"`): app cards, and the stage until the live preview
loads. It needs no sandbox. Before the first build there is no URL, and the card shows the
screenshot or a placeholder.
- **Live preview** (`target: "live_preview"`): while editing. It runs the app's sandbox and shows
each change as the agent makes it.

When copying the component, include the `preview-frame`, `preview-loading-frame`, `preview-fallback`, `preview-controls`, `preview-spinner`, `widget-placeholder`, and `secondary` styles from `app/globals.css`, or provide equivalent styles and a sized parent container.
Each URL works once and expires in 60 seconds, so [AppPreview](client/components/AppPreview.tsx)
asks for a new one whenever it loads a frame, and again on **Refresh preview**. While editing, the
latest build stays visible until the live preview has loaded.
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { handlers } from "../../../../lib/server/auth";
import { handlers } from "../../../../server/auth";
export const { GET, POST } = handlers;
41 changes: 0 additions & 41 deletions examples/white-label-minimal/app/api/base44/connection/route.ts

This file was deleted.

6 changes: 0 additions & 6 deletions examples/white-label-minimal/app/api/base44/route.ts

This file was deleted.

Loading