- Introduced `allowed_urls` in configuration to specify whitelisted domains/patterns. - Implemented `RestrictedWebEngineView` to enforce URL restrictions in the web view. - Updated `MainWindow` to utilize the new restricted web view and added navigation toolbar. - Enhanced unit tests for configuration and restricted web view to cover new functionality.
25 KiB
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)
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:
src/webdrop_bridge/config.py- Configuration management.env.example- Environment template- 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://
- Examples:
Acceptance Criteria:
- Config loads from
.envfile - All environment variables have sensible defaults
- Invalid values raise
ConfigurationError
1.1.2 Logging System (src/webdrop_bridge/utils/logging.py)
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)
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)
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
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:
src/webdrop_bridge/ui/main_window.pysrc/webdrop_bridge/ui/restricted_web_view.py- URL whitelist enforcement- 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.
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)
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:
src/webdrop_bridge/main.py- 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:
tests/unit/test_config.py- 12 tests (Config loading, validation, allowed_urls)tests/unit/test_logging.py- 9 tests (Console/file logging, rotation, format)tests/unit/test_validator.py- 16 tests (Path validation, symlinks, traversal attacks)tests/unit/test_restricted_web_view.py- 15 tests (URL whitelist, patterns, browser fallback)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:
tests/unit/test_config.py- Completetests/unit/test_validator.py- Completetests/unit/test_drag_interceptor.pytests/unit/test_main_window.py
Target Coverage: 80%+ line coverage
2.2 Integration Tests
Files to create:
tests/integration/test_drag_workflow.pytests/integration/test_webapp_loading.pytests/integration/test_end_to_end.py
Test Scenarios:
- Start app → Load webapp → Verify ready
- Initiate drag → Validate → Drop file → Verify received
- 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:
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
.appbundle - 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:
- Embed static web app (current PoC approach)
- Load from remote server
- 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:
- Manual downloads from website
- Auto-update with staged rollout
- Package manager (Chocolatey, Brew)
Decision: GitHub Releases + PyInstaller
- Phase 1: Manual downloads
- Phase 5: Auto-update via custom launcher
Decision: Telemetry
Options:
- No telemetry
- Anonymous usage statistics
- 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
-
Immediate (This week):
- Set up project directories ✅
- Create configuration system
- Implement path validator
- Set up CI/CD
-
Near term (Next 2 weeks):
- Complete core components
- Write comprehensive tests
- Build installers
-
Medium term (Weeks 5-8):
- Code review & QA
- Performance optimization
- Documentation
-
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.