diff --git a/.github/WORKFLOW_README.md b/.github/WORKFLOW_README.md index 51a15fd4..5689fb2c 100644 --- a/.github/WORKFLOW_README.md +++ b/.github/WORKFLOW_README.md @@ -55,20 +55,20 @@ git push origin v1.0.0 ### Windows -- **NSIS Installer**: `StratoSort--win-x64.exe` -- **Portable**: `StratoSort--win-x64.portable.exe` +- **NSIS Installer**: `StratoSortCore-Setup-.exe` +- **Portable**: `StratoSortCore--win-x64.exe` - **Checksums**: `checksums.sha256` - **Updater metadata**: `latest.yml`, `*.blockmap` ### macOS (manual only) -- **DMG**: `StratoSort--mac-.dmg` -- **ZIP**: `StratoSort--mac-.zip` +- **DMG**: `StratoSortCore--mac-.dmg` +- **ZIP**: `StratoSortCore--mac-.zip` ### Linux (manual only) -- **AppImage**: `StratoSort--linux-x64.AppImage` -- **DEB**: `StratoSort--linux-x64.deb` +- **AppImage**: `StratoSortCore--linux-x64.AppImage` +- **DEB**: `StratoSortCore--linux-x64.deb` ## Configuration Files @@ -76,7 +76,7 @@ git push origin v1.0.0 - Configures build outputs and installer settings - Publishing is handled by GitHub Actions; build commands use `--publish never` -- Clean artifact naming: `StratoSort-${version}-${os}-${arch}.${ext}` +- Clean artifact naming: `StratoSortCore-${version}-${os}-${arch}.${ext}` ## Required Secrets diff --git a/CHANGELOG.md b/CHANGELOG.md index 69becb72..a3192a0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -215,7 +215,7 @@ notes. - Accessibility: Added `aria-live="polite"` to AnalysisProgress for screen readers - Replaced hardcoded timeout values with centralized constants -## [1.0.0] - 2024-12-XX +## [1.0.0] - 2024-12-01 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index ef1c911e..a0dd1bf6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -65,7 +65,8 @@ npm run dist:win # Build Windows installer - **IPC channels** are defined in `src/shared/constants.js` and validated in preload. Run `npm run generate:channels:check` to verify channel consistency. -- **Services** follow dependency injection patterns documented in `docs/DI_PATTERNS.md`. +- **Services** follow dependency injection via `ServiceContainer` + (`src/main/services/ServiceContainer.js`). - **Error handling** uses custom error types in `src/main/errors/` with a centralized error classifier. - **File operations** use atomic writes via `src/shared/atomicFileOperations.js` (write-to-temp then @@ -101,21 +102,20 @@ These are tracked gaps that the project slash commands help address: - ~~Test coverage unknown~~ - 50% global thresholds (branches/functions/lines/statements) enforced in `test/jest.config.js`; 346 suites / 5,500+ tests passing. -## Cursor Rules & Commands - -The following capabilities are available as Cursor Rules. You can invoke them by asking for the -specific audit or check in natural language. - -| Request | Purpose | Rule File | -| :-------------------- | :------------------------------------ | :-------------------------------------- | -| "Run security audit" | Full Electron security audit | `.cursor/rules/audit-security.mdc` | -| "Harden electron" | Verify sandbox, CSP, webPreferences | `.cursor/rules/harden-electron.mdc` | -| "Audit IPC" | Validate IPC contracts and security | `.cursor/rules/audit-ipc.mdc` | -| "Check test coverage" | Run tests, analyze coverage gaps | `.cursor/rules/check-coverage.mdc` | -| "Pre-release check" | Pre-release checklist validation | `.cursor/rules/check-prerelease.mdc` | -| "Performance audit" | Performance and memory analysis | `.cursor/rules/audit-perf.mdc` | -| "Accessibility audit" | Accessibility/WCAG compliance audit | `.cursor/rules/audit-a11y.mdc` | -| "Dependency audit" | Dependency security and license audit | `.cursor/rules/audit-deps.mdc` | -| "Check build" | Build config and packaging validation | `.cursor/rules/check-build.mdc` | -| "Validate state" | State persistence and migration audit | `.cursor/rules/validate-state.mdc` | -| "Fix production gaps" | Interactive production gap fixer | `.cursor/rules/fix-production-gaps.mdc` | +## Audits & Checks + +The following audits can be invoked by asking in natural language: + +| Request | Purpose | +| :-------------------- | :------------------------------------ | +| "Run security audit" | Full Electron security audit | +| "Harden electron" | Verify sandbox, CSP, webPreferences | +| "Audit IPC" | Validate IPC contracts and security | +| "Check test coverage" | Run tests, analyze coverage gaps | +| "Pre-release check" | Pre-release checklist validation | +| "Performance audit" | Performance and memory analysis | +| "Accessibility audit" | Accessibility/WCAG compliance audit | +| "Dependency audit" | Dependency security and license audit | +| "Check build" | Build config and packaging validation | +| "Validate state" | State persistence and migration audit | +| "Fix production gaps" | Interactive production gap fixer | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66baddcc..d016b0cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,13 +42,11 @@ npm run dev ## Where to Learn the Codebase -| Document | Purpose | -| ---------------------------------------------- | ----------------------------- | -| [Learning Guide](docs/LEARNING_GUIDE.md) | Onboarding and glossary | -| [Architecture](docs/ARCHITECTURE.md) | System design and data flow | -| [DI Patterns](docs/DI_PATTERNS.md) | Dependency injection patterns | -| [Error Handling](docs/ERROR_HANDLING_GUIDE.md) | Error handling standards | -| [Code Quality](docs/CODE_QUALITY_STANDARDS.md) | Style and review expectations | +| Document | Purpose | +| ---------------------------------------------- | --------------------------- | +| [Architecture](docs/ARCHITECTURE.md) | System design and data flow | +| [Error Handling](docs/ERROR_HANDLING_GUIDE.md) | Error handling standards | +| [IPC Contracts](docs/IPC_CONTRACTS.md) | IPC communication specs | ## Development Workflow diff --git a/README.md b/README.md index f3b7e10c..497d2eb3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@

Features • + Download • + SupportQuick StartDocumentationContributing • @@ -31,10 +33,35 @@ --- -**StratoSort Core** transforms file chaos into intelligent order using privacy-first local AI. It -automatically categorizes, tags, and organizes your documents completely offline—leveraging -**built-in AI** (node-llama-cpp) for intelligence and **Orama** for semantic search—ensuring your -data never leaves your machine. **Zero external dependencies required.** +StratoSort Core helps you organize messy files with local AI that runs on your machine. It analyzes +content (not just filenames), suggests where files belong, and gives you semantic search with +Knowledge OS and graph tools. Your data stays local, and you can start with a normal installer - no +CLI setup required. + +## Download + +### End Users (No CLI) + +

+ Download Latest Release +

+ +- **Windows/macOS installers:** + [GitHub Releases](https://github.com/iLevyTate/StratoSortCore/releases) +- **Step-by-step install help:** [Install Guide](docs/INSTALL_GUIDE.md) +- **How to use the app:** [User Guide](docs/USER_GUIDE.md) +- **Want to help test?** [Beta Tester Guide](docs/BETA_TESTER_GUIDE.md) + +## Support and Feedback + +If you run into an issue or have an idea, these links are the fastest way to help: + +- **Issues tab:** [View all issues](https://github.com/iLevyTate/StratoSortCore/issues) +- **Report a bug:** + [Open bug report template](https://github.com/iLevyTate/StratoSortCore/issues/new?template=bug_report.md) +- **Request a feature:** + [Open feature request issue](https://github.com/iLevyTate/StratoSortCore/issues/new) +- **Contributing guide:** [CONTRIBUTING.md](CONTRIBUTING.md) ## Demo @@ -146,15 +173,16 @@ See **[SECURITY.md](SECURITY.md)** for the complete security policy. ## Documentation -| Document | Description | -| :--------------------------------------------- | :--------------------------------------- | -| **[Install Guide](docs/INSTALL_GUIDE.md)** | End-user install (Windows & Mac, no CLI) | -| **[Getting Started](docs/GETTING_STARTED.md)** | Developer setup and build guide | -| **[Architecture](docs/ARCHITECTURE.md)** | System design and data flow | -| **[Learning Guide](docs/LEARNING_GUIDE.md)** | Codebase onboarding | -| **[Graph Features](docs/FEATURES_GRAPH.md)** | Knowledge Graph capabilities | -| **[IPC Contracts](docs/IPC_CONTRACTS.md)** | IPC communication specifications | -| **[Release Guide](docs/RELEASING.md)** | Release process and checks | +| Document | Description | +| :------------------------------------------------- | :--------------------------------------- | +| **[Install Guide](docs/INSTALL_GUIDE.md)** | End-user install (Windows & Mac, no CLI) | +| **[User Guide](docs/USER_GUIDE.md)** | Feature walkthrough for everyday use | +| **[Beta Tester Guide](docs/BETA_TESTER_GUIDE.md)** | Testing + bug reporting for contributors | +| **[Getting Started](docs/GETTING_STARTED.md)** | Developer setup and build guide | +| **[Architecture](docs/ARCHITECTURE.md)** | System design and data flow | +| **[Graph Features](docs/FEATURES_GRAPH.md)** | Knowledge Graph capabilities | +| **[IPC Contracts](docs/IPC_CONTRACTS.md)** | IPC communication specifications | +| **[Release Guide](docs/RELEASING.md)** | Release process and checks | ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 6cf1a3a9..5e3e74f9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -9,9 +9,9 @@ | Version | Supported | | ------- | ------------------ | -| 1.1.x | :white_check_mark: | -| 1.0.x | :white_check_mark: | -| < 1.0 | :x: | +| 2.0.x | :white_check_mark: | +| 1.1.x | :x: | +| < 1.1 | :x: | ## Security Design Principles @@ -112,4 +112,4 @@ Contributors who report valid security issues will be acknowledged here (with pe --- -_Last updated: January 2026_ +_Last updated: February 2026_ diff --git a/TESTING.md b/TESTING.md index 158b99fe..c2ca8c52 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,6 +1,6 @@ # StratoSort Core Testing Guide -**Version:** 2.0.0 +**Version:** 2.0.1 **Date:** 2026-02-13 **Purpose:** Single source of truth for manual QA and automated test expectations. diff --git a/assets/icons/README.md b/assets/icons/README.md deleted file mode 100644 index 26332b7b..00000000 --- a/assets/icons/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# StratoSort Application Icons - -This directory should contain platform-specific application icons for building the app. - -## Required Icon Files: - -### **Windows** -- `win/icon.ico` - Windows application icon (256x256, 128x128, 64x64, 48x48, 32x32, 16x16) - -### **macOS** -- `mac/icon.icns` - macOS application icon (1024x1024 down to 16x16) - -### **Linux** -- `icon.png` - Linux application icon (512x512 recommended) - -## Icon Generation - -Icons are automatically generated from the source logo (`assets/stratosort-logo.png`) using the built-in generation scripts: - -```bash -# Generate all icons for all platforms -npm run generate:icons - -# Or generate all assets (icons + installer graphics) -npm run generate:assets -``` - -The generation script (`scripts/generate-icons.js`) creates: -- Windows ICO file (multi-resolution: 16px to 256px) -- macOS ICNS file (all required sizes including Retina @2x variants) -- Linux PNG files (16px to 1024px) -- Favicon files for web usage - -## Current Status - -Icons are auto-generated during `npm install` (postinstall hook) and before production builds. - -## Manual Regeneration - -If you update the source logo (`assets/stratosort-logo.png`), regenerate icons by running: - -```bash -npm run generate:icons -``` \ No newline at end of file diff --git a/assets/installer/README.md b/assets/installer/README.md deleted file mode 100644 index b4443c3c..00000000 --- a/assets/installer/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# StratoSort Windows Installer Theming - -This folder contains branding assets for the NSIS installer wizard on Windows. - -## Image Assets - -| File | Dimensions | Purpose | -|------|-----------|---------| -| `welcome.png` | 164x314 px | Left sidebar on Welcome/Finish pages | -| `finish.png` | 164x314 px | Left sidebar on Uninstall pages | -| `header.png` | 150x57 px | Header banner on all pages | -| `dmg-background.png` | 660x400 px | macOS DMG background (not NSIS) | - -## Configuration - -The NSIS installer is configured via: - -1. **electron-builder.json** - Main build configuration - - `nsis.include` points to `build/installer-themed.nsh` - - Icons, shortcuts, and installation options - -2. **build/installer-themed.nsh** - NSIS customization script - - Welcome/Finish page text - - Image references - - Custom install/uninstall hooks - - Abort warning dialogs - -## Features - -The installer includes: -- Custom branded images on all pages -- Welcome page with app description -- Directory selection with custom text -- Finish page with "Launch now" checkbox -- Start Menu shortcuts (StratoSort folder) -- Desktop shortcut (optional) -- First-run marker file for AI setup prompt -- Clean uninstall with optional app data removal - -## Building - -```bash -npm run dist:win -``` - -The installer will be output to `release/build/StratoSort-Setup-{version}.exe` - -## Notes - -- Keep file names exactly as listed -- Files live under `assets/installer/` because `electron-builder.json` sets `buildResources` to `assets` -- PNG images are automatically converted to BMP format by electron-builder -- The `BUILD_RESOURCES_DIR` variable in NSH files points to the `assets` folder - - diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a9620742..66bf3885 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -258,9 +258,4 @@ We maintain separate queues to manage different types of bottlenecks: ## Code Standards -For information on coding patterns, error handling, and dependency injection, refer to the other -documentation files: - -- [CODE_QUALITY_STANDARDS.md](CODE_QUALITY_STANDARDS.md) -- [DI_PATTERNS.md](DI_PATTERNS.md) -- [ERROR_HANDLING_GUIDE.md](ERROR_HANDLING_GUIDE.md) +For error handling patterns and utilities, see [ERROR_HANDLING_GUIDE.md](ERROR_HANDLING_GUIDE.md). diff --git a/docs/BETA_TESTER_GUIDE.md b/docs/BETA_TESTER_GUIDE.md new file mode 100644 index 00000000..c9f3a49c --- /dev/null +++ b/docs/BETA_TESTER_GUIDE.md @@ -0,0 +1,162 @@ +# StratoSort Core Beta Tester Guide + +This guide is for people who want to help test StratoSort Core without using the command line. + +If you can install an app, use it for normal work, and share clear bug reports, you can contribute. + +--- + +## Quick Links + +- **Download:** [Latest release installers](https://github.com/iLevyTate/StratoSortCore/releases) +- **Install help:** [INSTALL_GUIDE.md](./INSTALL_GUIDE.md) +- **Bug reports:** + [Open a bug report](https://github.com/iLevyTate/StratoSortCore/issues/new?template=bug_report.md) +- **General issues:** [Issues board](https://github.com/iLevyTate/StratoSortCore/issues) + +--- + +## Who This Guide Is For + +- You want to help improve StratoSort Core. +- You prefer installers over building from source. +- You can spend a little time reproducing issues and reporting them clearly. + +--- + +## Part 1: Install (No CLI) + +Use the full install walkthrough here: [INSTALL_GUIDE.md](./INSTALL_GUIDE.md). + +### Windows + +1. Download the latest `.exe` from [Releases](https://github.com/iLevyTate/StratoSortCore/releases). +2. Run the installer. +3. If SmartScreen appears, click **More info** then **Run anyway**. +4. Launch StratoSort Core. + +### macOS + +1. Download the matching `.dmg` from + [Releases](https://github.com/iLevyTate/StratoSortCore/releases): + - `mac-arm64` for Apple Silicon (M1/M2/M3/M4) + - `mac-x64` for Intel Macs +2. Drag StratoSort Core into Applications. +3. Open the app. +4. If macOS blocks it, right-click app -> **Open**, or use **System Settings -> Privacy & Security + -> Open Anyway**. + +### First Launch + +1. In the setup wizard, choose **Download recommended models** (one-time download). +2. Wait until setup finishes. +3. Continue into the app workflow. + +--- + +## Part 2: Run a Useful Beta Test Session + +Use this short checklist to create high-value feedback: + +1. **Setup phase** + - Add at least 3-5 Smart Folders. + - Include clear folder descriptions. +2. **Discover phase** + - Analyze a mixed batch (documents, images, screenshots, PDFs). +3. **Organize phase** + - Accept some suggestions, reject others, test rename options. +4. **Search / Knowledge OS** + - Try natural-language queries in search. + - Open the Knowledge Graph view and inspect relationships. +5. **Settings** + - Review these sections at minimum: + - **AI Configuration** + - **Performance** + - **Default Locations** + - **Application** +6. **Undo/Redo safety** + - Perform a few file operations and verify undo/redo behavior. + +Tip: Real-world folders (Downloads, screenshots, invoices, project docs) are better test data than +synthetic files. + +--- + +## Part 3: How To Report Bugs So They Are Actionable + +Submit reports with this template: +[Bug report form](https://github.com/iLevyTate/StratoSortCore/issues/new?template=bug_report.md) + +### Include these every time + +- **Clear title:** what broke, where. +- **Reproduction steps:** exact step-by-step path. +- **Expected behavior:** what should happen. +- **Actual behavior:** what happened instead. +- **Environment details:** + - OS + version + - App version + - Install type (installer vs source) + - Hardware notes (RAM/GPU) if performance or AI related + +### Attach useful evidence + +- Screenshot or short screen recording. +- **Logs** (see below). +- Any visible error text (copy exact message). + +### Sharing logs with bug reports + +StratoSort can export logs from **Settings → Application preferences → Troubleshooting Logs**. + +| Option | When to use | +| --------------------- | --------------------------------------------------------------------------------------------------- | +| **Export (Redacted)** | Default for bug reports. Removes file paths and document analysis content. Safe to attach publicly. | +| **Export Full** | Only if a maintainer asks. Includes full paths and crash dumps. Keep private. | + +**Steps:** + +1. Open **Settings** → **Application preferences**. +2. Under **Troubleshooting Logs**, click **Export (Redacted)**. +3. Save the `.zip` file (e.g. to Desktop). +4. When filing a bug report, drag the zip into the GitHub issue or use **Attach files**. +5. Add a note like: _"Logs attached (redacted). Error occurred when [brief context]."_ + +Redacted logs still include error messages, stack traces, file types, and operation flow—enough for +most debugging. If we need more detail, we’ll ask you to share a full export privately. + +**Manual log locations** (if you prefer copying files yourself): + +- **Windows:** `%APPDATA%/stratosort/logs/` +- **macOS:** `~/Library/Logs/stratosort/` +- **Linux:** `~/.config/stratosort/logs/` + +### High-quality bug report example + +> **Title:** Knowledge OS search returns zero results after embedding model switch +> **Steps:** +> +> 1. Open Settings -> AI Configuration -> Default AI models +> 2. Change Embedding Model +> 3. Return to search and query "invoice from last month" +> **Expected:** Existing indexed files still appear, or app prompts to rebuild before search +> **Actual:** Empty results + warning about model mismatch +> **Environment:** Windows 11, app 2.0.x installer, RTX 3060, 32GB RAM +> **Logs:** attached redacted export (Settings → Export Redacted) + +--- + +## Part 4: Other Ways To Contribute (No Coding Required) + +- Confirm bugs reported by others (same issue, same version, same/different OS). +- Test a new release and report regressions. +- Suggest UX improvements with screenshots and concrete before/after notes. +- Improve docs when something feels unclear. + +--- + +## Part 5: If You Do Want To Contribute Code Later + +Start here: [CONTRIBUTING.md](../CONTRIBUTING.md) + +You can help as a tester today and become a code contributor later. Both are valuable. diff --git a/docs/CODE_QUALITY_STANDARDS.md b/docs/CODE_QUALITY_STANDARDS.md deleted file mode 100644 index 20771d78..00000000 --- a/docs/CODE_QUALITY_STANDARDS.md +++ /dev/null @@ -1,688 +0,0 @@ -# Code Quality Standards and Style Guide - -## Overview - -This document establishes coding standards for consistency, readability, and maintainability across -the StratoSort Core codebase. - -## Table of Contents - -1. [Naming Conventions](#naming-conventions) -2. [Error Handling Standards](#error-handling-standards) -3. [Promise Handling](#promise-handling) -4. [Code Formatting](#code-formatting) -5. [JSDoc Documentation](#jsdoc-documentation) -6. [Function Length and Complexity](#function-length-and-complexity) -7. [Import Organization](#import-organization) - -## Naming Conventions - -### Variables and Functions - -#### Use Descriptive Names - -```javascript -// Bad: Ambiguous, unclear purpose -const d = new Date(); -const x = files.length; -function proc(f) { ... } - -// Good: Clear, descriptive -const currentDate = new Date(); -const fileCount = files.length; -function processFileAnalysis(filePath) { ... } -``` - -#### Naming Patterns by Type - -**Boolean Variables** - Use `is`, `has`, `should` prefixes - -```javascript -const isAnalyzing = false; -const hasResults = results.length > 0; -const shouldRetry = attempt < maxAttempts; -``` - -**Arrays and Collections** - Use plural nouns - -```javascript -const files = []; -const results = []; -const analysisErrors = new Map(); -``` - -**Functions** - Use verb + noun pattern - -```javascript -function analyzeFile(filePath) {} -function getSettings() {} -function validateInput(data) {} -function createFolder(path) {} -``` - -**Async Functions** - Consider async prefix for clarity - -```javascript -async function fetchAnalysisResults() {} -async function loadConfiguration() {} -``` - -**Event Handlers** - Use `handle` or `on` prefix - -```javascript -function handleFileSelect(event) {} -function onAnalysisComplete(results) {} -``` - -**Class Names** - Use PascalCase nouns - -```javascript -class FileAnalysisService {} -class OramaVectorService {} -class ErrorHandler {} -``` - -**Constants** - Use UPPER_SNAKE_CASE for true constants - -```javascript -const MAX_FILE_SIZE = 50 * 1024 * 1024; -const DEFAULT_TIMEOUT = 5000; -const ERROR_CODES = { ... }; -``` - -**Private Methods** - Use underscore prefix (convention, not enforcement) - -```javascript -class Service { - publicMethod() {} - _privateHelper() {} -} -``` - -### Avoid Ambiguous Names - -```javascript -// Bad: Unclear what these represent -const data = getInfo(); -const temp = process(input); -const result = doStuff(); - -// Good: Clear purpose -const analysisResults = getFileAnalysis(); -const temporaryFilePath = createTempFile(input); -const folderMatchScore = calculateSimilarity(); -``` - -## Error Handling Standards - -**See `docs/ERROR_HANDLING_GUIDE.md` for comprehensive error handling patterns and decision tree.** - -### Use Centralized Error Utilities - -```javascript -const { - createErrorResponse, - createSuccessResponse, - withErrorHandling -} = require('../shared/errorHandlingUtils'); - -// Wrap IPC handlers -const handler = withErrorHandling( - async (filePath) => { - const result = await analyzeFile(filePath); - return result; - }, - { - context: 'FileAnalysis', - operation: 'analyze-file' - } -); -``` - -### Standardized Error Format - -```javascript -// Always return consistent error structure -try { - const result = await operation(); - return createSuccessResponse(result); -} catch (error) { - logger.error('Operation failed', { error: error.message }); - return createErrorResponse(error.message, ERROR_CODES.OPERATION_FAILED, { - originalError: error.name - }); -} -``` - -### Error Logging Pattern - -```javascript -// Standard error logging format -logger.error('Failed to analyze file', { - filePath, - error: error.message, - stack: error.stack, - code: error.code, - context: 'additional context' -}); -``` - -### Never Swallow Errors - -```javascript -// Bad: Silent failure -try { - await riskyOperation(); -} catch (error) { - // Nothing - error is lost! -} - -// Good: Log and handle -try { - await riskyOperation(); -} catch (error) { - logger.error('Risky operation failed', { error: error.message }); - // Rethrow, return error, or provide fallback - throw error; -} -``` - -## Promise Handling - -### Prefer async/await Over .then() - -```javascript -// Bad: Promise chains with .then() -function processFile(filePath) { - return readFile(filePath) - .then((content) => extractText(content)) - .then((text) => analyzeText(text)) - .then((results) => saveResults(results)) - .catch((error) => handleError(error)); -} - -// Good: async/await for clarity -async function processFile(filePath) { - try { - const content = await readFile(filePath); - const text = await extractText(content); - const results = await analyzeText(text); - await saveResults(results); - return results; - } catch (error) { - handleError(error); - throw error; - } -} -``` - -### Parallel vs Sequential - -```javascript -// Sequential (when order matters or operations depend on each other) -async function processInOrder() { - const file1 = await readFile('file1.txt'); - const file2 = await readFile('file2.txt'); // Waits for file1 - return [file1, file2]; -} - -// Parallel (when operations are independent) -async function processInParallel() { - const [file1, file2] = await Promise.all([ - readFile('file1.txt'), - readFile('file2.txt') // Runs concurrently - ]); - return [file1, file2]; -} -``` - -### Always Handle Promise Rejections - -```javascript -// Bad: Unhandled rejection -someAsyncFunction(); // If this rejects, it's unhandled - -// Good: Proper handling -someAsyncFunction().catch((error) => { - logger.error('Async operation failed', { error: error.message }); -}); - -// Better: Use try-catch in async context -async function caller() { - try { - await someAsyncFunction(); - } catch (error) { - logger.error('Async operation failed', { error: error.message }); - } -} -``` - -## Code Formatting - -StratoSort Core standardizes formatting with Prettier and linting with ESLint. Use the following -scripts before committing: - -```bash -npm run format -npm run lint -``` - -CI enforces `format:check` and `lint`, so ensure both pass locally. - -### Indentation - -- **Standard**: 2 spaces (already configured in project) -- Use consistent indentation across all files -- Configure editor to show whitespace - -### Semicolons - -- **Standard**: Use semicolons (project convention) -- Prevents ASI (Automatic Semicolon Insertion) bugs - -```javascript -// Good: Explicit semicolons -const value = getValue(); -doSomething(value); - -// Bad: Missing semicolons (can cause issues) -const value = getValue(); -doSomething(value); -``` - -### Line Length - -- **Target**: 80-100 characters -- **Maximum**: 120 characters -- Break long lines for readability - -```javascript -// Bad: Too long -const result = await someVeryLongFunctionName( - parameter1, - parameter2, - parameter3, - parameter4, - parameter5 -); - -// Good: Broken for readability -const result = await someVeryLongFunctionName( - parameter1, - parameter2, - parameter3, - parameter4, - parameter5 -); -``` - -### Object and Array Formatting - -```javascript -// Short objects: Single line -const point = { x: 10, y: 20 }; - -// Long objects: Multi-line with trailing comma -const config = { - timeout: 5000, - retries: 3, - model: 'llama3.2:1b', - verbose: true -}; - -// Arrays: Multi-line for multiple items -const supportedFormats = ['.pdf', '.docx', '.txt', '.jpg']; -``` - -### Blank Lines - -```javascript -// Use blank lines to separate logical sections -function processFile(filePath) { - // Validation - if (!filePath) { - throw new Error('File path required'); - } - - // Processing - const content = readFile(filePath); - const analysis = analyzeContent(content); - - // Return results - return { - filePath, - analysis, - timestamp: Date.now() - }; -} -``` - -## JSDoc Documentation - -### Document All Public Methods - -```javascript -/** - * Analyzes a file and generates organization suggestions - * @param {string} filePath - Absolute path to the file - * @param {Object} options - Analysis options - * @param {boolean} [options.skipCache=false] - Skip cache lookup - * @param {number} [options.timeout=30000] - Timeout in milliseconds - * @returns {Promise} Analysis results with suggestions - * @throws {Error} If file cannot be read or analysis fails - * @example - * const result = await analyzeFile('/path/to/file.pdf', { skipCache: true }); - */ -async function analyzeFile(filePath, options = {}) { - // Implementation -} -``` - -### Type Definitions - -```javascript -/** - * @typedef {Object} AnalysisResult - * @property {string} filePath - Path to analyzed file - * @property {string} category - Detected category - * @property {string[]} keywords - Extracted keywords - * @property {number} confidence - Confidence score (0-1) - * @property {string} suggestedFolder - Suggested destination folder - */ - -/** - * @typedef {Object} FileMetadata - * @property {string} name - File name - * @property {number} size - File size in bytes - * @property {Date} created - Creation date - * @property {Date} modified - Last modified date - */ -``` - -### Class Documentation - -```javascript -/** - * Service for analyzing files using AI models - * Handles document and image analysis with caching - */ -class FileAnalysisService { - /** - * Creates a new FileAnalysisService - * @param {LlamaService} llamaService - Llama service instance - */ - constructor(llamaService) { - this.llamaService = llamaService; - } - - /** - * Analyzes a document file - * @param {string} filePath - Path to document - * @returns {Promise} Analysis results - */ - async analyzeDocument(filePath) { - // Implementation - } -} -``` - -## Function Length and Complexity - -### Function Length Guidelines - -- **Target**: < 50 lines -- **Warning**: 50-100 lines -- **Refactor**: > 100 lines - -```javascript -// Bad: Long function doing too much (150+ lines) -async function processAndOrganizeFiles(files) { - // Validation (20 lines) - // File reading (30 lines) - // Analysis (40 lines) - // Suggestion generation (30 lines) - // Organization (30 lines) - // Error handling (20 lines) -} - -// Good: Split into focused functions -async function processAndOrganizeFiles(files) { - validateFiles(files); - const contents = await readFiles(files); - const analyses = await analyzeFiles(contents); - const suggestions = generateSuggestions(analyses); - await organizeFiles(suggestions); -} -``` - -### Reduce Nesting Depth - -- **Target**: < 3 levels -- **Warning**: 3-4 levels -- **Refactor**: > 4 levels - -```javascript -// Bad: Deep nesting (5 levels) -function processItem(item) { - if (item) { - if (item.isValid) { - if (item.hasData) { - if (item.data.length > 0) { - if (item.data[0].isReady) { - return process(item.data[0]); - } - } - } - } - } -} - -// Good: Early returns (2 levels max) -function processItem(item) { - if (!item) return null; - if (!item.isValid) return null; - if (!item.hasData) return null; - if (item.data.length === 0) return null; - if (!item.data[0].isReady) return null; - - return process(item.data[0]); -} -``` - -### Cyclomatic Complexity - -- **Target**: < 10 -- **Warning**: 10-15 -- **Refactor**: > 15 - -```javascript -// Bad: High complexity (many branches) -function determineAction(type, status, user) { - if (type === 'A') { - if (status === 'active') { - if (user.isAdmin) { - return 'admin-action-A'; - } else { - return 'user-action-A'; - } - } else { - return 'inactive-A'; - } - } else if (type === 'B') { - // More branches... - } - // ... many more conditions -} - -// Good: Use lookup tables or strategy pattern -const ACTION_MAP = { - 'A-active-admin': 'admin-action-A', - 'A-active-user': 'user-action-A', - 'A-inactive': 'inactive-A' - // ... -}; - -function determineAction(type, status, user) { - const role = user.isAdmin ? 'admin' : 'user'; - const key = `${type}-${status}-${role}`; - return ACTION_MAP[key] || 'default-action'; -} -``` - -## Import Organization - -### Import Path Standards - -**Standard:** Use consistent relative paths based on file location. - -**Main Process (CommonJS):** - -- From `src/main/`: `require('../shared/logger')` -- From `src/main/services/`, `src/main/utils/`, etc.: `require('../../shared/logger')` - -**Renderer Process (ES6):** - -- From `src/renderer/`: `import { logger } from '../shared/logger'` -- From `src/renderer/components/`, `src/renderer/phases/`: - `import { logger } from '../../shared/logger'` -- From `src/renderer/utils/`, `src/renderer/contexts/`: `import { logger } from '../shared/logger'` -- From `src/renderer/components/ui/`, `src/renderer/components/organize/`: - `import { logger } from '../../../shared/logger'` - -The examples above demonstrate the standard import path patterns used throughout the codebase. - -### Import Order - -1. Node built-ins -2. External packages -3. Internal absolute imports -4. Internal relative imports - -```javascript -// 1. Node built-ins -const fs = require('fs').promises; -const path = require('path'); - -// 2. External packages -const { getInstance: getLlamaService } = require('../services/LlamaService'); -const sharp = require('sharp'); - -// 3. Internal absolute imports (from src/) -const { logger } = require('../shared/logger'); -const { ERROR_CODES } = require('../shared/errorHandlingUtils'); - -// 4. Internal relative imports -const { extractText } = require('./documentExtractors'); -const { analyzeImage } = require('./imageAnalysis'); -``` - -### Remove Unused Imports - -```javascript -// Bad: Unused imports -const fs = require('fs'); // Not used -const path = require('path'); // Used -const { logger } = require('../shared/logger'); // Not used - -// Good: Only what's needed -const path = require('path'); -``` - -### Group Related Imports - -```javascript -// Good: Grouped by purpose -// File operations -const fs = require('fs').promises; -const path = require('path'); - -// AI services -const { getInstance: getLlamaService } = require('../services/LlamaService'); -const { analyzeWithLLM } = require('./llmService'); - -// Utilities -const { logger } = require('../shared/logger'); -const { sanitizePath } = require('../shared/pathSanitization'); -``` - -## Code Review Checklist - -Before submitting code, verify: - -### Naming - -- [ ] Variables have descriptive names -- [ ] Functions use verb+noun pattern -- [ ] Boolean variables use is/has/should prefix -- [ ] Constants use UPPER_SNAKE_CASE -- [ ] No single-letter variables (except loop counters) - -### Error Handling - -- [ ] All promises have .catch() or try-catch -- [ ] Errors are logged with context -- [ ] Error responses use standard format -- [ ] No swallowed errors - -### Formatting - -- [ ] Consistent 2-space indentation -- [ ] Semicolons used consistently -- [ ] Line length < 120 characters -- [ ] Blank lines separate logical sections - -### Documentation - -- [ ] Public functions have JSDoc -- [ ] Complex logic has inline comments -- [ ] Type definitions for complex objects -- [ ] Examples for non-obvious usage - -### Function Quality - -- [ ] Functions < 100 lines -- [ ] Nesting depth < 4 levels -- [ ] Single responsibility principle -- [ ] Extracted helper functions for repeated logic - -### Imports - -- [ ] Organized by category -- [ ] No unused imports -- [ ] No commented-out imports - -### Testing - -- [ ] Unit tests for new functions -- [ ] Edge cases covered -- [ ] Error cases tested - -## Automated Checks - -Configure ESLint rules: - -```json -{ - "rules": { - "max-len": ["warn", { "code": 120 }], - "max-lines-per-function": ["warn", 100], - "max-depth": ["warn", 4], - "complexity": ["warn", 15], - "no-unused-vars": "error", - "semi": ["error", "always"], - "indent": ["error", 2] - } -} -``` - -Configure Prettier: - -```json -{ - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "printWidth": 100, - "trailingComma": "es5" -} -``` diff --git a/docs/CONFIG.md b/docs/CONFIG.md index e866c595..f6a3d0e1 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -51,7 +51,7 @@ Users can add new models via **Settings → Local AI Engine**: All installable models must be in `modelRegistry.js`; the catalog lists download URLs and metadata. -### Fastest Models with Best Performance (2025) +### Fastest Models with Best Performance Based on node-llama-cpp docs and llama.cpp ecosystem: diff --git a/docs/DEPENDENCY_BOUNDARIES.md b/docs/DEPENDENCY_BOUNDARIES.md deleted file mode 100644 index 873dffc6..00000000 --- a/docs/DEPENDENCY_BOUNDARIES.md +++ /dev/null @@ -1,21 +0,0 @@ -# Dependency Boundaries - -This document defines main/renderer/shared boundaries to prevent cross-layer coupling. - -## Rules - -1. **Renderer must not import main-only modules** (e.g., `src/main/*`). -2. **Main must not import renderer-only modules** (e.g., `src/renderer/*`). -3. **Shared modules** (`src/shared/*`) must avoid Node APIs that are not available in the renderer. -4. **IPC is the only bridge** between main and renderer. Use typed IPC contracts. - -## Allowed Imports - -- Renderer: `src/shared/*`, `src/renderer/*` -- Main: `src/shared/*`, `src/main/*` -- Shared: `src/shared/*` only - -## Enforcement - -- Prefer IPC channels and payload schemas for cross-process data. -- Add lint rules for import boundaries when feasible. diff --git a/docs/DI_PATTERNS.md b/docs/DI_PATTERNS.md deleted file mode 100644 index f2fe1687..00000000 --- a/docs/DI_PATTERNS.md +++ /dev/null @@ -1,152 +0,0 @@ -# Dependency Injection Patterns - -This document describes the dependency injection (DI) patterns used in the StratoSort Core codebase. - -## Overview - -The codebase uses a centralized DI container (`ServiceContainer`) for managing service dependencies. -All services should be accessed through the container rather than direct instantiation or -`getInstance()` calls. - -## ServiceContainer - -The `ServiceContainer` class (`src/main/services/ServiceContainer.js`) provides: - -- **Singleton services**: Created once and reused -- **Transient services**: Created fresh each time -- **Lazy initialization**: Services created on first request -- **Circular dependency detection** -- **Graceful shutdown** - -## Service IDs - -All services are registered with unique identifiers in `ServiceIds`: - -```javascript -const { container, ServiceIds } = require('./ServiceContainer'); - -// Available service IDs: -ServiceIds.ORAMA_VECTOR; // Orama vector database -ServiceIds.SETTINGS; // Application settings -ServiceIds.LLAMA_SERVICE; // Llama LLM service -ServiceIds.PARALLEL_EMBEDDING; // Parallel embedding processor -ServiceIds.EMBEDDING_CACHE; // Embedding cache -ServiceIds.FOLDER_MATCHING; // Folder matching service -ServiceIds.ORGANIZATION_SUGGESTION; // Organization suggestions -ServiceIds.AUTO_ORGANIZE; // Auto-organize service -ServiceIds.ANALYSIS_HISTORY; // Analysis history -ServiceIds.UNDO_REDO; // Undo/redo service -ServiceIds.PROCESSING_STATE; // Processing state tracker -``` - -## Usage Patterns - -### Recommended: Container Resolution - -```javascript -const { container, ServiceIds } = require('./services/ServiceContainer'); - -// Resolve a service -const vectorDb = container.resolve(ServiceIds.ORAMA_VECTOR); -const llama = container.resolve(ServiceIds.LLAMA_SERVICE); -``` - -### Legacy: getInstance() (Deprecated) - -Some services still export `getInstance()` for backward compatibility. **Do not use in new code**: - -```javascript -// DEPRECATED - avoid in new code -const { getInstance } = require('./LlamaService'); -const llama = getInstance(); -``` - -## Registering New Services - -### Singleton Services - -```javascript -container.registerSingleton(ServiceIds.MY_SERVICE, (c) => { - // c is the container - use it to resolve dependencies - return new MyService({ - vectorDb: c.resolve(ServiceIds.ORAMA_VECTOR), - settings: c.resolve(ServiceIds.SETTINGS) - }); -}); -``` - -### Transient Services - -```javascript -container.registerTransient('myTransient', () => { - return new TransientService(); -}); -``` - -## Service Integration - -The `ServiceIntegration` class (`src/main/services/ServiceIntegration.js`) handles: - -1. Registering all core services with the container -2. Initializing services in dependency order -3. Providing backward-compatible property access -4. Coordinating service shutdown - -### Initialization - -```javascript -const ServiceIntegration = require('./services/ServiceIntegration'); - -const integration = new ServiceIntegration(); -await integration.initialize(); - -// Access via container (recommended) -const vectorDb = container.resolve(ServiceIds.ORAMA_VECTOR); - -// Or via integration properties (backward compatible) -const vectorDb = integration.oramaVectorService; -``` - -## Testing - -The DI container makes testing easier by allowing mock injection: - -```javascript -// In tests, register mocks before resolving -container.registerInstance(ServiceIds.ORAMA_VECTOR, mockVectorDb); - -// Your service will receive the mock -const folderMatching = container.resolve(ServiceIds.FOLDER_MATCHING); -``` - -## Migration Guide - -When migrating from `getInstance()` to container resolution: - -1. Import the container and ServiceIds -2. Replace `getInstance()` calls with `container.resolve()` -3. Ensure the service is registered in `ServiceIntegration._registerCoreServices()` - -### Before - -```javascript -const { getInstance } = require('./LlamaService'); -const llama = getInstance(); -await llama.generateEmbedding(text); -``` - -### After - -```javascript -const { container, ServiceIds } = require('./ServiceContainer'); -const llama = container.resolve(ServiceIds.LLAMA_SERVICE); -await llama.generateEmbedding(text); -``` - -## Best Practices - -1. **Always use the container** for service access in new code -2. **Accept dependencies via constructor** rather than grabbing singletons -3. **Register services in ServiceIntegration** for proper lifecycle management -4. **Use ServiceIds** constants instead of string literals -5. **Mock services in tests** by registering test instances diff --git a/docs/FEATURES_GRAPH.md b/docs/FEATURES_GRAPH.md index 232878e7..a1d0bb10 100644 --- a/docs/FEATURES_GRAPH.md +++ b/docs/FEATURES_GRAPH.md @@ -36,13 +36,11 @@ The graph doesn't just show files; it shows _meaning_. ### Frontend (`Renderer`) -- `GraphView.jsx`: Main container component. -- `useGraphState.js`: Manages the complex state of nodes and edges. - `UnifiedSearchModal.jsx`: Bridges the search experience with graph visualization. +- `useGraphState.js`: Manages the complex state of nodes and edges. ### Backend (`Main Process`) -- `GraphService.js`: Handles the heavy lifting of graph construction. - `OramaVectorService`: Provides the raw vector data and similarity scores. - `ReRankerService`: Refines connections to ensure high-quality edges. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 1d2583a1..88a4f9ca 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -205,7 +205,7 @@ Once setup is complete, you're ready to start organizing your files: 2. **Enable Auto-Organization** - Turn on folder watching in Settings 3. **Explore the Knowledge Graph** - Visualize relationships between your files -For more information, see the [main README](../README.md) or explore the [documentation](README.md). +For more information, see the [main README](../README.md). --- diff --git a/docs/INSTALL_GUIDE.md b/docs/INSTALL_GUIDE.md index 5f92e5d1..b9c003e2 100644 --- a/docs/INSTALL_GUIDE.md +++ b/docs/INSTALL_GUIDE.md @@ -151,3 +151,10 @@ StratoSort Core: The "developer cannot be verified" / SmartScreen warnings appear because the app is not yet signed with a publisher certificate. That affects trust prompts only—not how the app works. + +--- + +## Next Steps + +- **Learn the app:** [User Guide](./USER_GUIDE.md) +- **Help test and report bugs:** [Beta Tester Guide](./BETA_TESTER_GUIDE.md) diff --git a/docs/LEARNING_GUIDE.md b/docs/LEARNING_GUIDE.md deleted file mode 100644 index 12afca69..00000000 --- a/docs/LEARNING_GUIDE.md +++ /dev/null @@ -1,557 +0,0 @@ -# StratoSort Core Codebase Learning Guide - -Welcome to the StratoSort Core codebase! This guide is designed to serve as a comprehensive map for -understanding what you have built. It breaks down the software from multiple engineering -perspectives, ranging from high-level architecture to specific design patterns and critical system -concepts. - ---- - -## Table of Contents - -1. [Architecture View](#1-architecture-view) (The Blueprint) -2. [Design Pattern View](#2-design-pattern-view) (The Building Blocks) -3. [Data Engineering View](#3-data-engineering-view) (The Flow) -4. [AI & ML View](#4-ai--ml-view) (The Brain) -5. [Resilience Engineering View](#5-resilience-engineering-view) (The Safety Nets) -6. [Security View](#6-security-view) (The Shields) -7. [Glossary of Terms](#7-glossary-of-terms) -8. [Code Examples](#8-code-examples) - ---- - -## 1. Architecture View - -**Pattern:** **Multi-Process Architecture (Electron)** This is not a standard web app. It is a -distributed system running locally on one machine. - -- **Main Process (Node.js):** - - **Role:** The "Server" or "Backend". It has full OS access (files, processes). - - **Responsibility:** It orchestrates everything—launching AI models, reading files, managing the - database, and creating windows. - - **Key File:** `src/main/simple-main.js` (The Entry Point). - -- **Renderer Process (React/Chrome):** - - **Role:** The "Client" or "Frontend". It lives in a sandboxed web page. - - **Responsibility:** Displaying UI, managing user state (Redux), and asking the Main process to - do heavy lifting. - - **Key File:** `src/renderer/App.js`. - -- **IPC (Inter-Process Communication):** - - **Role:** The "Network Bridge". Since Main and Renderer are separate processes (with separate - memory), they cannot share variables. They must send messages to each other. - - **Mechanism:** Asynchronous message passing (like HTTP requests but internal). - -**Diagram:** - -``` -[Renderer Process (UI)] <===> [IPC Bridge (Security)] <===> [Main Process (Backend)] -(React, Redux) (preload.js) (Node.js, Services, DB) -``` - ---- - -## 2. Design Pattern View - -Your codebase isn't just a script; it uses established "Gang of Four" (GoF) design patterns to solve -common software problems. - -### A. Singleton Pattern - -**Concept:** Ensure a class has only one instance and provide a global point of access to it. -**Usage:** Essential for managing shared resources like database connections or AI models. -**Examples in Code:** - -- **`ServiceContainer.js`**: A massive Registry that holds Singletons. It ensures we don't create 10 - vector DB instances, but reuse the same one everywhere. -- **`LlamaService.js`**: The AI client is a Singleton (`getInstance`). We only want one in-process - model manager at a time. - -### B. Observer Pattern - -**Concept:** An object (Subject) maintains a list of dependents (Observers) and notifies them of -state changes. **Usage:** Decoupling components. The component changing the settings doesn't need to -know _who_ is listening, just that it changed. **Examples in Code:** - -- **`OramaVectorService`**: Extends `EventEmitter`. It emits `'online'`, `'dimension-mismatch'`, and - `'embedding-blocked'`. The UI listens for these events to show the status connection badge. -- **`SettingsService`**: When `settings.json` changes on disk, it emits an event so the app updates - live without a restart. - -### C. Strategy Pattern - -**Concept:** Define a family of algorithms, encapsulate each one, and make them interchangeable. -**Usage:** Handling different file types without a giant `if/else` block. **Examples in Code:** - -- **`documentExtractors.js`**: We have different "strategies" for extracting text. - - _PDF Strategy_: `extractTextFromPdf` - - _Word Strategy_: `extractTextFromDocx` - - _Image Strategy_: `ocrPdfIfNeeded` (OCR) The main analysis service just says "Extract", and the - correct strategy is chosen based on the file extension. - -### D. Factory Pattern - -**Concept:** Create objects without specifying the exact class of object that will be created. -**Usage:** Simplifying complex setup logic. **Examples in Code:** - -- **`ServiceContainer.js`**: Uses "Factory Functions" (`registerSingleton('name', factoryFn)`) to - lazy-load services only when they are needed. -- **`createWindow.js`**: A factory that produces a configured Browser Window with all the correct - security settings and event listeners attached. - ---- - -## 3. Data Engineering View - -How does data move and persist? - -**A. State Management (Redux)** - -- **Concept:** Single Source of Truth. -- **Implementation:** The frontend doesn't store data in random variables. It stores it in a giant - tree called the **Store**. -- **Flow:** `Action (User Clicks)` -> `Reducer (Updates State)` -> `View (Re-renders)`. - -**B. Vector Database (Orama)** - -- **Concept:** High-dimensional data storage. Standard databases (SQL) store text. Vector DBs store - _meaning_. -- **Data:** We store "Embeddings" (arrays of floating-point numbers like `[0.12, -0.98, 0.33...]`). -- **Querying:** We don't search for "keyword matches". We search for "Cosine Similarity" - (mathematical closeness). -- **Key File:** `src/main/services/OramaVectorService.js`. - -**C. Caching Strategy** - -- **Concept:** Don't do the same work twice. -- **Implementation:** - - **File Analysis Cache:** `FileAnalysisService.js` keeps a map of `path + size + mtime`. If a - file hasn't changed, we return the previous AI result instantly (0ms) instead of re-running the - LLM (3000ms). - - **Query Cache:** `OramaVectorService` caches vector search results to keep the UI snappy. - ---- - -## 4. AI & ML View - -This is the "Brain" of the operation. - -**A. RAG (Retrieval Augmented Generation)** - -- **Concept:** Giving the AI "memory" by retrieving relevant data before asking it a question. -- **Flow:** - 1. User asks: "Where are my tax documents?" - 2. App converts question to Vector. - 3. App queries Orama for files with similar vectors (Retrieval). - 4. App sends the _question_ + _file summaries_ to the Llama engine (Generation). -- **Code:** `FolderMatchingService.js` implements the retrieval part of this flow. - -**B. Embeddings** - -- **Concept:** Translating human language into machine language (vectors). -- **Implementation:** We use GGUF embedding models via node-llama-cpp to turn file content into - vectors. - -**C. Local Inference** - -- **Concept:** Running AI on the user's GPU, not in the cloud. -- **Engineering Challenge:** This is resource-intensive. -- **Solution:** `ParallelEmbeddingService.js` manages concurrency. It ensures we don't crash the - user's computer by trying to process 100 files at once. It uses a semaphore/queue system to limit - active jobs. - -**D. Knowledge Visualization (Explainable AI)** - -- **Concept:** Making the "black box" of AI decisions transparent to the user. -- **Implementation:** The "Knowledge Graph" visualizes high-dimensional vector relationships in 2D - space. -- **Key Engineering Decisions:** - - **Brandes-Koepf Layout:** We use the `BRANDES_KOEPF` algorithm (via ELK.js) instead of standard - force-directed layouts. This forces nodes into clean, straight lines and prioritized ranks, - preventing the "hairball" or "outlier" effect common in graph visualizations. - - **Metadata Injection:** The edges (lines) connecting nodes are not just lines; they carry - metadata (`category`, `commonTags`). This allows the UI to display "Relationship Analysis" - tooltips explaining _why_ two files are connected (e.g., "Both Images", "95% Similar"). - - **Color Encoding:** Nodes are programmatically color-coded by file type (using a shared - `FileCategory` logic) to turn the graph into an instant visual map. - ---- - -## 5. Resilience Engineering View - -How does the software handle failure? (This distinguishes "scripts" from "systems"). - -**A. Circuit Breaker Pattern** - -- **Problem:** If the vector DB fails, asking it for data 100 times a second will just generate 100 - errors and maybe freeze the app. -- **Solution:** The `CircuitBreaker` (`CircuitBreaker.js`) monitors failures. - - _Closed (Normal):_ Requests go through. - - _Open (Broken):_ If 5 errors happen in a row, the breaker "trips". Requests fail _immediately_ - without trying the DB. - - _Half-Open (Recovery):_ After 30s, it lets one request through to test if the DB is back. - -**B. Deferred Retry Pattern** - -- **Problem:** A transient storage or analysis failure could drop an operation. -- **Solution:** The in-process Orama storage reduces external dependency failures. When a transient - error does occur, operations return actionable errors and are retried via bounded queues (e.g., - embedding queues) rather than a persistent offline queue on disk. - -**C. Dead Letter Handling** - -- **Concept:** What happens to items that _never_ succeed? -- **Implementation:** If a file fails analysis repeatedly, it is marked with a specific error state - rather than crashing the batch processor. - ---- - -## 6. Security View - -**A. Context Isolation** - -- **Concept:** The "Sandbox". -- **Implementation:** The renderer (web page) **cannot** require Node.js modules. It doesn't know - `fs` (filesystem) exists. It can only use `window.electronAPI`. - -**B. The Preload Bridge** - -- **Key File:** `src/preload/preload.js`. -- **Mechanism:** - - It "Preloads" before the website runs. - - It has access to both Node.js and the DOM. - - It creates a safe API (`contextBridge.exposeInMainWorld`). -- **Sanitization:** The `SecureIPCManager` strips dangerous characters from file paths to prevent - "Path Traversal Attacks" (e.g., trying to read `../../../../etc/passwd`). - ---- - -## 7. Glossary of Terms - -### General Software Engineering - -- **Async/Await:** Modern JavaScript syntax for handling operations that take time (like reading a - file or querying a database) without freezing the application. Used extensively in the Main - Process (e.g., `await fs.readFile()`). - -- **Dependency Injection (DI):** A design pattern where a class receives its dependencies from the - outside rather than creating them itself. Our `ServiceContainer` injects services like - `OramaVectorService` into `FolderMatchingService`, making testing easier. - -- **Memoization:** An optimization technique where the result of a function is cached. If the - function is called again with the same inputs, the cached result is returned instantly. Used in - React (`React.memo`) and backend (`FileAnalysisService` caches results). - -- **Singleton:** A pattern ensuring a class has only one instance. Used for `LlamaService` (one AI - engine) and `SettingsService` (one source of truth). - -- **Circuit Breaker:** A resilience pattern that detects failures and prevents cascading errors. If - the vector DB fails repeatedly, the breaker "trips" and stops requests for a recovery period. - -### Electron & Architecture - -- **Main Process:** The entry point of an Electron app running in Node.js with full OS access. - Handles file I/O, spawning processes, managing windows, and IPC events. - -- **Renderer Process:** The web page displayed in the application window running Chromium. - Responsible for UI (React), user interactions, and local state (Redux). Sandboxed for security. - -- **IPC (Inter-Process Communication):** The communication mechanism between Main and Renderer - processes using named channels (e.g., `files:analyze`). Methods include `invoke` (request/reply) - and `send` (fire and forget). - -- **Preload Script:** A script that runs before the web page loads with access to both Node.js APIs - and the DOM. Creates a secure bridge (`contextBridge`) to expose safe methods to the Renderer. - -- **Context Bridge:** An Electron API that isolates the Renderer from the Main process context, - preventing security attacks. We expose `window.electronAPI` via the Context Bridge. - -### AI & Data Science - -- **LLM (Large Language Model):** An AI model trained on vast amounts of text to understand and - generate human language. We use GGUF models via node-llama-cpp. - -- **Inference:** Running live data through a trained AI model to get a prediction. When you click - "Analyze", the app performs local inference on your GPU. - -- **Embedding (Vector):** A representation of text as a list of numbers (e.g., `[0.1, -0.5, ...]`). - Similar concepts have mathematically similar vectors, enabling semantic search. - -- **RAG (Retrieval-Augmented Generation):** A technique where an AI is given relevant external data - (retrieved from a database) to help it answer accurately. We retrieve similar folders from Orama, - then ask the AI where a file belongs. - -- **Cosine Similarity:** A metric measuring how similar two vectors are. Used by Orama to rank - folder matches. - -- **Brandes-Koepf:** An algorithm used in graph visualization to minimize edge crossings and - straighten long edges in layered graphs. We use this to keep the Knowledge Graph clean and - legible. - -- **node-llama-cpp:** A native binding to llama.cpp used for in-process local inference. - -### Frontend & UI (React/Redux) - -- **Component:** A reusable, self-contained piece of UI code (e.g., `Button.jsx`, `FileList.jsx`). - -- **Hook:** A special React function (starting with `use`) that lets you access React features like - state. Examples: `useState`, `useEffect`, `useSelector`. - -- **Redux Store:** A centralized container for the entire application's state. Holds files, - settings, and analysis status. - -- **Slice:** A portion of the Redux store dedicated to a specific feature (e.g., `filesSlice`, - `uiSlice`). - -- **Tailwind CSS:** A utility-first CSS framework using pre-defined classes like `flex`, `p-4`, - `text-red-500`. - -### Project-Specific - -- **Smart Folder:** A folder configuration that includes a Vector Embedding, acting as a "magnet" - for semantically similar files. - -- **ServiceContainer:** Our custom Dependency Injection system in - `src/main/services/ServiceContainer.js` managing service lifecycle. - -- **OramaVectorService:** The service wrapper for the Orama vector database, handling embedding - validation and search caching. - -- **File Signature:** A unique string (`path + size + lastModifiedTime`) used as a cache key to - detect file changes. - -- **Zod Schema:** A data validation definition ensuring IPC data is correct before use. - -### Infrastructure & Tools - -- **Webpack:** A module bundler that takes JS, CSS, and images and bundles them into optimized - files. - -- **Jest:** JavaScript testing framework for unit tests. - -- **Playwright:** End-to-end testing tool that launches the app and simulates user interactions. - -- **ESLint / Prettier:** Code quality tools. ESLint finds bugs; Prettier formats code consistently. - ---- - -## 8. Code Examples - -This section provides concrete code snippets for common patterns in the codebase. - -### 8.1 Backend Services (Main Process) - -#### Defining a Service - -```javascript -// src/main/services/MyNewService.js -const { logger } = require('../../shared/logger'); - -class MyNewService { - constructor(dependencyA, dependencyB) { - this.depA = dependencyA; - this.depB = dependencyB; - this.initialized = false; - } - - async initialize() { - if (this.initialized) return; - logger.info('[MyNewService] Initializing...'); - // ... setup logic ... - this.initialized = true; - } - - doSomething(data) { - if (!this.initialized) throw new Error('Service not initialized'); - return this.depA.process(data); - } -} - -module.exports = MyNewService; -``` - -#### Registering with ServiceContainer - -```javascript -// src/main/services/ServiceIntegration.js -const { container, ServiceIds } = require('./ServiceContainer'); -const MyNewService = require('./MyNewService'); - -// Inside _registerCoreServices(): -if (!container.has('myNewService')) { - container.registerSingleton('myNewService', (c) => { - const depA = c.resolve(ServiceIds.ORAMA_VECTOR); - const depB = c.resolve(ServiceIds.SETTINGS); - return new MyNewService(depA, depB); - }); -} -``` - -#### Accessing a Service - -```javascript -const { container, ServiceIds } = require('./ServiceContainer'); - -// Standard Resolution (throws if missing) -const myService = container.resolve('myNewService'); - -// Safe Resolution (returns null if missing) -const maybeService = container.tryResolve('myNewService'); -if (maybeService) { - maybeService.doSomething(); -} -``` - -### 8.2 IPC (Inter-Process Communication) - -#### Creating a Handler (Backend) - -```javascript -// src/main/ipc/myFeature.js -const { createHandler } = require('./ipcWrappers'); - -function registerMyFeatureIpc({ ipcMain, IPC_CHANNELS, logger }) { - // Standard Request/Response - createHandler(ipcMain, 'my-feature:get-data', async (event, params) => { - logger.info('Received request for data', params); - const result = await someDatabaseCall(params.id); - return { success: true, data: result }; - }); - - // Streaming/Events (Backend -> Frontend) - createHandler(ipcMain, 'my-feature:start-job', async (event, params) => { - event.sender.send('my-feature:progress', { percent: 0 }); - await doLongTask(); - event.sender.send('my-feature:progress', { percent: 100 }); - return { success: true }; - }); -} - -module.exports = registerMyFeatureIpc; -``` - -#### Exposing to Frontend (Preload) - -```javascript -// src/preload/preload.js -contextBridge.exposeInMainWorld('electronAPI', { - myFeature: { - getData: (id) => ipcRenderer.invoke('my-feature:get-data', { id }), - onProgress: (callback) => { - const subscription = (event, data) => callback(data); - ipcRenderer.on('my-feature:progress', subscription); - return () => ipcRenderer.removeListener('my-feature:progress', subscription); - } - } -}); -``` - -#### Using in React (Frontend) - -```jsx -// src/renderer/components/MyComponent.jsx -import React, { useEffect, useState } from 'react'; - -export const MyComponent = () => { - const [data, setData] = useState(null); - - useEffect(() => { - const fetchData = async () => { - const result = await window.electronAPI.myFeature.getData(123); - if (result.success) setData(result.data); - }; - fetchData(); - - const unsubscribe = window.electronAPI.myFeature.onProgress((progress) => { - console.log(`Job is ${progress.percent}% done`); - }); - return () => unsubscribe(); - }, []); - - if (!data) return
Loading...
; - return
{data.name}
; -}; -``` - -### 8.3 AI & Llama Integration - -```javascript -const { container, ServiceIds } = require('./ServiceContainer'); - -async function summarizeText(text) { - const llamaService = container.resolve(ServiceIds.LLAMA_SERVICE); - const response = await llamaService.generateText({ - prompt: `Summarize this: ${text}`, - maxTokens: 512, - temperature: 0.3 - }); - return response.response; -} - -async function getVector(text) { - const embeddingService = container.resolve(ServiceIds.PARALLEL_EMBEDDING); - return await embeddingService.generateEmbedding(text); -} -``` - -### 8.4 Orama Vector Operations - -```javascript -const { container, ServiceIds } = require('./ServiceContainer'); - -async function findSimilarFolders(fileContent) { - const vectorDb = container.resolve(ServiceIds.ORAMA_VECTOR); - const embeddingService = container.resolve(ServiceIds.PARALLEL_EMBEDDING); - - const queryVector = await embeddingService.generateEmbedding(fileContent); - return await vectorDb.queryFoldersByEmbedding(queryVector, 5); -} -``` - -### 8.5 Redux State Management - -#### Creating a Slice - -```javascript -// src/renderer/store/slices/mySlice.js -import { createSlice } from '@reduxjs/toolkit'; - -const mySlice = createSlice({ - name: 'myFeature', - initialState: { items: [], loading: false }, - reducers: { - setLoading: (state, action) => { - state.loading = action.payload; - }, - addItems: (state, action) => { - state.items.push(...action.payload); - } - } -}); - -export const { setLoading, addItems } = mySlice.actions; -export default mySlice.reducer; -``` - -#### Using in Components - -```jsx -import { useDispatch, useSelector } from 'react-redux'; -import { setLoading } from '../store/slices/mySlice'; - -const MyButton = () => { - const dispatch = useDispatch(); - const isLoading = useSelector((state) => state.myFeature.loading); - - return ( - - ); -}; -``` - ---- - -_This document acts as the engineering manual for StratoSort Core. It covers the "Why" and "How" -behind the code architecture._ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 7e4194bd..00000000 --- a/docs/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# StratoSort Core Documentation Index - -This directory contains comprehensive documentation for the StratoSort codebase. Use this guide to -find the right documentation for your needs. - -## Quick Links - -| Document | Description | Audience | -| -------------------------------------------------------- | ----------------------------------------------------- | -------------- | -| [CONFIG.md](./CONFIG.md) | Installation, dependencies & configuration | All users | -| [ARCHITECTURE.md](./ARCHITECTURE.md) | High-level system design and data flow | All developers | -| [LEARNING_GUIDE.md](./LEARNING_GUIDE.md) | Codebase learning guide with glossary & code examples | New developers | -| [CODE_QUALITY_STANDARDS.md](./CODE_QUALITY_STANDARDS.md) | Coding standards and style guide | All developers | -| [ERROR_HANDLING_GUIDE.md](./ERROR_HANDLING_GUIDE.md) | Error handling patterns and best practices | All developers | - -## Installation & Dependencies - -> **Note**: Model setup is handled by `setup:models` (OCR uses system Tesseract or the JS fallback). -> See [CONFIG.md](./CONFIG.md#model--ocr-setup) for details. - -- **[CONFIG.md](./CONFIG.md)** - Complete setup guide including: - - Default AI models (text, vision, embedding) - - Model download and OCR setup - - Environment variable reference - - Troubleshooting tips - -## Architecture & Design - -- **[ARCHITECTURE.md](./ARCHITECTURE.md)** - System architecture diagram showing the relationship - between Renderer, IPC, and Main processes -- **[DI_PATTERNS.md](./DI_PATTERNS.md)** - Dependency injection patterns and ServiceContainer usage -- **[LEARNING_GUIDE.md](./LEARNING_GUIDE.md)** - Comprehensive developer onboarding guide covering: - - Architecture, design patterns, and data flow - - AI/ML concepts and resilience engineering - - Expanded glossary of terms - - Code examples for common patterns - -## Development Standards - -- **[CODE_QUALITY_STANDARDS.md](./CODE_QUALITY_STANDARDS.md)** - Comprehensive style guide covering: - - Naming conventions - - Function length and complexity guidelines - - JSDoc documentation standards - - Code review checklist - -- **[ERROR_HANDLING_GUIDE.md](./ERROR_HANDLING_GUIDE.md)** - Centralized error handling patterns and - utilities - -## Testing - -- **[TESTING.md](../TESTING.md)** - **Single Source of Truth** for: - - Quick Manual QA Checklist - - Automated Test Commands - - Critical Path Strategy - - Debugging Tips - -## Active Development - -- **[GRAPH_INTEGRATION_PLAN.md](./GRAPH_INTEGRATION_PLAN.md)** - Graph visualization feature roadmap - and implementation status - -## Configuration - -Environment variables and configuration are centralized in: - -- `src/shared/performanceConstants.js` - All timing and performance tuning constants -- `src/shared/config/configSchema.js` - Configuration schema definitions -- See [CONFIG.md](./CONFIG.md) for environment variable reference - -## Directory Structure - -``` -docs/ -├── README.md # This index file -├── ARCHITECTURE.md # System design -├── CODE_QUALITY_STANDARDS.md # Style guide -├── CONFIG.md # Environment variables -├── DI_PATTERNS.md # Dependency injection -├── ERROR_HANDLING_GUIDE.md # Error patterns -├── GRAPH_INTEGRATION_PLAN.md # Graph feature roadmap -└── LEARNING_GUIDE.md # Developer onboarding (glossary + examples) -``` - -## Contributing - -When adding new documentation: - -1. Follow the naming convention: `UPPERCASE_WITH_UNDERSCORES.md` -2. Add an entry to this README.md index -3. Include a clear description of the document's purpose -4. Link to related documents where appropriate diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 00000000..90437acc --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,186 @@ +# StratoSort Core User Guide + +This guide explains how to use StratoSort Core day-to-day, including Smart Folders, Knowledge OS, +and key settings. + +For installation help, use [INSTALL_GUIDE.md](./INSTALL_GUIDE.md). +For beta testing and bug reporting, use [BETA_TESTER_GUIDE.md](./BETA_TESTER_GUIDE.md). + +--- + +## 1) What StratoSort Does + +StratoSort uses local AI to: + +- Analyze your files by content (not just filename) +- Suggest where files belong +- Rename files using your naming rules +- Help you search by meaning using Knowledge OS +- Visualize relationships in the Knowledge Graph + +Everything runs locally after model download. + +--- + +## 2) Typical Workflow + +StratoSort is organized into phases: + +1. **Welcome** - Start and choose your flow. +2. **Setup** - Configure Smart Folders (trusted destinations). +3. **Discover** - Analyze files and review suggestions. +4. **Organize** - Approve and apply moves/renames. +5. **Complete** - Review results and finish. + +--- + +## 3) Smart Folders (Most Important First Setup) + +Smart Folders are destination folders that the AI routes files into. + +### Best practices + +- Create folders around real outcomes (for example: `Invoices`, `Receipts`, `Screenshots`, + `Contracts`). +- Keep descriptions specific and plain language. +- Start with fewer, clearer folders before adding many overlapping categories. + +### Good folder description examples + +- **Invoices:** "Bills from vendors and monthly service invoices." +- **Tax Documents:** "W-2, 1099, tax forms, receipts needed for filing." +- **Project Specs:** "Requirements, architecture docs, and project briefs." + +--- + +## 4) Naming Conventions + +Open **Settings -> Default Locations -> File naming defaults**. + +You can control: + +- **Convention** + - `subject-date` + - `date-subject` + - `project-subject-date` + - `category-subject` + - `keep-original` +- **Date Format** + - `YYYY-MM-DD` + - `MM-DD-YYYY` + - `DD-MM-YYYY` + - `YYYYMMDD` +- **Case** + - `kebab-case`, `snake_case`, `camelCase`, `PascalCase`, `lowercase`, `UPPERCASE` +- **Separator** + - Use safe separators like `-` or `_` + +Tip: If you need maximum compatibility across apps and systems, prefer `kebab-case` plus +`YYYY-MM-DD`. + +--- + +## 5) Knowledge OS Search and Knowledge Graph + +Open search and use natural language queries like: + +- "Show invoices from last quarter" +- "Find screenshots related to onboarding" +- "Documents about pricing changes" + +### Knowledge OS tips + +- Be specific in your query (topic + time period + file type). +- If results are weak, rephrase with clearer intent. +- If semantic results seem empty, check embedding/model health in Settings. + +### Knowledge Graph tips + +- Use graph view to inspect relationships between files. +- Great for finding clusters, duplicates, and concept neighborhoods. +- Use it as an exploration tool, then open/reveal files directly. + +--- + +## 6) Settings Walkthrough + +Open **Settings** and focus on these sections: + +### AI Configuration + +- **Local AI Engine**: check model and GPU status. +- **Default AI models**: set text, vision, and embedding models. +- **Embedding behavior / rebuild**: rebuild index when embedding model changes. + +### Performance + +- **Auto-organize**: enable automatic routing from downloads. +- **Confidence threshold**: adjust how strict automation is. +- **Graph retrieval**: tune graph expansion and contextual chunk settings. + +### Default Locations + +- Set where Smart Folders are created by default. +- Configure file naming defaults. + +### Application + +- Notification behavior +- Backups/import/export settings +- Analysis history access + +--- + +## 7) Recommended Starter Configuration + +If you want a safe default profile: + +- Enable **Auto-organize** +- Keep confidence around **75-85%** +- Start with **Smart folder routing: Auto** +- Use naming convention `subject-date` +- Use date format `YYYY-MM-DD` +- Keep separators simple (`-`) + +Then run a small batch first and review outcomes before scaling up. + +--- + +## 8) Daily Usage Pattern + +1. Drop or collect files in your intake location (for example Downloads). +2. Run Discover/analysis. +3. Review suggested destinations and names. +4. Approve organize actions. +5. Use Knowledge OS search to find and validate file placement. +6. Use Undo/Redo when needed. + +--- + +## 9) Troubleshooting Quick Fixes + +### Search is weak or empty + +- Check **Settings -> AI Configuration** for model status. +- Confirm embeddings exist and rebuild if needed. +- Retry with a more specific query. + +### Auto-organize feels too risky + +- Increase confidence threshold. +- Keep Smart Folders tightly defined. +- Start with manual review before fully trusting automation. + +### File names are not what you expect + +- Review naming defaults in Settings. +- Confirm convention/date/case/separator values. + +--- + +## 10) Reporting Problems + +Use the beta guide for full reporting instructions: [BETA_TESTER_GUIDE.md](./BETA_TESTER_GUIDE.md) + +Direct bug form: +[Open a bug report](https://github.com/iLevyTate/StratoSortCore/issues/new?template=bug_report.md) diff --git a/docs/migration-audit.md b/docs/migration-audit.md deleted file mode 100644 index 8d4e1a54..00000000 --- a/docs/migration-audit.md +++ /dev/null @@ -1,23 +0,0 @@ -# StratoSort Migration Audit (Closed) - -**Status:** Complete -**Audit date:** 2026-02-04 -**Scope:** Full codebase scan + plan reconciliation - -## Summary - -The migration to the fully in-process AI and vector stack is complete. Legacy external service -references and compatibility shims have been removed from the runtime code, settings, IPC contracts, -setup scripts, and documentation. - -## Verification - -- Runtime: All services, IPC handlers, and settings align with the in-process stack. -- UI: Configuration and setup flows point to in-process model management only. -- Scripts: Setup and build scripts no longer reference legacy external services. -- Docs: Configuration and architecture references now reflect the current stack. - -## Next Steps - -- Run the full test suite (`npm test`) and fix any regressions. -- Validate e2e flows for first-run setup, model downloads, and semantic search. diff --git a/package.json b/package.json index ecdb70b1..309ebb1f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "stratosort-core", + "productName": "StratoSort Core", "version": "2.0.1", "description": "StratoSort Core - Local AI Document Organizer. Privacy-focused document organization using in-process AI (node-llama-cpp + Orama). Zero external dependencies.", "keywords": [ diff --git a/scripts/patch-electron-mac.js b/scripts/patch-electron-mac.js new file mode 100644 index 00000000..f0509d44 --- /dev/null +++ b/scripts/patch-electron-mac.js @@ -0,0 +1,158 @@ +#!/usr/bin/env node +/** + * Patch Electron.app Info.plist for macOS development + * + * During development, macOS reads the dock label and Activity Monitor process + * name from the Electron binary's Info.plist, which defaults to "Electron". + * This script patches CFBundleName and CFBundleDisplayName so the dock shows + * "StratoSort Core" instead. + * + * This is safe and idempotent — re-running produces the same result. + * Only affects node_modules (dev-only); production builds use electron-builder + * which sets these values correctly from electron-builder.json. + * + * Usage: + * node scripts/patch-electron-mac.js # Patch Info.plist + * node scripts/patch-electron-mac.js --check # Check current values (no changes) + */ + +const fs = require('fs'); +const path = require('path'); + +const APP_NAME = 'StratoSort Core'; +const BUNDLE_ID = 'com.stratosort.app'; + +/** + * Locate the Electron.app Info.plist inside node_modules. + * Returns null if not found (e.g. running on non-macOS or Electron not installed). + */ +function findInfoPlist() { + const candidates = [ + // Standard npm install location + path.join( + __dirname, + '..', + 'node_modules', + 'electron', + 'dist', + 'Electron.app', + 'Contents', + 'Info.plist' + ), + // Hoisted or alternative layouts + path.join(__dirname, '..', '..', 'electron', 'dist', 'Electron.app', 'Contents', 'Info.plist') + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + return null; +} + +/** + * Replace a plist string value. + * Matches: KEY\n\tOLD + * and replaces OLD with NEW. + */ +function replacePlistValue(content, key, newValue) { + // Match the key followed by a string value (handles various whitespace) + const regex = new RegExp(`(${escapeRegex(key)}\\s*)(.*?)()`, 'g'); + return content.replace(regex, `$1${newValue}$3`); +} + +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function readPlistValue(content, key) { + const regex = new RegExp(`${escapeRegex(key)}\\s*(.*?)`); + const match = content.match(regex); + return match ? match[1] : null; +} + +function main() { + if (process.platform !== 'darwin') { + console.log('[patch-electron-mac] Skipped — not macOS'); + return 0; + } + + const plistPath = findInfoPlist(); + if (!plistPath) { + console.log('[patch-electron-mac] Skipped — Electron.app not found'); + return 0; + } + + const content = fs.readFileSync(plistPath, 'utf8'); + + const currentName = readPlistValue(content, 'CFBundleName'); + const currentDisplayName = readPlistValue(content, 'CFBundleDisplayName'); + const currentBundleId = readPlistValue(content, 'CFBundleIdentifier'); + + // --check mode: report current values without modifying + if (process.argv.includes('--check')) { + console.log('[patch-electron-mac] Info.plist:', plistPath); + console.log(` CFBundleName: ${currentName}`); + console.log(` CFBundleDisplayName: ${currentDisplayName}`); + console.log(` CFBundleIdentifier: ${currentBundleId}`); + + const isPatched = currentName === APP_NAME; + console.log( + ` Status: ${isPatched ? '✅ Patched' : '⚠️ Not patched (shows "Electron" in dock)'}` + ); + return isPatched ? 0 : 1; + } + + // Check if already patched + if (currentName === APP_NAME && currentDisplayName === APP_NAME) { + console.log('[patch-electron-mac] Already patched — dock will show "StratoSort Core"'); + return 0; + } + + // Apply patches + let patched = content; + patched = replacePlistValue(patched, 'CFBundleName', APP_NAME); + + // CFBundleDisplayName may not exist in the original; add it if missing + if (currentDisplayName !== null) { + patched = replacePlistValue(patched, 'CFBundleDisplayName', APP_NAME); + } else { + // Insert after CFBundleName + patched = patched.replace( + /(CFBundleName<\/key>\s*.*?<\/string>)/, + `$1\n\tCFBundleDisplayName\n\t${APP_NAME}` + ); + } + + // Patch bundle identifier so macOS associates dock state correctly + if (currentBundleId && currentBundleId !== BUNDLE_ID) { + patched = replacePlistValue(patched, 'CFBundleIdentifier', BUNDLE_ID); + } + + fs.writeFileSync(plistPath, patched, 'utf8'); + + // Verify + const verify = fs.readFileSync(plistPath, 'utf8'); + const verifiedName = readPlistValue(verify, 'CFBundleName'); + if (verifiedName !== APP_NAME) { + console.error('[patch-electron-mac] ❌ Patch failed — CFBundleName is still:', verifiedName); + return 1; + } + + console.log('[patch-electron-mac] ✅ Patched Electron.app Info.plist'); + console.log(` CFBundleName: "${currentName}" → "${APP_NAME}"`); + console.log(` CFBundleDisplayName: "${currentDisplayName || '(missing)'}" → "${APP_NAME}"`); + if (currentBundleId && currentBundleId !== BUNDLE_ID) { + console.log(` CFBundleIdentifier: "${currentBundleId}" → "${BUNDLE_ID}"`); + } + console.log(' The macOS dock will now show "StratoSort Core" in development.'); + + return 0; +} + +if (require.main === module) { + process.exit(main()); +} + +module.exports = { main }; diff --git a/scripts/postinstall.js b/scripts/postinstall.js index ef29573c..0786e5d3 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -53,6 +53,17 @@ function main( } } + // On macOS, patch the Electron.app Info.plist so the dock label and Activity + // Monitor show "StratoSort Core" instead of "Electron" during development. + if (platform === 'darwin') { + try { + const { main: patchMac } = require('./patch-electron-mac'); + patchMac(); + } catch (e) { + log.warn(`[postinstall] macOS Electron patch skipped: ${e.message}`); + } + } + log.log('\n[StratoSort] Setup complete!'); log.log(' Run: npm run dev'); log.log(' The app will use local AI engine (node-llama-cpp + Orama).\n'); diff --git a/src/main/ipc/smartFolders.js b/src/main/ipc/smartFolders.js index 994ab619..de107c4c 100644 --- a/src/main/ipc/smartFolders.js +++ b/src/main/ipc/smartFolders.js @@ -990,79 +990,20 @@ Now generate a description for "${folderName}":`; }; } - let llmEnhancedData = {}; - try { - const llmAnalysis = await enhanceSmartFolderWithLLM( - folder, - customFolders, - getTextModel - ); - if (llmAnalysis && !llmAnalysis.error) llmEnhancedData = llmAnalysis; - } catch (e) { - logger.warn( - '[SMART-FOLDERS] LLM enhancement failed, continuing with basic data:', - e.message - ); - } - - const descriptionFromLlm = - (typeof llmEnhancedData.improvedDescription === 'string' && - llmEnhancedData.improvedDescription.trim()) || - (typeof llmEnhancedData.enhancedDescription === 'string' && - llmEnhancedData.enhancedDescription.trim()) || - ''; - const keywordsFromLlm = Array.isArray(llmEnhancedData.suggestedKeywords) - ? llmEnhancedData.suggestedKeywords - .map((v) => String(v || '').trim()) - .filter(Boolean) - .slice(0, 12) - : []; - const semanticTagsFromLlm = Array.isArray(llmEnhancedData.semanticTags) - ? llmEnhancedData.semanticTags - .map((v) => String(v || '').trim()) - .filter(Boolean) - .slice(0, 12) - : []; - const existingFolderNameLookup = new Map( - customFolders - .filter((f) => f && typeof f.name === 'string' && f.name.trim()) - .map((f) => [f.name.toLowerCase(), f.name]) - ); - const relatedFoldersFromLlm = Array.isArray(llmEnhancedData.relatedFolders) - ? llmEnhancedData.relatedFolders - .map((v) => String(v || '').trim()) - .filter(Boolean) - .map((name) => existingFolderNameLookup.get(name.toLowerCase()) || null) - .filter(Boolean) - .slice(0, 8) - : []; - const confidenceFromLlm = Number(llmEnhancedData.confidence); - const normalizedConfidence = Number.isFinite(confidenceFromLlm) - ? Math.max( - 0, - Math.min(1, confidenceFromLlm > 1 ? confidenceFromLlm / 100 : confidenceFromLlm) - ) - : 0.8; - const categoryFromLlm = - typeof llmEnhancedData.suggestedCategory === 'string' && - llmEnhancedData.suggestedCategory.trim() - ? llmEnhancedData.suggestedCategory.trim().toLowerCase().slice(0, 60) - : 'general'; + // Build folder immediately with basic data — no LLM blocking. + // LLM enhancement runs in the background after the folder is saved. const newFolder = { id: `sf-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`, name: sanitizedName, path: normalizedPath, - description: - descriptionFromLlm || - folder.description?.trim() || - `Smart folder for ${sanitizedName}`, - keywords: keywordsFromLlm, - category: categoryFromLlm, + description: folder.description?.trim() || `Smart folder for ${sanitizedName}`, + keywords: [], + category: 'general', isDefault: folder.isDefault || false, createdAt: new Date().toISOString(), - semanticTags: semanticTagsFromLlm, - relatedFolders: relatedFoldersFromLlm, - confidenceScore: normalizedConfidence, + semanticTags: [], + relatedFolders: [], + confidenceScore: 0.8, usageCount: 0, lastUsed: null }; @@ -1119,6 +1060,102 @@ Now generate a description for "${folderName}":`; 'Existed:', directoryExisted ); + + // Fire-and-forget: enhance with LLM in the background. + // The folder is already saved and visible to the user. + const folderId = newFolder.id; + const folderSnapshot = { ...folder }; + const foldersSnapshot = customFolders.map((f) => ({ ...f })); + setImmediate(() => { + enhanceSmartFolderWithLLM(folderSnapshot, foldersSnapshot, getTextModel) + .then(async (llmAnalysis) => { + if (!llmAnalysis || llmAnalysis.error) { + logger.info( + '[SMART-FOLDERS] Background LLM enhancement skipped or failed:', + llmAnalysis?.error || 'no result' + ); + return; + } + + // Apply LLM data to the live folder list + const currentFolders = getCustomFolders(); + const idx = currentFolders.findIndex((f) => f.id === folderId); + if (idx === -1) { + logger.warn( + '[SMART-FOLDERS] Folder removed before LLM enhancement completed:', + folderId + ); + return; + } + + const descriptionFromLlm = + (typeof llmAnalysis.improvedDescription === 'string' && + llmAnalysis.improvedDescription.trim()) || + (typeof llmAnalysis.enhancedDescription === 'string' && + llmAnalysis.enhancedDescription.trim()) || + ''; + const keywordsFromLlm = Array.isArray(llmAnalysis.suggestedKeywords) + ? llmAnalysis.suggestedKeywords + .map((v) => String(v || '').trim()) + .filter(Boolean) + .slice(0, 12) + : []; + const semanticTagsFromLlm = Array.isArray(llmAnalysis.semanticTags) + ? llmAnalysis.semanticTags + .map((v) => String(v || '').trim()) + .filter(Boolean) + .slice(0, 12) + : []; + const existingFolderNameLookup = new Map( + currentFolders + .filter((f) => f && typeof f.name === 'string' && f.name.trim()) + .map((f) => [f.name.toLowerCase(), f.name]) + ); + const relatedFoldersFromLlm = Array.isArray(llmAnalysis.relatedFolders) + ? llmAnalysis.relatedFolders + .map((v) => String(v || '').trim()) + .filter(Boolean) + .map((name) => existingFolderNameLookup.get(name.toLowerCase()) || null) + .filter(Boolean) + .slice(0, 8) + : []; + const confidenceFromLlm = Number(llmAnalysis.confidence); + const normalizedConfidence = Number.isFinite(confidenceFromLlm) + ? Math.max( + 0, + Math.min( + 1, + confidenceFromLlm > 1 ? confidenceFromLlm / 100 : confidenceFromLlm + ) + ) + : 0.8; + const categoryFromLlm = + typeof llmAnalysis.suggestedCategory === 'string' && + llmAnalysis.suggestedCategory.trim() + ? llmAnalysis.suggestedCategory.trim().toLowerCase().slice(0, 60) + : undefined; + + // Merge LLM data into the existing folder + const target = currentFolders[idx]; + if (descriptionFromLlm) target.description = descriptionFromLlm; + if (keywordsFromLlm.length) target.keywords = keywordsFromLlm; + if (semanticTagsFromLlm.length) target.semanticTags = semanticTagsFromLlm; + if (relatedFoldersFromLlm.length) target.relatedFolders = relatedFoldersFromLlm; + if (categoryFromLlm) target.category = categoryFromLlm; + target.confidenceScore = normalizedConfidence; + + setCustomFolders(currentFolders); + await saveCustomFolders(currentFolders); + logger.info( + '[SMART-FOLDERS] Background LLM enhancement applied to folder:', + folderId + ); + }) + .catch((err) => { + logger.warn('[SMART-FOLDERS] Background LLM enhancement error:', err.message); + }); + }); + return { success: true, folder: newFolder, @@ -1128,7 +1165,7 @@ Now generate a description for "${folderName}":`; : 'Smart folder added (directory already existed)', directoryCreated, directoryExisted, - llmEnhanced: !!llmEnhancedData.enhancedDescription + llmEnhanced: false }; } catch (saveError) { setCustomFolders(originalFolders); diff --git a/src/main/ipc/system.js b/src/main/ipc/system.js index 476e188c..02500573 100644 --- a/src/main/ipc/system.js +++ b/src/main/ipc/system.js @@ -299,37 +299,52 @@ function registerSystemIpc(servicesOrParams) { }) ); - // Export logs to a zip file + // Export logs to a zip file (optionally redacted for safe troubleshooting uploads) safeHandle( ipcMain, IPC_CHANNELS.SYSTEM.EXPORT_LOGS, createHandler({ logger, context, - handler: async () => { + handler: async (_event, payload) => { try { const { app, dialog } = require('electron'); const path = require('path'); const fs = require('fs'); + const fsPromises = require('fs').promises; const AdmZip = require('adm-zip'); + const { redactLogContent } = require('../../shared/logRedaction'); + const redact = Boolean(payload?.redact); const logsDir = path.join(app.getPath('userData'), 'logs'); if (!fs.existsSync(logsDir)) { return { success: false, error: 'No logs found' }; } const zip = new AdmZip(); - zip.addLocalFolder(logsDir, 'logs'); - // Also include crash dumps if they exist - const crashDumpsDir = path.join(app.getPath('userData'), 'crash-dumps'); - if (fs.existsSync(crashDumpsDir)) { - zip.addLocalFolder(crashDumpsDir, 'crash-dumps'); + if (redact) { + const logFiles = await fsPromises.readdir(logsDir); + for (const name of logFiles) { + const fullPath = path.join(logsDir, name); + const stat = await fsPromises.stat(fullPath); + if (!stat.isFile() || !name.endsWith('.log')) continue; + const content = await fsPromises.readFile(fullPath, 'utf8'); + const redacted = redactLogContent(content); + zip.addFile(`logs/${name}`, Buffer.from(redacted, 'utf8')); + } + } else { + zip.addLocalFolder(logsDir, 'logs'); + const crashDumpsDir = path.join(app.getPath('userData'), 'crash-dumps'); + if (fs.existsSync(crashDumpsDir)) { + zip.addLocalFolder(crashDumpsDir, 'crash-dumps'); + } } + const suffix = redact ? '-redacted' : ''; const { filePath } = await dialog.showSaveDialog({ - title: 'Export Debug Logs', - defaultPath: `stratosort-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.zip`, + title: redact ? 'Export Logs (Redacted)' : 'Export Debug Logs', + defaultPath: `stratosort-logs${suffix}-${new Date().toISOString().replace(/[:.]/g, '-')}.zip`, filters: [{ name: 'Zip Files', extensions: ['zip'] }] }); diff --git a/src/main/services/OramaVectorService.js b/src/main/services/OramaVectorService.js index 836c16ed..a1c489fc 100644 --- a/src/main/services/OramaVectorService.js +++ b/src/main/services/OramaVectorService.js @@ -1176,8 +1176,54 @@ class OramaVectorService extends EventEmitter { } async _fallbackQuerySimilarFiles(queryEmbedding, topK) { - const docs = await this._getAllFileDocuments(); + const normalizedTopK = Number.isFinite(topK) && topK > 0 ? Math.max(1, Math.floor(topK)) : 10; + const embStore = this._embeddingStore?.files; const scored = []; + + // Fast path: use in-memory sidecar vectors and only fetch metadata for top candidates. + if (embStore instanceof Map && embStore.size > 0) { + for (const [docId, rawVector] of embStore.entries()) { + const emb = this._normalizeEmbeddingVector(rawVector, queryEmbedding.length); + if (!Array.isArray(emb) || emb.length !== queryEmbedding.length) continue; + if (emb[0] === 0 && emb.every((v) => v === 0)) continue; + const score = this._cosineSimilarity(queryEmbedding, emb); + if (!Number.isFinite(score) || score <= 0) continue; + scored.push({ id: docId, score }); + } + + scored.sort((a, b) => b.score - a.score); + const candidates = scored.slice(0, Math.max(normalizedTopK * 3, normalizedTopK)); + const results = []; + + for (const candidate of candidates) { + const doc = await getByID(this._databases.files, candidate.id); + if (!doc || doc.isOrphaned || doc.hasVector === false) continue; + results.push({ + id: doc.id, + score: candidate.score, + distance: 1 - candidate.score, + metadata: { + path: doc.filePath, + filePath: doc.filePath, + fileName: doc.fileName, + fileType: doc.fileType, + analyzedAt: doc.analyzedAt, + suggestedName: doc.suggestedName, + keywords: doc.keywords, + tags: doc.tags + } + }); + if (results.length >= normalizedTopK) break; + } + + if (results.length > 0) { + return results; + } + } + + // Slow path fallback for edge cases where sidecar map is unavailable. + const docs = await this._getAllFileDocuments(); + const scoredDocs = []; for (const doc of docs) { if (doc?.isOrphaned) continue; const emb = this._normalizeEmbeddingVector( @@ -1188,10 +1234,10 @@ class OramaVectorService extends EventEmitter { if (emb[0] === 0 && emb.every((v) => v === 0)) continue; const score = this._cosineSimilarity(queryEmbedding, emb); if (!Number.isFinite(score) || score <= 0) continue; - scored.push({ doc, score }); + scoredDocs.push({ doc, score }); } - scored.sort((a, b) => b.score - a.score); - return scored.slice(0, topK).map(({ doc, score }) => ({ + scoredDocs.sort((a, b) => b.score - a.score); + return scoredDocs.slice(0, normalizedTopK).map(({ doc, score }) => ({ id: doc.id, score, distance: 1 - score, @@ -1522,7 +1568,17 @@ class OramaVectorService extends EventEmitter { ); return await this._fallbackQuerySimilarFiles(queryEmbedding, topK); } - return []; + + // Even if the self-check passes, some Orama states can still return empty vector hits + // for regular queries. Use a cosine fallback scorer so semantic retrieval remains useful. + logger.debug( + '[OramaVectorService] Primary vector query returned no hits; using fallback scorer', + { + topK, + health + } + ); + return await this._fallbackQuerySimilarFiles(queryEmbedding, topK); } catch (error) { throw attachErrorCode(error, ERROR_CODES.VECTOR_DB_QUERY_FAILED); } @@ -2726,6 +2782,78 @@ class OramaVectorService extends EventEmitter { }; } + /** + * Diagnose eligibility for primary file-level vector search. + * Helps explain cases where file embeddings exist but vector retrieval returns no hits. + * + * @param {{ sampleSize?: number }} [options] + * @returns {Promise} + */ + async getFileVectorDiagnostics(options = {}) { + await this.initialize(); + + const sampleSize = Math.max(1, Math.min(Number(options?.sampleSize) || 5, 20)); + const docs = await this._getAllFileDocuments(); + const embStore = this._embeddingStore?.files; + const sidecarSize = embStore instanceof Map ? embStore.size : 0; + + let hasVectorTrue = 0; + let hasVectorFalse = 0; + let orphaned = 0; + let missingEmbedding = 0; + let placeholderVectors = 0; + let eligibleForPrimaryVector = 0; + const ineligibleSample = []; + + for (const doc of docs) { + const hasVector = doc?.hasVector === true; + const isOrphaned = doc?.isOrphaned === true; + + if (hasVector) hasVectorTrue++; + else hasVectorFalse++; + if (isOrphaned) orphaned++; + + const normalizedEmbedding = this._normalizeEmbeddingVector( + (embStore instanceof Map ? embStore.get(doc?.id) : null) || doc?.embedding, + this._dimension + ); + const hasEmbedding = + Array.isArray(normalizedEmbedding) && normalizedEmbedding.length === this._dimension; + const isPlaceholder = + hasEmbedding && + normalizedEmbedding[0] === 0 && + normalizedEmbedding.every((value) => value === 0); + + if (!hasEmbedding) missingEmbedding++; + if (isPlaceholder) placeholderVectors++; + + const eligible = hasVector && !isOrphaned && hasEmbedding && !isPlaceholder; + if (eligible) { + eligibleForPrimaryVector++; + } else if (ineligibleSample.length < sampleSize) { + ineligibleSample.push({ + id: doc?.id, + hasVector, + isOrphaned, + hasEmbedding, + isPlaceholder + }); + } + } + + return { + totalFiles: docs.length, + eligibleForPrimaryVector, + hasVectorTrue, + hasVectorFalse, + orphaned, + missingEmbedding, + placeholderVectors, + sidecarEmbeddings: sidecarSize, + ineligibleSample + }; + } + getVectorHealth() { return this._getVectorHealthSnapshot(); } diff --git a/src/main/services/SearchService.js b/src/main/services/SearchService.js index b73d3f6f..9a42812e 100644 --- a/src/main/services/SearchService.js +++ b/src/main/services/SearchService.js @@ -141,6 +141,10 @@ class SearchService { // Maximum cache size in bytes (50MB) to prevent unbounded growth this._maxCacheSize = 50 * 1024 * 1024; + + // Throttle noisy diagnostics when primary vector retrieval returns no hits. + this._lastZeroVectorDiagnosticsAt = 0; + this._ZERO_VECTOR_DIAGNOSTICS_INTERVAL_MS = 60 * 1000; } /** @@ -681,6 +685,11 @@ class SearchService { return []; } + if (vectorResults.length === 0) { + await this._logZeroVectorDiagnostics(query, topK); + return []; + } + // Extract query words for tag/category matching const queryWords = query .toLowerCase() @@ -739,6 +748,41 @@ class SearchService { } } + async _logZeroVectorDiagnostics(query, topK) { + const now = Date.now(); + if (now - this._lastZeroVectorDiagnosticsAt < this._ZERO_VECTOR_DIAGNOSTICS_INTERVAL_MS) { + return; + } + this._lastZeroVectorDiagnosticsAt = now; + + try { + const stats = + typeof this.vectorDb?.getStats === 'function' ? await this.vectorDb.getStats() : null; + const diagnostics = + typeof this.vectorDb?.getFileVectorDiagnostics === 'function' + ? await this.vectorDb.getFileVectorDiagnostics({ sampleSize: 3 }) + : null; + const vectorHealth = + stats?.vectorHealth || + (typeof this.vectorDb?.getVectorHealth === 'function' + ? this.vectorDb.getVectorHealth() + : null); + + logger.warn('[SearchService] Vector search returned zero results', { + query: typeof query === 'string' ? query.slice(0, 120) : '', + topK, + fileEmbeddings: stats?.files ?? null, + chunkEmbeddings: stats?.fileChunks ?? null, + vectorHealth, + diagnostics + }); + } catch (error) { + logger.debug('[SearchService] Failed to gather zero-vector diagnostics', { + error: error?.message + }); + } + } + /** * Chunk search: query against extractedText chunk embeddings. * diff --git a/src/main/services/ServiceIntegration.js b/src/main/services/ServiceIntegration.js index ec09296a..0c965757 100644 --- a/src/main/services/ServiceIntegration.js +++ b/src/main/services/ServiceIntegration.js @@ -616,6 +616,7 @@ class ServiceIntegration { vectorDbService: c.resolve(ServiceIds.ORAMA_VECTOR), analysisHistoryService: c.resolve(ServiceIds.ANALYSIS_HISTORY), parallelEmbeddingService: c.resolve(ServiceIds.PARALLEL_EMBEDDING), + llamaService: c.tryResolve(ServiceIds.LLAMA_SERVICE), relationshipIndexService: c.tryResolve(ServiceIds.RELATIONSHIP_INDEX) }); }); diff --git a/src/main/simple-main.js b/src/main/simple-main.js index fdd5543e..311ed98a 100644 --- a/src/main/simple-main.js +++ b/src/main/simple-main.js @@ -8,6 +8,14 @@ try { const { app, BrowserWindow, ipcMain, dialog, shell, crashReporter } = require('electron'); +// Set the application name as early as possible. +// On macOS this controls the dock label and application menu title. +// Electron defaults to package.json "productName" → "name", but the raw Electron +// binary in development reports itself as "Electron" to the OS. An explicit +// assignment ensures the correct name in all contexts (dock, Activity Monitor menu +// label, window title fallback) regardless of dev vs packaged mode. +app.name = 'StratoSort Core'; + const isDev = process.env.NODE_ENV === 'development'; // Logging utility @@ -543,6 +551,28 @@ if (gotTheLock || process.env.STRATOSORT_FORCE_LAUNCH === '1') { // Initialize services after app is ready app.whenReady().then(async () => { logger.info('[STARTUP] app.whenReady resolved'); + + // macOS: Set the dock icon to the StratoSort logo so it replaces the default + // Electron atom icon during development. In production (packaged) builds + // electron-builder bakes the icon into the .app bundle so this is a no-op. + if (process.platform === 'darwin' && app.dock) { + try { + const { nativeImage } = require('electron'); + const iconPath = path.join(__dirname, '..', 'assets', 'icons', 'png', '512x512.png'); + // Resolve from repo root in dev, or from resources in packaged build + const resolvedIcon = app.isPackaged + ? path.join(process.resourcesPath, 'assets', 'icons', 'png', '512x512.png') + : path.resolve(iconPath); + const fsSync = require('fs'); + if (fsSync.existsSync(resolvedIcon)) { + app.dock.setIcon(nativeImage.createFromPath(resolvedIcon)); + logger.info('[DOCK] Set macOS dock icon from', resolvedIcon); + } + } catch (dockErr) { + logger.debug('[DOCK] Could not set macOS dock icon:', dockErr?.message); + } + } + // FIX: Create a referenced interval to keep the event loop alive during startup // This prevents premature exit when async operations use unreferenced timeouts const startupKeepalive = trackInterval(() => {}, 1000); diff --git a/src/preload/preload.js b/src/preload/preload.js index 2c4f886a..3e317978 100644 --- a/src/preload/preload.js +++ b/src/preload/preload.js @@ -1,4 +1,4 @@ -const { contextBridge, ipcRenderer } = require('electron'); +const { contextBridge, ipcRenderer, webUtils } = require('electron'); const { Logger, LOG_LEVELS } = require('../shared/logger'); const { IpcRateLimiter } = require('./ipcRateLimiter'); const { createIpcSanitizer } = require('./ipcSanitizer'); @@ -760,6 +760,15 @@ window.addEventListener('beforeunload', () => { contextBridge.exposeInMainWorld('electronAPI', { // File Operations files: { + // Resolve the absolute file-system path for a DOM File object. + // Required because sandbox: true makes File.path empty. + getPathForFile: (file) => { + try { + return webUtils.getPathForFile(file); + } catch { + return ''; + } + }, select: () => secureIPC.safeInvoke(IPC_CHANNELS.FILES.SELECT), selectDirectory: () => secureIPC.safeInvoke(IPC_CHANNELS.FILES.SELECT_DIRECTORY), getDocumentsPath: () => secureIPC.safeInvoke(IPC_CHANNELS.FILES.GET_DOCUMENTS_PATH), @@ -1058,7 +1067,10 @@ contextBridge.exposeInMainWorld('electronAPI', { secureIPC.safeInvoke(IPC_CHANNELS.SYSTEM.GET_RECOMMENDED_CONCURRENCY), log: (level, message, data) => secureIPC.safeInvoke(IPC_CHANNELS.SYSTEM.LOG, { level, message, data }), - exportLogs: () => secureIPC.safeInvoke(IPC_CHANNELS.SYSTEM.EXPORT_LOGS), + exportLogs: (redact = false) => + secureIPC.safeInvoke(IPC_CHANNELS.SYSTEM.EXPORT_LOGS, { + redact: Boolean(redact) + }), onOpenSemanticSearch: (callback) => secureIPC.safeOn('open-semantic-search', callback) }, diff --git a/src/renderer/App.js b/src/renderer/App.js index c747bed7..5ae12ab3 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -61,7 +61,10 @@ function AppContent() { return ( <> - + Skip to main content diff --git a/src/renderer/components/NavigationBar.jsx b/src/renderer/components/NavigationBar.jsx index e8d2f4ba..f05dd2e1 100644 --- a/src/renderer/components/NavigationBar.jsx +++ b/src/renderer/components/NavigationBar.jsx @@ -231,10 +231,10 @@ const NavTab = memo(function NavTab({ focus:outline-none focus-visible:ring-2 focus-visible:ring-stratosort-blue focus-visible:ring-offset-2 ${ isActive - ? 'bg-white text-stratosort-blue shadow-sm border border-system-gray-200' + ? 'phase-nav-tab-active' : canNavigate - ? 'text-system-gray-600 hover:text-system-gray-900 hover:bg-white/60 border border-transparent' - : 'text-system-gray-400 cursor-not-allowed border border-transparent' + ? 'phase-nav-tab-interactive' + : 'phase-nav-tab-disabled' } `} aria-label={metadata?.title} @@ -631,7 +631,10 @@ function NavigationBar() { >
{/* Left: Brand */} -
+
@@ -640,7 +643,7 @@ function NavigationBar() { {/* the inner