This roadmap defines a sane path for JSKIT to support mobile while remaining a server + web app platform.
The key distinction is:
- Stage 1: strong mobile-shell support
- Stage 2: true broad mobile platform
These are not the same commitment.
Stage 1 means:
- the same JSKIT web app runs inside a native shell
- JSKIT still renders web UI
- Capacitor is packaging/integration glue
- Android is the only MVP target
Stage 2 means:
- JSKIT treats mobile as a first-class runtime target
- JSKIT exposes stable mobile capability seams
- multiple native-facing features are intentionally supported
- broader platform promises become real and maintained
Do not blur those two promises.
Always keep these true unless the roadmap is explicitly changed:
- JSKIT remains a server + web UI platform.
- JSKIT mobile support does not mean a native UI abstraction layer.
- JSKIT mobile support does not mean a React Native equivalent.
- JSKIT mobile support does not create a second route system.
- Mobile launch URLs must normalize into normal JSKIT router paths.
- Android-first is the MVP. iOS is later.
Package an existing JSKIT web app into an Android shell that:
- serves bundled local web assets in production
- can optionally use a remote dev server in development
- handles deep links into normal JSKIT routes
- supports auth callback return into the app
- can produce a release AAB
Before stage 1 is considered complete, one real JSKIT app must:
- launch in an Android shell
- load bundled local assets
- call the remote JSKIT server/API successfully
- complete login through browser-to-app callback
- deep-link into a normal app route
- prove the rewarded-ad flow works inside the Android shell
- build a release AAB
- Lock the product statement:
-
JSKIT mobile-shell support packages JSKIT web assets into a native Android shell.
-
- Explicitly reject:
- native UI abstraction work
- multi-platform parity promises
- offline-first platform work
- push/notifications in the MVP
- Add a first-class
mobilesection to app config. - Keep the config intentionally narrow.
- Define the MVP config shape in
config/public.js:
config.mobile = {
enabled: true,
strategy: "capacitor",
appId: "com.example.app",
appName: "Example App",
assetMode: "bundled", // bundled | dev_server
devServerUrl: "",
apiBaseUrl: "https://api.example.com",
auth: {
callbackPath: "/auth/login",
customScheme: "exampleapp",
appLinkDomains: []
},
android: {
packageName: "com.example.app",
minSdk: 26,
targetSdk: 35,
versionCode: 1,
versionName: "1.0.0"
}
};- Add helper accessors:
-
resolveMobileConfig() -
resolveClientAssetMode() -
resolveMobileCallbackUrls()
-
This is the minimum core work needed to make mobile-shell real without turning JSKIT into a broad mobile platform.
- Add a bootstrap seam for incoming app URLs.
- Teach JSKIT to distinguish:
- web asset origin
- API origin
- browser callback URL
- mobile callback scheme/domain
- Ensure client bootstrap can defer final navigation until session/auth hydration is ready.
- Keep
usePaths()web-path based. - Do not create a second path language for mobile.
- Add only the target awareness needed to distinguish:
- normal web build
- mobile packaged build
- mobile dev-server build
- Add a normalizer:
-
normalizeIncomingAppUrl(url, mobileConfig)
-
- Add a bootstrap helper:
-
registerMobileLaunchRouting({ router, mobileConfig, getInitialLaunchUrl, subscribeToLaunchUrls })
-
- Add an auth helper:
- reusable callback completer for normalized
/auth/loginOAuth callback URLs
- reusable callback completer for normalized
- Accept custom schemes.
- Preserve query parameters.
- Strip scheme/host where appropriate.
- Reject non-owned schemes/domains.
- Output exactly one canonical router path.
- Map examples like:
-
exampleapp://auth/login?...->/auth/login?... -
exampleapp://w/foo/workouts/2026-05-07->/w/foo/workouts/2026-05-07 -
https://app.example.com/auth/login?...->/auth/login?...
-
- Cold start:
- receive launch URL
- normalize it
- complete auth if needed
- navigate once
- Warm resume:
- receive app-open event
- normalize URL
- complete auth if needed
- route idempotently
- Avoid duplicate navigation on startup.
Create one thin integration package.
@jskit-ai/mobile-capacitor should own:
- Capacitor scaffolding
- Android shell setup
- sync/run/build commands
- deep-link/bootstrap glue
- mobile-shell docs
- mobile-shell doctor checks
It should not own:
- app-specific auth provider logic
- app-specific route definitions
- billing
- notifications
- product-specific native features
- Create
@jskit-ai/mobile-capacitor. - Make it a thin integration package, not a mobile framework.
- Define the actual Stage 1 package layout:
packages/mobile-capacitor/
package.json
src/
client/
index.js
providers/
MobileCapacitorClientProvider.js
runtime/
globalCapacitorAppAdapter.js
mobileCapacitorRuntime.js
server/
buildTemplateContext.js
templates/
capacitor.config.json
mobile-capacitor.md- Generate repo/app artifacts:
-
capacitor.config.json -
android/ -
.jskit/mobile-capacitor.md
-
- Include a manifest/intents template story for custom-scheme deep links.
- Add:
-
jskit add package @jskit-ai/mobile-capacitor -
jskit mobile android sync -
jskit mobile android run -
jskit mobile android build -
jskit mobile android doctor
-
- Install:
-
@capacitor/core -
@capacitor/cli -
@capacitor/android
-
- Create
capacitor.config.json. - Create
android/. - Add
config.mobilestub if missing. - Add npm scripts:
-
mobile:sync:android -
mobile:run:android -
mobile:build:web -
mobile:build:android
-
- Boot the mobile runtime through the normal JSKIT client provider path instead of an app-local import hook.
- Add
.jskit/mobile-capacitor.md.
- Run the correct JSKIT frontend build.
- Copy built assets into the Capacitor web dir.
- Run
npx cap sync android. - Start with one bundle strategy:
- bundle the app as a whole
- Consider selected-surface or all-surface bundle strategies later only if the whole-app bundle proves too heavy.
- If
assetMode=dev_server, use the live dev server. - Otherwise sync bundled assets first.
- Run
npx cap run android.
- Build production web assets.
- Sync Android.
- Run the Gradle bundle task that produces AAB.
- Standardize one mobile auth callback flow:
- app opens external browser/custom tab
- provider redirects to
customscheme://auth/login?... - Capacitor receives URL
- JSKIT helper completes auth/session
- router resumes intended destination
- Ensure login preserves intended destination.
- Ensure logout clears session cleanly in mobile-shell context.
- Support MVP deep-link scope:
- custom scheme only
- auth callback
- ordinary app route opening
- Generate Android package/app-id config.
- Add app-name wiring.
- Add icon/splash workflow.
- Add Android back-button guidance/default handling.
- Add versionCode/versionName strategy.
- Add AAB build path.
- Document signing flow:
- upload key
- Play App Signing
- Choose one real JSKIT app as the reference implementation.
- Package it with bundled local assets.
- Verify:
- app launches
- local bundled assets load
- remote API calls work
- login works
- workspace route navigation works
- deep links work
-
google-rewarded-webgate opens inside the Android shell - the rewarded video ad loads in the Android shell
- reward completion reaches
google-rewarded-core - unlock receipt state is written correctly
- the gated feature unlocks correctly after reward grant
- release AAB builds
- Add tests:
- URL normalization unit tests
- auth callback resume tests
- mobile config resolution tests
- one Android emulator smoke path
- one deep-link test
- one unknown-route fallback test
- one manual real-device QA checklist
- Document Android-first MVP scope clearly.
- Document what mobile-shell support does not promise.
- Document the generated
capacitor.config.jsonshape, including:-
webDir -
androidScheme - optional dev-server
url -
cleartext: truein dev-server mode only
-
- Document Play Store operational requirements:
- AAB requirement
- target SDK requirement
- Play App Signing
- Data safety form
- account deletion requirements when accounts exist
- Add fitness/health policy guidance as documentation only.
- Add billing guidance as documentation only.
Do not start stage 2 as a platform commitment until all of these are true:
-
config.mobileis real and stable enough for one production app -
@jskit-ai/mobile-capacitorscaffolds a working Android shell - bundled assets work in production mode
- deep-link routing works
- mobile auth callback works
- one real app ships through the path
- the rewarded-ad flow is proven inside the Android shell
- release AAB generation is proven
- docs match reality
Promote selected mobile capabilities into maintained JSKIT platform seams while still using the same web app model.
This is a platform commitment, not just packaging.
It means JSKIT intentionally supports a first-class mobile-shell runtime
target and a growing set of mobile-facing capabilities.
Do not begin stage 2 because the shell exists. Begin it only if:
- at least one real app is using stage 1 successfully
- recurring mobile needs appear across more than one app
- the team is willing to maintain mobile behavior as platform surface
- Formalize
mobile-shellas a JSKIT client target. - Distinguish cleanly between:
- browser
- mobile-shell
- Make build/runtime/config aware of the target without forking the app model.
- Add package metadata support for:
- mobile-safe
- mobile-unsupported
- capability requirements
Do not let feature packages import Capacitor directly everywhere.
- Define JSKIT capability seams for:
- incoming app URL
- app foreground/background state
- back-button handling
- safe-area access
- keyboard state/overlap handling
- Make
mobile-capacitorthe first implementation of those seams. - Keep capability APIs small and explicit.
- Avoid ad hoc
if nativechecks spread across packages.
Only promote capabilities that have real repeat demand.
- Share/open-in APIs
- File picking
- Camera/media capture
- basic device lifecycle hooks
- richer emulator/device testing support
These should be staged one by one, not promised as a bundle.
If JSKIT claims broader mobile support, the shell experience needs explicit care.
- Standard safe-area support
- keyboard-safe layout guidance/helpers
- back-button behavior guidance/helpers
- mobile-shell-specific QA guidance for common UI patterns
- package-level guidance for touch-friendly interactions where needed
This still does not mean a native UI framework.
- Extend
jskit doctorwith mobile-aware checks:- mobile config validity
- callback scheme validity
- shell files present
- build output dir matches Capacitor config
- target/capability mismatches
- Add package-author guidance for mobile-shell-safe integrations.
- Add reference docs for mobile-shell-safe package design.
These are separate product decisions, not automatic consequences of stage 2:
- verified app links
- Digital Asset Links management/docs
- iOS support
- notifications
- offline helpers
- biometric auth
- billing integrations
- background tasks
Each of these should enter only with its own scoped proposal.
JSKIT can only claim broad mobile-platform support when:
-
mobile-shellis a first-class maintained target - capability seams exist and are used instead of direct Capacitor imports
- multiple real apps depend on the mobile support
- doctor/docs/testing all reflect the real support promise
- the team is prepared to maintain the added platform surface
These are roadmap anti-goals:
- no second route system for mobile
- no scattered direct Capacitor calls in feature packages by default
- no fake promise that JSKIT is a native UI framework
- no stage-3/4 native features smuggled into stage 1 work
- no claim of broad mobile support before one real app ships through the shell path
- create
MOBILE-ROADMAP.md - use this file as the staged replacement for the flat Capacitor backlog
- narrow the active MVP to stage 1 only
- implement stage 1A through 1C first
- prove one reference app before widening scope
- decide explicitly whether stage 2 is worth the long-term maintenance cost