一键导入
local-preview
Build, serve, and tunnel the Flutter web app for remote testing, and query prod RemoteLog from `client_logs` to debug issues users hit in production.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Build, serve, and tunnel the Flutter web app for remote testing, and query prod RemoteLog from `client_logs` to debug issues users hit in production.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | local-preview |
| description | Build, serve, and tunnel the Flutter web app for remote testing, and query prod RemoteLog from `client_logs` to debug issues users hit in production. |
Two workflows used together to iterate on features:
RemoteLog.log(...) into the failing path, query client_logs via Supabase MCP.Default: flutter build web --debug + Python static server. Debug builds run fast (~10–30s per rebuild after the first), and the Python server serves the pre-built files instantly — no compile-on-request latency. A release bundle is the fallback when you specifically need prod artifacts.
Why not
flutter run -d web-server? It compiles on-demand, which means the first browser reload after any code change sits for 10–30s waiting formain.dart.jsto be generated. On the tunnel this looks like a stuck loading spinner on the HTML play screen because the Flutter bundle hasn't finished downloading yet. A pre-built bundle avoids that entirely.
flutter build web --debug # ~10s incremental, ~60s cold
cd build/web && python3 -m http.server 8080 # serve in background
Run the Python server in background (run_in_background: true). Iterate:
flutter build web --debug — ~10s per rebuild.Skip/Play buttons on the HTML splash will feel instant because main.dart.js is already sitting on disk.
flutter build web --release # 1–2 min per change
cd build/web && python3 -m http.server 8080
Use this when testing:
beforeinstallprompt, display-mode: standalone)greping main.dart.js for specific strings)Preferred: Cloudflare (no bandwidth cap, no auth required for quick tunnels).
/home/joelc0193/.local/bin/cloudflared tunnel --url http://localhost:8080 --no-autoupdate
Grep the output for the generated URL:
grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' <background-output-file> | head -1
Important CORS note: the Supabase Edge Functions' CORS allowlist in supabase/functions/_shared/cors.ts already allows *.trycloudflare.com, *.ngrok-free.app, localhost, and 127.0.0.1. If you use a different tunnel host, add it there and redeploy the affected edge functions — otherwise the browser will block the preflight with "Failed to fetch".
Fallback: ngrok — has a bandwidth cap on the free tier. Binary at /home/joelc0193/.npm/_npx/094a17e86d981b10/node_modules/ngrok/bin/ngrok. URL discovery:
curl -s http://localhost:4040/api/tunnels | python3 -c "import json,sys; print(json.load(sys.stdin)['tunnels'][0]['public_url'])"
When the user loads the tunnel URL on a mobile browser, the origin (<id>.trycloudflare.com) is different from onemind.life. Their browser has no localStorage for this origin, which means:
redirect in lib/config/router.dart sees tutorial_completed = false and redirects / → /tutorial. HomeScreen never mounts.initState logs, etc.) won't run until the user taps through or skips the tutorial.If you're debugging something on Home and seeing zero log output, the tutorial redirect is the first suspect — not a code bug.
lib/.flutter build web --debug — ~10s incremental.If you edit .arb files (localization), run flutter gen-l10n before flutter build — the build doesn't regenerate them automatically.
flutter build web --release registers a service worker that aggressively caches the old bundle. To pick up a new release build in the browser:
When a change "isn't appearing", verify the server is really serving it before chasing client-side caches:
# Local file
grep -c "your-new-string-literal" build/web/main.dart.js
# Remote via tunnel — proves the public URL is serving the new bundle
curl -s https://<tunnel>.trycloudflare.com/main.dart.js | grep -c "your-new-string-literal"
Non-zero → the bundle is right; problem is client-side (SW cache, wrong URL, etc.). Zero → rebuild didn't include the change.
client_logsid bigint PK
user_id uuid auth.uid() at insert time
event text NOT NULL — category/tag, e.g. 'proposition_submit_error'
message text free-text summary
metadata jsonb arbitrary structured context
created_at timestamptz default now()
Project ID: ccyuxrtrklgpkzcryzpj (OneMind SaaS prod).
Service lives at lib/services/remote_log_service.dart. Import it:
import '../../services/remote_log_service.dart';
Call:
RemoteLog.log(
'proposition_submit_error', // event — use snake_case tags
e.toString(), // message
{ // metadata (optional)
'error_type': e.runtimeType.toString(),
'chat_id': chatId,
'round_id': roundId,
'content_length': content.length,
'stack': stack.toString().split('\n').take(12).join('\n'),
},
);
RemoteLog.log is silent-fail — it never throws and never blocks. Safe to call from UI paths.
catch block of the failing operation — include e.runtimeType, e.toString(), a truncated stack, and the domain IDs that matter for that operation.FunctionException, log e.status, e.details, e.reasonPhrase before rethrowing.Use the Supabase MCP tool mcp__supabase__execute_sql with project_id: ccyuxrtrklgpkzcryzpj:
SELECT id, event, message, metadata, created_at
FROM client_logs
WHERE event = 'proposition_submit_error'
AND created_at > now() - interval '15 minutes'
ORDER BY created_at DESC
LIMIT 20;
Common filters:
WHERE user_id = '<uuid>'WHERE created_at > now() - interval '1 hour'WHERE metadata->>'chat_id' = '123'WHERE metadata->>'error_type' = 'FunctionException'When an issue is resolved and you don't want the logging to keep running in prod:
RemoteLog.log(...) calls from Dart.DELETE FROM client_logs WHERE event = 'proposition_submit_error';
flutter build web --debug and cd build/web && python3 -m http.server 8080 (background).:8080; copy the *.trycloudflare.com URL.RemoteLog.log(...) at the suspected failure point.flutter build web --debug — ~10s — then tell the user to reload the tunnel URL.client_logs via MCP to see what actually happened.Background processes persist across Claude sessions. Before starting anything new:
pgrep -af "flutter|python3 -m http.server|cloudflared|ngrok" | head
Kill stragglers if the port is held by something stale:
pkill -f "flutter run -d web-server"
pkill -f "python3 -m http.server 8080"
pkill -f "cloudflared tunnel"
Cloudflare's quick tunnels rotate their URL on every restart, so if you kill and relaunch cloudflared, grep a fresh URL out of its new output and re-share it.