# Pikku Studio Cloud — build and deploy an app that uses the whole platform You are an AI agent. This is the bigger brief: a real product with enforced access control, background work, an AI agent, tested user journeys, and a design pass — deployed. **Do everything in https://pikkustudio.app/prompt.md first, and keep it open.** That file is the base — scaffold, skills, the not-optional coding rules, running locally, the design pass, deploy. This file does not repeat it; it adds the platform features on top and raises the bar for "done". Where the two overlap, both apply. Do not declare done until §5's checklist is green, and do not report a step as passing that you have not actually run. Two deltas from the base while you set up: - **The roles question is not optional here.** The base already asks who uses the app and turns the answer into `defineVirtualUsers` / `defineSystemRole`; on this brief the access-control bar in §1 rests on it. If the answer is one kind of person, that is a real answer — the bar applies as ownership rather than role. Do not invent a role to satisfy it. - **Install extra skills.** On top of the base set, read `pikku-workflow`, `pikku-cron`, `pikku-ai-agent`, and — for the Arabic/RTL locale below — `pikku-i18n` and `pikku-rtl` before you write that kind of code. ## 1. The bar A submission that does not have all seven is not finished: 1. **Four or more screens** that a person would actually visit — not one list plus a form. A dashboard/overview, the main working surface, a detail view, and something not everyone sees — a role-specific screen, or the owner's own. 2. **Access control**, enforced in the `permissions` field of each function. Something that only changes what the UI renders is not access control — prove the RPC itself refuses. With two or more roles that means one role denied a capability another has; with a single kind of person it means one owner denied another owner's row. Either satisfies this; a made-up role does not. 3. **A workflow.** Multi-step, durable, resumable — onboarding, approval, settlement, publish. Not a function with three awaits in it. 4. **A scheduled task.** A real cron that does something the app needs: digests, reminders, expiry, recomputation. 5. **An AI agent.** Something the LLM is genuinely better at than a SQL query — summarize, triage, draft, extract, match. Wire it to a screen. 6. **Scenarios with coverage.** The user journeys run end-to-end over the real transport as each role, and you report the coverage number (§4). 7. **A design pass.** Impeccable critiques every screen and you fix what it finds (§3). Enable the features you need — several are off in a fresh scaffold: bunx pikku enable workflow # workflow workers bunx pikku enable agent # agent endpoints bunx pikku enable scenarios # scenario instrumentation + coverage RPCs ## 2. Build order — the base order, plus the platform pieces Follow the base build order (migration → functions → `pikku all` → UI). This brief adds four things to slot into it: 1. **Seed for everyone the app has.** The base already has you seeding `db/sqlite-dev-seed.sql` for every virtual user; here a restricted screen has to have something on it for each of them. 2. **Workflow, schedule, agent** — workflows in `*.workflow.ts`, scheduled tasks via the cron wiring, agents in their own files. Read `pikku-workflow`, `pikku-cron`, `pikku-ai-agent` first. 3. **A scenario per workflow, schedule and agent too** — the base covers the user journeys (§4 adds the coverage number). 4. **Ship three languages.** The base already makes every string a translation key; here you must actually supply the locales: **English, German, and Arabic** (default to those three unless the user named others). That means a full `messages/{en,de,ar}.json` — no missing keys, no English left in the `de`/`ar` files. Arabic is right-to-left: read the `pikku-rtl` skill and make the layout flip (direction, logical margins/padding, icon mirroring) — a RTL locale that renders left-to-right is not done. Switching language must not require a rebuild; it is a locale file, never a component change. 5. **File uploads — presign, don't proxy.** If the app takes files (avatars, attachments, images), use the injected `content` service, never a route that streams bytes through the API. The flow: - A function calls `content.getUploadURL({ bucket, fileKey, contentType })` and returns the `{ uploadUrl, assetKey, uploadHeaders }` it gets back. - The browser `PUT`s the file **straight to `uploadUrl`** (direct to storage), then tells the API the `assetKey`. You persist the `assetKey` in the DB — never a full URL, and never the file bytes through your function. - To show a private file, mint a short-lived link on demand: `content.signContentKey({ bucket, contentKey, dateLessThan: })` returns a time-limited signed URL. Sign at read time; do not store a public URL, and do not make a private bucket public. Wire a storage backend in `services.ts` — local for dev, S3/B2/R2 in prod (`pikku-aws`, `pikku-backblaze`); on Pikku Studio Cloud the platform provides the bucket. **The signing key and storage credentials are wired secrets** — read them via the typed `secrets` service, never hardcode them and never put them in a function's input schema. Classify the column that holds a private asset key (item 6) so an endpoint can't leak someone else's file key. 6. **Classify the sensitive columns.** Data classification rides on the same `db/annotations.ts` the base already uses for coercion: add `security: 'pii' | 'private' | 'secret'` to a column by hand (once the file carries manual fields, `pikku db migrate` stops overwriting it). Then `bunx pikku all --security --fail-on-error` statically scans function return types and fails the build if a classified field leaks from an endpoint it should not. A meetup app stores attendee emails — mark that column `pii`, and an admin-only list handed to a viewer is exactly the leak this catches. The base's not-optional rules all still apply (schema-is-the-type, permissions field, no `process.env`, inline errors, i18n, RPC-first with `expose: true`). When you run it (base §5), walk every screen as **each** virtual user, in a browser. HTTP 200 is not evidence, and a screen you never opened is not built. ## 3. The design pass — every screen, not a sample Do the base's design pass in full (theme first, compose with shadcn's rich components — charts, calendar, tabs, progress, tables — then critique every screen with impeccable from real screenshots, at desktop and at ≈390px, until no P0 findings remain). Three things this brief adds on top: - **Every screen, as every role.** A role-specific screen you never opened in that role's session has not been critiqued. - **A dashboard/overview is required here** (§1), so the charts are not optional — a platform app whose overview is a column of numbers in `Text` is not done. - **Record each screen's score before and after**, so the improvement is visible rather than asserted. ## 4. Scenarios and coverage The base already has you writing scenarios — one per slice, refusals included, run with `pikku scenario run local`. Two things this brief adds: cover the workflow, the schedule and the agent as well, and report the coverage number. Run the API with coverage on, then run the scenarios against it: bunx pikku dev --coverage SCENARIO_VIRTUAL_USER_SECRET= bunx pikku scenario run local --coverage That writes `coverage/scenario-coverage.json` and prints how many functions each journey exercised. Report the number. A function no scenario touches is a function nobody has ever run — either cover it or delete it. ## 5. Deploy — and the done checklist Deploy exactly as the base's deploy section describes (login needs a human; `deploy apply --production --auto-apply`; the GitHub-App and branch-upstream gotchas). One addition: **the AI agent needs a balance** — it will not run in production until the account is topped up any amount, though deploying, stages, and databases are all free. After deploy, run the scenarios against production, not just local: SCENARIO_VIRTUAL_USER_SECRET= bunx pikku scenario run production **Done means all of this is true**, and you have run each one: - [ ] 4+ screens, each opened in a browser as every kind of person the app has - [ ] every screen critiqued by impeccable at desktop AND phone width, no P0s left - [ ] access control with a scenario proving the RPC refuses — the wrong role, or the wrong owner - [ ] a workflow that survives a restart mid-run - [ ] a scheduled task that has actually fired - [ ] an agent wired to a screen, answering from real app data - [ ] three complete locales (en, de, ar), no missing keys — and Arabic renders right-to-left, checked in a browser - [ ] if it takes files: upload is presigned direct-to-storage, reads are signed on demand, and signing keys/credentials live in `secrets` - [ ] `pikku all --security` clean — no classified field leaks - [ ] scenarios green locally and against production, coverage number reported - [ ] deployed, and the deployed URL opened and clicked through When something breaks in production, use the base's debug commands (`pikku cloud status` / `errors` / `trace` / `metrics`) — read the trace, don't redeploy with extra logging. ## Reference - The base brief (do it first): https://pikkustudio.app/prompt.md - Docs: https://pikku.dev - Skills (authoritative): `.claude/skills/`