Skip to content

Tiny Sunny: slim the integration files so they read easily - #49

Merged
amitbardos merged 30 commits into
mainfrom
chore/tiny-sunny-slim
Oct 4, 2026
Merged

amitbardos merged 30 commits into
mainfrom
chore/tiny-sunny-slim

Conversation

@amitbardos

@amitbardos amitbardos commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Makes the Tiny Sunny (examples/white-label-minimal) integration files shorter and easier to read for people copying them. Behavior is unchanged except where noted below.

Changes

  • lib/server/api-handler.ts: the action switch is now a table with one line per action. Each line validates and returns the arguments for the AppClient method of the same name. Any request field an action doesn't read is rejected, so there's no separate list of allowed fields to keep in sync.
  • lib/base44/http.ts (new): one fetch helper for client.ts and identity.ts. It handles timeouts, redirect: "error", no-store, and errors that are safe to show. Each caller maps HTTP statuses to its own messages.
  • lib/base44/static-preview.ts (new): derives the static preview URL and checks that it serves a page.
  • lib/base44/client.ts: a hint table, a shared post helper, and explicit toApp / toMessage mapping. Size: 219 → 125 lines.
  • lib/base44/identity.ts: uses the shared fetch helper; short comments on the non-obvious parts (fixed ID, row lock, best-effort revoke). Size: 151 → 119 lines.
  • Smaller changes: config.ts checks for a bare HTTPS origin in one condition, the page size is a named constant, and the README's copy list includes the new files.

UI components (second commit)

  • Workspace.tsx: the active app name is derived from the app on the stage; removed an unused ref; the connect call moves to builder-api.ts.
  • Question.tsx: one buildPayload helper (returns null while nothing is filled in) and a small toggle helper.
  • Builder.tsx: one composerDisabled flag drives both the send guard and the composer.
  • Base44Preview.tsx: removed the onStatusChange prop, which nothing passed.
  • Removed two unused CSS rules and an unreachable branch in ToolActivity.tsx.

client.ts as a pure Base44 wrapper (third commit)

  • App IDs are encoded in every path, so the client is safe on its own rather than relying on the handler's check.
  • Status hints move to http.ts, next to the transport.
  • The static preview is Tiny's own addition, so it moves to lib/server/static-preview.ts and is applied in app-service.ts.
  • The conversation page size is one constant, shared with the browser paging code.
  • client.ts and the README link to Base44's OpenAPI spec.

Single-account model (fourth and fifth commits)
Base44's tenancy docs describe one integration account that owns every app, authenticated with its personal access token. Tiny now follows them:

  • Every call sends Authorization: Bearer <PAT> and X-Active-Workspace-Id, and createApp sends organization_id, so apps get the workspace's design system, skills and plan.
  • Removes service users: identity.ts, the connection route, the Base44Link model, the connect/disconnect UI and the identity tests. The fourth commit (re-mint instead of refresh) is superseded by this one.
  • Builders stay isolated by Tiny's ownership table, which the API handler checks before every app call.
  • New settings: BASE44_ACCESS_TOKEN and BASE44_WORKSPACE_ID, which replace BASE44_ORG_ID. BASE44_SVC_KEY stays, but only for live updates: socket sessions (Live builder updates in Tiny Sunny with @base44/platform #50) accept only a workspace key with the apps:watch scope.
  • Merged main (Live builder updates in Tiny Sunny with @base44/platform #50–Fix Tiny Sunny live updates: send the workspace key as Bearer #52), so live socket updates are kept on top of the new structure.

Behavior changes

  • The static-preview check cache used to be created inside each per-request client, so it never actually cached anything. It now lives at module level and lasts 60s, as its comment intended.
  • When Base44 can't be reached during connect or disconnect, the error now says "may still be running", the same message the client uses.
  • A request with both an extra field and an invalid value now gets the invalid-value message. It's still a 400 with not_started.

Verification

  • npm run minimal:typecheck and npm run minimal:test pass.
  • npx playwright test passes (23/23).

🤖 Generated with Claude Code

- api-handler: one action table replaces the switch; unread fields are rejected
- client and identity share one fetch helper (http.ts)
- static preview derivation and probe move to static-preview.ts; the probe
  cache is now module-level, so it survives across requests
- smaller config check, named page size, README copy list updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for sunny44-builder ready!

Name Link
🔨 Latest commit 64b8e10
🔍 Latest deploy log https://app.netlify.com/projects/sunny44-builder/deploys/6ac2987e702c1a00084febc1
😎 Deploy Preview https://deploy-preview-49--sunny44-builder.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for base44-platform-starter ready!

Name Link
🔨 Latest commit 64b8e10
🔍 Latest deploy log https://app.netlify.com/projects/base44-platform-starter/deploys/6ac2987ee38a330008d94e45
😎 Deploy Preview https://deploy-preview-49--base44-platform-starter.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

amitbardos and others added 27 commits October 4, 2026 16:01
- Workspace: derive the active name from the stage app, drop an unused ref,
  plain async load, connect call moves to builder-api
- Question: one buildPayload helper (null while empty) and a toggle helper
- Builder: one composerDisabled flag for the guard and the composer
- Base44Preview: drop the onStatusChange prop nobody passed
- remove unused CSS rules and an unreachable ToolActivity branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- encode app IDs in every path instead of relying on the handler's check
- status hints move to http.ts, next to the transport
- static preview is Tiny's own derivation, so it moves to app-service
- share the conversation page size with the browser paging helper
- link the Base44 OpenAPI spec from the client and the README

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the stored token is a cache: reuse it until a minute before expiry, then
  mint a new one with the workspace key (same approach as main Sunny's remint)
- drops the OAuth refresh flow and the row lock (identity-repository.ts)
- one workspace key; .env.example and README name the two scopes it needs
- connection status returns only { linked }
- Disconnect button in the topbar; disconnect still revokes the refresh token
  and keeps the service user so reconnecting restores the same apps
- README: mint rate limit and encrypt-at-rest note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base44's tenancy docs describe one integration account that owns every app,
called with its personal access token. Tiny now follows that model:

- every call sends Authorization: Bearer <PAT> and X-Active-Workspace-Id;
  createApp sends organization_id so apps use the workspace's design system
- client.ts is a plain module object; no per-user client
- removes service users: identity.ts, the connection route, the Base44Link
  model, the connect/disconnect UI and its tests
- builder isolation stays where the docs put it: Tiny's ownership table,
  checked by the API handler before every app call
- README and .env.example describe BASE44_ACCESS_TOKEN and BASE44_WORKSPACE_ID

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- origin check is one rule: BUILDER_ORIGIN when hosted, a loopback Host locally
  (refuses DNS-rebinding pages; request.url is not used because Next rewrites it)
- read the body with request.text() plus a size check instead of a chunked reader
- log unexpected errors before replacing them with a safe message

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ThreadPrimitive.Messages with UserMessage / AssistantMessage, and
  MessagePrimitive.Parts components={{ Text, tools: { Fallback } }} instead of
  one render function with role and part-type branches
- tool parts read appId and onAnswer from a small context, because
  assistant-ui renders part components itself
- answers still go through onAnswer, not assistant-ui's resume(), so failures
  reach the Question and its retry-the-original-answer flow keeps working

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	examples/white-label-minimal/.env.example
#	examples/white-label-minimal/components/Builder.tsx
#	examples/white-label-minimal/components/Question.tsx
#	examples/white-label-minimal/lib/base44/client.ts
#	examples/white-label-minimal/lib/server/api-handler.ts
#	examples/white-label-minimal/lib/server/app-service.ts
#	examples/white-label-minimal/package.json
#	examples/white-label-minimal/tests/server.test.ts
Every 401/403 used to read "needs apps:watch", which hid the other causes.
Base44's error code now picks the message: missing scope, sockets not enabled
for the workspace, or a key that cannot watch the app. A missing
BASE44_SVC_KEY is reported before any request is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- client.ts is ordered by the six steps of "The build turn", one commented
  section per step with a link to the docs
- Publish button on the ready card: deploy, then read back the published URL
  and link to it (step 6), with a browser test
- remove the unused REST conversation read (getConversation and the backward
  pager); the chat arrives over the live-updates socket since #50
- README opens with a table from each docs page to the file that implements it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App cards used a static preview URL Tiny derived from the slug and then probed.
They now ask Base44 for it, as "Embed the app" documents: provision the builder
as an app user, mint a one-time sign-in token with target latest_preview, and
load the returned embed_url. Private apps open signed in as the builder.

- new lib/base44/embed.ts; removes lib/server/static-preview.ts and
  BASE44_STATIC_PREVIEW_DOMAIN
- PreviewFrame fetches a fresh URL on mount and after each build; the static
  iframe is no longer lazy, since the token expires in 60 seconds
- before the first build (app_has_no_slug) the card shows the screenshot
- BASE44_SVC_KEY also needs Provision app users and Mint embed sign-in tokens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live preview now comes from "Embed the app" with target live_preview, like
the latest build in the cards. The builder is no Base44 user, so a private app
would otherwise show its own sign-in page inside the frame. Removes the
sandbox/preview-url call and the undocumented URL parameters Tiny added to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the embed and live-updates calls move into client.ts as their own sections,
  next to the build-turn steps; embed.ts and socket-session.ts are removed
- http.ts had one caller left, so its request() moves to the bottom of
  client.ts; it takes an optional auth header for the workspace-key calls
- Base44Error keeps Base44's error.code, which picks the live-updates message
- lib/base44 is now client.ts plus config, error and custom instructions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- openSocketSession / openBuilderSession -> openLiveUpdates (server function,
  API action and browser call); watchApp -> watchLiveUpdates;
  useBuilderSocket -> useLiveUpdates, whose resume() is now reconnect()
- live-messages.ts merges into live-updates.ts: one browser file for the flow
- the hook names its state for what it is (session, readySession) and says
  when it is loading; behaviour is unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the ready card and its Publish step move to ReadyCard.tsx
- busy is the kind of action (create, send, answer), not its display text
- remove the "resume an existing app" flow: after an uncertain create the
  ownership was never saved, so there was nothing it could resume; the
  server's error already says the operation may still be running
- builder-api: drop the unused abort signal and notStarted flag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AGENTS.md: style rules for the example (one call, one function; plain ifs;
  guard only what matters; comments say why)
- client.ts: one exported function per Base44 call, each showing its path,
  method and body; build-turn calls name their 120-second timeout; the
  request helper is a plain fetch with the token, workspace header and
  safe errors; status hints and defensive checks for impossible states removed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- build-turn.ts: steps 1-6 of "The build turn"
- embed.ts: "Embed the app"
- live-updates.ts: the live-updates socket session
- request.ts: the fetch all three share, and the workspace key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- one case per action: allowed fields, checked values, then the call
- small named checks (checkOrigin, readBody, onlyFields, checkId, checkText,
  checkSkip) replace the field-tracking body reader and the action table
- the "outcome" field is gone: nothing reads it since the resume flow went

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lib/chat/chat-state.ts: getChatState() names where the build turn stands
  (loading, paused, question, building, failed, ready, idle); the "has the
  agent written code" check moves in from build-readiness.ts
- Builder decides with plain ifs on that state; the status line is a small
  Progress component with one if per message
- BuilderChat takes the state instead of five booleans

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pages call app/actions.ts directly. Each action checks the signed-in builder
and, for an app, that they own it, then calls Base44. Next.js checks the
request origin and decodes the arguments, so the endpoint and its checks go:

- removes app/api/base44/route.ts, lib/server/api-handler.ts,
  lib/server/app-service.ts, lib/chat/builder-api.ts and BUILDER_ORIGIN
- actions return { data } or { error }, since Next.js hides thrown messages
  in production; lib/chat/unwrap.ts turns an error back into an exception
- browser tests: the fixture aliases app/actions to fake-actions.ts, which
  still sends each action to the mocked /api/base44 route
- server tests call the Base44 functions and the ownership check directly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- every action reads: signed-in user, requireOwner for an app, then the Base44
  call; the ownApp/signedIn wrappers are gone, one returnErrors remains
- lib/storage/ownership.ts replaces the app repository with four plain
  functions: saveOwner, requireOwner (which also checks the ID is plain),
  listOwnedApps, removeOwner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- App drops slug, logo_url and user_description: nothing shows them
- remove the unused AppPage type and the unused @/ path aliases
- stop exporting types and classes only their own file uses
- lib/server held only auth.ts, which moves to lib/auth.ts
- README's folder table names app/actions.ts and the current lib folders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Question.tsx follows "Answer the agent's questions": one small form for each
waiting_on.kind (ChoiceForm, SecretsForm, approve/reject), and Reject only
for a question it does not recognize. The main component sends the answer
and, after a failure, resends the same answer with the same request ID.
parseQuestion reads each kind with plain loops instead of throw-and-catch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- server/: Base44 calls (server/base44/), server actions, sign-in, ownership
  and the database
- client/: everything that runs in the browser, components included
- app/: Next.js routes only (page, layout, the sign-in callback)
- types.ts: what both sides share
- the fixture aliases server/actions to its fake, as before

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
client/useBuildTurn.ts holds everything the browser does with Base44, in the
order of the docs: open live updates (the platform client is created there,
in plain sight), send a prompt or create the app, answer a question, and the
chat state. Builder.tsx only renders it.

- removes client/live-updates.ts and client/components/useLiveUpdates.ts
- message conversion and the pending prompt move to client/messages.ts
- the fixture also aliases ../server/actions, imported by the hook

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- useApps: the builder's app list (load, remove, add, update) moves out
- AppsPage: loading, empty and list states as plain ifs
- EditorPanel: the assistant panel and its mobile open/close and focus
- Workspace keeps one "app in the assistant" state; the separate stage app
  always held the same value
- a new app focuses the composer through autoFocus instead of a DOM query

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- BuilderChat merges into Builder: the nine pass-through props go; parts that
  assistant-ui renders read the turn from one context
- no hidden-message filtering: live updates never send hidden messages
- the assistant-ui message conversion joins client/messages.ts
- tool arguments are formatted once, when a live message is converted
- ToolActivity and the status line read as plain ifs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AppPreview replaces Base44Preview and PreviewFrame. It shows the latest build
and, while editing, loads the live sandbox behind it until it has loaded.
Refresh preview asks for new one-time URLs.

Removed with it: recovery through the undocumented preview:requestRefresh
message, its retries and timeout, and carrying URL parameters over from the
old sandbox preview URL. None of it applies to one-time embed URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports #54 onto the new structure:
- useBuildTurn stops waiting on Send chat message once the build is running
- chat-state counts a code edit only from the latest turn
- both tests kept; the readiness and pending-prompt unit tests, lost with
  conversation.test.ts, return in messages.test.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amitbardos
amitbardos marked this pull request as ready for review October 4, 2026 18:12
Netlify's secrets scan failed every deploy preview since the README linked the
OpenAPI spec on the platform host, whose origin is the value of the secret
BASE44_PLATFORM_HOST. The README now links the Apps API reference in the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amitbardos
amitbardos merged commit 0862851 into main Oct 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant