- Added a professional HTML welcome page displayed when no web application is configured. - Enhanced `_load_webapp()` method to support improved path resolution for both development and bundled modes. - Updated error handling to show the welcome page instead of a bare error message when the webapp file is not found. - Modified unit tests to verify the welcome page is displayed in error scenarios. build: Complete Windows and macOS build scripts - Created `build_windows.py` for building Windows executable and optional MSI installer using PyInstaller. - Developed `build_macos.sh` for creating macOS application bundle and DMG image. - Added logging and error handling to build scripts for better user feedback. docs: Add build and icon requirements documentation - Created `PHASE_3_BUILD_SUMMARY.md` detailing the build process, results, and next steps. - Added `resources/icons/README.md` outlining icon requirements and creation guidelines. chore: Sync remotes script for repository maintenance - Introduced `sync_remotes.ps1` PowerShell script to fetch updates from origin and upstream remotes.
1049 lines
30 KiB
Markdown
1049 lines
30 KiB
Markdown
# WebDrop Bridge - Professional Development Plan
|
|
|
|
**Version**: 1.0
|
|
**Last Updated**: January 2026
|
|
**Status**: Pre-Release Development
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines the development roadmap for WebDrop Bridge, a professional Qt-based desktop application that converts web-based drag-and-drop text paths into native file operations for seamless integration with professional desktop software (InDesign, Word, Notepad++, etc.).
|
|
|
|
### Key Differences from PoC
|
|
|
|
| Aspect | PoC | Production |
|
|
|--------|-----|-----------|
|
|
| **Structure** | Monolithic | Modular, scalable |
|
|
| **Testing** | Ad-hoc | Comprehensive (unit/integration/e2e) |
|
|
| **Documentation** | Minimal | Full API docs, user guides |
|
|
| **Error Handling** | Basic | Robust with recovery |
|
|
| **Logging** | Console only | File + structured logging |
|
|
| **Security** | Basic validation | Enhanced, configurable |
|
|
| **Distribution** | Source code | MSI (Windows), DMG (macOS) |
|
|
| **CI/CD** | Manual | Automated GitHub Actions |
|
|
| **Versioning** | Single version | Semantic versioning, auto-updates |
|
|
|
|
---
|
|
|
|
## Phase 1: Foundation (Weeks 1-4)
|
|
|
|
### 1.1 Core Architecture Refinement
|
|
|
|
**Objectives:**
|
|
- Refactor PoC code into production-quality modules
|
|
- Implement proper logging and error handling
|
|
- Create configuration management system
|
|
|
|
**Tasks:**
|
|
|
|
#### 1.1.1 Configuration System (`src/webdrop_bridge/config.py`)
|
|
```python
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import List
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
@dataclass
|
|
class Config:
|
|
"""Application configuration."""
|
|
app_name: str
|
|
app_version: str
|
|
log_level: str
|
|
allowed_roots: List[Path]
|
|
allowed_urls: List[str] # Empty = no restriction, non-empty = Kiosk-mode
|
|
webapp_url: str
|
|
window_width: int
|
|
window_height: int
|
|
enable_logging: bool
|
|
|
|
@classmethod
|
|
def from_env(cls):
|
|
"""Load configuration from environment."""
|
|
load_dotenv()
|
|
allowed_roots_str = os.getenv("ALLOWED_ROOTS", "Z:/,C:/Users/Public")
|
|
allowed_roots = [Path(p.strip()) for p in allowed_roots_str.split(",")]
|
|
|
|
# URL whitelist (empty = no restriction)
|
|
allowed_urls_str = os.getenv("ALLOWED_URLS", "")
|
|
allowed_urls = [url.strip() for url in allowed_urls_str.split(",") if url.strip()]
|
|
|
|
return cls(
|
|
app_name=os.getenv("APP_NAME", "WebDrop Bridge"),
|
|
app_version=os.getenv("APP_VERSION", "1.0.0"),
|
|
log_level=os.getenv("LOG_LEVEL", "INFO"),
|
|
allowed_roots=allowed_roots,
|
|
allowed_urls=allowed_urls,
|
|
webapp_url=os.getenv("WEBAPP_URL", "file:///./webapp/index.html"),
|
|
window_width=int(os.getenv("WINDOW_WIDTH", "1024")),
|
|
window_height=int(os.getenv("WINDOW_HEIGHT", "768")),
|
|
enable_logging=os.getenv("ENABLE_LOGGING", "true").lower() == "true",
|
|
)
|
|
```
|
|
|
|
**Deliverables:**
|
|
- [x] `src/webdrop_bridge/config.py` - Configuration management
|
|
- [x] `.env.example` - Environment template
|
|
- [x] Validation for all config parameters
|
|
|
|
**Configuration Variables:**
|
|
- `ALLOWED_ROOTS` - Comma-separated file paths (drag-drop whitelist)
|
|
- `ALLOWED_URLS` - Comma-separated URL patterns for Kiosk-mode (empty = unrestricted)
|
|
- Examples: `localhost,127.0.0.1,*.example.com`
|
|
- Supports wildcards: `*.domain.com`
|
|
- File URLs always allowed: `file://`
|
|
|
|
**Acceptance Criteria:**
|
|
- Config loads from `.env` file
|
|
- All environment variables have sensible defaults
|
|
- Invalid values raise `ConfigurationError`
|
|
|
|
---
|
|
|
|
#### 1.1.2 Logging System (`src/webdrop_bridge/utils/logging.py`)
|
|
|
|
```python
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
def setup_logging(
|
|
level: str = "INFO",
|
|
log_file: Optional[Path] = None,
|
|
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
) -> logging.Logger:
|
|
"""Configure application-wide logging."""
|
|
logger = logging.getLogger("webdrop_bridge")
|
|
logger.setLevel(getattr(logging, level))
|
|
|
|
# Console handler
|
|
console = logging.StreamHandler()
|
|
console.setFormatter(logging.Formatter(format))
|
|
logger.addHandler(console)
|
|
|
|
# File handler (if enabled)
|
|
if log_file:
|
|
log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
file_handler = logging.FileHandler(log_file)
|
|
file_handler.setFormatter(logging.Formatter(format))
|
|
logger.addHandler(file_handler)
|
|
|
|
return logger
|
|
```
|
|
|
|
**Deliverables:**
|
|
- [ ] `src/webdrop_bridge/utils/logging.py` - Logging utilities
|
|
- [ ] Logs directory with `.gitkeep`
|
|
- [ ] Log rotation policy
|
|
|
|
**Acceptance Criteria:**
|
|
- Logs written to `logs/webdrop_bridge.log`
|
|
- Console output matches log file
|
|
- Log level configurable via environment
|
|
|
|
---
|
|
|
|
### 1.2 Drag Interceptor Component
|
|
|
|
**Objectives:**
|
|
- Implement robust drag-and-drop interception
|
|
- Validate paths against whitelist
|
|
- Create MimeData with proper file URLs
|
|
|
|
**Tasks:**
|
|
|
|
#### 1.2.1 Path Validator (`src/webdrop_bridge/core/validator.py`)
|
|
|
|
```python
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
class PathValidator:
|
|
"""Validates file paths against security policies."""
|
|
|
|
def __init__(self, allowed_roots: List[Path]):
|
|
self.allowed_roots = [p.resolve() for p in allowed_roots]
|
|
|
|
def is_allowed(self, path: Path) -> bool:
|
|
"""Check if path is within allowed roots."""
|
|
try:
|
|
resolved = path.resolve()
|
|
return any(
|
|
self._is_relative_to(resolved, root)
|
|
for root in self.allowed_roots
|
|
)
|
|
except (ValueError, OSError):
|
|
return False
|
|
|
|
def is_valid_file(self, path: Path) -> bool:
|
|
"""Check if path is allowed and exists as file."""
|
|
return self.is_allowed(path) and path.exists() and path.is_file()
|
|
|
|
@staticmethod
|
|
def _is_relative_to(path: Path, other: Path) -> bool:
|
|
"""Backcompat for Path.is_relative_to (Python 3.9+)."""
|
|
try:
|
|
path.relative_to(other)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
```
|
|
|
|
**Deliverables:**
|
|
- [ ] `src/webdrop_bridge/core/validator.py` - Path validation
|
|
- [ ] Unit tests for `PathValidator`
|
|
- [ ] Security documentation
|
|
|
|
**Acceptance Criteria:**
|
|
- All paths resolved to absolute
|
|
- Whitelist enforcement working
|
|
- Symlink handling documented
|
|
|
|
---
|
|
|
|
#### 1.2.2 Drag Interceptor Widget (`src/webdrop_bridge/core/drag_interceptor.py`)
|
|
|
|
```python
|
|
from PySide6.QtCore import Qt, QUrl, QMimeData, pyqtSignal
|
|
from PySide6.QtGui import QDrag
|
|
from PySide6.QtWidgets import QWidget
|
|
from pathlib import Path
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class DragInterceptor(QWidget):
|
|
"""Intercepts and converts text drags to file drags."""
|
|
|
|
file_dropped = pyqtSignal(Path)
|
|
|
|
def __init__(self, validator, parent=None):
|
|
super().__init__(parent)
|
|
self.validator = validator
|
|
self.setAcceptDrops(True)
|
|
|
|
def dragEnterEvent(self, event):
|
|
"""Handle drag enter."""
|
|
if event.mimeData().hasText():
|
|
text = event.mimeData().text().strip()
|
|
path = Path(text.replace("\\", "/"))
|
|
|
|
if self.validator.is_valid_file(path):
|
|
event.acceptProposedAction()
|
|
logger.debug(f"Drag accepted: {path}")
|
|
self._start_file_drag(path)
|
|
else:
|
|
event.ignore()
|
|
logger.warning(f"Invalid path rejected: {text}")
|
|
|
|
def _start_file_drag(self, path: Path):
|
|
"""Convert to native file drag."""
|
|
mime_data = QMimeData()
|
|
url = QUrl.fromLocalFile(str(path))
|
|
mime_data.setUrls([url])
|
|
|
|
drag = QDrag(self)
|
|
drag.setMimeData(mime_data)
|
|
|
|
# Use move for typical file operations
|
|
drag.exec(Qt.MoveAction | Qt.CopyAction)
|
|
self.file_dropped.emit(path)
|
|
logger.info(f"File dragged: {path}")
|
|
```
|
|
|
|
**Deliverables:**
|
|
- [ ] `src/webdrop_bridge/core/drag_interceptor.py` - Drag handling
|
|
- [ ] Unit tests with mocking
|
|
- [ ] Platform-specific tests (Windows/macOS)
|
|
|
|
**Acceptance Criteria:**
|
|
- Drag events properly intercepted
|
|
- File URLs created correctly
|
|
- Signals emit appropriately
|
|
- Cross-platform compatibility verified
|
|
|
|
---
|
|
|
|
### 1.3 Main Application Window
|
|
|
|
**Tasks:**
|
|
|
|
#### 1.3.1 Main Window (`src/webdrop_bridge/ui/main_window.py`)
|
|
|
|
**Features:**
|
|
- Qt QMainWindow with WebEngine integration
|
|
- Navigation toolbar (Home, Back, Forward, Refresh) for Kiosk-mode
|
|
- URL whitelist enforcement for restricted browsing
|
|
- Drag-and-drop file path integration
|
|
|
|
```python
|
|
from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QWidget, QToolBar
|
|
from PySide6.QtCore import QUrl, QSize, Qt
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MainWindow(QMainWindow):
|
|
"""Application main window with restricted browsing."""
|
|
|
|
def __init__(self, config):
|
|
super().__init__()
|
|
self.config = config
|
|
self.setWindowTitle(config.app_name)
|
|
self.setGeometry(100, 100, config.window_width, config.window_height)
|
|
|
|
# Create restricted web engine view (with URL whitelist)
|
|
from webdrop_bridge.ui.restricted_web_view import RestrictedWebEngineView
|
|
self.web_view = RestrictedWebEngineView(config.allowed_urls)
|
|
self._create_navigation_toolbar()
|
|
|
|
# Set as central widget
|
|
self.setCentralWidget(self.web_view)
|
|
|
|
logger.info(f"Loading webapp from: {config.webapp_url}")
|
|
self.web_view.load(QUrl(config.webapp_url))
|
|
|
|
def _create_navigation_toolbar(self):
|
|
"""Create Kiosk-mode navigation toolbar."""
|
|
toolbar = QToolBar("Navigation")
|
|
toolbar.setMovable(False)
|
|
toolbar.setIconSize(QSize(24, 24))
|
|
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
|
|
|
|
# Add Back, Forward, Home, Refresh buttons
|
|
toolbar.addAction(self.web_view.pageAction(self.web_view.WebAction.Back))
|
|
toolbar.addAction(self.web_view.pageAction(self.web_view.WebAction.Forward))
|
|
toolbar.addSeparator()
|
|
home_action = toolbar.addAction("Home")
|
|
home_action.triggered.connect(self._navigate_home)
|
|
toolbar.addAction(self.web_view.pageAction(self.web_view.WebAction.Reload))
|
|
|
|
def _navigate_home(self):
|
|
"""Navigate to home (startup) URL."""
|
|
self.web_view.load(QUrl(self.config.webapp_url))
|
|
```
|
|
|
|
**URL Whitelist (Kiosk-Mode):**
|
|
|
|
When `ALLOWED_URLS` is configured, the application enforces strict browsing:
|
|
- ✅ Whitelisted URLs load in the WebView
|
|
- ❌ Non-whitelisted URLs open in system default browser
|
|
- ✅ File URLs (`file://`) always allowed
|
|
- ✅ Empty whitelist = unrestricted (all URLs allowed)
|
|
|
|
**Deliverables:**
|
|
- [x] `src/webdrop_bridge/ui/main_window.py`
|
|
- [x] `src/webdrop_bridge/ui/restricted_web_view.py` - URL whitelist enforcement
|
|
- [x] UI tests
|
|
|
|
**Acceptance Criteria:**
|
|
- Window opens with correct title
|
|
- WebEngine loads correctly
|
|
- Toolbar displays with Home, Back, Forward, Refresh buttons
|
|
- URL whitelist enforces restrictions
|
|
- Non-whitelisted URLs open in system browser
|
|
|
|
---
|
|
|
|
#### 1.3.2 Restricted Web View (`src/webdrop_bridge/ui/restricted_web_view.py`)
|
|
|
|
**Purpose:** Enforce URL whitelist for Kiosk-mode browsing security.
|
|
|
|
```python
|
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
from PySide6.QtGui import QDesktopServices
|
|
import fnmatch
|
|
|
|
class RestrictedWebEngineView(QWebEngineView):
|
|
"""Web view with URL whitelist enforcement."""
|
|
|
|
def __init__(self, allowed_urls: List[str] = None):
|
|
super().__init__()
|
|
self.allowed_urls = allowed_urls or []
|
|
self.page().navigationRequested.connect(self._on_navigation_requested)
|
|
|
|
def _on_navigation_requested(self, request):
|
|
"""Enforce URL whitelist on navigation."""
|
|
if not self.allowed_urls:
|
|
# No restrictions
|
|
return
|
|
|
|
if not self._is_url_allowed(request.url):
|
|
# Reject and open in system browser
|
|
request.reject()
|
|
QDesktopServices.openUrl(request.url)
|
|
|
|
def _is_url_allowed(self, url):
|
|
"""Check if URL matches whitelist patterns."""
|
|
# file:// URLs always allowed
|
|
if url.scheme() == "file":
|
|
return True
|
|
|
|
# Check wildcard patterns (*.example.com, localhost, etc.)
|
|
for pattern in self.allowed_urls:
|
|
if fnmatch.fnmatch(url.host(), pattern):
|
|
return True
|
|
|
|
return False
|
|
```
|
|
|
|
**Features:**
|
|
- Wildcard pattern support: `*.example.com`
|
|
- Exact domain matching: `example.com`
|
|
- Port-aware: `localhost:8000`
|
|
- File URL bypass: `file://` always allowed
|
|
- Fallback to system browser for blocked URLs
|
|
|
|
---
|
|
|
|
### 1.4 Application Entry Point
|
|
|
|
**Tasks:**
|
|
|
|
#### 1.4.1 Main Entry (`src/webdrop_bridge/main.py`)
|
|
|
|
```python
|
|
import sys
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
from webdrop_bridge.config import Config
|
|
from webdrop_bridge.utils.logging import setup_logging
|
|
from webdrop_bridge.core.validator import PathValidator
|
|
from webdrop_bridge.core.drag_interceptor import DragInterceptor
|
|
from webdrop_bridge.ui.main_window import MainWindow
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def main():
|
|
"""Application entry point."""
|
|
# Load configuration
|
|
config = Config.from_env()
|
|
|
|
# Setup logging
|
|
log_file = Path("logs") / "webdrop_bridge.log" if config.enable_logging else None
|
|
setup_logging(config.log_level, log_file)
|
|
|
|
logger.info(f"Starting {config.app_name} v{config.app_version}")
|
|
|
|
# Create application
|
|
app = QApplication(sys.argv)
|
|
app.setApplicationName(config.app_name)
|
|
|
|
# Create validator and interceptor
|
|
validator = PathValidator(config.allowed_roots)
|
|
interceptor = DragInterceptor(validator)
|
|
|
|
# Create main window
|
|
window = MainWindow(config)
|
|
window.show()
|
|
|
|
logger.info("Application started")
|
|
sys.exit(app.exec())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
```
|
|
|
|
**Deliverables:**
|
|
- [x] `src/webdrop_bridge/main.py`
|
|
- [x] Entry point tested
|
|
|
|
**Acceptance Criteria:**
|
|
- Application starts without errors
|
|
- Configuration loaded correctly
|
|
- Logging initialized
|
|
|
|
---
|
|
|
|
## Phase 1.5: Comprehensive Unit Testing
|
|
|
|
### 1.5.1 Unit Test Suite
|
|
|
|
**Objective:** Achieve high code coverage with comprehensive unit tests
|
|
|
|
**Test Files:**
|
|
- [x] `tests/unit/test_config.py` - 12 tests (Config loading, validation, allowed_urls)
|
|
- [x] `tests/unit/test_logging.py` - 9 tests (Console/file logging, rotation, format)
|
|
- [x] `tests/unit/test_validator.py` - 16 tests (Path validation, symlinks, traversal attacks)
|
|
- [x] `tests/unit/test_restricted_web_view.py` - 15 tests (URL whitelist, patterns, browser fallback)
|
|
- [x] `tests/unit/test_project_structure.py` - 3 structural validation tests
|
|
|
|
**Test Coverage Achieved:**
|
|
- Config: 95%
|
|
- Logging: 100%
|
|
- Validator: 94%
|
|
- RestrictedWebEngineView: 95%
|
|
- **Total: 53 tests passing, 48%+ overall coverage**
|
|
|
|
**Key Testing Features:**
|
|
- Pytest with pytest-qt for Qt components
|
|
- Fixtures for temp directories and environment isolation
|
|
- Mock usage for external services (QDesktopServices)
|
|
- Platform-specific tests (Windows symlinks, path handling)
|
|
|
|
---
|
|
|
|
## Phase 2: Testing & Quality (Weeks 5-6)
|
|
|
|
### 2.1 Extended Unit Tests
|
|
|
|
**Files to create/extend:**
|
|
- [x] `tests/unit/test_config.py` - Complete
|
|
- [x] `tests/unit/test_validator.py` - Complete
|
|
- [x] `tests/unit/test_drag_interceptor.py` - 25 tests (96% coverage)
|
|
- [x] `tests/unit/test_main_window.py` - 38 tests (88% coverage)
|
|
|
|
**Target Coverage**: 80%+ line coverage ✅ ACHIEVED (85% overall)
|
|
|
|
**Test Suite Results:**
|
|
- **Total Tests**: 99 passing
|
|
- **Overall Coverage**: 85%
|
|
- Config: 95%
|
|
- DragInterceptor: 96%
|
|
- Validator: 94%
|
|
- MainWindow: 88%
|
|
- RestrictedWebEngineView: 95%
|
|
- Logging: 100%
|
|
|
|
---
|
|
|
|
### 2.2 Integration Tests
|
|
|
|
**Files to create:**
|
|
- [ ] `tests/integration/test_drag_workflow.py`
|
|
- [ ] `tests/integration/test_webapp_loading.py`
|
|
- [ ] `tests/integration/test_end_to_end.py`
|
|
|
|
**Test Scenarios:**
|
|
1. Start app → Load webapp → Verify ready
|
|
2. Initiate drag → Validate → Drop file → Verify received
|
|
3. Invalid path → Reject → No file created
|
|
|
|
---
|
|
|
|
### 2.3 Code Quality
|
|
|
|
**Checklist:**
|
|
- [x] Black formatting: `tox -e format`
|
|
- [x] Ruff linting: `tox -e lint`
|
|
- [x] Type checking: `tox -e type` (with pragmatic type: ignore for Qt inheritance)
|
|
- [x] Coverage report: `pytest --cov=src/webdrop_bridge` (85% achieved)
|
|
- [ ] Security scan: `pip audit` (optional future)
|
|
- [ ] Coverage report: `pytest --cov=src/webdrop_bridge`
|
|
- [ ] Security scan: `pip audit`
|
|
|
|
---
|
|
|
|
## Phase 3: Build & Distribution (Weeks 7-8)
|
|
|
|
### 3.1 Windows Installer (Executable + MSI)
|
|
|
|
**Build Script** (`build/scripts/build_windows.py`):
|
|
- PyInstaller compilation with proper spec file
|
|
- Standalone executable generation
|
|
- Optional WiX MSI installer creation
|
|
- Optional code signing support
|
|
- Clean build management
|
|
|
|
**Features:**
|
|
- ✅ Automatic dependency bundling (PySide6, Qt, Chromium)
|
|
- ✅ Resource embedding (webapp, icons, stylesheets)
|
|
- ✅ Hidden imports configuration for Qt Web Engine
|
|
- ✅ Output validation and size reporting
|
|
- ✅ WiX support for professional MSI creation
|
|
|
|
**PyInstaller Configuration** (`build/webdrop_bridge.spec`):
|
|
- Bundles all dependencies (PySide6, Qt6 libraries)
|
|
- Includes webapp files and resources
|
|
- Sets up GUI mode (no console window)
|
|
- Cross-platform compatible
|
|
|
|
**Usage:**
|
|
```bash
|
|
# Build executable only
|
|
python build/scripts/build_windows.py
|
|
|
|
# Build with MSI installer (requires WiX)
|
|
python build/scripts/build_windows.py --msi
|
|
|
|
# Build with code signing (requires certificate)
|
|
python build/scripts/build_windows.py --sign
|
|
```
|
|
|
|
**Build Results:**
|
|
- ✅ **Executable**: `WebDropBridge.exe` (195.66 MB)
|
|
- ✅ **Output Directory**: `build/dist/windows/`
|
|
- ✅ Contains all dependencies including Chromium engine
|
|
|
|
**Acceptance Criteria:**
|
|
- [x] Executable builds successfully
|
|
- [x] Executable runs standalone (no Python required)
|
|
- [x] All dependencies bundled correctly
|
|
- [ ] MSI installer creation (requires WiX installation)
|
|
- [ ] Code signing (requires certificate)
|
|
|
|
---
|
|
|
|
### 3.2 macOS DMG Package
|
|
|
|
**Build Script** (`build/scripts/build_macos.sh`):
|
|
- PyInstaller for .app bundle creation
|
|
- DMG image generation
|
|
- Professional DMG styling (optional via create-dmg)
|
|
- Code signing and notarization support
|
|
- Comprehensive error handling
|
|
|
|
**Features:**
|
|
- ✅ Creates proper macOS .app bundle
|
|
- ✅ DMG image for distribution
|
|
- ✅ Professional volume icon and layout
|
|
- ✅ Code signing with signing identities
|
|
- ✅ Apple notarization support
|
|
- ✅ Checksum verification
|
|
|
|
**Usage:**
|
|
```bash
|
|
# Build .app bundle and DMG
|
|
bash build/scripts/build_macos.sh
|
|
|
|
# With code signing
|
|
bash build/scripts/build_macos.sh --sign
|
|
|
|
# With notarization
|
|
bash build/scripts/build_macos.sh --notarize
|
|
```
|
|
|
|
**Configuration (Environment Variables):**
|
|
```bash
|
|
# For code signing
|
|
export APPLE_SIGNING_ID="Developer ID Application: Company Name"
|
|
|
|
# For notarization
|
|
export APPLE_ID="your@apple.id"
|
|
export APPLE_PASSWORD="app-specific-password"
|
|
export APPLE_TEAM_ID="XXXXXXXXXX"
|
|
```
|
|
|
|
**Acceptance Criteria:**
|
|
- [ ] .app bundle builds successfully
|
|
- [ ] DMG image creates without errors
|
|
- [ ] DMG mounts and shows contents properly
|
|
- [ ] Code signing works
|
|
- [ ] Notarization passes
|
|
|
|
---
|
|
|
|
## Phase 4: Professional Features & Auto-Update (Weeks 9-12)
|
|
|
|
### 4.1 Auto-Update System with Forgejo Integration
|
|
|
|
**Forgejo Configuration:**
|
|
```
|
|
Host: https://git.him-tools.de
|
|
Organization: HIM-public
|
|
Repository: webdrop-bridge
|
|
API Endpoint: https://git.him-tools.de/api/v1/repos/HIM-public/webdrop-bridge
|
|
```
|
|
|
|
**Tasks:**
|
|
|
|
#### 4.1.1 Update Manager (`src/webdrop_bridge/core/updater.py`)
|
|
|
|
```python
|
|
class UpdateManager:
|
|
"""Manages auto-updates via Forgejo releases."""
|
|
|
|
def __init__(self, config: Config):
|
|
self.forgejo_url = "https://git.him-tools.de"
|
|
self.repo = "HIM-public/webdrop-bridge"
|
|
self.current_version = config.app_version
|
|
|
|
async def check_for_updates(self) -> Optional[Release]:
|
|
"""Query Forgejo API for latest release.
|
|
|
|
Returns:
|
|
Release info if newer version available, None otherwise
|
|
"""
|
|
# GET https://git.him-tools.de/api/v1/repos/HIM-public/webdrop-bridge/releases/latest
|
|
# Compare version semantic versioning
|
|
|
|
async def download_update(self, release: Release) -> Path:
|
|
"""Download MSI/DMG from Forgejo release artifacts.
|
|
|
|
Args:
|
|
release: Release information from API
|
|
|
|
Returns:
|
|
Path to downloaded installer file
|
|
"""
|
|
# Download from release assets
|
|
# Verify checksums
|
|
|
|
def install_update(self, installer_path: Path) -> bool:
|
|
"""Launch installer and schedule restart.
|
|
|
|
Args:
|
|
installer_path: Path to MSI or DMG file
|
|
|
|
Returns:
|
|
True if installation scheduled, False otherwise
|
|
"""
|
|
```
|
|
|
|
**Configuration** (`.env`):
|
|
```env
|
|
# Auto-Update Settings
|
|
FORGEJO_URL=https://git.him-tools.de
|
|
FORGEJO_REPO=HIM-public/webdrop-bridge
|
|
AUTO_UPDATE_CHECK=true
|
|
AUTO_UPDATE_INTERVAL=86400 # 24 hours in seconds
|
|
AUTO_UPDATE_NOTIFY=true
|
|
```
|
|
|
|
**Features:**
|
|
- Check on startup (with 24h cache)
|
|
- Manual "Check for Updates" menu option
|
|
- Background download (non-blocking)
|
|
- User notification with changelog
|
|
- Automatic restart capability
|
|
- Rollback to previous version (optional)
|
|
- Security: HTTPS-only, checksum verification
|
|
|
|
**Deliverables:**
|
|
- [ ] `src/webdrop_bridge/core/updater.py` - Update manager
|
|
- [ ] Menu item for manual update check
|
|
- [ ] Update notification dialog
|
|
- [ ] Unit tests for update checking and downloading
|
|
- [ ] Integration with Forgejo API
|
|
|
|
**Acceptance Criteria:**
|
|
- Can query Forgejo releases API
|
|
- Detects new versions correctly
|
|
- Downloads and verifies checksums
|
|
- Prompts user for restart
|
|
- Manual check works from menu
|
|
- Gracefully handles network errors
|
|
- Version comparison uses semantic versioning
|
|
|
|
---
|
|
|
|
### 4.2 Enhanced Logging & Monitoring
|
|
|
|
**Deliverables:**
|
|
- [ ] Structured logging (JSON format option)
|
|
- [ ] Log rotation/archival
|
|
- [ ] Performance metrics collection
|
|
- [ ] Crash reporting (optional)
|
|
|
|
---
|
|
|
|
### 4.3 Advanced Configuration
|
|
|
|
**Deliverables:**
|
|
- [ ] UI settings dialog
|
|
- [ ] Configuration validation schema
|
|
- [ ] Profile support (work, personal, etc.)
|
|
- [ ] Export/import settings
|
|
|
|
---
|
|
|
|
### 4.4 User Documentation
|
|
|
|
**Deliverables:**
|
|
- [ ] User manual (PDF, HTML)
|
|
- [ ] Video tutorials
|
|
- [ ] Troubleshooting guide
|
|
- [ ] API documentation for developers
|
|
|
|
---
|
|
|
|
## Phase 5: Post-Release (Months 2-3)
|
|
|
|
### 5.1 Analytics & Monitoring
|
|
|
|
**Requirements:**
|
|
- App usage statistics
|
|
- Error/crash reporting
|
|
- Feature usage tracking
|
|
|
|
---
|
|
|
|
### 5.2 Community Support
|
|
|
|
**Channels:**
|
|
- GitHub Issues
|
|
- Discussions forum
|
|
- Community Slack
|
|
- Email support
|
|
|
|
---
|
|
|
|
## Technical Specifications
|
|
|
|
### Supported Platforms
|
|
|
|
```
|
|
┌─────────────────┬──────────┬────────┬────────────┐
|
|
│ Platform │ Version │ Arch │ Status │
|
|
├─────────────────┼──────────┼────────┼────────────┤
|
|
│ Windows │ 10, 11 │ x64 │ Primary │
|
|
│ macOS │ 12-14 │ Intel │ Primary │
|
|
│ macOS │ 12-14 │ ARM64 │ Primary │
|
|
│ Linux │ Ubuntu │ x64 │ Experimental│
|
|
└─────────────────┴──────────┴────────┴────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### Dependencies
|
|
|
|
**Core:**
|
|
- PySide6 6.6.0+
|
|
- Python 3.10+
|
|
|
|
**Optional:**
|
|
- PyInstaller (building)
|
|
- Sphinx (documentation)
|
|
- pytest (testing)
|
|
- aiohttp (auto-update downloads)
|
|
- packaging (version comparison)
|
|
|
|
---
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
webdrop-bridge/
|
|
│
|
|
├── src/webdrop_bridge/
|
|
│ ├── __init__.py
|
|
│ ├── main.py ← Entry point
|
|
│ ├── config.py ← Configuration
|
|
│ ├── core/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── validator.py ← Path validation
|
|
│ │ ├── drag_interceptor.py ← Drag handling
|
|
│ │ ├── updater.py ← Auto-update system (Phase 4)
|
|
│ │ └── errors.py ← Custom exceptions
|
|
│ ├── ui/
|
|
│ │ ├── __init__.py
|
|
│ │ ├── main_window.py ← Main UI
|
|
│ │ ├── widgets.py ← Reusable widgets
|
|
│ │ └── styles.py ← UI styling
|
|
│ └── utils/
|
|
│ ├── __init__.py
|
|
│ ├── logging.py ← Logging setup
|
|
│ ├── constants.py ← App constants
|
|
│ └── helpers.py ← Utility functions
|
|
│
|
|
├── tests/
|
|
│ ├── __init__.py
|
|
│ ├── conftest.py ← Pytest fixtures
|
|
│ ├── unit/ ← Unit tests
|
|
│ ├── integration/ ← Integration tests
|
|
│ └── fixtures/ ← Test data
|
|
│
|
|
├── build/
|
|
│ ├── windows/ ← Windows build config
|
|
│ ├── macos/ ← macOS build config
|
|
│ └── scripts/
|
|
│ ├── build_windows.py
|
|
│ └── build_macos.sh
|
|
│
|
|
├── webapp/ ← Embedded web app
|
|
│ └── index.html
|
|
│
|
|
├── resources/
|
|
│ ├── icons/ ← App icons
|
|
│ └── stylesheets/ ← Qt stylesheets
|
|
│
|
|
├── docs/ ← Documentation
|
|
│ ├── architecture.md
|
|
│ ├── api.md
|
|
│ └── troubleshooting.md
|
|
│
|
|
└── Configuration Files
|
|
├── pyproject.toml ← Modern Python packaging
|
|
├── setup.py ← Backwards compatibility
|
|
├── pytest.ini ← Test config
|
|
├── tox.ini ← Automation config
|
|
└── .github/workflows/ ← CI/CD
|
|
```
|
|
|
|
---
|
|
|
|
## Risk Analysis & Mitigation
|
|
|
|
| Risk | Probability | Impact | Mitigation |
|
|
|------|-------------|--------|-----------|
|
|
| Qt/PySide6 API changes | Low | High | Lock versions, monitor releases |
|
|
| macOS code signing | Medium | Medium | Use Apple Developer account, automate |
|
|
| Drag performance issues | Low | Medium | Performance testing early, profiling |
|
|
| Cross-platform bugs | Medium | Medium | Extensive testing on both platforms |
|
|
| Security vulnerabilities | Low | High | Regular audits, dependency scanning |
|
|
| User adoption | Medium | Medium | Clear documentation, community engagement |
|
|
|
|
---
|
|
|
|
## Success Metrics
|
|
|
|
| Metric | Target | Timeline |
|
|
|--------|--------|----------|
|
|
| Code coverage | 80%+ | Week 6 |
|
|
| Test pass rate | 100% | Continuous |
|
|
| Build time | <2 min | Week 8 |
|
|
| Application startup | <1 sec | Week 8 |
|
|
| Installer size | <150 MB | Week 8 |
|
|
| Documentation completeness | 100% | Week 12 |
|
|
| Community contributions | 5+ | Month 3 |
|
|
|
|
---
|
|
|
|
## Milestones & Timeline
|
|
|
|
```
|
|
January 2026
|
|
├── Week 1-2: Core Architecture (config, logging, validator)
|
|
├── Week 3-4: UI Components (main window, drag interceptor)
|
|
├── Week 5-6: Testing & Quality Assurance
|
|
├── Week 7-8: Build & Installer Creation
|
|
├── Week 9-10: Advanced Features & Polish
|
|
├── Week 11-12: Documentation & Release
|
|
│
|
|
February 2026
|
|
└── Post-release: Auto-updates, Analytics, Community Support
|
|
```
|
|
|
|
---
|
|
|
|
## Open Questions & Decisions
|
|
|
|
### Decision: Embedded Web App vs. External URL
|
|
|
|
**Options:**
|
|
1. Embed static web app (current PoC approach)
|
|
2. Load from remote server
|
|
3. Hybrid: Support both
|
|
|
|
**Decision**: **Hybrid approach**
|
|
- Default: Load from `WEBAPP_URL` (local file)
|
|
- Configurable: Allow remote URLs for advanced users
|
|
- Security: Validate origins against whitelist
|
|
|
|
---
|
|
|
|
### Decision: Update Mechanism
|
|
|
|
**Options:**
|
|
1. Manual downloads from website
|
|
2. Auto-update with staged rollout
|
|
3. Package manager (Chocolatey, Brew)
|
|
|
|
**Decision**: **GitHub Releases + PyInstaller**
|
|
- Phase 1: Manual downloads
|
|
- Phase 5: Auto-update via custom launcher
|
|
|
|
---
|
|
|
|
### Decision: Telemetry
|
|
|
|
**Options:**
|
|
1. No telemetry
|
|
2. Anonymous usage statistics
|
|
3. Detailed crash reporting
|
|
|
|
**Decision**: **Opt-in telemetry**
|
|
- No data collection by default
|
|
- Users can enable for error reporting
|
|
- Full transparency about data collected
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Immediate** (This week):
|
|
- [ ] Set up project directories ✅
|
|
- [ ] Create configuration system
|
|
- [ ] Implement path validator
|
|
- [ ] Set up CI/CD
|
|
|
|
2. **Near term** (Next 2 weeks):
|
|
- [ ] Complete core components
|
|
- [ ] Write comprehensive tests
|
|
- [ ] Build installers
|
|
|
|
3. **Medium term** (Weeks 5-8):
|
|
- [ ] Code review & QA
|
|
- [ ] Performance optimization
|
|
- [ ] Documentation
|
|
|
|
4. **Long term** (Months 2-3):
|
|
- [ ] Advanced features
|
|
- [ ] Community engagement
|
|
- [ ] Auto-update system
|
|
|
|
---
|
|
|
|
## Document Control
|
|
|
|
| Version | Date | Author | Changes |
|
|
|---------|------|--------|---------|
|
|
| 1.0 | Jan 28, 2026 | Team | Initial plan |
|
|
|
|
---
|
|
|
|
## Appendices
|
|
|
|
### A. Technology Stack Justification
|
|
|
|
**PySide6 over PyQt5/6:**
|
|
- Modern LGPL licensing
|
|
- Excellent Windows & macOS support
|
|
- Strong community and documentation
|
|
- Built-in WebEngine
|
|
|
|
**PyInstaller over Briefcase/Nuitka:**
|
|
- Mature, stable, well-tested
|
|
- Excellent one-file executable support
|
|
- Cross-platform build scripts
|
|
|
|
**pytest over unittest:**
|
|
- Modern, expressive syntax
|
|
- Powerful fixtures and plugins
|
|
- Better integration with CI/CD
|
|
|
|
---
|
|
|
|
### B. Security Considerations
|
|
|
|
**Path Validation:**
|
|
- Only allow whitelisted directories
|
|
- Resolve paths to prevent symlink attacks
|
|
- Validate file existence before drag
|
|
|
|
**Web Engine:**
|
|
- Disable remote URL loading by default
|
|
- Implement CSP headers
|
|
- Regular security audits
|
|
|
|
**User Data:**
|
|
- No personally identifiable information stored
|
|
- Configuration stored locally
|
|
- Encrypted settings (future)
|
|
|
|
---
|
|
|
|
### C. Performance Targets
|
|
|
|
```
|
|
- Application startup: < 1 second
|
|
- Drag interception: < 10ms
|
|
- File drag initiation: < 50ms
|
|
- Memory usage: < 200MB baseline
|
|
- Memory/file size ratio: < 2MB per 100 files
|
|
```
|
|
|
|
---
|
|
|
|
**For updates or clarifications, see the main README.md or open an issue on GitHub.**
|