App Store Readiness Code Review
Overview
Systematic code review process for applications targeting Apple App Store, Google Play, or desktop distribution. Identifies crash risks, security vulnerabilities, resource leaks, and compliance issues that cause rejection or poor user experience.
This skill now includes a mandatory cross-repo consistency pass for macOS app websites + README licensing language before release. This skill also enforces MimikaCODE production UX baselines (system logs, queue/history/models/settings/file-path surfaces) for both existing projects and newly created projects.
Repository Layout (Mandatory in This Workspace)
All macOS app projects must follow this structure under artifacts/code:
artifacts/code/<AppName>PRJ/<AppName>CODE- source code repositoryartifacts/code/<AppName>PRJ/<AppName>WEB- static website repository (moved fromartifacts/all-web)
For licensing and legal checks, always use these surfaces:
- README surface:
<AppName>CODE/README.md - Flutter app surface:
<AppName>CODE/flutter_app/(legal screens + bundled app resources) - Website surface:
<AppName>WEB/(index.html,license.html,privacy.html,terms.html,privacy-consent.js)
Do not create or update app sites in artifacts/all-web for these macOS apps.
When to Use
- Before App Store/Play Store submission
- Before any production release
- When user says "ship", "release", "production ready", "App Store"
- After major feature completion
- When reviewing cross-platform apps (Flutter, React Native, etc.)
iOS/iPad Baseline (Current)
For iOS/iPad submissions, enforce these before final sign-off:
- Run
bash ./skills/osx-ios/scripts/check_ios_dist.sh --app-root <APP_ROOT>and resolve allFAILfindings - Archive/upload baseline matches current App Store Connect tooling requirements
- TestFlight constraints checked (tester caps, build age, beta review flow)
- Screenshot coverage satisfies current iPhone + iPad minimum requirements
- Privacy manifest and required-reason API declarations validated
Review Categories
Review ALL categories systematically. Do not skip any.
digraph review_flow {
rankdir=TB;
node [shape=box];
"Start Review" -> "1. Crash Prevention";
"1. Crash Prevention" -> "2. Resource Management";
"2. Resource Management" -> "3. Network & API";
"3. Network & API" -> "4. Security";
"4. Security" -> "5. Data Persistence";
"5. Data Persistence" -> "6. Platform Compliance";
"6. Platform Compliance" -> "7. Error Handling";
"7. Error Handling" -> "8. MCP Integration (macOS)";
"8. MCP Integration (macOS)" -> "9. Performance";
"9. Performance" -> "10. Product Information";
"10. Product Information" -> "Generate Report";
}
Severity Classification
| Severity | Definition | Action |
|---|---|---|
| Critical | Will cause crashes, data loss, or rejection | Must fix before submission |
| High | Likely to cause issues under normal use | Should fix before submission |
| Medium | Edge cases, degraded experience | Fix in next release |
| Low | Code quality, best practices | Nice to have |
1. Crash Prevention Checklist
Flutter/Dart
- All async callbacks check
mountedbeforesetState() -
StreamSubscriptioncancelled indispose() -
Timercancelled indispose() -
AnimationControllerdisposed -
TextEditingControllerdisposed -
ScrollControllerdisposed -
FocusNodedisposed - Null safety: no force unwraps (
!) without guaranteed non-null - List/Map access with bounds checking or
.elementAtOrNull()
Flutter UI Patterns (Reference: flutter-python-fullstack)
- Theme: Uses
ColorScheme.fromSeed()with Material 3 - Dark mode: Supports
ThemeMode.system(respects OS preference) - Backend check: Health check on startup with loading/disconnected states
- Bundled backend autostart: If backend is down and app is bundled, UI attempts backend startup automatically (no manual CLI prerequisite for end users)
- Startup status UX: UI shows backend startup progress/status (for example: starting/waiting/failed)
- Exit shutdown hook: App intercepts desktop window-close/exit requests and runs graceful backend shutdown before process exit
- Shutdown UX: During close, app shows a blocking "Stopping server/backend..." progress dialog until shutdown finishes or timeout path is handled
- Production messaging: Disconnected-state copy does not instruct end users to run terminal commands
- Stats polling: Uses
Future.doWhile()withmountedguard - Status chips: Color-coded (green/orange/red) using
withValues(alpha:) - Deprecated APIs: No
withOpacity()(usewithValues(alpha:)instead) - ApiService: Centralized HTTP client with typed endpoints
- System log visibility: App exposes a user-visible system log panel (not only startup status text)
- System log actions: Users can copy logs and export logs directly from UI controls
- Footer log console: App includes a footer system-log area that is collapsible and resizable
- Footer parity: Footer log area also provides copy/export actions without navigating to settings
- Log export surface: Backend provides a plain-text system log export endpoint (separate from full diagnostics bundle)
- Job Queue surface: App has a visible job queue with live per-job status (
queued,processing,paused,cancelling,completed,failed,cancelled), queue position, and controls (pause,resume,cancel,delete) - Persistent Job History: Job history persists across app restarts with metadata (created time, model/engine, status, chunk progress, timing metrics, output path/URLs)
- Jobs History playback: App has a jobs-history UI page that supports audio/video playback plus save/download and open-in-folder actions for generated outputs
- Queue event sync: Queue/history UI updates live from websocket events (
job_created,job_update,job_completed,job_failed,job_cancelled) - File path visibility: Generation results and history rows show full output file paths with an
Open Folder/Reveal in Finderaction
iOS/Swift
- No force unwraps (
!) on optionals from external data -
weak selfin closures to prevent retain cycles -
deinitcalled (add print to verify during testing) - No unhandled
fatalError()orpreconditionFailure()
Android/Kotlin
- Null checks on Intent extras
- Activity lifecycle handled (no operations on destroyed activity)
- Fragment lifecycle handled
- No
!!on nullable external data
Backend/Python
- All exceptions caught at API boundary
- No bare
except:clauses (catch specific exceptions) - Thread safety for shared resources
- Connection pool limits configured
2. Resource Management Checklist
Memory Leaks
- Large objects released when not needed
- Image/media caching bounded
- Listeners/observers removed
- Background tasks cancelled on screen exit
- File handles closed in finally blocks
- Voice-clone pipelines profiled with Instruments (Allocations + Leaks) for full clone lifecycle (load model -> clone -> teardown)
- Add standalone clone regression tests using
NatashaandSuzanvoices to detect runaway memory growth or unreleased buffers
File System
- Temp files cleaned up
- File existence checked before read
- File permissions checked
- Path sanitization (no
../injection) - Disk space checked before large writes
- Runtime writes never target
.app/Contents/...or mounted.dmgpaths - Mutable runtime storage uses user-writable locations (
~/Library/Application Support/<App>,~/Library/Caches/<App>,~/Library/Logs/<App>)
Audio/Video
- Players disposed when done
- Audio session properly configured
- Background audio handled correctly
- Interruption handling (phone calls)
3. Network & API Checklist
Timeouts
- All HTTP requests have timeout configured
- Reasonable timeout values (10-30s for normal, 60-120s for uploads)
- Timeout errors handled gracefully
Error Handling
- Network unavailable handled
- Server errors (5xx) handled
- Client errors (4xx) handled with user feedback
- Malformed response handled
- Empty response handled
Resilience
- Retry logic with exponential backoff
- Circuit breaker for failing services
- Offline mode / cached data fallback
- Request cancellation on screen exit
Configuration
- Base URL configurable (not hardcoded localhost)
- API version handling
- Certificate pinning (if required)
- Bundled desktop apps can fully start backend without any external shell command
- Port-conflict path handled (if port already bound, user gets clear action instead of silent failure)
4. Security Checklist
Input Validation
- All user input validated
- Path traversal prevention (
../) - SQL injection prevention (parameterized queries)
- XSS prevention (output encoding)
- File type validation for uploads
Authentication
- Tokens stored securely (Keychain/Keystore)
- Token refresh logic
- Session expiration handling
- Logout clears all sensitive data
Network Security
- HTTPS only (no HTTP except localhost)
- CORS configured properly (not
*in production) - Sensitive data not logged
- API keys not in source code
Data Protection
- Sensitive data encrypted at rest
- No sensitive data in logs
- No sensitive data in crash reports
- Clipboard cleared after paste of sensitive data
5. Data Persistence Checklist
Database
- Schema migrations for updates
- Database connection pooling
- Thread-safe access (locking or connection per thread)
- Backup/restore capability
- Corruption recovery
- Database path resolves to user-writable runtime directory (not app bundle path)
Preferences/Settings
- Default values for all settings
- Settings validation on load
- Settings migration for app updates
Cache
- Cache size limits
- Cache expiration
- Cache invalidation logic
- Graceful handling of corrupted cache
- ML/model cache path is app-scoped for bundled builds (avoid accidental reuse of developer/global cache unless explicitly intended)
- Model-download detection logic honors runtime cache environment variables (
HUGGINGFACE_HUB_CACHE/HF_HOME/XDG_CACHE_HOME)
6. Platform Compliance Checklist
Apple App Store
- Privacy manifest (PrivacyInfo.xcprivacy) present
- Required-reason API declarations and third-party SDK manifests validated
- Required device capabilities declared
- App Transport Security configured
- No private API usage
- Proper entitlements configured
- App icons all sizes present
- Launch screen configured
- Build uploaded with current supported Xcode/SDK baseline
- TestFlight readiness verified (internal/external path + beta review expectations)
- iPhone and iPad screenshot requirements satisfied for enabled device families
Google Play
- Target SDK meets requirements
- Permissions declared and justified
- Data safety form ready
- 64-bit support
- App bundle (not APK)
macOS App Store
- Sandboxing configured
- Hardened runtime enabled
- Notarization ready
- Entitlements minimal and justified
macOS Distribution
- DMG builder script present (
scripts/build_dmg.sh) - DMG includes app bundle, Applications symlink, and background image
-
hdiutilfallback packages the DMG staging directory (not only the.app) so Applications symlink survives fallback builds - Code signing for DMG distribution
- Notarization of DMG for Gatekeeper
- If DMG is unsigned, release notes + README + website include explicit Gatekeeper bypass steps with concrete date and
Open Anywaypath - Volume name and window layout configured
- SHA256 hash generated alongside DMG (
.dmg.sha256) - Version extracted from centralized version file
- DMG root includes
LICENSE(source) andBINARY-LICENSE.txt(binary/EULA) - App bundle embeds
Contents/Resources/LICENSEandContents/Resources/BINARY-LICENSE.txt - DMG license agreement configured (when supported by the DMG toolchain)
- Bundled-app smoke test validates
GET /api/health,GET /api/pdf/list, and directGET /pdf/<bundled-file>after launch from/Applications - Bundled PDF/runtime assets resolve via app-relative paths (no hardcoded source checkout paths)
Bundled Python Backend (Mandatory for macOS Desktop Distribution)
- Backend process is launched by the app itself on first run (no terminal dependency for end users)
- Launch uses bundled Python runtime, not system Python
- Backend startup path works when app is run from
/Applicationsand does not rely on source checkout paths - Backend runtime env config sets app-specific writable paths for logs/data/outputs/cache
- Backend does not require writing launcher logs into app bundle directories
- Backend model/cache env vars are set for app-scoped storage (
HF_HOME,HUGGINGFACE_HUB_CACHE,TRANSFORMERS_CACHE) - Backend health check retries include clear startup status and failure state
- First-launch UI includes explicit startup/waiting log state while bundled backend warms up
- Disconnected-state primary action is a user-safe restart flow (for example
Restart Server) and avoids shell-command instructions - Backend port-conflict path is handled explicitly (detect in-use port, prompt/confirm stop conflicting process, then retry)
- First-run behavior tested with no existing localhost backend process running
- Closing the app window must terminate bundled backend child processes (no orphan backend after UI exit)
Project Scripts (Reference: flutter-python-fullstack pattern)
- Control script (
bin/appctl):appctl up- Start all servicesappctl down- Stop all servicesappctl status- Show running/stopped with colorsappctl logs- Tail log filesappctl clean- Clean logs and temp files
- Install script (
install.sh):- Check/install dependencies (Homebrew, Flutter, etc.)
- Create virtual environments
- Download required models
- Colored output with status indicators
- Diagnostic script (
issues.sh):- System info (OS, architecture, disk space)
- Tool versions (Flutter, Python, git)
- Port status checks
- Network/health checks
- Last 50 lines of runtime logs
- Timestamped output file
Release Scripts (Mandatory for All macOS Apps)
Every macOS app MUST have a scripts/release.sh that automates the full release workflow. Manual releases are error-prone and forbidden.
Release Script Requirements
- Release script (
scripts/release.sh) exists and is executable - Script extracts version from
pubspec.yamlautomatically (no hardcoded versions) - Script supports
--uploadflag for GitHub release upload - Script supports
--sync-websiteflag for website download link updates - Script generates SHA256 checksum alongside DMG
- Script creates or updates GitHub release for the current tag (never leave tag-only/empty release pages)
- Script uploads full asset set: DMG + DMG SHA256 + source ZIP + source ZIP SHA256 + release notes + release notes SHA256
- Script updates website download URLs with new version
- Script updates website download URLs to direct DMG asset links (not generic release listing pages)
- Script commits and pushes website changes automatically
- Script provides clear success/failure output with colored status
Version Advancement Protocol
- Version follows semantic versioning:
MAJOR.MINOR.PATCH(e.g.,1.0.0,1.1.0,2.0.0) - Version is stored in a single source of truth:
pubspec.yamlfor Flutter apps - Build number increments with each release (e.g.,
1.0.0+1→1.0.0+2) - Release tag format:
v{VERSION}(e.g.,v1.0.0,v1.1.0) - Never reuse version numbers - always increment
- PATCH version for bug fixes (1.0.0 → 1.0.1)
- MINOR version for new features (1.0.0 → 1.1.0)
- MAJOR version for breaking changes (1.0.0 → 2.0.0)
Release Script Pattern
#!/usr/bin/env bash
# scripts/release.sh - Full Release Script
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
WEBSITE_DIR="$(dirname "$PROJECT_DIR")/${APP_NAME}WEB"
# Extract version from pubspec.yaml
VERSION=$(grep 'version:' "$PROJECT_DIR/pubspec.yaml" | head -1 | cut -d'+' -f1 | cut -d':' -f2 | xargs)
# Parse flags
UPLOAD_TO_GITHUB=false
SYNC_WEBSITE=false
for arg in "$@"; do
case $arg in
--upload) UPLOAD_TO_GITHUB=true ;;
--sync-website) SYNC_WEBSITE=true ;;
esac
done
# 1. Build DMG
"$SCRIPT_DIR/build_dmg.sh" || "$SCRIPT_DIR/build-dmg.sh"
# 2. Generate SHA256
DMG_PATH="$PROJECT_DIR/build/${APP_NAME}-${VERSION}.dmg"
shasum -a 256 "$DMG_PATH" > "$DMG_PATH.sha256"
# 3. Upload to GitHub (if --upload)
if [ "$UPLOAD_TO_GITHUB" = true ]; then
TAG="v$VERSION"
if ! gh release view "$TAG" &> /dev/null; then
gh release create "$TAG" --title "$APP_NAME $VERSION" --notes "Release notes..." --draft
fi
gh release upload "$TAG" "$DMG_PATH" "$DMG_PATH.sha256" --clobber
fi
# 4. Sync website (if --sync-website)
if [ "$SYNC_WEBSITE" = true ]; then
sed -i '' -E "s|/releases/download/v[0-9.]+/${APP_NAME}-[0-9.]+|/releases/download/v$VERSION/${APP_NAME}-$VERSION|g" "$WEBSITE_DIR/index.html"
cd "$WEBSITE_DIR"
git add index.html && git commit -m "Update to v$VERSION" && git push
fi
Release Checklist (Execute in Order)
- Verify all tests pass
- Update version in
pubspec.yamlif needed - Run
./scripts/release.sh --upload --sync-website - Verify DMG created and checksum generated
- Verify GitHub release exists for the tag and is not empty
- Verify release assets uploaded (DMG, DMG SHA256, source ZIP, source ZIP SHA256, release notes, release notes SHA256)
- If unsigned/not notarized, verify release notes include the Gatekeeper section with current date and launch steps
- Verify website download links updated and point directly to current DMG asset URL
- Verify website changes committed and pushed
- Test DMG direct-download URL (for example with
curl -I -L) and confirm HTTP 200 - Run fresh-user smoke test (no old app in
/Applications, no pre-downloaded model cache assumptions)
Release Script Red Flags
| Issue | Pattern | Fix |
|---|---|---|
| No release script | Manual DMG + upload | Create scripts/release.sh |
| Hardcoded version | VERSION="1.0.0" |
Extract from pubspec.yaml |
| No --upload flag | Separate manual upload | Add GitHub release upload |
| No --sync-website | Manual website edit | Add website URL update |
| No checksum | DMG only | Generate .sha256 file |
| Empty release page | Tag exists but no assets | Enforce upload of full asset set in release.sh |
| Missing source/notes artifacts | DMG uploaded without source zip/release notes checksums | Upload source ZIP + notes + both SHA256 files |
| Non-direct download links | Website points to generic /releases page |
Point website CTAs to /releases/download/<tag>/<asset>.dmg |
| No website commit | Website not updated | Auto-commit and push |
| Version reuse | Same tag twice | Always increment version |
General
- Version number format correct
- Build number incremented
- Release notes prepared
- Screenshots current
7. Error Handling Checklist
User Feedback
- All errors show user-friendly message
- Error messages actionable (what user can do)
- No technical jargon in user-facing errors
- Loading states for all async operations
- Empty states for lists
Logging
- Errors logged with context
- No sensitive data in logs
- Log levels appropriate
- Crash reporting configured
Recovery
- Retry option for transient failures
- Data preserved on error
- App state recoverable after crash
- Graceful degradation when features unavailable
8. MCP Tool Integration Checklist (Required for macOS Apps)
macOS apps MUST expose full functionality via MCP (Model Context Protocol) tools to enable Claude integration. This ensures AI assistants can interact with the app programmatically.
MCP Server Requirements
- MCP server script exists at
bin/<appname>_mcp_server.pyor similar - Server implements JSON-RPC 2.0 over HTTP protocol
- Server handles MCP methods:
initialize,tools/list,tools/call - Server binds to configurable host/port (default:
127.0.0.1:80XX) - Server logs to
runs/logs/<appname>_mcp_server.logwith rotation - Backend URL configurable via environment variable (e.g.,
<APPNAME>_BACKEND_URL)
MCP Tool Definitions
-
MCP_TOOLSlist contains all available tools - Each tool has required keys:
name,description,inputSchema - Tool names are unique and follow
<domain>_<action>pattern (e.g.,tts_generate_kokoro) -
inputSchemais a valid JSON Schema withtype: "object" - Required parameters listed in
inputSchema.requiredarray - Tool descriptions are clear and explain what the tool does
Required MCP Tools (minimum set)
-
health_check- Check if backend is running and healthy -
<domain>_statusorsystem_info- Get system/service information -
<domain>_list_*- List available resources (voices, models, files, etc.) -
<domain>_<primary_action>- Core functionality (generate, process, create)
MCP Tool Schema Pattern
MCP_TOOLS = [
{
"name": "app_speak",
"description": "Generate and play speech with specified text and voice.",
"inputSchema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text to speak"},
"speaker": {"type": "string", "description": "Voice/speaker name"},
"quality_mode": {
"type": "string",
"enum": ["fast", "balanced", "quality"],
"description": "Quality mode (fast=realtime)"
}
},
"required": ["text"]
}
},
{
"name": "app_speak_streaming",
"description": "Generate speech with sentence-by-sentence streaming.",
"inputSchema": {
"type": "object",
"properties": {
"text": {"type": "string", "description": "Text to speak (supports multiple sentences)"}
},
"required": ["text"]
}
},
{
"name": "app_status",
"description": "Check service status and available resources.",
"inputSchema": {"type": "object", "properties": {}}
}
]
HTTP API Parity
- All MCP tools have corresponding HTTP API endpoints
- HTTP API follows RESTful conventions:
GET /api/<domain>/status- Status checkGET /api/<domain>/<resources>- List resourcesPOST /api/<domain>/<action>- Perform actionsDELETE /api/<domain>/<resource>/<id>- Delete resources
- HTTP API example (curl):
# Generate speech
curl -X POST http://localhost:8000/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello", "speaker": "default", "quality_mode": "balanced"}'
# Check status
curl http://localhost:8000/status
# List voices
curl http://localhost:8000/voices
MCP Server Handler Pattern
class MCPHandler(BaseHTTPRequestHandler):
def do_POST(self):
# Parse JSON-RPC request
obj = json.loads(self.rfile.read(...))
method = obj.get("method")
params = obj.get("params") or {}
if method == "initialize":
# Return server info and capabilities
return {"protocolVersion": "...", "serverInfo": {...}, "capabilities": {...}}
if method in ("tools/list", "tools.list"):
return {"tools": MCP_TOOLS}
if method in ("tools/call", "tools.call"):
result = handle_tool_call(params.get("name"), params.get("arguments"))
return {"content": [{"type": "text", "text": result}]}
MCP Tests
- Unit tests for tool definitions (
test_mcp_server.py) - Test each tool has required keys
- Test tool names are unique
- Test tool call handler dispatches correctly
- Test JSON-RPC protocol handling (initialize, tools/list, tools/call)
- Test error handling for unknown tools and malformed requests
Claude Code Configuration
- MCP server registered in Claude Code settings or documented for user setup
- Configuration example provided in README:
{
"mcpServers": {
"<appname>": {
"command": "python3",
"args": ["/path/to/bin/<appname>_mcp_server.py", "--port", "80XX"],
"env": {
"<APPNAME>_BACKEND_URL": "http://localhost:8000"
}
}
}
}
MCP UI Screen (Required for macOS Apps with MCP)
Every macOS app with MCP integration MUST include a dedicated MCP management screen accessible from Settings or main navigation.
MCP Screen Structure
- MCP screen exists at
lib/pages/mcp_page.dartorlib/screens/mcp_screen.dart - MCP screen accessible from Settings page or main navigation sidebar
- MCP screen header shows "MCP Integration" or "Claude Integration" title
MCP Server Status Section
- Server status indicator (Running/Stopped/Error) with color-coded chip
- Server host and port display (e.g.,
127.0.0.1:8087) - Start/Stop server toggle or buttons
- Server uptime display (optional)
- Last activity timestamp (optional)
Available Tools Section
- List of all MCP tools with names and descriptions
- Tool count badge (e.g., "10 tools available")
- Expandable tool cards showing:
- Tool name (e.g.,
quantum_run_benchmark) - Tool description
- Input parameters (from inputSchema)
- Required vs optional parameters indicator
- Tool name (e.g.,
- Tool category grouping (optional, e.g., "System", "Benchmarks", "Queue")
Configuration Section
- Backend URL field (editable, defaults to environment variable)
- MCP server port field (editable)
- Auto-start MCP server toggle
- Connection test button with success/failure feedback
Claude Code Setup Section
- Copy-to-clipboard button for Claude Code MCP configuration JSON
- Pre-filled configuration template with correct paths
- Instructions for adding to Claude Code settings
- Link to Claude Code MCP documentation (if available)
MCP Logs Section (Optional but Recommended)
- Recent MCP server log entries (last 20-50 lines)
- Log level filter (Debug/Info/Warning/Error)
- Clear logs button
- Export logs button
MCP Screen Pattern (Reference Implementation)
// lib/pages/mcp_page.dart
class McpPage extends StatefulWidget {
const McpPage({super.key});
@override
State<McpPage> createState() => _McpPageState();
}
class _McpPageState extends State<McpPage> {
bool _serverRunning = false;
List<Map<String, dynamic>> _tools = [];
@override
void initState() {
super.initState();
_loadMcpStatus();
_loadTools();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
_buildHeader(),
const SizedBox(height: 24),
// Server Status Card
_buildServerStatusCard(),
const SizedBox(height: 24),
// Available Tools Card
_buildToolsCard(),
const SizedBox(height: 24),
// Configuration Card
_buildConfigCard(),
const SizedBox(height: 24),
// Claude Code Setup Card
_buildClaudeCodeSetupCard(),
],
),
);
}
Widget _buildServerStatusCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.dns, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
const Text('MCP Server Status', style: TextStyle(fontWeight: FontWeight.bold)),
const Spacer(),
Chip(
label: Text(_serverRunning ? 'Running' : 'Stopped'),
backgroundColor: _serverRunning ? Colors.green.shade100 : Colors.grey.shade200,
labelStyle: TextStyle(color: _serverRunning ? Colors.green.shade800 : Colors.grey.shade700),
),
],
),
const SizedBox(height: 16),
Text('Host: 127.0.0.1:8087'),
const SizedBox(height: 8),
Row(
children: [
FilledButton.icon(
onPressed: _toggleServer,
icon: Icon(_serverRunning ? Icons.stop : Icons.play_arrow),
label: Text(_serverRunning ? 'Stop Server' : 'Start Server'),
),
const SizedBox(width: 8),
OutlinedButton.icon(
onPressed: _testConnection,
icon: const Icon(Icons.refresh),
label: const Text('Test Connection'),
),
],
),
],
),
),
);
}
Widget _buildToolsCard() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.build, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
const Text('Available Tools', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(width: 8),
Chip(label: Text('${_tools.length} tools')),
],
),
const SizedBox(height: 16),
..._tools.map((tool) => ExpansionTile(
title: Text(tool['name']),
subtitle: Text(tool['description'], maxLines: 1, overflow: TextOverflow.ellipsis),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Parameters:', style: TextStyle(fontWeight: FontWeight.bold)),
// Show inputSchema properties
],
),
),
],
)),
],
),
),
);
}
Widget _buildClaudeCodeSetupCard() {
final config = '''
{
"mcpServers": {
"appname": {
"command": "python3",
"args": ["/path/to/bin/appname_mcp_server.py"],
"env": {
"APPNAME_BACKEND_URL": "http://localhost:8000"
}
}
}
}''';
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.terminal, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 12),
const Text('Claude Code Setup', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 16),
const Text('Add this configuration to your Claude Code settings:'),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: SelectableText(config, style: const TextStyle(fontFamily: 'monospace', fontSize: 12)),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: () => Clipboard.setData(ClipboardData(text: config)),
icon: const Icon(Icons.copy),
label: const Text('Copy Configuration'),
),
],
),
),
);
}
}
MCP UI Accessibility
- All MCP tool names readable by screen readers
- Status indicators have text alternatives (not color-only)
- Copy buttons have clear labels
- Expandable sections properly announce state
Common MCP Integration Issues
| Issue | Pattern | Fix |
|---|---|---|
| Missing tools | No MCP server | Create bin/<appname>_mcp_server.py |
| No tool schemas | Tools missing inputSchema | Add JSON Schema for each tool |
| Backend not proxied | MCP calls backend directly | Use _call_backend() helper pattern |
| No error handling | Exceptions crash server | Wrap tool calls in try/except |
| No logging | Silent failures | Add rotating file logger |
| Hardcoded ports | Port conflicts | Make host/port configurable |
9. Performance Checklist
Startup
- Cold start < 3 seconds
- No blocking operations on main thread at startup
- Lazy loading for non-essential features
UI Responsiveness
- No jank (dropped frames)
- Heavy work on background threads
- UI updates batched
- Large lists virtualized
Memory
- Memory usage stable (no growth over time)
- Large assets loaded on demand
- Image resolution appropriate for display
Battery
- Location updates minimal
- Background refresh minimal
- No unnecessary polling
10. Product Information Checklist
Required App Information
- Version number displayed in app (Settings or About page)
- Build number accessible for support purposes
- Centralized version file pattern:
// lib/version.dart const String appVersion = "2026.02.1"; const int buildNumber = 1; const String versionName = "Initial Release"; String get versionString => "$appVersion (build $buildNumber)"; - About page present and accessible from main navigation
About Page Contents
- App logo/icon prominently displayed
- App name as headline
- Version number from centralized version file (e.g.,
version.dart) - Version codename (optional, e.g., "Sunrise")
- Brief app description/tagline
- Links section with buttons for:
- Website: https://qneura.ai/apps.html
- GitHub repository (if open source)
- Report Issue / Bug tracker
- License link (in-app License screen or repo license overview)
- Credits/Powered By section listing dependencies with clickable links
- Footer with:
- License type (e.g., "Licensed under BSL-1.1")
- Binary distribution restriction summary (if applicable)
- Copyright: "© [YEAR] Qneura.ai"
- Clickable Qneura.ai link to https://qneura.ai
Legal Pages
- Privacy Policy page (required by App Store)
- Terms of Service / EULA page
- License overview page (source vs binary terms, plain English)
- Binary distribution license / EULA page (DMG/executable terms)
- Repo
LICENSEfile present and referenced in README - All legal pages accessible from Settings or About
- Website legal pages exist in
<AppName>WEB:index.html,license.html,privacy.html,terms.html
License Integration (Source vs Binary)
- Create
<AppName>CODE/LICENSEfor source code (BSL-style, parameterized) - Create
<AppName>CODE/BINARY-LICENSE.txt(orEULA-DMG.txt) for DMG/executable - Add
<AppName>CODE/LICENSE.md(or<AppName>CODE/docs/licensing.md) explaining source vs binary terms - Update
<AppName>CODE/README.mdLicense section with links toLICENSE,BINARY-LICENSE.txt, andLICENSE.md - UI integration: About footer mentions license + binary restriction; Legal section links to License page
- Terms of Service includes binary distribution restrictions and link to
BINARY-LICENSE.txt - Bundle both license files into the app (
Contents/Resources/) and DMG root - Website license page
<AppName>WEB/license.htmlmatches README and repo license files
Three-Surface License Completeness (Required)
- Website licenses are written and published in
<AppName>WEB/license.html(not placeholders) and clearly state source + binary terms. - Website hero/meta references to licensing (
Open Source,License) link to<AppName>WEB/license.html. - Flutter macOS app licenses are written and visible in
<AppName>CODE/flutter_applegal screens; source + binary terms are discoverable in-app. - Flutter macOS app bundle contains written license files:
Contents/Resources/LICENSEandContents/Resources/BINARY-LICENSE.txt. - Git repository licenses are written and versioned in
<AppName>CODE:LICENSE+BINARY-LICENSE.txt+ license overview doc (LICENSE.mdordocs/licensing.md). -
<AppName>CODE/README.mdtop section and License section both link to the repo license files and website license page. - Release is blocked if any one of the three surfaces (website, app, repo) is missing written license content.
Cross-Repo Website + README Consistency (Mandatory)
- Use this canonical sentence (copy exactly, replace app name only):
License: Source code is licensed under Business Source License 1.1 (BSL-1.1), and binary distributions are licensed under the [APP_NAME] Binary Distribution License. See LICENSE, BINARY-LICENSE.txt, and the website License page.
- Place the canonical sentence in README near the top, immediately after the primary app-description paragraph.
- Keep the binary-availability sentence explicit in README and website CTA/meta:
The codebase is cross-platform, but we currently provide macOS binaries only.
- Link
we currently provide macOS binaries only.in README top block to the app website home page. - Link
Open Sourcelabels in website hero/meta rows tolicense.html(not plain text). - In website hero badges/benefits, remove
Lifetime Updatesand avoid reintroducing it in future copy revisions. - Add a primary
Download for macOSCTA on the left hero column beforeGet Started/View on GitHubstyle links. - Primary nav
Downloadand heroDownloadCTA both point directly to the current DMG asset URL for the latest tag. - Ensure each macOS app site under
<AppName>WEBuses the same wording pattern (only app name varies). - Verify
<AppName>CODE/LICENSE,<AppName>CODE/BINARY-LICENSE.txt, and<AppName>WEB/license.htmlall exist and are mutually consistent. - In multi-repo updates, stage and commit only intended files from
<AppName>CODEand<AppName>WEBwhen worktrees are already dirty. - Keep
README.mdand websiteSupported Modelstables fully synchronized with the app's runtime model registry (include aliases, quantized variants, and namespace-specific surfaces like CosyVoice/Supertonic when present). - Keep README pregenerated-example index synchronized with shipped files in
backend/data/pregenerated(no missing demos). - If DMG is unsigned/not notarized,
…(truncated)