Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

156 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

⚑ SortForge

One-click local file organizer that permanently sorts messy folders into clean, structured hierarchies.


Version License Node Platform Status

GitHub stars GitHub forks GitHub issues



No cloud. No virtual folders. Your real filesystem gets organized β€” permanently.



🎯 What It Does

Select a folder, click Organize, and SortForge scans every file, creates category folders, and moves files into their correct locations β€” directly on your computer.

Before:                            After:
─────────                          ──────
Project/                           Project/
β”œβ”€β”€ clip1.mp4                      β”œβ”€β”€ Videos/
β”œβ”€β”€ clip2.mp4                      β”‚   β”œβ”€β”€ clip1.mp4
β”œβ”€β”€ music.mp3                      β”‚   └── clip2.mp4
β”œβ”€β”€ logo.png                       β”œβ”€β”€ Audio/
β”œβ”€β”€ document.pdf                   β”‚   └── music.mp3
β”œβ”€β”€ photo.jpg                      β”œβ”€β”€ Images/
β”œβ”€β”€ notes.txt                      β”‚   β”œβ”€β”€ logo.png
β”œβ”€β”€ backup.zip                     β”‚   └── photo.jpg
└── song.flac                      β”œβ”€β”€ Documents/
                                   β”‚   β”œβ”€β”€ document.pdf
                                   β”‚   └── notes.txt
                                   β”œβ”€β”€ Archives/
                                   β”‚   └── backup.zip
                                   └── Audio/
                                       └── song.flac

πŸš€ Quick Start

Prerequisites

None. start.bat automatically detects and installs Node.js if it's missing.

Option 1 β€” Double-click to launch

start.bat

Auto-installs Node.js if needed, installs dependencies, starts both servers, and opens the browser automatically. Works out of the box for non-technical users.

Option 2 β€” Manual start (two terminals)

# Terminal 1 β€” Bridge (backend)
cd apps/bridge
npm run dev

# Terminal 2 β€” Frontend (UI)
cd apps/web
npm run dev

Then open http://localhost:3000 in your browser.

To stop

stop.bat

πŸ”„ How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                                                     β”‚
β”‚  1. Click Browse        β†’ Folder picker opens (drives, quick       β”‚
β”‚                             access, visual navigation)               β”‚
β”‚  2. Navigate & Select   β†’ Choose any folder on your computer       β”‚
β”‚  3. Scan                β†’ SortForge scans every file recursively    β”‚
β”‚  4. Review Summary      β†’ See file counts by category               β”‚
β”‚  5. Click Organize      β†’ Folders created, files moved             β”‚
β”‚  6. Watch Live          β†’ Real-time folder tree + activity feed     β”‚
β”‚  7. Done                β†’ Completion summary with stats             β”‚
β”‚                                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    HTTP/WS     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    Filesystem    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      β”‚                β”‚                      β”‚                  β”‚              β”‚
β”‚   Frontend (UI)      β”‚ ────────────── β”‚   Bridge (Server)    β”‚ ────────────── β”‚  Your Disk   β”‚
β”‚   Next.js :3000      β”‚                β”‚   Node.js :43125     β”‚                  β”‚              β”‚
β”‚                      β”‚                β”‚                      β”‚                  β”‚              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚                β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚                  β”‚              β”‚
β”‚   β”‚ FolderPicker β”‚   β”‚   GET /scan    β”‚   β”‚   Scanner    β”‚   β”‚  Recursive walk  β”‚              β”‚
β”‚   β”‚ Modal        β”‚   β”‚ ──────────────▢│   β”‚   + Classify │◀──│──────────────────│              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚                β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚                  β”‚              β”‚
β”‚                      β”‚   POST /organize                       β”‚                  β”‚              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚ β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Άβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚  Create folders  β”‚              β”‚
β”‚   β”‚ Event Tree   │◀──│ ◀──────────── β”‚ β”‚  Planner +      │───│────────────────▢│              β”‚
β”‚   β”‚ View         β”‚   β”‚   WS events   β”‚ β”‚  Executor       β”‚   β”‚  Move files     β”‚              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚                  β”‚              β”‚
β”‚                      β”‚                                       β”‚                  β”‚              β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚   WS events   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚                  β”‚              β”‚
β”‚   β”‚ Activity     │◀──│ ◀──────────── β”‚ WebSocket Server β”‚   β”‚                  β”‚              β”‚
β”‚   β”‚ Feed         β”‚   β”‚               β”‚ (real-time)      β”‚   β”‚                  β”‚              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚                  β”‚              β”‚
β”‚                      β”‚                                       β”‚                  β”‚              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Communication Flow

Browser ──HTTP POST──▢ Bridge ──filesystem──▢ Disk
Browser ◀──HTTP 202── Bridge (organization started)
Browser ◀──WS events── Bridge (real-time progress)

πŸ“¦ Tech Stack

LayerTechnologyPurpose
FrontendNext.js 16React framework, SSR, routing
UI LibraryReact 19Component rendering
LanguageTypeScript 5/6Type safety across all packages
StylingTailwind CSS 4Utility-first CSS
StateZustand 5Lightweight global state
IconsLucide ReactIcon library
AnimationsFramer MotionUI micro-animations
Data FetchingTanStack Query 5Async operations, caching
BackendNode.js + Express 5HTTP server, API endpoints
WebSocketws 8Real-time event streaming
ValidationZod 4Schema validation (shared)
TestingJest 30 + VitestUnit tests (bridge + web)
Dev ServertsxTypeScript execution for bridge

πŸ“ Project Structure

SortForge/
β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ web/                          # Next.js frontend
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ layout.tsx        # Root layout (Geist font, metadata)
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ page.tsx          # Entry page β†’ OrganizerScreen
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ globals.css       # Tailwind directives + global styles
β”‚   β”‚   β”‚   β”‚   └── favicon.ico       # Application favicon
β”‚   β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ organizer-screen.tsx   # Main screen composing all UI
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ folder-picker-modal.tsx # File Explorer-style modal
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ folder-picker.tsx      # Basic folder picker
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ folder-browser.tsx     # Inline folder browser
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ scan-summary.tsx       # Category count display
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ organize-button.tsx    # Organize trigger button
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ event-tree.tsx         # Live folder tree visualization
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ activity-feed.tsx      # Real-time event log
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ summary-panel.tsx      # Completion summary stats
β”‚   β”‚   β”‚   β”‚   └── index.ts              # Barrel export
β”‚   β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”‚   β”‚   └── use-bridge-connection.ts # Health check polling (5s)
β”‚   β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   β”‚   └── bridge-client.ts    # HTTP/WS client wrapper
β”‚   β”‚   β”‚   β”œβ”€β”€ store/
β”‚   β”‚   β”‚   β”‚   └── organizer-store.ts  # Zustand global state
β”‚   β”‚   β”‚   └── types/
β”‚   β”‚   β”‚       └── organizer.ts        # TypeScript interfaces
β”‚   β”‚   β”œβ”€β”€ public/                    # Static assets
β”‚   β”‚   β”‚   β”œβ”€β”€ file.svg
β”‚   β”‚   β”‚   β”œβ”€β”€ globe.svg
β”‚   β”‚   β”‚   β”œβ”€β”€ next.svg
β”‚   β”‚   β”‚   β”œβ”€β”€ vercel.svg
β”‚   β”‚   β”‚   └── window.svg
β”‚   β”‚   β”œβ”€β”€ package.json               # Next.js + React dependencies
β”‚   β”‚   β”œβ”€β”€ tsconfig.json              # TypeScript config
β”‚   β”‚   β”œβ”€β”€ next.config.ts             # Next.js config
β”‚   β”‚   β”œβ”€β”€ postcss.config.mjs         # PostCSS + Tailwind
β”‚   β”‚   └── eslint.config.mjs          # ESLint config
β”‚   β”‚
β”‚   └── bridge/                        # Node.js file organizer server
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ main.ts                # Entry point, starts server
β”‚       β”‚   β”œβ”€β”€ ipc/
β”‚       β”‚   β”‚   β”œβ”€β”€ server.ts          # HTTP + WebSocket server
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   β”œβ”€β”€ scanner/
β”‚       β”‚   β”‚   β”œβ”€β”€ walk.ts            # Recursive directory scanner
β”‚       β”‚   β”‚   β”œβ”€β”€ classify.ts        # File extension β†’ category mapper
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   β”œβ”€β”€ organizer/
β”‚       β”‚   β”‚   β”œβ”€β”€ planner.ts         # Builds operation plan from scan
β”‚       β”‚   β”‚   β”œβ”€β”€ executor.ts        # Executes plan, moves files, emits events
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   β”œβ”€β”€ utils/
β”‚       β”‚   β”‚   β”œβ”€β”€ path-utils.ts      # Path traversal safety, relative paths
β”‚       β”‚   β”‚   β”œβ”€β”€ fs-utils.ts        # Filesystem helpers (exists, move, ensure)
β”‚       β”‚   β”‚   β”œβ”€β”€ name-utils.ts      # Duplicate filename resolution
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   β”œβ”€β”€ errors/
β”‚       β”‚   β”‚   β”œβ”€β”€ organizer-error.ts # Typed error classes
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   β”œβ”€β”€ config/
β”‚       β”‚   β”‚   β”œβ”€β”€ defaults.ts        # Port, WS path, CORS, max file size
β”‚       β”‚   β”‚   └── index.ts           # Barrel export
β”‚       β”‚   └── models/
β”‚       β”‚       └── index.ts           # Model types
β”‚       β”œβ”€β”€ dist/                      # Compiled output (committed)
β”‚       β”œβ”€β”€ package.json               # Express, ws, zod dependencies
β”‚       └── tsconfig.json              # TypeScript config (ES2022, commonjs)
β”‚
β”œβ”€β”€ packages/
β”‚   └── shared/                        # Shared types + Zod schemas
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   └── index.ts              # All shared schemas and types
β”‚       β”œβ”€β”€ package.json               # Zod dependency
β”‚       └── tsconfig.json              # TypeScript config
β”‚
β”œβ”€β”€ start.bat                          # One-click launcher (auto-installs Node.js)
β”œβ”€β”€ stop.bat                           # Server stopper
β”œβ”€β”€ README.md                          # This file
└── .gitignore                         # Git ignore rules

πŸ”Œ API Endpoints (Bridge Server)

MethodEndpointDescriptionRequestResponse
GET/healthHealth checkβ€”{ "status": "ok", "timestamp": ... }
GET/list-dirs?path=List drives, quick-access folders, or subdirectoriesQuery: path (or __root__){ currentPath, parentPath, isRoot, separator, drives, quickAccess, directories }
POST/scanScan a folder recursively, classify files by extension{ "folderPath": "..." }{ folderPath, files[], categorySummary, totalFiles }
POST/organizeStart organizing files (async, streams events via WS){ "folderPath": "..." }202 { message, folderPath, totalFiles }
WSws://localhost:43125/eventsReal-time organization eventsβ€”Stream of OrganizerEvent objects

WebSocket Event Types

Event Type Payload Fields Description
job-started folderPath, totalOperations Organization job has begun
folder-created path, folderName A category folder was created
file-move-started sourcePath, targetPath File move operation began
file-move-completed sourcePath, targetPath, fileName File successfully moved
file-skipped sourcePath, reason File was skipped (already in correct folder)
duplicate-resolved originalPath, resolvedPath, fileName Duplicate filename resolved with (1), (2), etc.
job-completed filesOrganized, foldersCreated, filesSkipped, duplicatesResolved, duration, errors Job finished successfully
job-failed error Job failed with error

πŸ“‚ Supported File Categories

CategoryDestination FolderExtensions
🎬 VideosVideos/.mp4 .mov .webm .avi .mkv .flv .wmv .m4v .mpg .mpeg
🎡 AudioAudio/.mp3 .wav .flac .ogg .aac .wma .m4a .opus
πŸ–ΌοΈ ImagesImages/.png .jpg .jpeg .webp .gif .svg .bmp .tiff .tif .ico .heic .heif
πŸ“„ DocumentsDocuments/.pdf .doc .docx .txt .rtf .odt .xls .xlsx .ppt .pptx .csv .md
πŸ“¦ ArchivesArchives/.zip .rar .7z .tar .gz .bz2 .xz
❓ OtherOther/Everything else

Total: 55+ file extensions recognized across 6 categories.


🧩 Frontend Components

ComponentFileDescription
OrganizerScreenorganize-screen.tsxMain composition β€” assembles all UI sections, manages scan/organize flow, handles WebSocket subscription
FolderPickerModalfolder-picker-modal.tsxFile Explorer-style modal with drives sidebar, quick-access folders (Desktop, Downloads, Documents, Pictures, Music, Videos), visual directory navigation, path input bar, back/up/home buttons
FolderBrowserfolder-browser.tsxInline folder browser (alternative to modal) with drive detection and directory listing
ScanSummaryscan-summary.tsxDisplays file counts per category in a color-coded grid (blue=video, green=audio, purple=image, orange=document, red=archive, gray=other)
OrganizeButtonorganize-button.tsxGreen action button with loading state β€” sends POST to bridge, triggers organization
EventTreeViewevent-tree.tsxLive folder tree visualization β€” shows folders being created and files being placed in real-time as WebSocket events arrive
ActivityFeedactivity-feed.tsxScrollable event log with timestamps β€” every action (create, move, skip, duplicate, complete) displayed in plain language
SummaryPanelsummary-panel.tsxCompletion statistics β€” status, duration, files organized, folders created, skipped, duplicates resolved, errors list

βš™οΈ Bridge Modules

ModuleFileResponsibilities
Serveripc/server.tsHTTP server (Express-style), CORS handling, route dispatch, WebSocket upgrade, client broadcasting
Scannerscanner/walk.tsRecursive directory traversal, file stat collection, UUID generation, category summary building, skips .sortforge folder
Classifierscanner/classify.tsExtension-to-category mapping (55+ extensions), destination folder name resolution
Plannerorganizer/planner.tsBuilds operation plan: determines folders to create, files to move, files to skip (already in correct folder)
Executororganizer/executor.ts3-phase execution: (1) create folders, (2) move files with duplicate resolution, (3) handle skips β€” emits events for every action
Path Utilsutils/path-utils.tsPath traversal prevention (isInsideBase), relative path computation, extension extraction
FS Utilsutils/fs-utils.tsFilesystem helpers: pathExists, isDirectory, isFile, ensureDir, moveFile, getFileSize, getFileTimestamps
Name Utilsutils/name-utils.tsDuplicate filename resolution: appends (1), (2), etc. until unique
Errorserrors/organizer-error.tsTyped error classes: invalidSession, bridgeUnavailable, folderAccessDenied, pathTraversalRejected, fileAlreadyExists, folderCreationFailed, fileMoveFailed, fileLocked
Configconfig/defaults.tsPort (43125), WebSocket path (/events), CORS origin (*), max file size (10GB)

πŸ—ƒοΈ Shared Package (packages/shared)

Provides Zod schemas and TypeScript types used by both frontend and bridge:

Schema/Type Purpose
FileCategorySchema Enum: video, audio, image, document, archive, other
CATEGORY_FOLDER_NAMES Maps categories to folder names (video β†’ Videos, etc.)
FileNodeSchema File metadata: id, name, extension, paths, category, size, timestamps, status
FolderNodeSchema Recursive folder tree: id, name, path, children (folders + files), status
OrganizeOperationSchema Operation: create-folder, move-file, rename-file, skip-file
OrganizerEventSchema WebSocket event: type, jobId, timestamp, payload
ScanResultSchema Scan output: folderPath, files[], categorySummary, totalFiles
OrganizeRequestSchema Request body for /organize
ScanRequestSchema Request body for /scan
JobStatusSchema Job state: idle, scanning, organizing, completed, failed
JobResultSchema Final result: jobId, status, filesOrganized, foldersCreated, filesSkipped, duplicatesResolved, errors, duration

πŸ›‘οΈ Security Model

Rule Implementation
Path traversal prevention isInsideBase() β€” normalized path comparison ensures target stays inside selected folder
No overwrites resolveDuplicate() β€” appends (1), (2), etc. until unique filename found
No deletions Version 1 only moves files, never deletes
Scope validation Bridge only operates inside the selected folder tree
Hidden folder skip Scanner skips .sortforge metadata directory
CORS enabled Bridge allows all origins for local development
No network exposure Bridge binds to localhost only (port 43125)

πŸ“Š Organization Pipeline

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Select     │────▢│  Recursive  │────▢│  Classify    │────▢│  Build Plan  β”‚
β”‚  Folder     β”‚     β”‚  Scan       β”‚     β”‚  by Extensionβ”‚     β”‚  (operations)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                    β”‚
                                                                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Job        │◀────│  Emit       │◀────│  Move Files  │◀────│  Create      β”‚
β”‚  Complete   β”‚     β”‚  Events     β”‚     β”‚  (dedup)     β”‚     β”‚  Folders     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Live UI     β”‚
                    β”‚  Update      β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Execution Phases

Phase Action Events Emitted
Phase 1 Create category folders (mkdir -p) folder-created for each
Phase 2 Move files one-by-one (renameSync) file-move-started, file-move-completed (or duplicate-resolved if conflict)
Phase 3 Skip already-correct files file-skipped for each
Final Emit completion job-completed with stats

⚑ Frontend State Management

Zustand store (organizer-store.ts) manages:

State Type Description
folderPath string Currently selected folder
connected boolean Bridge health status
scanResult ScanResult | null Last scan results
jobStatus JobStatus idle β†’ scanning β†’ organizing β†’ completed/failed
jobId string | null Active job identifier
events OrganizerEvent[] All received WebSocket events
tree Map<string, FileNode> File status tracking map
error string | null Current error message
duration number Job duration in ms

πŸ“œ Scripts

CommandLocationDescription
start.batRootOne-click launcher β€” auto-installs Node.js, installs deps, starts servers, opens browser
stop.batRootKills processes on ports 43125 and 3000, stops named windows
npm run devapps/bridgeStart bridge in dev mode (tsx watch)
npm run buildapps/bridgeCompile TypeScript to dist/
npm startapps/bridgeRun compiled bridge
npm testapps/bridgeRun Jest tests
npm run typecheckapps/bridgeTypeScript type checking (no emit)
npm run devapps/webStart Next.js dev server
npm run buildapps/webProduction build
npm run lintapps/webESLint checks
npm run buildpackages/sharedCompile shared types
npm run typecheckpackages/sharedType check shared package

πŸ§ͺ Testing

PackageFrameworkTest FilesWhat's Tested
apps/bridgeJest 30 + ts-jest*.test.tsScanner, classifier, planner, executor, path utils, name utils, server
apps/webVitest + @testing-library/reactβ€”Component rendering, store logic

Running Tests

# Bridge tests
cd apps/bridge
npm test

# Bridge type check
npm run typecheck

# Web lint
cd apps/web
npm run lint

🧠 Domain Model

FileCategory

type FileCategory = "video" | "audio" | "image" | "document" | "archive" | "other";

FileNode

interface FileNode {
  id: string;              // UUID
  name: string;            // filename with extension
  extension: string;       // .mp4, .pdf, etc.
  absolutePath: string;    // full path on disk
  relativePath: string;    // path relative to scan root
  category: FileCategory;
  size: number;            // bytes
  createdAt: string;       // ISO timestamp
  modifiedAt: string;      // ISO timestamp
  status: "pending" | "moving" | "done" | "skipped" | "error";
}

FolderNode

interface FolderNode {
  id: string;
  name: string;
  relativePath: string;
  children: Array<FolderNode | FileNode>;
  status: "pending" | "creating" | "created" | "error";
}

OrganizerEvent

interface OrganizerEvent {
  type:
    | "job-started"
    | "folder-created"
    | "file-move-started"
    | "file-move-completed"
    | "file-skipped"
    | "duplicate-resolved"
    | "job-completed"
    | "job-failed";
  jobId: string;
  timestamp: number;
  payload: Record<string, unknown>;
}

JobResult

interface JobResult {
  jobId: string;
  status: JobStatus;
  filesOrganized: number;
  foldersCreated: number;
  filesSkipped: number;
  duplicatesResolved: number;
  errors: string[];
  duration: number;          // milliseconds
}

πŸ”§ Configuration

Bridge Config (config/defaults.ts)

Setting Value Description
PORT 43125 HTTP/WebSocket server port
WS_PATH /events WebSocket endpoint path
CORS_ORIGIN * CORS allowed origins
MAX_FILE_SIZE 10 GB Maximum file size limit

TypeScript Configs

Package Target Module Strict
apps/bridge ES2022 CommonJS Yes
apps/web ES2017 ESNext Yes
packages/shared ES2022 CommonJS Yes

🎨 UI Features

Feature Implementation
Dark/Light mode Tailwind dark: class variants on all components
Responsive layout Mobile-first with sm: breakpoints
Visual folder picker Drives sidebar, quick-access grid, folder list, path input
Quick Access icons Custom SVG icons per folder type (Desktop, Downloads, Documents, Pictures, Music, Videos)
Color-coded categories Blue (video), Green (audio), Purple (image), Orange (document), Red (archive), Gray (other)
Live tree animation Folders and files appear in real-time as events arrive
Activity feed Timestamped log with scroll, auto-scroll support
Loading states Spinner during scan, button state changes during organize
Error display Red alert box with error message
Back/Up navigation Folder picker supports back button, up button, home button
Path input Type any path and press Enter to navigate directly

🚧 Non-Goals (Version 1)

The following are intentionally out of scope:

  • ❌ AI categorization / face detection / scene recognition
  • ❌ Video editing / media conversion
  • ❌ Timeline / project management
  • ❌ Cloud synchronization / team collaboration
  • ❌ Metadata editing / asset tagging
  • ❌ Search engine / user accounts
  • ❌ Duplicate cleanup (files are never deleted)
  • ❌ Browser-only state as source of truth

πŸ—ΊοΈ Future Vision

Potential features for future versions:

  • βœ… Continuous folder monitoring (file watcher)
  • βœ… Custom organization rules
  • βœ… Project memory / manifest
  • βœ… Smart media classification
  • βœ… Metadata indexing
  • βœ… Asset management dashboard
  • βœ… Video editor integration
  • βœ… Cross-device synchronization
  • βœ… Plugin support

πŸ“‹ Development Rules

  1. Never mutate state outside the store or bridge command layer
  2. Never move files without validating target scope
  3. Never overwrite files silently
  4. Never rely on browser memory as the source of truth
  5. Never show fake progress when real events are available
  6. Every action must be observable in logs or UI
  7. Every module must have one responsibility

πŸ‘₯ Target Users

  • Video editors
  • Content creators
  • YouTubers
  • Freelancers
  • Social media managers
  • Photographers
  • Students
  • Anyone with messy folders

πŸ“„ License

ISC


Built with ❀️ using Next.js, Node.js, React, TypeScript, Tailwind CSS, Zustand, Express, WebSocket, and Zod.

About

SortForge is a lightweight application that automatically organizes a user's media files into a clean, structured folder hierarchy with a single click.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages