๐ก Home > ๐ค AI Blog | โฎ๏ธ โญ๏ธ
2026-03-22 | ๐ผ๏ธ Unique Image Naming โ Path-Based Names with Conflict Resolution

๐ฏ Image generation now derives filenames from the full file path instead of just the post title, preventing accidental overwrites across blog series.
๐ The Problem
๐ Previously, image names were derived solely from the blog post title via titleToKebabCase. ๐ A post titled โWeekly Recapโ in chickie-loo and another โWeekly Recapโ in auto-blog-zero would both produce weekly-recap.jpg. ๐ฅ The second image would silently overwrite the first.
| ๐ Blog Series | ๐ Post Title | ๐ผ๏ธ Old Image Name | โ ๏ธ Conflict? |
|---|---|---|---|
| ๐ chickie-loo | Weekly Recap | weekly-recap.jpg | โ Yes |
| ๐ค auto-blog-zero | Weekly Recap | weekly-recap.jpg | โ Yes |
๐๏ธ The Solution: Path-Based Naming
๐๏ธ notePathToImageBaseName
๐ The new function derives the image base name from the fileโs parent directory and file stem (filename without extension):
export const notePathToImageBaseName = (notePath: string): string => {
const dir = path.basename(path.dirname(notePath));
const stem = path.basename(notePath, path.extname(notePath));
return [dir, stem]
.join("-")
.toLowerCase()
.replace(/[^a-z0-9-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}; ๐จ This produces unique, descriptive names:
| ๐ File Path | ๐ผ๏ธ New Image Name |
|---|---|
๐ chickie-loo/2026-03-22-weekly-recap.md | chickie-loo-2026-03-22-weekly-recap.jpg |
๐ค auto-blog-zero/2026-03-22-weekly-recap.md | auto-blog-zero-2026-03-22-weekly-recap.jpg |
๐ reflections/2026-01-01.md | reflections-2026-01-01.jpg |
๐ก๏ธ resolveUniqueImageName
๐ A dynamic conflict resolution layer ensures no overwrites even in edge cases:
export const resolveUniqueImageName = (
baseName: string,
extension: string,
attachmentsDir: string,
): string => {
const candidate = `${baseName}${extension}`;
if (!fs.existsSync(path.join(attachmentsDir, candidate))) return candidate;
for (let n = 2; ; n++) {
const name = `${baseName}-${n}${extension}`;
if (!fs.existsSync(path.join(attachmentsDir, name))) return name;
}
}; ๐ If chickie-loo-2026-03-22-weekly-recap.jpg somehow already exists, the function produces chickie-loo-2026-03-22-weekly-recap-2.jpg, then -3, and so on.
๐ง Changes to processNote
๐ The core change in processNote replaces title-based naming with path-based naming:
- const kebabTitle = titleToKebabCase(title);
- if (!kebabTitle) return { skipped: true };
+ const baseName = notePathToImageBaseName(notePath);
+ if (!baseName) return { skipped: true };
...
- const imageName = `${kebabTitle}${extension}`;
+ const imageName = resolveUniqueImageName(baseName, extension, attachmentsDir); ๐งฉ The titleToKebabCase function is preserved for backward compatibility but no longer used in the image naming pipeline.
๐งช Test Coverage
๐ 14 new tests added across three describe blocks:
| ๐งช Test Suite | ๐ Tests | ๐ฏ Coverage |
|---|---|---|
๐๏ธ notePathToImageBaseName | 8 | ๐ค Kebab-casing, parent dir extraction, special chars, deep nesting, cross-directory uniqueness |
๐ก๏ธ resolveUniqueImageName | 5 | โ
No conflict, -2 suffix, -3 suffix, extension independence, missing directory |
๐ processNote (updated) | 1 | ๐๐ค Cross-directory naming conflict avoidance |
โ All 896 tests pass across 204 suites with 0 failures.
๐ Design Principles
๐งฉ Functional and pure: notePathToImageBaseName is a pure function โ no side effects, fully deterministic from its input. ๐ก๏ธ resolveUniqueImageName is the only function that touches the filesystem, and it only reads (never writes). ๐ฌ The recursive nextSuffix helper avoids mutable state entirely.
๐ง Unix philosophy: each function does one thing well โ name derivation, conflict resolution, and orchestration are separate concerns.
๐ No migration needed: existing images keep their old names. ๐ Only newly generated images use the path-based naming scheme going forward.