Web Guide
Port 5555, viewport scaling, and device preview in the browser.
Fixed port and session
Login stores the session by origin (which includes the port), whether Supabase Auth or Firebase Auth. kasy run --web uses 5555 so the session persists across runs.
Viewport scale (0.93)
Flutter Web renders ~10% larger than the equivalent native size. The kit applies a 0.93 scale on the web to stay aligned with the mobile design.
Knobs in lib/core/web_viewport_scale.dart:
const bool kWebViewportScaleEnabled = true;
const double kWebViewportScale = 0.93;Native never scales (kIsWeb). Device preview turns off the scale to simulate the phone's real size.
Device preview (debug)
Cmd/Ctrl + Shift + D turns on an iPhone/iPad frame in the browser, handy for seeing how a screen looks on a smaller size without opening the simulator. It shows up on any web debug build, whether you pick Chrome in kasy run's picker or use kasy run --web: the frame is rendered by the app itself, regardless of which window hosts the page. It doesn't show up in production builds and doesn't affect native.
On the first run (new project or cleared storage), the preview background and the in-frame app theme follow system light/dark (macOS/Windows). The standalone sun/moon button in the bottom-right corner changes only the canvas behind the device; the sun/moon icon in the preview toolbar changes the app theme. Both choices are saved to localStorage and come back the same on the next kasy run --web.
Don't confuse this with DevInspector. Device Preview changes the simulated screen size. DevInspector (long-press a widget) identifies that widget and copies its context for an AI. Two different debug tools.
Onboarding and authentication
Preview doesn't only change the frame size. It also controls which auth flow you're testing in kasy run --web:
| Mode | First visit | Guest / "Continue without account" | After logout |
|---|---|---|---|
| Preview iOS, Android, or iPad | Onboarding (same as native) | Yes, on Sign in (not on Sign up) | /signin |
| Preview Desktop or preview off | Goes straight to Sign in | No | /signin |
| Real web (phone, tablet, or desktop browser, production) | Goes straight to Sign in | No | /signin |
Layout ≠ flow. Opening the app in iPhone Safari or a tablet browser uses mobile/tablet breakpoints (responsive UI), but account behavior stays web: sign-in required, no onboarding, no anonymous guest. That's intentional for the kit's SaaS default: trials, subscriptions (RevenueCat/Stripe), and support need an identifiable user.
Use preview on iOS / Android / iPad when you want to validate onboarding, paywall, anonymous accounts, and skip without opening a simulator. On published web (mobile included), the default is to require an account before using the app.
In-app navigation: Settings → Sign up (or Sign in) shows the back arrow on mobile/tablet preview, same as native, when the route was opened with push. Desktop preview or plain web without the frame hides it (direct entry at /signin or /signup).
Supabase: after the loader ("creating your account"), check Authentication → Users. If the search dropdown is Email address, anonymous users vanish from the list (they have no email). Use Unified search or search by User ID from public.users. See Troubleshooting.
Switching toolbar tabs (e.g. iPhone → Desktop) updates the app's redirect immediately. If you were in onboarding and switch to Desktop, the flow returns to web mode.
/onboarding route
GoRouter controls who may stay on /onboarding:
| Situation | Redirect |
|---|---|
| First install (native or preview iOS / Android / iPad), no session | Real onboarding flow |
Already signed in, onboarded guest with ID, or onboarding_completed flag | Home (onboarding does not open again) |
Typing /onboarding with an established session | Home |
Web SaaS (preview Desktop, preview off, or published web) on /onboarding | Sign in (no session) or Home (with session) |
Admin → Test onboarding, or /onboarding?preview=true on a debug build | Side-effect-free walkthrough (no account, no profile writes, no real permission prompts) |
/onboarding?preview=true on release without admin role | Home |
No duplicate account: if a backend ID already exists (signed-in or anonymous guest), the loader skips anonymous creation. Only first install without an ID creates a guest at the end of the flow.
See onboarding again in dev
In a normal browser tab (including Cursor's browser), the kit remembers state across reloads:
| What | Where | Effect |
|---|---|---|
onboarding_completed | localStorage (SharedPreferences) | After finish or skip, redirect no longer sends you to onboarding |
dev.last_route | localStorage | In debug, hot restart (R) resumes the last route (e.g. /, /settings) instead of the first screen |
That's why an incognito tab "works": empty storage = first visit again. kasy clear-web and kasy reset --web open the ?kasy_clear_storage=1 URL in your default browser (macOS, Windows, and Linux). That URL clears localStorage, sessionStorage, and IndexedDB (Firebase Auth sessions on web live in IndexedDB).
Options without incognito:
- Admin → Test onboarding (
/onboarding?preview=true): walk every screen without creating a guest, writing profile data, or firing real permission prompts. Returns to Admin when done. On release, admin only (or debug). Good for layout and copy. - Real first visit again:
kasy clear-web(withkasy run --webrunning),kasy run --web --open-browser --fresh, an incognito/private tab, or clear site data forlocalhost:5555in DevTools → Application. On MCP:run_appwithfreshStorage=trueorclear_web_storage. With device preview on an iOS/Android/iPad tab, you land on onboarding. - Direct URL:
/onboarding?preview=trueonly in debug or as admin. The real flow (/onboardingwithout query) is first install only; an established session goes Home.
Reloading only the browser tab (F5) does not recompile Dart. After code changes, use hot reload or hot restart (below).
Hot reload and hot restart (r / R)
On kasy run --web, update the build in two ways:
| Where | How |
|---|---|
| Device preview toolbar | r and R buttons on the preview bar (preferred when you are already in the browser) |
kasy run --web terminal | Type r or R |
| Change | Use |
|---|---|
| Layout, colour, padding, text, icons, style | r (hot reload, fast, keeps state) |
Provider, initState, routes, main(), wiring a rebuild won't pick up | R (hot restart) |
r failed or Flutter asks for restart | R |
Do not use R for visual-only tweaks: it is slower and clears forms, navigation, and guest session.
Who sends r / R
| Situation | Reload |
|---|---|
kasy run not running | Clean flutter analyze. The agent does not start the app on its own. |
kasy run running (you or the agent finished a Dart edit) | r (UI/style) or R (logic/bug). See AI guidance. |
| Agent will navigate or test in the browser | Checklist in docs/agent-browser-qa.md (linked from AI guidance). |
Human dev: toolbar or terminal whenever you want. Agent: full rules in AI guidance (site docs at kasy.dev/docs, not the marketing landing page).
AI navigation (Browser QA)
Flutter Web paints the whole screen to canvas — without special handling, an AI agent in the browser (Cursor, Claude, Playwright) can't see a button or a text field the way it would on a normal React page, only pixels.
The kit fixes this by turning on Flutter's semantics tree on every debug/profile build (never in production) and installing a JS bridge (window.__kasyBrowserQa) that lets the agent fill fields reliably:
if (!kReleaseMode) {
SemanticsBinding.instance.ensureSemantics();
installBrowserQaBridge();
}With that on, an AI agent can read the page as a normal accessibility tree (every button's, field's, switch's name) and call window.__kasyBrowserQa.setTextIn(label, value) to type into a field by name, instead of relying on click-then-hope-it-focused.
Agent ritual (reload r/R, terminal, snapshot, setTextIn): AI guidance and docs/agent-browser-qa.md in the project. MCP: browser_qa_guide.
If you're using an AI agent with Kasy's MCP connected, call browser_qa_guide after implementing or changing a screen (see MCP).
This changes nothing for the end user — it's just a debug-only layer, stripped out of release builds.
Google Cloud
Add http://localhost:5555 and your production domain as authorized origins in Google Cloud Console → Credentials → your OAuth Client.
Last updated on 08/02/2026

