diff --git a/RFCP-Phase-2.1-Desktop-Complete.md b/RFCP-Phase-2.1-Desktop-Complete.md
new file mode 100644
index 0000000..cddf9f4
--- /dev/null
+++ b/RFCP-Phase-2.1-Desktop-Complete.md
@@ -0,0 +1,1085 @@
+# RFCP Phase 2.1: Desktop Application (Windows, Linux, macOS)
+
+**Date:** January 31, 2025
+**Type:** Packaging & Distribution
+**Estimated:** 16-20 hours
+**Priority:** HIGH β main product delivery
+
+---
+
+## π― Goal
+
+Package RFCP as standalone desktop application for Windows, Linux, and macOS. Fully offline after initial region download. No VPS, no MongoDB, no internet required.
+
+---
+
+## π Target Specs
+
+| Metric | Target |
+|--------|--------|
+| Installer size | 200-300 MB |
+| Installed size | 500MB - 1GB (without map data) |
+| With Ukraine region | ~3.5 GB |
+| **Platforms** | **Windows 10/11, Ubuntu 22.04+, macOS 12+** |
+| Offline | Full offline after region download |
+| GPU | Optional CUDA acceleration (Win/Linux) |
+
+---
+
+## ποΈ Architecture
+
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββ
+β RFCP Desktop App β
+βββββββββββββββββββββββββββββββββββββββββββββββββββ€
+β βββββββββββββββββββββββββββββββββββββββββββ β
+β β Electron Shell β β
+β β (Chromium + Node.js runtime) β β
+β ββββββββββββββββββββ¬βββββββββββββββββββββββ β
+β β β
+β ββββββββββββββββββββΌβββββββββββββββββββββββ β
+β β React Frontend (bundled) β β
+β ββββββββββββββββββββ¬βββββββββββββββββββββββ β
+β β http://localhost:8888 β
+β ββββββββββββββββββββΌβββββββββββββββββββββββ β
+β β FastAPI Backend (PyInstaller exe) β β
+β β ββββββββββββββββββββββββββββββββββ β β
+β β β Propagation Engine β β β
+β β β βββ CPU (NumPy/SciPy) β β β
+β β β βββ GPU (CuPy) [optional] β β β
+β β ββββββββββββββββββββββββββββββββββ β β
+β ββββββββββββββββββββ¬βββββββββββββββββββββββ β
+β β β
+β ββββββββββββββββββββΌβββββββββββββββββββββββ β
+β β Local Data Store β β
+β β βββ SQLite (projects, settings) β β
+β β βββ SRTM tiles (~25MB each) β β
+β β βββ OSM cache (buildings, water, veg) β β
+β β βββ Map tiles (optional offline maps) β β
+β βββββββββββββββββββββββββββββββββββββββββββ β
+βββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
+
+---
+
+## π Project Structure
+
+```
+rfcp/
+βββ frontend/ # React (existing)
+βββ backend/ # FastAPI (existing)
+βββ desktop/ # NEW β Electron app
+β βββ main.js # Electron main process
+β βββ preload.js # Context bridge
+β βββ splash.html # Loading screen
+β βββ package.json # Electron deps + build config
+β βββ assets/
+β βββ icon.ico # Windows icon (256x256)
+β βββ icon.png # Linux icon (512x512)
+β βββ icon.icns # macOS icon
+βββ installer/ # NEW β Build scripts
+β βββ build-win.sh # Windows build script
+β βββ build-linux.sh # Linux build script
+β βββ build-mac.sh # macOS build script
+β βββ build-all.sh # Build all platforms
+β βββ rfcp-server.spec # PyInstaller spec
+βββ docs/
+βββ scripts/
+βββ CLAUDE.md
+```
+
+---
+
+## π Installed Directory Structure
+
+### Windows
+```
+C:\Program Files\RFCP\
+βββ RFCP.exe # Electron app
+βββ resources/
+β βββ app.asar # Frontend bundle
+β βββ backend/
+β βββ rfcp-server.exe # PyInstaller bundle
+βββ Uninstall RFCP.exe
+
+%APPDATA%\RFCP\ # User data
+βββ rfcp.db # SQLite database
+βββ terrain/ # SRTM tiles
+βββ osm/ # OSM cache
+βββ projects/ # User projects
+```
+
+### Linux
+```
+/opt/RFCP/ # AppImage extracts here
+βββ rfcp # Main binary
+βββ resources/
+ βββ ...
+
+~/.local/share/RFCP/ # User data
+βββ rfcp.db
+βββ terrain/
+βββ osm/
+βββ projects/
+```
+
+### macOS
+```
+/Applications/RFCP.app/ # App bundle
+βββ Contents/
+ βββ MacOS/rfcp # Main binary
+ βββ Resources/
+ β βββ backend/
+ β βββ frontend/
+ βββ Info.plist
+
+~/Library/Application Support/RFCP/ # User data
+βββ rfcp.db
+βββ terrain/
+βββ osm/
+βββ projects/
+```
+
+---
+
+## β
Tasks
+
+### Task 2.1.1: Project Setup (1-2 hours)
+
+**Create `desktop/package.json`:**
+```json
+{
+ "name": "rfcp-desktop",
+ "version": "1.6.1",
+ "description": "RF Coverage Planner - Tactical Communications",
+ "main": "main.js",
+ "author": "UMTC Project",
+ "license": "MIT",
+ "scripts": {
+ "start": "electron .",
+ "dev": "NODE_ENV=development electron .",
+ "build": "electron-builder",
+ "build:win": "electron-builder --win",
+ "build:linux": "electron-builder --linux",
+ "build:mac": "electron-builder --mac",
+ "build:all": "electron-builder --win --linux --mac"
+ },
+ "dependencies": {
+ "electron-store": "^8.1.0"
+ },
+ "devDependencies": {
+ "electron": "^28.0.0",
+ "electron-builder": "^24.9.0"
+ },
+ "build": {
+ "appId": "one.eliah.rfcp",
+ "productName": "RFCP",
+ "copyright": "Copyright Β© 2025 UMTC Project",
+ "directories": {
+ "output": "dist",
+ "buildResources": "assets"
+ },
+ "files": [
+ "main.js",
+ "preload.js",
+ "splash.html"
+ ],
+ "extraResources": [
+ {
+ "from": "../frontend/dist",
+ "to": "frontend"
+ },
+ {
+ "from": "./backend-dist/${os}",
+ "to": "backend"
+ }
+ ],
+ "win": {
+ "target": ["nsis"],
+ "icon": "assets/icon.ico"
+ },
+ "nsis": {
+ "oneClick": false,
+ "allowToChangeInstallationDirectory": true,
+ "createDesktopShortcut": true,
+ "createStartMenuShortcut": true,
+ "shortcutName": "RFCP"
+ },
+ "linux": {
+ "target": ["AppImage", "deb"],
+ "icon": "assets/icon.png",
+ "category": "Science"
+ },
+ "mac": {
+ "target": ["dmg", "zip"],
+ "icon": "assets/icon.icns",
+ "category": "public.app-category.developer-tools",
+ "hardenedRuntime": false,
+ "gatekeeperAssess": false
+ },
+ "dmg": {
+ "title": "RFCP Installer",
+ "backgroundColor": "#1a1a2e",
+ "contents": [
+ { "x": 130, "y": 220 },
+ { "x": 410, "y": 220, "type": "link", "path": "/Applications" }
+ ]
+ }
+ }
+}
+```
+
+---
+
+### Task 2.1.2: Electron Main Process (3-4 hours)
+
+**desktop/main.js:**
+```javascript
+const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
+const { spawn, execSync } = require('child_process');
+const path = require('path');
+const fs = require('fs');
+const Store = require('electron-store');
+
+const store = new Store();
+let mainWindow;
+let splashWindow;
+let backendProcess;
+
+// Paths
+const isDev = process.env.NODE_ENV === 'development';
+
+const getResourcePath = (relativePath) => {
+ if (isDev) {
+ return path.join(__dirname, '..', relativePath);
+ }
+ return path.join(process.resourcesPath, relativePath);
+};
+
+const getDataPath = () => {
+ return path.join(app.getPath('userData'), 'data');
+};
+
+// Get backend executable name based on platform
+const getBackendExeName = () => {
+ switch (process.platform) {
+ case 'win32':
+ return 'rfcp-server.exe';
+ case 'darwin':
+ return 'rfcp-server';
+ case 'linux':
+ return 'rfcp-server';
+ default:
+ return 'rfcp-server';
+ }
+};
+
+// Ensure data directories exist
+function ensureDataDirs() {
+ const dirs = ['terrain', 'osm', 'projects', 'cache'];
+ const dataPath = getDataPath();
+
+ dirs.forEach(dir => {
+ const fullPath = path.join(dataPath, dir);
+ if (!fs.existsSync(fullPath)) {
+ fs.mkdirSync(fullPath, { recursive: true });
+ }
+ });
+
+ return dataPath;
+}
+
+// Create splash window
+function createSplashWindow() {
+ splashWindow = new BrowserWindow({
+ width: 400,
+ height: 300,
+ frame: false,
+ transparent: true,
+ alwaysOnTop: true,
+ resizable: false,
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false
+ }
+ });
+
+ splashWindow.loadFile(path.join(__dirname, 'splash.html'));
+ splashWindow.center();
+}
+
+// Start Python backend
+async function startBackend() {
+ const dataPath = ensureDataDirs();
+
+ const env = {
+ ...process.env,
+ RFCP_DATA_PATH: dataPath,
+ RFCP_DATABASE_URL: `sqlite:///${path.join(dataPath, 'rfcp.db')}`,
+ RFCP_HOST: '127.0.0.1',
+ RFCP_PORT: '8888',
+ };
+
+ if (isDev) {
+ // Development: run uvicorn
+ const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
+ backendProcess = spawn(pythonCmd, [
+ '-m', 'uvicorn',
+ 'app.main:app',
+ '--host', '127.0.0.1',
+ '--port', '8888',
+ '--reload'
+ ], {
+ cwd: path.join(__dirname, '..', 'backend'),
+ env,
+ stdio: ['ignore', 'pipe', 'pipe']
+ });
+ } else {
+ // Production: run PyInstaller bundle
+ const backendExe = path.join(
+ getResourcePath('backend'),
+ getBackendExeName()
+ );
+
+ // Make executable on Unix
+ if (process.platform !== 'win32') {
+ try {
+ fs.chmodSync(backendExe, '755');
+ } catch (e) {
+ console.log('chmod failed:', e);
+ }
+ }
+
+ backendProcess = spawn(backendExe, [], {
+ env,
+ stdio: ['ignore', 'pipe', 'pipe']
+ });
+ }
+
+ // Log backend output
+ backendProcess.stdout?.on('data', (data) => {
+ console.log(`[Backend] ${data}`);
+ });
+
+ backendProcess.stderr?.on('data', (data) => {
+ console.error(`[Backend Error] ${data}`);
+ });
+
+ backendProcess.on('error', (err) => {
+ console.error('Failed to start backend:', err);
+ dialog.showErrorBox('Backend Error', `Failed to start backend: ${err.message}`);
+ });
+
+ // Wait for backend to be ready
+ return new Promise((resolve) => {
+ const maxAttempts = 60; // 30 seconds
+ let attempts = 0;
+
+ const checkBackend = setInterval(async () => {
+ attempts++;
+
+ try {
+ const response = await fetch('http://127.0.0.1:8888/api/health/');
+ if (response.ok) {
+ clearInterval(checkBackend);
+ console.log('Backend ready!');
+ resolve(true);
+ }
+ } catch (e) {
+ // Not ready yet
+ }
+
+ if (attempts >= maxAttempts) {
+ clearInterval(checkBackend);
+ console.error('Backend failed to start in time');
+ resolve(false);
+ }
+ }, 500);
+ });
+}
+
+// Create main window
+function createMainWindow() {
+ const windowState = store.get('windowState', {
+ width: 1400,
+ height: 900
+ });
+
+ mainWindow = new BrowserWindow({
+ width: windowState.width,
+ height: windowState.height,
+ x: windowState.x,
+ y: windowState.y,
+ minWidth: 1024,
+ minHeight: 768,
+ show: false,
+ webPreferences: {
+ nodeIntegration: false,
+ contextIsolation: true,
+ preload: path.join(__dirname, 'preload.js')
+ },
+ icon: path.join(__dirname, 'assets',
+ process.platform === 'win32' ? 'icon.ico' : 'icon.png'
+ ),
+ title: 'RFCP - RF Coverage Planner',
+ titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
+ });
+
+ // Save window state on close
+ mainWindow.on('close', () => {
+ const bounds = mainWindow.getBounds();
+ store.set('windowState', bounds);
+ });
+
+ // Load frontend
+ if (isDev) {
+ mainWindow.loadURL('http://localhost:5173');
+ mainWindow.webContents.openDevTools();
+ } else {
+ mainWindow.loadFile(path.join(getResourcePath('frontend'), 'index.html'));
+ }
+
+ mainWindow.once('ready-to-show', () => {
+ if (splashWindow) {
+ splashWindow.close();
+ splashWindow = null;
+ }
+ mainWindow.show();
+ });
+
+ mainWindow.on('closed', () => {
+ mainWindow = null;
+ });
+
+ // Handle external links
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+ shell.openExternal(url);
+ return { action: 'deny' };
+ });
+}
+
+// App lifecycle
+app.whenReady().then(async () => {
+ createSplashWindow();
+
+ const backendStarted = await startBackend();
+
+ if (!backendStarted) {
+ const result = await dialog.showMessageBox({
+ type: 'error',
+ title: 'Startup Error',
+ message: 'Failed to start backend server.',
+ detail: 'Please check logs and try again. Click "Show Logs" to open the log folder.',
+ buttons: ['Quit', 'Show Logs']
+ });
+
+ if (result.response === 1) {
+ shell.openPath(app.getPath('logs'));
+ }
+
+ app.quit();
+ return;
+ }
+
+ createMainWindow();
+});
+
+app.on('window-all-closed', () => {
+ if (backendProcess) {
+ backendProcess.kill();
+ backendProcess = null;
+ }
+
+ if (process.platform !== 'darwin') {
+ app.quit();
+ }
+});
+
+app.on('activate', () => {
+ if (mainWindow === null) {
+ createMainWindow();
+ }
+});
+
+app.on('before-quit', () => {
+ if (backendProcess) {
+ backendProcess.kill();
+ }
+});
+
+// IPC Handlers
+ipcMain.handle('get-data-path', () => getDataPath());
+ipcMain.handle('get-app-version', () => app.getVersion());
+ipcMain.handle('get-platform', () => process.platform);
+
+ipcMain.handle('get-gpu-info', async () => {
+ // TODO: Implement GPU detection
+ return {
+ available: false,
+ name: null,
+ cudaVersion: null
+ };
+});
+
+ipcMain.handle('select-directory', async () => {
+ const result = await dialog.showOpenDialog(mainWindow, {
+ properties: ['openDirectory']
+ });
+ return result.filePaths[0] || null;
+});
+
+ipcMain.handle('select-file', async (event, options) => {
+ const result = await dialog.showOpenDialog(mainWindow, {
+ properties: ['openFile'],
+ filters: options?.filters || []
+ });
+ return result.filePaths[0] || null;
+});
+
+ipcMain.handle('save-file', async (event, options) => {
+ const result = await dialog.showSaveDialog(mainWindow, {
+ defaultPath: options?.defaultPath,
+ filters: options?.filters || []
+ });
+ return result.filePath || null;
+});
+
+ipcMain.handle('get-setting', (event, key) => store.get(key));
+ipcMain.handle('set-setting', (event, key, value) => store.set(key, value));
+
+ipcMain.handle('open-external', (event, url) => shell.openExternal(url));
+ipcMain.handle('open-path', (event, path) => shell.openPath(path));
+```
+
+---
+
+### Task 2.1.3: Preload Script (1 hour)
+
+**desktop/preload.js:**
+```javascript
+const { contextBridge, ipcRenderer } = require('electron');
+
+contextBridge.exposeInMainWorld('rfcp', {
+ // System info
+ getDataPath: () => ipcRenderer.invoke('get-data-path'),
+ getAppVersion: () => ipcRenderer.invoke('get-app-version'),
+ getPlatform: () => ipcRenderer.invoke('get-platform'),
+ getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
+
+ // Dialogs
+ selectDirectory: () => ipcRenderer.invoke('select-directory'),
+ selectFile: (options) => ipcRenderer.invoke('select-file', options),
+ saveFile: (options) => ipcRenderer.invoke('save-file', options),
+
+ // Settings (persistent)
+ getSetting: (key) => ipcRenderer.invoke('get-setting', key),
+ setSetting: (key, value) => ipcRenderer.invoke('set-setting', key, value),
+
+ // Shell
+ openExternal: (url) => ipcRenderer.invoke('open-external', url),
+ openPath: (path) => ipcRenderer.invoke('open-path', path),
+
+ // Platform info
+ platform: process.platform,
+ isDesktop: true,
+ isMac: process.platform === 'darwin',
+ isWindows: process.platform === 'win32',
+ isLinux: process.platform === 'linux',
+});
+```
+
+---
+
+### Task 2.1.4: Splash Screen (30 min)
+
+**desktop/splash.html:**
+```html
+
+
+
+
+ RFCP Loading
+
+
+
+ RFCP
+ RF Coverage Planner
+
+ Starting backend server...
+ v1.6.1
+
+
+```
+
+---
+
+### Task 2.1.5: PyInstaller Backend Bundle (3-4 hours)
+
+**backend/run_server.py (new entry point):**
+```python
+"""Entry point for PyInstaller bundle"""
+import os
+import sys
+
+# Set base path for PyInstaller
+if getattr(sys, 'frozen', False):
+ # Running as compiled
+ os.chdir(os.path.dirname(sys.executable))
+
+import uvicorn
+from app.main import app
+
+if __name__ == '__main__':
+ host = os.environ.get('RFCP_HOST', '127.0.0.1')
+ port = int(os.environ.get('RFCP_PORT', '8888'))
+
+ uvicorn.run(
+ app,
+ host=host,
+ port=port,
+ log_level='info',
+ )
+```
+
+**installer/rfcp-server.spec:**
+```python
+# -*- mode: python ; coding: utf-8 -*-
+import sys
+from pathlib import Path
+
+block_cipher = None
+backend_path = Path('../backend')
+
+a = Analysis(
+ [str(backend_path / 'run_server.py')],
+ pathex=[str(backend_path)],
+ binaries=[],
+ datas=[
+ (str(backend_path / 'app'), 'app'),
+ ],
+ hiddenimports=[
+ 'uvicorn.logging',
+ 'uvicorn.protocols.http',
+ 'uvicorn.protocols.http.auto',
+ 'uvicorn.protocols.http.h11_impl',
+ 'uvicorn.protocols.websockets',
+ 'uvicorn.protocols.websockets.auto',
+ 'uvicorn.lifespan',
+ 'uvicorn.lifespan.on',
+ 'uvicorn.lifespan.off',
+ 'httpx',
+ 'h11',
+ 'numpy',
+ 'scipy',
+ 'scipy.special',
+ 'scipy.interpolate',
+ 'aiosqlite',
+ 'sqlalchemy',
+ 'sqlalchemy.dialects.sqlite',
+ ],
+ hookspath=[],
+ hooksconfig={},
+ runtime_hooks=[],
+ excludes=['tkinter', 'matplotlib', 'PIL', 'pandas', 'IPython'],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False,
+)
+
+pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
+
+exe = EXE(
+ pyz,
+ a.scripts,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ [],
+ name='rfcp-server',
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ upx_exclude=[],
+ runtime_tmpdir=None,
+ console=False, # No console window
+ disable_windowed_traceback=False,
+ target_arch=None,
+ codesign_identity=None,
+ entitlements_file=None,
+ icon='../desktop/assets/icon.ico' if sys.platform == 'win32' else None,
+)
+```
+
+---
+
+### Task 2.1.6: Build Scripts (2-3 hours)
+
+**installer/build-win.sh:**
+```bash
+#!/bin/bash
+set -e
+
+echo "========================================="
+echo " RFCP Desktop Build (Windows)"
+echo "========================================="
+
+cd "$(dirname "$0")/.."
+
+# 1. Build frontend
+echo "[1/4] Building frontend..."
+cd frontend
+npm ci
+npm run build
+cd ..
+
+# 2. Build backend with PyInstaller
+echo "[2/4] Building backend..."
+cd backend
+python -m pip install -r requirements.txt
+python -m pip install pyinstaller
+cd ../installer
+python -m PyInstaller rfcp-server.spec --clean --noconfirm
+mkdir -p ../desktop/backend-dist/win32
+cp dist/rfcp-server.exe ../desktop/backend-dist/win32/
+cd ..
+
+# 3. Build Electron app
+echo "[3/4] Building Electron app..."
+cd desktop
+npm ci
+npm run build:win
+cd ..
+
+# 4. Done
+echo "[4/4] Build complete!"
+echo ""
+echo "Output: desktop/dist/"
+ls -la desktop/dist/*.exe 2>/dev/null || echo "Build artifacts in desktop/dist/"
+```
+
+**installer/build-linux.sh:**
+```bash
+#!/bin/bash
+set -e
+
+echo "========================================="
+echo " RFCP Desktop Build (Linux)"
+echo "========================================="
+
+cd "$(dirname "$0")/.."
+
+# 1. Build frontend
+echo "[1/4] Building frontend..."
+cd frontend
+npm ci
+npm run build
+cd ..
+
+# 2. Build backend with PyInstaller
+echo "[2/4] Building backend..."
+cd backend
+python3 -m pip install -r requirements.txt
+python3 -m pip install pyinstaller
+cd ../installer
+python3 -m PyInstaller rfcp-server.spec --clean --noconfirm
+mkdir -p ../desktop/backend-dist/linux
+cp dist/rfcp-server ../desktop/backend-dist/linux/
+cd ..
+
+# 3. Build Electron app
+echo "[3/4] Building Electron app..."
+cd desktop
+npm ci
+npm run build:linux
+cd ..
+
+# 4. Done
+echo "[4/4] Build complete!"
+ls -la desktop/dist/*.AppImage 2>/dev/null || echo "AppImage in desktop/dist/"
+ls -la desktop/dist/*.deb 2>/dev/null || echo "Deb in desktop/dist/"
+```
+
+**installer/build-mac.sh:**
+```bash
+#!/bin/bash
+set -e
+
+echo "========================================="
+echo " RFCP Desktop Build (macOS)"
+echo "========================================="
+
+cd "$(dirname "$0")/.."
+
+# Check if running on macOS
+if [[ "$(uname)" != "Darwin" ]]; then
+ echo "Error: This script must run on macOS"
+ exit 1
+fi
+
+# 1. Build frontend
+echo "[1/4] Building frontend..."
+cd frontend
+npm ci
+npm run build
+cd ..
+
+# 2. Build backend with PyInstaller
+echo "[2/4] Building backend..."
+cd backend
+python3 -m pip install -r requirements.txt
+python3 -m pip install pyinstaller
+cd ../installer
+python3 -m PyInstaller rfcp-server.spec --clean --noconfirm
+mkdir -p ../desktop/backend-dist/darwin
+cp dist/rfcp-server ../desktop/backend-dist/darwin/
+cd ..
+
+# 3. Create .icns icon if not exists
+if [ ! -f desktop/assets/icon.icns ]; then
+ echo "Creating macOS icon..."
+ mkdir -p icon.iconset
+ sips -z 16 16 desktop/assets/icon.png --out icon.iconset/icon_16x16.png
+ sips -z 32 32 desktop/assets/icon.png --out icon.iconset/icon_16x16@2x.png
+ sips -z 32 32 desktop/assets/icon.png --out icon.iconset/icon_32x32.png
+ sips -z 64 64 desktop/assets/icon.png --out icon.iconset/icon_32x32@2x.png
+ sips -z 128 128 desktop/assets/icon.png --out icon.iconset/icon_128x128.png
+ sips -z 256 256 desktop/assets/icon.png --out icon.iconset/icon_128x128@2x.png
+ sips -z 256 256 desktop/assets/icon.png --out icon.iconset/icon_256x256.png
+ sips -z 512 512 desktop/assets/icon.png --out icon.iconset/icon_256x256@2x.png
+ sips -z 512 512 desktop/assets/icon.png --out icon.iconset/icon_512x512.png
+ sips -z 1024 1024 desktop/assets/icon.png --out icon.iconset/icon_512x512@2x.png
+ iconutil -c icns icon.iconset -o desktop/assets/icon.icns
+ rm -rf icon.iconset
+fi
+
+# 4. Build Electron app
+echo "[3/4] Building Electron app..."
+cd desktop
+npm ci
+npm run build:mac
+cd ..
+
+# 5. Done
+echo "[4/4] Build complete!"
+ls -la desktop/dist/*.dmg 2>/dev/null || echo "DMG in desktop/dist/"
+```
+
+---
+
+### Task 2.1.7: Frontend Desktop Detection (1 hour)
+
+**frontend/src/lib/desktop.ts:**
+```typescript
+interface RFCPDesktop {
+ getDataPath: () => Promise;
+ getAppVersion: () => Promise;
+ getPlatform: () => Promise;
+ getGpuInfo: () => Promise<{
+ available: boolean;
+ name: string | null;
+ cudaVersion: string | null;
+ }>;
+ selectDirectory: () => Promise;
+ selectFile: (options?: { filters?: Array<{ name: string; extensions: string[] }> }) => Promise;
+ saveFile: (options?: { defaultPath?: string; filters?: Array<{ name: string; extensions: string[] }> }) => Promise;
+ getSetting: (key: string) => Promise;
+ setSetting: (key: string, value: any) => Promise;
+ openExternal: (url: string) => Promise;
+ openPath: (path: string) => Promise;
+ platform: string;
+ isDesktop: boolean;
+ isMac: boolean;
+ isWindows: boolean;
+ isLinux: boolean;
+}
+
+declare global {
+ interface Window {
+ rfcp?: RFCPDesktop;
+ }
+}
+
+export const isDesktop = (): boolean => {
+ return window.rfcp?.isDesktop === true;
+};
+
+export const getDesktopApi = (): RFCPDesktop | null => {
+ return window.rfcp || null;
+};
+
+export const getApiBaseUrl = (): string => {
+ if (isDesktop()) {
+ return 'http://127.0.0.1:8888';
+ }
+ return import.meta.env.VITE_API_URL || 'https://api.rfcp.eliah.one';
+};
+
+export const isMac = (): boolean => {
+ return window.rfcp?.isMac === true;
+};
+```
+
+**Update frontend/src/services/api.ts:**
+```typescript
+import { getApiBaseUrl } from '../lib/desktop';
+
+const API_BASE = getApiBaseUrl();
+// ... rest remains same
+```
+
+---
+
+## π§ͺ Testing
+
+### Development Testing
+```bash
+# Terminal 1: Backend
+cd backend
+uvicorn app.main:app --host 127.0.0.1 --port 8888 --reload
+
+# Terminal 2: Frontend
+cd frontend
+npm run dev
+
+# Terminal 3: Electron
+cd desktop
+npm run dev
+```
+
+### Build Testing
+
+**Windows:**
+```bash
+cd installer
+./build-win.sh
+# Test: desktop/dist/RFCP Setup 1.6.1.exe
+```
+
+**Linux:**
+```bash
+cd installer
+./build-linux.sh
+# Test: desktop/dist/RFCP-1.6.1.AppImage
+```
+
+**macOS (run on Mac):**
+```bash
+cd installer
+./build-mac.sh
+# Test: desktop/dist/RFCP-1.6.1.dmg
+```
+
+### Checklist
+- [ ] Installer runs without errors
+- [ ] Backend starts automatically (check splash β main window)
+- [ ] Frontend loads in Electron window
+- [ ] Coverage calculation works
+- [ ] Data persists between sessions (SQLite)
+- [ ] Window state persists (size, position)
+- [ ] File dialogs work (import/export)
+- [ ] External links open in browser
+
+---
+
+## π¦ Deliverables
+
+| Platform | File | Size |
+|----------|------|------|
+| Windows | `RFCP-Setup-1.6.1.exe` | ~250MB |
+| Linux | `RFCP-1.6.1.AppImage` | ~200MB |
+| Linux | `RFCP-1.6.1.deb` | ~200MB |
+| macOS | `RFCP-1.6.1.dmg` | ~220MB |
+| macOS | `RFCP-1.6.1-mac.zip` | ~210MB |
+
+---
+
+## π Future (2.2+)
+
+- [ ] First-run region download wizard
+- [ ] GPU acceleration toggle in settings
+- [ ] Auto-updater (electron-updater)
+- [ ] Portable mode (no install, run from USB)
+- [ ] Code signing (Windows/macOS)
+- [ ] Notarization (macOS)
+
+---
+
+## π Notes
+
+- **macOS signing:** Without Apple Developer account ($99/yr), users see "unverified developer" warning. They can bypass via System Preferences β Security.
+- **SQLite** replaces MongoDB β simpler, no server needed
+- **SRTM tiles** ~25MB each, Ukraine needs ~120 tiles = ~3GB
+- **Icons:** Need 512x512 PNG source, scripts generate .ico and .icns
+
+---
+
+**Ready for Claude Code** π
diff --git a/RFCP-Phase-2.1-Desktop-Installer.md b/RFCP-Phase-2.1-Desktop-Installer.md
deleted file mode 100644
index 2f9f546..0000000
--- a/RFCP-Phase-2.1-Desktop-Installer.md
+++ /dev/null
@@ -1,730 +0,0 @@
-# RFCP Phase 2.1: Desktop Application & Installer
-
-**Date:** January 31, 2025
-**Type:** Packaging & Distribution
-**Estimated:** 20-30 hours
-**Priority:** After frontend-backend integration (1.5)
-
----
-
-## π― Goal
-
-Package RFCP as standalone desktop application with installer for Windows and Linux. Fully offline capable after initial setup.
-
----
-
-## π Target Specs
-
-| Metric | Target |
-|--------|--------|
-| Installer size | 200-300 MB |
-| Installed size | 500MB - 1GB |
-| Platforms | Windows 10/11, Ubuntu 22.04+ |
-| Offline | Full offline after region download |
-| GPU | Optional, configurable in settings |
-
----
-
-## ποΈ Architecture
-
-```
-βββββββββββββββββββββββββββββββββββββββββββββββ
-β RFCP Desktop App β
-βββββββββββββββββββββββββββββββββββββββββββββββ€
-β βββββββββββββββββββββββββββββββββββββββ β
-β β Electron Shell β β
-β β (Chromium + Node.js runtime) β β
-β ββββββββββββββββ¬βββββββββββββββββββββββ β
-β β β
-β ββββββββββββββββΌβββββββββββββββββββββββ β
-β β React Frontend (UI) β β
-β β localhost:5173 (dev) / bundled β β
-β ββββββββββββββββ¬βββββββββββββββββββββββ β
-β β HTTP API β
-β ββββββββββββββββΌβββββββββββββββββββββββ β
-β β FastAPI Backend (Python) β β
-β β localhost:8888 β β
-β β ββββββββββββββββββββββββββββββ β β
-β β β Propagation Engine β β β
-β β β βββ CPU (default) β β β
-β β β βββ GPU (optional/CUDA) β β β
-β β ββββββββββββββββββββββββββββββ β β
-β ββββββββββββββββ¬βββββββββββββββββββββββ β
-β β β
-β ββββββββββββββββΌβββββββββββββββββββββββ β
-β β Local Data Store β β
-β β βββ SQLite (projects, settings) β β
-β β βββ SRTM tiles (elevation) β β
-β β βββ OSM cache (buildings, roads) β β
-β βββββββββββββββββββββββββββββββββββββββ β
-βββββββββββββββββββββββββββββββββββββββββββββββ
-```
-
----
-
-## π Directory Structure
-
-### Development
-```
-rfcp/
-βββ electron/
-β βββ main.js # Electron main process
-β βββ preload.js # Context bridge
-β βββ package.json # Electron deps
-β βββ build/ # electron-builder config
-β βββ icon.ico
-β βββ icon.png
-β βββ installer.nsh # NSIS customization
-βββ frontend/ # React (existing)
-βββ backend/ # FastAPI (existing)
-βββ scripts/
- βββ build-windows.sh
- βββ build-linux.sh
- βββ package-python.sh
-```
-
-### Installed (Windows)
-```
-C:\Program Files\RFCP\
-βββ RFCP.exe # Electron app
-βββ resources/
-β βββ app.asar # Frontend bundle
-β βββ backend/
-β βββ rfcp-server.exe # PyInstaller bundle
-β βββ app/ # Python code
-βββ data/
-β βββ rfcp.db # SQLite database
-β βββ srtm/ # Elevation tiles
-β βββ osm/ # OSM cache
-β βββ projects/ # User projects
-βββ python/ # Embedded Python (if not PyInstaller)
-βββ Uninstall RFCP.exe
-```
-
-### Installed (Linux)
-```
-/opt/rfcp/
-βββ rfcp # AppImage or binary
-βββ resources/
-β βββ ...
-βββ data/
- βββ ...
-
-~/.local/share/rfcp/ # User data
-βββ rfcp.db
-βββ srtm/
-βββ osm/
-βββ projects/
-```
-
----
-
-## β
Tasks
-
-### Task 2.1.1: Electron Shell (4-6 hours)
-
-**electron/main.js:**
-```javascript
-const { app, BrowserWindow, ipcMain } = require('electron');
-const { spawn } = require('child_process');
-const path = require('path');
-const fs = require('fs');
-
-let mainWindow;
-let backendProcess;
-
-// Paths
-const isDev = process.env.NODE_ENV === 'development';
-const backendPath = isDev
- ? path.join(__dirname, '../backend')
- : path.join(process.resourcesPath, 'backend');
-
-const dataPath = isDev
- ? path.join(__dirname, '../data')
- : path.join(app.getPath('userData'), 'data');
-
-// Ensure data directories exist
-function ensureDataDirs() {
- const dirs = ['srtm', 'osm', 'projects'];
- dirs.forEach(dir => {
- const fullPath = path.join(dataPath, dir);
- if (!fs.existsSync(fullPath)) {
- fs.mkdirSync(fullPath, { recursive: true });
- }
- });
-}
-
-// Start Python backend
-function startBackend() {
- const pythonExe = isDev
- ? 'python'
- : path.join(process.resourcesPath, 'backend', 'rfcp-server.exe');
-
- const env = {
- ...process.env,
- RFCP_DATA_PATH: dataPath,
- RFCP_PORT: '8888'
- };
-
- if (isDev) {
- backendProcess = spawn(pythonExe, ['-m', 'uvicorn', 'app.main:app', '--port', '8888'], {
- cwd: backendPath,
- env
- });
- } else {
- backendProcess = spawn(pythonExe, [], { env });
- }
-
- backendProcess.stdout.on('data', (data) => {
- console.log(`Backend: ${data}`);
- });
-
- backendProcess.stderr.on('data', (data) => {
- console.error(`Backend Error: ${data}`);
- });
-
- return new Promise((resolve) => {
- // Wait for backend to be ready
- const checkBackend = setInterval(async () => {
- try {
- const response = await fetch('http://localhost:8888/api/health/');
- if (response.ok) {
- clearInterval(checkBackend);
- resolve();
- }
- } catch (e) {
- // Not ready yet
- }
- }, 500);
-
- // Timeout after 30s
- setTimeout(() => {
- clearInterval(checkBackend);
- resolve(); // Continue anyway
- }, 30000);
- });
-}
-
-// Create window
-async function createWindow() {
- ensureDataDirs();
-
- // Show splash while loading
- const splash = new BrowserWindow({
- width: 400,
- height: 300,
- frame: false,
- alwaysOnTop: true,
- transparent: true
- });
- splash.loadFile('splash.html');
-
- // Start backend
- await startBackend();
-
- // Create main window
- mainWindow = new BrowserWindow({
- width: 1400,
- height: 900,
- minWidth: 1024,
- minHeight: 768,
- webPreferences: {
- nodeIntegration: false,
- contextIsolation: true,
- preload: path.join(__dirname, 'preload.js')
- },
- icon: path.join(__dirname, 'build/icon.png'),
- title: 'RFCP - RF Coverage Planner'
- });
-
- // Load frontend
- if (isDev) {
- mainWindow.loadURL('http://localhost:5173');
- mainWindow.webContents.openDevTools();
- } else {
- mainWindow.loadFile(path.join(process.resourcesPath, 'frontend', 'index.html'));
- }
-
- // Close splash
- splash.close();
-
- mainWindow.on('closed', () => {
- mainWindow = null;
- });
-}
-
-// App lifecycle
-app.whenReady().then(createWindow);
-
-app.on('window-all-closed', () => {
- if (backendProcess) {
- backendProcess.kill();
- }
- if (process.platform !== 'darwin') {
- app.quit();
- }
-});
-
-app.on('activate', () => {
- if (mainWindow === null) {
- createWindow();
- }
-});
-
-// IPC handlers
-ipcMain.handle('get-data-path', () => dataPath);
-ipcMain.handle('get-gpu-info', () => {
- // Detect GPU availability
- // Could use node-gpu or check CUDA
- return {
- available: false, // TODO: implement detection
- name: null
- };
-});
-```
-
-**electron/preload.js:**
-```javascript
-const { contextBridge, ipcRenderer } = require('electron');
-
-contextBridge.exposeInMainWorld('rfcp', {
- getDataPath: () => ipcRenderer.invoke('get-data-path'),
- getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
- platform: process.platform,
- version: require('./package.json').version
-});
-```
-
----
-
-### Task 2.1.2: Python Packaging (4-6 hours)
-
-**PyInstaller spec file (rfcp-server.spec):**
-```python
-# -*- mode: python ; coding: utf-8 -*-
-
-block_cipher = None
-
-a = Analysis(
- ['app/main.py'],
- pathex=[],
- binaries=[],
- datas=[
- ('app', 'app'),
- ],
- hiddenimports=[
- 'uvicorn.logging',
- 'uvicorn.protocols.http',
- 'uvicorn.protocols.http.auto',
- 'uvicorn.protocols.websockets',
- 'uvicorn.protocols.websockets.auto',
- 'uvicorn.lifespan.on',
- 'uvicorn.lifespan.off',
- ],
- hookspath=[],
- hooksconfig={},
- runtime_hooks=[],
- excludes=[],
- win_no_prefer_redirects=False,
- win_private_assemblies=False,
- cipher=block_cipher,
- noarchive=False,
-)
-
-pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
-
-exe = EXE(
- pyz,
- a.scripts,
- a.binaries,
- a.zipfiles,
- a.datas,
- [],
- name='rfcp-server',
- debug=False,
- bootloader_ignore_signals=False,
- strip=False,
- upx=True,
- upx_exclude=[],
- runtime_tmpdir=None,
- console=True, # False for production
- disable_windowed_traceback=False,
- argv_emulation=False,
- target_arch=None,
- codesign_identity=None,
- entitlements_file=None,
- icon='../electron/build/icon.ico'
-)
-```
-
-**Build script (scripts/package-python.sh):**
-```bash
-#!/bin/bash
-set -e
-
-cd backend
-
-# Create venv for packaging
-python -m venv build_env
-source build_env/bin/activate # or build_env\Scripts\activate on Windows
-
-# Install deps
-pip install -r requirements.txt
-pip install pyinstaller
-
-# Build
-pyinstaller rfcp-server.spec --clean
-
-# Output in dist/rfcp-server.exe
-echo "Built: dist/rfcp-server.exe"
-```
-
----
-
-### Task 2.1.3: Electron Builder Config (3-4 hours)
-
-**electron/package.json:**
-```json
-{
- "name": "rfcp",
- "version": "1.0.0",
- "description": "RF Coverage Planner for Tactical Communications",
- "main": "main.js",
- "author": "UMTC Project",
- "license": "MIT",
- "scripts": {
- "start": "electron .",
- "build:win": "electron-builder --win",
- "build:linux": "electron-builder --linux",
- "build:all": "electron-builder --win --linux"
- },
- "devDependencies": {
- "electron": "^28.0.0",
- "electron-builder": "^24.9.0"
- },
- "build": {
- "appId": "one.eliah.rfcp",
- "productName": "RFCP",
- "copyright": "Copyright Β© 2025 UMTC Project",
- "directories": {
- "output": "dist",
- "buildResources": "build"
- },
- "files": [
- "main.js",
- "preload.js",
- "splash.html"
- ],
- "extraResources": [
- {
- "from": "../frontend/dist",
- "to": "frontend"
- },
- {
- "from": "../backend/dist/rfcp-server",
- "to": "backend"
- }
- ],
- "win": {
- "target": [
- {
- "target": "nsis",
- "arch": ["x64"]
- }
- ],
- "icon": "build/icon.ico"
- },
- "nsis": {
- "oneClick": false,
- "allowToChangeInstallationDirectory": true,
- "installerIcon": "build/icon.ico",
- "uninstallerIcon": "build/icon.ico",
- "installerHeaderIcon": "build/icon.ico",
- "createDesktopShortcut": true,
- "createStartMenuShortcut": true,
- "shortcutName": "RFCP"
- },
- "linux": {
- "target": [
- "AppImage",
- "deb"
- ],
- "icon": "build/icon.png",
- "category": "Science"
- }
- }
-}
-```
-
----
-
-### Task 2.1.4: First Run & Region Selection (4-6 hours)
-
-**First run wizard:**
-```
-βββββββββββββββββββββββββββββββββββββββββββ
-β Welcome to RFCP β
-β RF Coverage Planner β
-βββββββββββββββββββββββββββββββββββββββββββ€
-β β
-β Select your region for offline maps: β
-β β
-β βββββββββββββββββββββββββββββββββββ β
-β β [x] Ukraine (~2.5 GB) β β
-β β [ ] Poland (~1.8 GB) β β
-β β [ ] Germany (~2.1 GB) β β
-β β [ ] Custom bounding box... β β
-β βββββββββββββββββββββββββββββββββββ β
-β β
-β Data includes: β
-β β’ Terrain elevation (SRTM 30m) β
-β β’ Building footprints (OSM) β
-β β’ Road network (OSM) β
-β β’ Base map tiles β
-β β
-β [ ] Download now β
-β [ ] Download later (online mode) β
-β β
-β [Continue β] β
-βββββββββββββββββββββββββββββββββββββββββββ
-```
-
-**Region download API endpoint:**
-```python
-@router.post("/regions/download")
-async def download_region(region: str, background_tasks: BackgroundTasks):
- """Start region download in background"""
-
- REGIONS = {
- "ukraine": {
- "bbox": [44.0, 22.0, 52.5, 40.5], # S, W, N, E
- "srtm_tiles": ["N44E022", "N44E023", ...], # ~120 tiles
- "estimated_size": 2.5 * 1024 * 1024 * 1024 # 2.5 GB
- },
- # ...
- }
-
- if region not in REGIONS:
- raise HTTPException(400, "Unknown region")
-
- task_id = str(uuid4())
- background_tasks.add_task(download_region_data, task_id, REGIONS[region])
-
- return {"task_id": task_id, "status": "started"}
-
-@router.get("/regions/progress/{task_id}")
-async def get_download_progress(task_id: str):
- """Get download progress"""
- return {
- "task_id": task_id,
- "status": "downloading", # started, downloading, extracting, done, error
- "progress": 45.5, # percentage
- "current_file": "N48E035.hgt",
- "downloaded_mb": 1250,
- "total_mb": 2500
- }
-```
-
----
-
-### Task 2.1.5: Settings - GPU Configuration (2-3 hours)
-
-**Settings panel addition:**
-```typescript
-// frontend/src/components/settings/PerformanceSettings.tsx
-
-interface PerformanceSettingsProps {
- gpuInfo: {
- available: boolean;
- name: string | null;
- memory: number | null;
- };
-}
-
-export function PerformanceSettings({ gpuInfo }: PerformanceSettingsProps) {
- const [useGpu, setUseGpu] = useState(false);
- const [maxWorkers, setMaxWorkers] = useState(4);
-
- return (
-
-
Performance
-
-
-
- {gpuInfo.available ? (
- Detected: {gpuInfo.name}
- ) : (
- No compatible GPU detected
- )}
-
-
-
-
- setMaxWorkers(Number(e.target.value))}
- />
- {maxWorkers} threads
-
-
-
-
-
-
-
- );
-}
-```
-
-**Backend GPU detection:**
-```python
-# app/services/gpu_service.py
-
-def detect_gpu() -> dict:
- """Detect available GPU for CUDA"""
- result = {
- "available": False,
- "name": None,
- "memory": None,
- "cuda_version": None
- }
-
- try:
- import cupy as cp
- device = cp.cuda.Device(0)
- props = cp.cuda.runtime.getDeviceProperties(0)
-
- result["available"] = True
- result["name"] = props["name"].decode()
- result["memory"] = props["totalGlobalMem"] // (1024**3) # GB
- result["cuda_version"] = cp.cuda.runtime.runtimeGetVersion()
- except ImportError:
- pass # CuPy not installed
- except Exception as e:
- print(f"GPU detection error: {e}")
-
- return result
-```
-
----
-
-### Task 2.1.6: Build & Test (3-4 hours)
-
-**Build script (scripts/build-windows.sh):**
-```bash
-#!/bin/bash
-set -e
-
-echo "=== RFCP Windows Build ==="
-
-# 1. Build frontend
-echo "Building frontend..."
-cd frontend
-npm run build
-cd ..
-
-# 2. Build backend
-echo "Building backend..."
-cd backend
-python -m venv build_env
-source build_env/Scripts/activate
-pip install -r requirements.txt
-pip install pyinstaller
-pyinstaller rfcp-server.spec --clean
-cd ..
-
-# 3. Build Electron
-echo "Building Electron app..."
-cd electron
-npm install
-npm run build:win
-cd ..
-
-echo "=== Build complete ==="
-echo "Installer: electron/dist/RFCP Setup*.exe"
-```
-
-**Test checklist:**
-```markdown
-## Install Test
-- [ ] Installer runs without admin (user install)
-- [ ] Installer runs with admin (program files)
-- [ ] Desktop shortcut created
-- [ ] Start menu entry created
-- [ ] Uninstaller works
-
-## First Run
-- [ ] Splash screen appears
-- [ ] Backend starts successfully
-- [ ] Main window loads
-- [ ] Region selection dialog shows
-- [ ] Can skip region download
-
-## Functionality
-- [ ] Map loads (online tiles)
-- [ ] Can create sites
-- [ ] Coverage calculation works
-- [ ] All presets work
-- [ ] Settings persist
-
-## Offline
-- [ ] Works without internet (after region download)
-- [ ] Offline tiles load
-- [ ] Terrain data works
-- [ ] Buildings/roads cached
-
-## Performance
-- [ ] GPU toggle appears (if GPU present)
-- [ ] GPU acceleration works
-- [ ] Memory usage reasonable (<1GB idle)
-```
-
----
-
-## π¦ Deliverables
-
-1. **RFCP-Setup-1.0.0.exe** β Windows installer (~200MB)
-2. **RFCP-1.0.0.AppImage** β Linux portable (~180MB)
-3. **RFCP-1.0.0.deb** β Debian package (~180MB)
-4. **SHA256SUMS** β Checksums file
-5. **README.md** β Installation instructions
-
----
-
-## π Future Enhancements (2.2+)
-
-- [ ] Auto-updater (electron-updater)
-- [ ] macOS support (.dmg)
-- [ ] Portable mode (no install, run from USB)
-- [ ] Silent install for deployment
-- [ ] MSI installer for enterprise
-- [ ] Code signing (Windows/macOS)
-
----
-
-## π Notes
-
-- SQLite Π·Π°ΠΌΡΡΡΡ MongoDB Π΄Π»Ρ Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎΠ³ΠΎ Π·Π±Π΅ΡΡΠ³Π°Π½Π½Ρ (ΠΏΡΠΎΡΡΡΡΠ΅, Π½Π΅ ΠΏΠΎΡΡΠ΅Π±ΡΡ ΡΠ΅ΡΠ²Π΅ΡΠ°)
-- SRTM tiles ~25MB ΠΊΠΎΠΆΠ΅Π½, Π£ΠΊΡΠ°ΡΠ½Π° ΠΏΠΎΡΡΠ΅Π±ΡΡ ~120 tiles = ~3GB
-- OSM Π΄Π°Π½Ρ ΠΌΠΎΠΆΠ½Π° Π·Π°Π²Π°Π½ΡΠ°ΠΆΠΈΡΠΈ Π· Geofabrik (ukraine-latest.osm.pbf ~1.5GB)
-- Map tiles ΠΌΠΎΠΆΠ½Π° ΠΊΠ΅ΡΡΠ²Π°ΡΠΈ Π· OpenStreetMap Π°Π±ΠΎ Π²ΠΈΠΊΠΎΡΠΈΡΡΠ°ΡΠΈ MBTiles
-
----
-
-**Ready for implementation after 1.5** π
diff --git a/backend/requirements.txt b/backend/requirements.txt
index fdd26f9..ec6db3d 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -9,3 +9,5 @@ numpy==1.26.4
scipy==1.12.0
requests==2.31.0
httpx==0.27.0
+aiosqlite>=0.19.0
+sqlalchemy>=2.0.0
diff --git a/desktop/assets/icon-256.png b/desktop/assets/icon-256.png
new file mode 100644
index 0000000..1e13030
Binary files /dev/null and b/desktop/assets/icon-256.png differ
diff --git a/desktop/assets/icon-original.png b/desktop/assets/icon-original.png
new file mode 100644
index 0000000..e7cbfba
Binary files /dev/null and b/desktop/assets/icon-original.png differ
diff --git a/desktop/assets/icon.ico b/desktop/assets/icon.ico
new file mode 100644
index 0000000..7eb140c
Binary files /dev/null and b/desktop/assets/icon.ico differ
diff --git a/desktop/assets/icon.png b/desktop/assets/icon.png
new file mode 100644
index 0000000..d0c62e6
Binary files /dev/null and b/desktop/assets/icon.png differ
diff --git a/RFCP-Backend-Roadmap-Complete.md b/docs/devlog/back/RFCP-Backend-Roadmap-Complete.md
similarity index 100%
rename from RFCP-Backend-Roadmap-Complete.md
rename to docs/devlog/back/RFCP-Backend-Roadmap-Complete.md
diff --git a/RFCP-Iteration-1.1-Backend-Foundation.md b/docs/devlog/back/RFCP-Iteration-1.1-Backend-Foundation.md
similarity index 100%
rename from RFCP-Iteration-1.1-Backend-Foundation.md
rename to docs/devlog/back/RFCP-Iteration-1.1-Backend-Foundation.md
diff --git a/RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md b/docs/devlog/back/RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md
similarity index 100%
rename from RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md
rename to docs/devlog/back/RFCP-Iteration-1.1.1-UX-Safety-Undo-Redo.md
diff --git a/RFCP-Iteration-1.2-Terrain-Integration.md b/docs/devlog/back/RFCP-Iteration-1.2-Terrain-Integration.md
similarity index 100%
rename from RFCP-Iteration-1.2-Terrain-Integration.md
rename to docs/devlog/back/RFCP-Iteration-1.2-Terrain-Integration.md
diff --git a/RFCP-Iteration-1.3-Coverage-OSM-Buildings.md b/docs/devlog/back/RFCP-Iteration-1.3-Coverage-OSM-Buildings.md
similarity index 100%
rename from RFCP-Iteration-1.3-Coverage-OSM-Buildings.md
rename to docs/devlog/back/RFCP-Iteration-1.3-Coverage-OSM-Buildings.md
diff --git a/RFCP-Iteration-1.4-Advanced-Propagation.md b/docs/devlog/back/RFCP-Iteration-1.4-Advanced-Propagation.md
similarity index 100%
rename from RFCP-Iteration-1.4-Advanced-Propagation.md
rename to docs/devlog/back/RFCP-Iteration-1.4-Advanced-Propagation.md
diff --git a/RFCP-Iteration-1.5-Frontend-Backend-Integration.md b/docs/devlog/back/RFCP-Iteration-1.5-Frontend-Backend-Integration.md
similarity index 100%
rename from RFCP-Iteration-1.5-Frontend-Backend-Integration.md
rename to docs/devlog/back/RFCP-Iteration-1.5-Frontend-Backend-Integration.md
diff --git a/RFCP-Iteration-1.5.1-Fixes-Boundaries.md b/docs/devlog/back/RFCP-Iteration-1.5.1-Fixes-Boundaries.md
similarity index 100%
rename from RFCP-Iteration-1.5.1-Fixes-Boundaries.md
rename to docs/devlog/back/RFCP-Iteration-1.5.1-Fixes-Boundaries.md
diff --git a/RFCP-Iteration-1.6-Enhanced-Environment.md b/docs/devlog/back/RFCP-Iteration-1.6-Enhanced-Environment.md
similarity index 100%
rename from RFCP-Iteration-1.6-Enhanced-Environment.md
rename to docs/devlog/back/RFCP-Iteration-1.6-Enhanced-Environment.md
diff --git a/RFCP-Iteration-1.6.1-Extra-Factors.md b/docs/devlog/back/RFCP-Iteration-1.6.1-Extra-Factors.md
similarity index 100%
rename from RFCP-Iteration-1.6.1-Extra-Factors.md
rename to docs/devlog/back/RFCP-Iteration-1.6.1-Extra-Factors.md
diff --git a/icon.png b/icon.png
new file mode 100644
index 0000000..fda259f
Binary files /dev/null and b/icon.png differ