repoimage 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/AGENTS.md +28 -0
  3. package/PROJECT-AGENTS.md +55 -0
  4. package/README.md +153 -0
  5. package/TODO.md +132 -0
  6. package/client/index.html +12 -0
  7. package/client/package.json +23 -0
  8. package/client/src/App.tsx +599 -0
  9. package/client/src/components/FsBrowser.tsx +210 -0
  10. package/client/src/components/Settings.tsx +81 -0
  11. package/client/src/index.css +797 -0
  12. package/client/src/lib/api.ts +69 -0
  13. package/client/src/lib/collect.ts +204 -0
  14. package/client/src/lib/format.ts +96 -0
  15. package/client/src/lib/session.ts +58 -0
  16. package/client/src/main.tsx +10 -0
  17. package/client/src/vite-env.d.ts +1 -0
  18. package/client/tsconfig.json +18 -0
  19. package/client/vite.config.ts +27 -0
  20. package/docs/README.md +28 -0
  21. package/docs/api/overview.md +65 -0
  22. package/docs/api/scan.md +188 -0
  23. package/docs/architecture.md +155 -0
  24. package/docs/design/invariants.md +19 -0
  25. package/docs/design/role-system.md +50 -0
  26. package/docs/development/README.md +94 -0
  27. package/docs/features/README.md +21 -0
  28. package/docs/features/compression-score.md +75 -0
  29. package/docs/features/exclusions.md +63 -0
  30. package/docs/features/session.md +64 -0
  31. package/package.json +37 -0
  32. package/server/dist/cli.d.ts +3 -0
  33. package/server/dist/cli.d.ts.map +1 -0
  34. package/server/dist/cli.js +54 -0
  35. package/server/dist/cli.js.map +1 -0
  36. package/server/dist/fs-list.d.ts +3 -0
  37. package/server/dist/fs-list.d.ts.map +1 -0
  38. package/server/dist/fs-list.js +73 -0
  39. package/server/dist/fs-list.js.map +1 -0
  40. package/server/dist/paths.d.ts +3 -0
  41. package/server/dist/paths.d.ts.map +1 -0
  42. package/server/dist/paths.js +12 -0
  43. package/server/dist/paths.js.map +1 -0
  44. package/server/dist/scan.d.ts +16 -0
  45. package/server/dist/scan.d.ts.map +1 -0
  46. package/server/dist/scan.js +158 -0
  47. package/server/dist/scan.js.map +1 -0
  48. package/server/dist/server.d.ts +6 -0
  49. package/server/dist/server.d.ts.map +1 -0
  50. package/server/dist/server.js +313 -0
  51. package/server/dist/server.js.map +1 -0
  52. package/server/package.json +22 -0
  53. package/server/src/cli.ts +63 -0
  54. package/server/src/fs-list.ts +70 -0
  55. package/server/src/paths.ts +6 -0
  56. package/server/src/scan.ts +203 -0
  57. package/server/src/server.ts +356 -0
  58. package/server/tsconfig.json +9 -0
  59. package/shared/package.json +10 -0
  60. package/shared/src/constants.ts +37 -0
  61. package/shared/src/index.ts +4 -0
  62. package/shared/src/role-guess.ts +103 -0
  63. package/shared/src/schema.ts +18 -0
  64. package/shared/src/types.ts +36 -0
  65. package/shared/tsconfig.json +9 -0
  66. package/test/cli.test.js +56 -0
  67. package/test/fs-list.test.js +39 -0
  68. package/test/role-guess.test.js +50 -0
  69. package/test/scan.test.js +150 -0
  70. package/test/server.test.js +308 -0
  71. package/tsconfig.base.json +14 -0
@@ -0,0 +1,188 @@
1
+ # POST /api/scan
2
+
3
+ Scan one or more directories for images. Returns an array of image rows with dimensions, compression estimates, and role guesses.
4
+
5
+ ## Request
6
+
7
+ ```json
8
+ {
9
+ "folder": "/absolute/path/to/project",
10
+ "folders": ["/path/1", "/path/2"],
11
+ "sortByScore": false,
12
+ "excludeCommonFolders": false
13
+ }
14
+ ```
15
+
16
+ ### Parameters
17
+
18
+ | Field | Type | Required | Default | Notes |
19
+ |-------|------|----------|---------|-------|
20
+ | `folder` | string | No* | — | Single directory path (alternative to `folders`) |
21
+ | `folders` | array of strings | No* | — | Multiple directory paths. Either `folder` or `folders` must be provided. |
22
+ | `sortByScore` | boolean | No | `false` | Sort results by compression score (worst first) |
23
+ | `excludeCommonFolders` | boolean | No | `false` | Skip scanning `node_modules`, `dist`, `.git`, etc. |
24
+
25
+ *Either `folder` or `folders` is required; providing both is allowed (they are merged).
26
+
27
+ ### Path Requirements
28
+
29
+ - Paths must be absolute
30
+ - Paths are resolved and normalized
31
+ - Path traversal (`..`) is rejected
32
+ - Paths outside the scanned root are rejected
33
+
34
+ ## Response (200 OK)
35
+
36
+ ```json
37
+ {
38
+ "images": [
39
+ {
40
+ "path": "assets/hero.jpg",
41
+ "size": 245000,
42
+ "width": 1600,
43
+ "height": 900,
44
+ "uncompressedSize": 4320000,
45
+ "compressionRatio": "4.1",
46
+ "role": null,
47
+ "roleGuess": "hero",
48
+ "roleGuessReason": "path suggests hero or banner"
49
+ }
50
+ ],
51
+ "issues": []
52
+ }
53
+ ```
54
+
55
+ ### Response Fields
56
+
57
+ | Field | Type | Notes |
58
+ |-------|------|-------|
59
+ | `path` | string | Relative path from scan root (using `/` separators) |
60
+ | `size` | number | File size in bytes |
61
+ | `width` | number or null | Image width in pixels (null if unreadable) |
62
+ | `height` | number or null | Image height in pixels (null if unreadable) |
63
+ | `uncompressedSize` | number or null | Estimated uncompressed size (null for SVG or missing dimensions) |
64
+ | `compressionRatio` | string or null | 0–10 score as string (null for SVG or missing dimensions) |
65
+ | `role` | null | Always `null` (confirmed roles come from `.repoimage.json` when implemented) |
66
+ | `roleGuess` | string | One of: `hero`, `content`, `thumbnail`, `unknown` |
67
+ | `roleGuessReason` | string (optional) | Human-readable explanation of the guess (omitted if `unknown`) |
68
+
69
+ ### Issues
70
+
71
+ The `issues` array reports problems during scanning:
72
+
73
+ ```json
74
+ {
75
+ "code": "MISSING_FOLDER",
76
+ "path": "/path/to/folder",
77
+ "message": "Folder does not exist: /path/to/folder"
78
+ }
79
+ ```
80
+
81
+ Common issue codes:
82
+ - `MISSING_FOLDER` — Directory does not exist
83
+ - `DIR_READ_ERROR` — Error reading directory contents
84
+
85
+ ## Error Responses
86
+
87
+ ### 400 Bad Request
88
+
89
+ ```json
90
+ {
91
+ "error": "Provide a project root: non-empty string \"folder\" or \"folders\" array."
92
+ }
93
+ ```
94
+
95
+ Missing or empty folder list.
96
+
97
+ ```json
98
+ {
99
+ "error": "Invalid JSON body",
100
+ "detail": "error message"
101
+ }
102
+ ```
103
+
104
+ Malformed JSON in request body.
105
+
106
+ ### 405 Method Not Allowed
107
+
108
+ Only POST is accepted.
109
+
110
+ ## Examples
111
+
112
+ ### Single folder scan
113
+
114
+ ```bash
115
+ curl -X POST http://127.0.0.1:3847/api/scan \
116
+ -H "Content-Type: application/json" \
117
+ -d '{"folder": "/home/user/my-project"}'
118
+ ```
119
+
120
+ ### Multiple folders, excluding common paths
121
+
122
+ ```bash
123
+ curl -X POST http://127.0.0.1:3847/api/scan \
124
+ -H "Content-Type: application/json" \
125
+ -d '{
126
+ "folders": ["/path/1", "/path/2"],
127
+ "excludeCommonFolders": true,
128
+ "sortByScore": true
129
+ }'
130
+ ```
131
+
132
+ ### From Node.js
133
+
134
+ ```javascript
135
+ const response = await fetch('http://127.0.0.1:3847/api/scan', {
136
+ method: 'POST',
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({
139
+ folder: '/home/user/my-project',
140
+ excludeCommonFolders: true
141
+ })
142
+ });
143
+ const data = await response.json();
144
+ console.log(data.images);
145
+ ```
146
+
147
+ ## Compression Score
148
+
149
+ The `compressionRatio` field is a 0–10 score where higher indicates worse compression (larger file relative to uncompressed estimate):
150
+
151
+ - **Score 0–2** — Excellent compression
152
+ - **Score 2–5** — Good compression
153
+ - **Score 5–8** — Fair compression (review)
154
+ - **Score 8–10** — Poor compression (candidates for optimization)
155
+
156
+ Calculation:
157
+ ```
158
+ uncompressed_size = width × height × bytes_per_pixel
159
+ (PNG/GIF: 4 bytes/pixel; others: 3 bytes/pixel)
160
+ compression_ratio = -log2(file_size / uncompressed_size)
161
+ (clamped to [0, 10])
162
+ ```
163
+
164
+ SVG files do not receive a score.
165
+
166
+ ## Exclude Common Folders
167
+
168
+ When `excludeCommonFolders: true`, the following directory names are skipped at walk time:
169
+
170
+ - `node_modules`, `.next`, `dist`, `.git`, `build`, `.bundle`
171
+ - `.cache`, `target`, `vendor`, `.idea`, `.vscode`
172
+ - `__pycache__`, `.pytest_cache`, `.tox`
173
+ - `venv`, `.venv`, `env`, `.env`
174
+
175
+ Exclusion is by directory name only (e.g., any `dist/` folder, anywhere in the tree).
176
+
177
+ ## Future: Role Confirmation
178
+
179
+ When `.repoimage.json` is implemented, confirmed roles will be merged into the response:
180
+
181
+ ```json
182
+ {
183
+ "path": "assets/hero.jpg",
184
+ "role": "hero", // ← confirmed from sidecar
185
+ "roleGuess": "hero",
186
+ "roleGuessReason": "..."
187
+ }
188
+ ```
@@ -0,0 +1,155 @@
1
+ # Architecture
2
+
3
+ RepoImage is organized as an npm workspace with three independent packages: `shared`, `server`, and `client`.
4
+
5
+ ## Workspace Layout
6
+
7
+ ```
8
+ repoimage/
9
+ ├── package.json # Root workspace config
10
+ ├── shared/ # Shared types and utilities
11
+ │ ├── src/
12
+ │ │ ├── constants.ts # Image extensions, exclude paths
13
+ │ │ ├── types.ts # ImageRow, ScanIssue, etc.
14
+ │ │ ├── schema.ts # Config file schema, validation
15
+ │ │ ├── role-guess.ts # Role guessing heuristics
16
+ │ │ └── index.ts # Public API
17
+ │ └── dist/ # Compiled output (tsc)
18
+ ├── server/ # HTTP API and CLI
19
+ │ ├── src/
20
+ │ │ ├── scan.ts # Image discovery and enrichment
21
+ │ │ ├── fs-list.ts # Filesystem browsing
22
+ │ │ ├── server.ts # HTTP server and route handlers
23
+ │ │ ├── cli.ts # CLI entry point
24
+ │ │ └── paths.ts # Path utilities
25
+ │ └── dist/ # Compiled output (tsc)
26
+ ├── client/ # React GUI
27
+ │ ├── src/
28
+ │ │ ├── App.tsx # Main React component
29
+ │ │ ├── components/ # UI components
30
+ │ │ └── ...
31
+ │ └── dist/ # Built output (vite build)
32
+ ├── test/ # Test suite (Node test runner)
33
+ └── docs/ # This documentation
34
+ ```
35
+
36
+ ## Data Flow
37
+
38
+ ### Scan Flow
39
+
40
+ ```
41
+ User → /api/scan (HTTP POST)
42
+
43
+ server/scan.ts: findImagesInFolders()
44
+
45
+ walkDirectory() — recurse, skip excluded paths if enabled
46
+
47
+ enrichImageEntry() — add dimensions, compression, roleGuess
48
+
49
+ Images array → HTTP response
50
+
51
+ Client displays in table
52
+ ```
53
+
54
+ ### Role Confirmation Flow
55
+
56
+ ```
57
+ User clicks row, opens popover
58
+
59
+ Client shows:
60
+ - roleGuess (read-only, from scan)
61
+ - role selector (for confirmed role)
62
+
63
+ User selects role and clicks Save
64
+
65
+ POST /api/repoimage (save endpoint, when implemented)
66
+
67
+ Server writes .repoimage.json (read-modify-write)
68
+
69
+ Next scan returns both roleGuess and role
70
+ ```
71
+
72
+ ## Package Responsibilities
73
+
74
+ ### shared/
75
+
76
+ **Pure TypeScript types and constants** — no Node.js dependencies, usable by both server and client.
77
+
78
+ - `IMAGE_EXTENSIONS` — supported file types (`.jpg`, `.png`, etc.)
79
+ - `COMMON_EXCLUDE_PATHS` — default folder exclusions (`node_modules`, `dist`, etc.)
80
+ - `ImageRow` — scan result row structure
81
+ - `guessRole()` — heuristic role suggestion from path and dimensions
82
+ - `schema.ts` — `.repoimage.json` validation and types
83
+
84
+ ### server/
85
+
86
+ **HTTP server and CLI** — compiled with `tsc` (not bundled).
87
+
88
+ - `scan.ts` — walks directory tree, filters by excluded paths, enriches with image metadata
89
+ - `fs-list.ts` — lists directory contents (for the file picker UI)
90
+ - `server.ts` — HTTP server with routes:
91
+ - `POST /api/scan` — scan folders for images
92
+ - `POST /api/scan-entries` — enrich client-provided rows
93
+ - `GET /api/fs/home` — user's home directory
94
+ - `GET /api/fs/list` — list directory contents
95
+ - `GET /api/file` — serve image file
96
+ - `GET /` — serve SPA index.html
97
+ - `cli.ts` — command-line interface for headless scanning
98
+
99
+ ### client/
100
+
101
+ **React GUI** — built with Vite.
102
+
103
+ - Handles folder browsing and selection
104
+ - Displays scan results in a sortable, filterable table
105
+ - Manages sort/filter/score range UI
106
+ - Will eventually include role confirmation UI (popover, save button)
107
+ - Communicates with server via `/api/` endpoints
108
+
109
+ ## Development vs Production
110
+
111
+ ### Development
112
+
113
+ ```bash
114
+ npm run dev
115
+ ```
116
+
117
+ Starts:
118
+ - Server on port 3847 (watches TypeScript changes)
119
+ - Vite dev server on port 5173 (hot reload)
120
+ - Client proxies `/api` requests to server:3847
121
+
122
+ ### Production
123
+
124
+ ```bash
125
+ npm run build
126
+ npm run gui
127
+ ```
128
+
129
+ Builds all packages, then:
130
+ - Server compiles and serves `client/dist` as static files
131
+ - Single process, port 3847
132
+
133
+ ## Key Design Decisions
134
+
135
+ 1. **Vite for client only** — Fast HMR during dev; `tsc` for server keeps things simple.
136
+ 2. **npm workspaces** — Monorepo for shared code and types; single `node_modules` install.
137
+ 3. **Stateless API** — Server doesn't cache state; each request is independent. Config file is the only persistent state.
138
+ 4. **Server-side path filtering** — Exclusions applied at walk time, not post-scan, for efficiency.
139
+ 5. **Role guesses are ephemeral** — Never persisted to disk; only confirmed roles in `.repoimage.json` survive re-scans.
140
+
141
+ ## Testing
142
+
143
+ Tests run with Node's built-in test runner:
144
+
145
+ ```bash
146
+ npm test
147
+ ```
148
+
149
+ Tests cover:
150
+ - `shared/` — role guessing, constants, schema validation
151
+ - `server/` — scan logic, file listing, API endpoints
152
+ - `client/` — (helpers only, not full E2E yet)
153
+ - Integration — API + CLI + config file
154
+
155
+ See [PROJECT-AGENTS.md](../PROJECT-AGENTS.md#testing-strategy) for testing guidelines.
@@ -0,0 +1,19 @@
1
+ # Global Invariants
2
+
3
+ These rules must always be true in the system. Violations are bugs.
4
+
5
+ 1. **`roleGuess` never written to `.repoimage.json`** — Only manually confirmed roles persist to disk. Guesses are computed fresh on each scan.
6
+
7
+ 2. **`images` contains only manually confirmed paths** — The sidecar stores only what the user has explicitly confirmed, never the full scan results.
8
+
9
+ 3. **Config `role` wins over guess in merged output** — When a path has both a confirmed role and a guess, the confirmed role is displayed/used. But `roleGuess` is still computed and returned alongside.
10
+
11
+ 4. **Re-scan updates guesses only, not confirmed `role` on disk** — Running a scan never modifies the `.repoimage.json` file. Guesses refresh; confirmed roles are untouched.
12
+
13
+ 5. **Every save sets canonical `_generator` and `version`** — The `_generator` field is the source of truth for which tool created/modified the file. Version is updated on every write.
14
+
15
+ 6. **Reject `..` and paths outside project root** — Path traversal and escapes are forbidden. All paths are relative to the scanned project root.
16
+
17
+ 7. **Persisted roles ∈ `hero` | `content` | `thumbnail` | `unknown`** — Only valid role values can be stored. Invalid values are rejected at save time.
18
+
19
+ 8. **No one-click guess → confirm in UI** — Users must explicitly choose a role and confirm. Accepting a guess is a deliberate action, not a shortcut.
@@ -0,0 +1,50 @@
1
+ # Image Role System Design
2
+
3
+ Design decisions for the image role system and `.repoimage.json` sidecar file.
4
+
5
+ ## Overview
6
+
7
+ Automatic **suggested** roles are computed at scan time. Users can confirm roles, which are persisted in `.repoimage.json`. Guesses are never saved to disk. The UI treats the sidecar as a **partial catalog** (N of M confirmed images).
8
+
9
+ ## Design Decisions
10
+
11
+ | Topic | Decision |
12
+ |-------|----------|
13
+ | Config path | `<projectRoot>/.repoimage.json` only |
14
+ | Role values | `hero` \| `content` \| `thumbnail` \| `unknown` |
15
+ | Persistence | Only manually confirmed images in `images` array |
16
+ | `_generator` | Canonical string on every write; insert or replace if missing/outdated |
17
+ | Confirm UX | Popover with role picker + Save/Cancel; **no** one-click accept suggestion |
18
+ | Banner | No file → guesses only; file exists → "N of M confirmed" partial catalog |
19
+
20
+ ## Config File Format
21
+
22
+ ```json
23
+ {
24
+ "version": 1,
25
+ "_generator": "Roles confirmed with RepoImage — <canonical repo URL>",
26
+ "images": {
27
+ "assets/hero.jpg": { "role": "hero" },
28
+ "public/banner.png": { "role": "hero" },
29
+ "src/logo.svg": { "role": "content" }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Key Constraints
35
+
36
+ 1. `roleGuess` is **never** persisted to disk
37
+ 2. `images` contains **only** manually confirmed paths (never bulk guesses)
38
+ 3. Config `role` **wins over** guess in merged output; `roleGuess` still computed on each scan
39
+ 4. Re-scan **never overwrites** confirmed roles on disk
40
+ 5. Every save **updates** `_generator` and `version`
41
+ 6. Reject `..` and paths outside project root
42
+ 7. Persisted roles must be valid: `hero` | `content` | `thumbnail` | `unknown`
43
+ 8. No one-click guess → confirm shortcut in UI
44
+
45
+ ## Related Sections
46
+
47
+ - **B** — Load and merge config file
48
+ - **C** — Save confirmed roles API
49
+ - **D** — Role review UI implementation
50
+ - **E** — CLI and docs for roles
@@ -0,0 +1,94 @@
1
+ # Development Guide
2
+
3
+ Getting started with developing RepoImage.
4
+
5
+ ## Local Setup
6
+
7
+ 1. **Clone and install**
8
+ ```bash
9
+ git clone <repo-url> repoimage
10
+ cd repoimage
11
+ npm install
12
+ ```
13
+
14
+ 2. **Start development servers**
15
+ ```bash
16
+ npm run dev
17
+ ```
18
+ Opens:
19
+ - Client: http://127.0.0.1:5173
20
+ - API: http://127.0.0.1:3847
21
+ - Client proxies `/api` to server automatically
22
+
23
+ 3. **Build and test**
24
+ ```bash
25
+ npm run build
26
+ npm test
27
+ ```
28
+
29
+ ## Project Structure
30
+
31
+ See [Architecture](../architecture.md) for workspace layout and package responsibilities.
32
+
33
+ ## Making Changes
34
+
35
+ 1. **Read the requirements** — Check the TODO item and [PROJECT-AGENTS.md](../../PROJECT-AGENTS.md)
36
+ 2. **Understand invariants** — Review [design/invariants.md](../design/invariants.md) if your change affects roles, config, or paths
37
+ 3. **Write tests first** — Tests are compulsory; see testing strategy in [PROJECT-AGENTS.md](../../PROJECT-AGENTS.md)
38
+ 4. **Implement** — Add feature code in the appropriate workspace (`shared`, `server`, or `client`)
39
+ 5. **Update docs** — Document API changes, feature behavior, or architectural decisions in `docs/`
40
+ 6. **Run tests** — `npm test` must pass
41
+ 7. **Rebuild** — `npm run build` to verify TypeScript and Vite compilation
42
+
43
+ ## Key Files
44
+
45
+ - `shared/src/constants.ts` — Image extensions, exclude paths
46
+ - `server/src/scan.ts` — Core scanning and filtering logic
47
+ - `server/src/server.ts` — HTTP API routes
48
+ - `test/scan.test.js` — Scan tests (a good reference)
49
+
50
+ ## Testing
51
+
52
+ Run all tests:
53
+ ```bash
54
+ npm test
55
+ ```
56
+
57
+ Tests use Node's built-in test runner. See test files in `test/` for patterns.
58
+
59
+ **Remember:** Every change requires tests. See [PROJECT-AGENTS.md](../../PROJECT-AGENTS.md#testing-strategy) for what to test.
60
+
61
+ ## CLI
62
+
63
+ For headless scanning:
64
+
65
+ ```bash
66
+ npm start -- /path/to/project
67
+ # or after build:
68
+ node server/dist/cli.js --json /path/to/project
69
+ ```
70
+
71
+ Flags:
72
+ - `--json` — Output JSON instead of table
73
+ - `--sort-by-score` — Order by compression (worst first)
74
+
75
+ ## Production Build
76
+
77
+ ```bash
78
+ npm run build
79
+ npm run gui
80
+ ```
81
+
82
+ Serves the built client from the server on port 3847 (or `PORT` env var).
83
+
84
+ ## Troubleshooting
85
+
86
+ **Build fails** — Check TypeScript errors. Run `npm run build` separately for each workspace to isolate issues.
87
+
88
+ **Tests fail** — Review the test output and [design/invariants.md](../design/invariants.md) to ensure your change respects all rules.
89
+
90
+ **Client changes not visible** — Make sure `npm run dev` is running and the Vite HMR connection is active.
91
+
92
+ ## Next Steps
93
+
94
+ Start with [Architecture](../architecture.md), then pick a TODO item and follow the workflow above.
@@ -0,0 +1,21 @@
1
+ # Features
2
+
3
+ User-facing documentation for RepoImage features.
4
+
5
+ - **[Compression Score](compression-score.md)** — How the compression estimate is calculated
6
+ - **[Path Exclusion](exclusions.md)** — Excluding common dependency folders from scans
7
+ - **[Session Persistence](session.md)** — Browser state recovery between visits
8
+ - **[Image Roles](roles.md)** — Role system and guessing heuristics (TBD)
9
+
10
+ ## Overview
11
+
12
+ RepoImage helps developers find oversized or poorly compressed images in their projects. Key features:
13
+
14
+ 1. **Scan** — Recursively find all images in a project
15
+ 2. **Analyze** — Measure dimensions, file size, and estimate compression quality
16
+ 3. **Sort & Filter** — Focus on the worst offenders by score, size, or path
17
+ 4. **Exclude** — Hide dependency folders (node_modules, dist, etc.)
18
+ 5. **Confirm Roles** — Mark images as hero, content, thumbnail, or other (future)
19
+ 6. **Persist** — Save confirmed roles in a `.repoimage.json` sidecar (future)
20
+
21
+ More details on each feature coming soon.
@@ -0,0 +1,75 @@
1
+ # Compression Score
2
+
3
+ The **compression score** is a 0–10 scale that estimates how well an image is compressed relative to its dimensions.
4
+
5
+ ## How It's Calculated
6
+
7
+ For each image with readable dimensions:
8
+
9
+ 1. **Estimate uncompressed size**
10
+ ```
11
+ uncompressed_size = width × height × bytes_per_pixel
12
+ ```
13
+ - PNG/GIF: 4 bytes/pixel (RGBA)
14
+ - JPG/other: 3 bytes/pixel (RGB)
15
+
16
+ 2. **Calculate compression ratio**
17
+ ```
18
+ ratio = file_size / uncompressed_size
19
+ compression_score = -log2(ratio), clamped to [0, 10]
20
+ ```
21
+
22
+ 3. **Interpretation**
23
+ - Lower score = better compressed
24
+ - Higher score = larger file relative to dimensions (candidate for optimization)
25
+
26
+ ## Score Scale
27
+
28
+ | Score | Interpretation | Action |
29
+ |-------|---|---|
30
+ | 0–2 | Excellent compression | ✓ No action needed |
31
+ | 2–5 | Good compression | ✓ Acceptable |
32
+ | 5–8 | Fair compression | ⚠ Review and optimize if large |
33
+ | 8–10 | Poor compression | 🔴 High priority for optimization |
34
+
35
+ ## Examples
36
+
37
+ ### Well-compressed JPG (score 2.1)
38
+
39
+ ```
40
+ Dimensions: 1600 × 900
41
+ File size: ~245 KB
42
+ Uncompressed estimate: 1600 × 900 × 3 = 4.32 MB
43
+ Ratio: 245K / 4.32M ≈ 0.057
44
+ Score: -log2(0.057) ≈ 4.1 (good)
45
+ ```
46
+
47
+ ### Uncompressed PNG (score 9.8)
48
+
49
+ ```
50
+ Dimensions: 800 × 600
51
+ File size: ~1.4 MB
52
+ Uncompressed estimate: 800 × 600 × 4 = 1.92 MB
53
+ Ratio: 1.4M / 1.92M ≈ 0.73
54
+ Score: -log2(0.73) ≈ 0.45... wait, that's low!
55
+ ```
56
+
57
+ Actually, scores near 0 mean the file is almost uncompressed. A higher ratio (closer to 1) produces a lower score.
58
+
59
+ ## Limitations
60
+
61
+ - **No score for SVG** — Vector images don't have pixel dimensions; scoring them doesn't make sense.
62
+ - **Estimates only** — The uncompressed size is an estimate based on typical RGBA/RGB. Actual compression depends on pixel data complexity.
63
+ - **No context** — A large, well-compressed background image might have a high score but be perfectly acceptable. Use the score as a starting point, not a rule.
64
+
65
+ ## Tips for Optimization
66
+
67
+ 1. Use appropriate formats: WebP or AVIF for photos, PNG for graphics, SVG for icons
68
+ 2. Resize to actual display dimensions (don't serve 4000px wide images for 800px displays)
69
+ 3. Use modern lossy compression (JPEG 2000, WebP, AVIF)
70
+ 4. Optimize metadata (remove EXIF, thumbnails)
71
+ 5. Consider responsive images (`srcset`) for different devices
72
+
73
+ ## See Also
74
+
75
+ - [API Reference](../api/scan.md#compression-score) — Technical details of the calculation
@@ -0,0 +1,63 @@
1
+ # Path Exclusion
2
+
3
+ The **Exclude common folders** toggle hides images in dependency and build output directories, helping you focus on asset images.
4
+
5
+ ## What Gets Excluded
6
+
7
+ When enabled, the following directory names are skipped during scanning:
8
+
9
+ - **Package managers:** `node_modules`, `vendor`
10
+ - **Build outputs:** `dist`, `build`, `.next`, `.bundle`
11
+ - **Version control:** `.git`
12
+ - **Caches:** `.cache`, `target`, `.idea`, `.vscode`
13
+ - **Python:** `__pycache__`, `.pytest_cache`, `.tox`, `venv`, `.venv`, `env`, `.env`
14
+
15
+ Exclusion is by directory *name* only, so any `dist/` folder anywhere in the tree is skipped.
16
+
17
+ ## Usage
18
+
19
+ 1. Click the ⚙️ (gear) icon in the top-right of the header
20
+ 2. Check "Exclude common folders"
21
+ 3. Run a new scan
22
+
23
+ The setting is remembered across browser sessions.
24
+
25
+ ## Behavior
26
+
27
+ - **Scan time:** Excluded directories are skipped during the walk, improving performance for large projects
28
+ - **Results:** Only images outside excluded paths are displayed
29
+ - **Counts and totals:** Updated to reflect filtered results
30
+ - **Re-scanning:** Toggling the preference re-runs the scan automatically
31
+
32
+ ## Example
33
+
34
+ Scanning `/myproject` with exclusion **disabled**:
35
+
36
+ ```
37
+ myproject/
38
+ ├── node_modules/
39
+ │ └── some-lib/
40
+ │ └── image.png ← included
41
+ ├── src/
42
+ │ └── logo.png ← included
43
+ └── dist/
44
+ └── banner.jpg ← included
45
+ Total: 3 images
46
+ ```
47
+
48
+ Scanning the same project with exclusion **enabled**:
49
+
50
+ ```
51
+ myproject/
52
+ ├── node_modules/ ← skipped
53
+ ├── src/
54
+ │ └── logo.png ← included
55
+ └── dist/ ← skipped
56
+ Total: 1 image
57
+ ```
58
+
59
+ ## Notes
60
+
61
+ - Exclusion is applied **at scan time**, not as a post-filter, so large projects scan faster
62
+ - The list of excluded paths is built into the app and cannot be customized yet (future feature)
63
+ - If you need to include a folder named `dist` or `node_modules`, use the **browser file picker** instead of entering a path directly