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.
- package/.claude/settings.local.json +8 -0
- package/AGENTS.md +28 -0
- package/PROJECT-AGENTS.md +55 -0
- package/README.md +153 -0
- package/TODO.md +132 -0
- package/client/index.html +12 -0
- package/client/package.json +23 -0
- package/client/src/App.tsx +599 -0
- package/client/src/components/FsBrowser.tsx +210 -0
- package/client/src/components/Settings.tsx +81 -0
- package/client/src/index.css +797 -0
- package/client/src/lib/api.ts +69 -0
- package/client/src/lib/collect.ts +204 -0
- package/client/src/lib/format.ts +96 -0
- package/client/src/lib/session.ts +58 -0
- package/client/src/main.tsx +10 -0
- package/client/src/vite-env.d.ts +1 -0
- package/client/tsconfig.json +18 -0
- package/client/vite.config.ts +27 -0
- package/docs/README.md +28 -0
- package/docs/api/overview.md +65 -0
- package/docs/api/scan.md +188 -0
- package/docs/architecture.md +155 -0
- package/docs/design/invariants.md +19 -0
- package/docs/design/role-system.md +50 -0
- package/docs/development/README.md +94 -0
- package/docs/features/README.md +21 -0
- package/docs/features/compression-score.md +75 -0
- package/docs/features/exclusions.md +63 -0
- package/docs/features/session.md +64 -0
- package/package.json +37 -0
- package/server/dist/cli.d.ts +3 -0
- package/server/dist/cli.d.ts.map +1 -0
- package/server/dist/cli.js +54 -0
- package/server/dist/cli.js.map +1 -0
- package/server/dist/fs-list.d.ts +3 -0
- package/server/dist/fs-list.d.ts.map +1 -0
- package/server/dist/fs-list.js +73 -0
- package/server/dist/fs-list.js.map +1 -0
- package/server/dist/paths.d.ts +3 -0
- package/server/dist/paths.d.ts.map +1 -0
- package/server/dist/paths.js +12 -0
- package/server/dist/paths.js.map +1 -0
- package/server/dist/scan.d.ts +16 -0
- package/server/dist/scan.d.ts.map +1 -0
- package/server/dist/scan.js +158 -0
- package/server/dist/scan.js.map +1 -0
- package/server/dist/server.d.ts +6 -0
- package/server/dist/server.d.ts.map +1 -0
- package/server/dist/server.js +313 -0
- package/server/dist/server.js.map +1 -0
- package/server/package.json +22 -0
- package/server/src/cli.ts +63 -0
- package/server/src/fs-list.ts +70 -0
- package/server/src/paths.ts +6 -0
- package/server/src/scan.ts +203 -0
- package/server/src/server.ts +356 -0
- package/server/tsconfig.json +9 -0
- package/shared/package.json +10 -0
- package/shared/src/constants.ts +37 -0
- package/shared/src/index.ts +4 -0
- package/shared/src/role-guess.ts +103 -0
- package/shared/src/schema.ts +18 -0
- package/shared/src/types.ts +36 -0
- package/shared/tsconfig.json +9 -0
- package/test/cli.test.js +56 -0
- package/test/fs-list.test.js +39 -0
- package/test/role-guess.test.js +50 -0
- package/test/scan.test.js +150 -0
- package/test/server.test.js +308 -0
- package/tsconfig.base.json +14 -0
package/docs/api/scan.md
ADDED
|
@@ -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
|