diff --git a/.gitignore b/.gitignore index 4e45676..06f7595 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__/ .ruff_cache/ dist/ build/ +dist/ # Sensitive vault exports, logs, and local runtime state *.json diff --git a/DataLoom.spec b/DataLoom.spec new file mode 100644 index 0000000..39aa33d --- /dev/null +++ b/DataLoom.spec @@ -0,0 +1,33 @@ +# PyInstaller one-file Windows build configuration. +# Build from the repository root with: pyinstaller --clean --noconfirm DataLoom.spec + +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = collect_submodules("PySide6") + +a = Analysis( + ["src/dataloom/app.py"], + pathex=["src"], + binaries=[], + datas=[], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) +pyz = PYZ(a.pure) +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name="DataLoom", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, +) diff --git a/build-windows.ps1 b/build-windows.ps1 new file mode 100644 index 0000000..94d1a70 --- /dev/null +++ b/build-windows.ps1 @@ -0,0 +1,13 @@ +$ErrorActionPreference = "Stop" + +Write-Host "Creating or reusing the DataLoom Windows build environment..." +if (-not (Test-Path ".venv\\Scripts\\python.exe")) { + py -3 -m venv .venv +} + +& .venv\\Scripts\\python.exe -m pip install --upgrade pip +& .venv\\Scripts\\python.exe -m pip install -e ".[build,test]" +& .venv\\Scripts\\python.exe -m pytest -q +& .venv\\Scripts\\python.exe -m PyInstaller --clean --noconfirm DataLoom.spec + +Write-Host "Portable executable created at dist\\DataLoom.exe" diff --git a/docs/windows-build.md b/docs/windows-build.md new file mode 100644 index 0000000..8d1e9fd --- /dev/null +++ b/docs/windows-build.md @@ -0,0 +1,41 @@ +# Windows 11 portable build + +The final portable executable must be built on Windows 11 because PyInstaller produces a platform-specific executable. + +## Requirements + +- Windows 11 64-bit +- Python 3.11 or newer from python.org, with the Python launcher (`py`) available +- Git, or a ZIP copy of this repository +- Internet access during the one-time dependency installation only + +## Build + +Open PowerShell in the repository root and run: + +```powershell +Set-ExecutionPolicy -Scope Process Bypass +.\\build-windows.ps1 +``` + +The script creates a project-local virtual environment, installs the application and build/test dependencies, runs the synthetic tests, and creates: + +```text +dist\\DataLoom.exe +``` + +The executable is intended to be portable and does not require Python on the destination Windows machine. + +## Validation checklist + +- Launch `dist\\DataLoom.exe` on Windows 11. +- Import `tests\\fixtures\\sample-export-a.json` and `sample-export-b.json`. +- Confirm 8 loaded, 7 included, 1 exact duplicate removed, and 2 review groups. +- Confirm secret logging is unchecked by default. +- Confirm the duplicate detail view shows both records and the newer revision preference. +- Export a merged JSON file and verify the displayed output and audit-log paths. +- Test on a second Windows 11 machine or clean user profile if practical. + +## Sensitive data + +Do not copy real Bitwarden exports, merged vaults, audit logs, `.venv`, `build`, or `dist` contents into the repository. Use copies of vault exports only for local testing. diff --git a/pyproject.toml b/pyproject.toml index 09dffb5..eaae520 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = ["PySide6>=6.6"] [project.optional-dependencies] test = ["pytest>=8.0"] +build = ["pyinstaller>=6.0"] [project.scripts] dataloom = "dataloom.app:main" diff --git a/src/dataloom/ui/main_window.py b/src/dataloom/ui/main_window.py index 6702fbf..a0d79c1 100644 --- a/src/dataloom/ui/main_window.py +++ b/src/dataloom/ui/main_window.py @@ -5,7 +5,7 @@ from pathlib import Path from PySide6.QtWidgets import ( QAbstractItemView, QApplication, QFileDialog, QLabel, QListWidget, QMainWindow, QMessageBox, QPushButton, QTabWidget, QTextEdit, - QVBoxLayout, QWidget, + QCheckBox, QVBoxLayout, QWidget, ) from dataloom.core.audit import AuditLog @@ -23,20 +23,26 @@ class MainWindow(QMainWindow): self.audit = None self.summary = QTextEdit(); self.summary.setReadOnly(True) self.duplicates = QListWidget(); self.duplicates.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.duplicate_detail = QTextEdit(); self.duplicate_detail.setReadOnly(True) + self.duplicates.currentRowChanged.connect(self.show_duplicate_detail) self.log_view = QTextEdit(); self.log_view.setReadOnly(True) self.entries_view = QListWidget() tabs = QTabWidget() tabs.addTab(self._page("Import Summary", self.summary), "Import Summary") tabs.addTab(self._page("Merged Entries", self.entries_view), "Merged Entries") - tabs.addTab(self._page("Duplicate Review", self.duplicates), "Duplicate Review") + duplicate_page = QWidget(); duplicate_layout = QVBoxLayout(duplicate_page) + duplicate_layout.addWidget(QLabel("