# 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 - [ ] `tests/unit/test_drag_interceptor.py` - [ ] `tests/unit/test_main_window.py` **Target Coverage**: 80%+ line coverage --- ### 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:** - [ ] Black formatting: `tox -e format` - [ ] Ruff linting: `tox -e lint` - [ ] Type checking: `tox -e type` - [ ] Coverage report: `pytest --cov=src/webdrop_bridge` - [ ] Security scan: `pip audit` --- ## Phase 3: Build & Distribution (Weeks 7-8) ### 3.1 Windows Installer (MSI) **Setup:** ```bash pip install pyinstaller ``` **Build Script** (`build/scripts/build_windows.py`): - Compile with PyInstaller - Create MSI with WiX (optional: advanced features) - Code signing (optional: professional deployment) - Output: `WebDropBridge-1.0.0-Setup.exe` **Acceptance Criteria:** - Executable runs standalone - Installer installs to Program Files - Uninstaller removes all files - Shortcuts created in Start Menu --- ### 3.2 macOS DMG Package **Build Script** (`build/scripts/build_macos.sh`): - Compile with PyInstaller - Create `.app` bundle - Generate DMG image - Code signing (optional) - Output: `WebDropBridge-1.0.0.dmg` **Acceptance Criteria:** - App bundle signed (if applicable) - DMG opens in Finder - Drag-to-Applications works - Notarization passes (if applicable) --- ## Phase 4: Professional Features (Weeks 9-12) ### 4.1 Enhanced Logging & Monitoring **Deliverables:** - [ ] Structured logging (JSON format option) - [ ] Log rotation/archival - [ ] Performance metrics collection - [ ] Crash reporting (optional) --- ### 4.2 Advanced Configuration **Deliverables:** - [ ] UI settings dialog - [ ] Configuration validation schema - [ ] Profile support (work, personal, etc.) - [ ] Export/import settings --- ### 4.3 User Documentation **Deliverables:** - [ ] User manual (PDF, HTML) - [ ] Video tutorials - [ ] Troubleshooting guide - [ ] API documentation for developers --- ## Phase 5: Post-Release (Months 2-3) ### 5.1 Auto-Update System **Requirements:** - Check for updates on startup - Download in background - Staged rollout support - Rollback capability --- ### 5.2 Analytics & Monitoring **Metrics:** - App usage statistics - Error/crash reporting - Feature usage tracking --- ### 5.3 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) --- ### 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 │ │ └── 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.**