No description
- migrate demo/web-vs-native + examples/todo shells: webview_all -> flutter_inappwebview
- scaffold: emit {type:connectivity, online} via connectivity_plus on network flips + web ready
- replace stock widget_test.dart (referenced nonexistent MyApp) with harness smoke tests
- untrack generated ios/Flutter/ephemeral + add gitignore rule
- docs/bridge-protocol: document the connectivity message
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .claude | ||
| demo/web-vs-native | ||
| docs | ||
| examples/todo | ||
| packages/vite-plugin | ||
| scripts | ||
| .gitignore | ||
| biome.json | ||
| bun.lock | ||
| package.json | ||
| README.md | ||
| tsconfig.base.json | ||
lion-turtle
Mobile-shell platform. Flutter native shell wraps any Vite + React app via a single plugin. Local-first storage with sync hooks. Browser-side dev mock so you can iterate without rebuilding the native shell. Full Flutter control + log viewer + tunnel + device picker built into the dev tooling.
Pieces
| Package | Purpose |
|---|---|
@lion-turtle/vite |
Vite plugin (dev-proxy, DevShell iframe mock, web-side bridge runtime, Flutter control plane, env sniff, tunnel) + bundled Flutter scaffold template. Bridge protocol + store + extension types re-exported from @lion-turtle/vite/runtime. |
Layout
demo/ spike — native widgets vs React-in-WebView, no server, no auth
web/ + shell/
examples/
todo/ full reference app — auth, offline-first, sync, prod build
web/ + server/ + shell-app/ + dev.ts + build.ts
docs/ AI-agent docs (read first)
packages/ @lion-turtle/vite
Quick start (consumer)
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { lionTurtle } from '@lion-turtle/vite';
export default defineConfig({
plugins: [
react(),
lionTurtle({
devProxy: { preferredPort: 9999, portRange: 100 },
devShell: { enabled: true, frame: 'iphone' },
scaffold: { shellDir: '../shell-app' },
flutter: {
enabled: true,
shellDir: '../shell-app',
prodRootTarget: 'https://app.example.com', // injected at build time
},
}),
],
});
// src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import {
DevShell,
ShellProvider,
isNative,
installDevShellControl,
installLogPipe,
} from '@lion-turtle/vite/runtime';
import { App } from './App';
if (import.meta.env.DEV) {
installLogPipe();
installDevShellControl();
}
const params = new URLSearchParams(location.search);
const showDevShell = import.meta.env.DEV && !isNative() && !params.has('_embed');
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ShellProvider>{showDevShell ? <DevShell /> : <App />}</ShellProvider>
</StrictMode>,
);
On first vite dev:
- dev-proxy probes ports 9999..10098, takes first free
- if
<project>/<shellDir>/is empty → scaffolds@lion-turtle/vite/scaffold/Flutter app into it (consumer owns the copy after that) - DevShell renders at
/, the iframe inside loads/?_embed=1
Run the demo (spike)
Native vs React rendering, same screens, side-by-side. No server.
bun install
bun run demo # http://localhost:5173
Run the example (TODO with auth + offline + sync)
bun run todo # interactive: pick device, asks about flutter shell
# OR
bun run todo:server # server alone (port 3001)
bun run todo:web # vite alone (port 5173 + dev-proxy 9999)
Build for production (ROOT_TARGET injected at build time):
PROD_ROOT_TARGET=https://todo.example.com bun run todo:build:apk
PROD_ROOT_TARGET=https://todo.example.com bun run todo:build:ios
# or:
bun examples/todo/build.ts ipa --root-target https://todo.example.com
What's in the box
- DevShell — phone-frame iframe mock with bridge log, console viewer, env editor, dev-forward routes editor, device picker, tunnel control, Flutter control. Auto-scales phone to fit canvas. Sleek scrollbars.
- Dev-proxy — Bun child process on port 9999 (auto-bumps if taken).
/__via/<scheme>/<host>:<port>/path forwarding, vite fallback for unmapped paths. - Local-first store —
useStore(table)hook, localStorage backing in v0.1, sync replay + pull against a server, lenient reconciliation. - Auto ROOT_TARGET resolution — picks the right URL for android emulator (
10.0.2.2) vs physical (localhost+adb reverse) vs iOS sim vs iOS phone (tunnel). - Offline-first WebView — scaffold runs a local cache+proxy server on
127.0.0.1:<random>; WebView loads through it. GETs cached on disk, non-GETs proxied. On cache miss + offline → 503 → Flutter shows "MAINFRAME UNREACHABLE" screen. - Flutter control plane — start / stop / hot-reload / hot-restart from DevShell. Logs streamed live, persisted, copyable as raw text or AI-prompt markdown.
- Tunnel — cloudflared / ngrok integration. Auto-feeds tunnel URL into Flutter
ROOT_TARGETwhen a physical iOS device is selected. - Env sniff — reads
VITE_*fromprocess.env, lets you override per-key in DevShell, exposesuseEnv("VITE_FOO")reactive hook. - Build script —
bun run todo:build:apkinjectsprodRootTargetvia--dart-define=ROOT_TARGET=…at build time. - Interactive
bun run todo— prompts for device, sets upadb reverseif Android physical, launches server + vite + offers Flutter via DevShell.
Docs
| File | Topic |
|---|---|
docs/architecture.md |
Layout + extension points |
docs/devshell.md |
DevShell tabs, controls, persistence, native-mode overlay |
docs/flutter-shell.md |
Flutter integration, ROOT_TARGET resolution, cache server, prod builds, debug checklist |
docs/integration-guide.md |
Plug into a Vite project from scratch |
docs/bridge-protocol.md |
Wire format + message kinds + handshake |
docs/store-api.md |
Local-first store CRUD + replay |
docs/extension-api.md |
Custom bridge handlers, store schemas |
docs/file-map.md |
Layout reference |
docs/conventions.md |
Naming, patterns, style |
docs/glossary.md |
Domain terms |
Status
Pre-1.0. Local-only, no remote. APIs may shift.