Feature/ai post suggestions (#40)

* feat: first cut on ai suggestion system for title and summary

* feat: completion of titling/excerpt/slug-suggestion AI quick action

* feat: feeds use existing excerpts. also documentation.

---------

Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
Georg Bauer
2026-03-07 09:54:13 +01:00
committed by GitHub
parent 72b21ddba7
commit 9871cb827f
30 changed files with 1270 additions and 245 deletions

View File

@@ -1028,6 +1028,40 @@ Original content`);
expect(result?.slug).toBe('new-title');
});
it('should honor explicit slug when title and slug both change on a never-published draft', async () => {
const created = await postEngine.createPost({ title: 'Original Title' });
vi.mocked(mockLocalDb.select).mockImplementation(() => {
const chain = createSelectChain();
chain.where = vi.fn().mockReturnValue({
...chain,
get: vi.fn().mockResolvedValue({
id: created.id,
projectId: created.projectId,
title: created.title,
slug: created.slug,
status: 'draft',
content: created.content || '',
filePath: '',
tags: '[]',
categories: '[]',
createdAt: created.createdAt,
updatedAt: created.updatedAt,
publishedAt: null,
}),
});
return chain;
});
const result = await postEngine.updatePost(created.id, {
title: 'New Title',
slug: 'custom-ai-slug',
});
expect(result).not.toBeNull();
expect(result?.slug).toBe('custom-ai-slug');
});
it('should NOT auto-update slug when title changes on a previously published post', async () => {
const created = await postEngine.createPost({ title: 'Published Post' });
@@ -1059,6 +1093,37 @@ Original content`);
expect(result?.slug).toBe('published-post'); // slug preserved
});
it('should ignore explicit slug changes on a previously published post', async () => {
const created = await postEngine.createPost({ title: 'Published Post' });
vi.mocked(mockLocalDb.select).mockImplementation(() => {
const chain = createSelectChain();
chain.where = vi.fn().mockReturnValue({
...chain,
get: vi.fn().mockResolvedValue({
id: created.id,
projectId: created.projectId,
title: created.title,
slug: created.slug,
status: 'draft',
content: created.content || '',
filePath: '',
tags: '[]',
categories: '[]',
createdAt: created.createdAt,
updatedAt: created.updatedAt,
publishedAt: new Date('2025-01-01'),
}),
});
return chain;
});
const result = await postEngine.updatePost(created.id, { slug: 'new-slug' });
expect(result).not.toBeNull();
expect(result?.slug).toBe('published-post');
});
it('should allow empty title and use untitled as slug base', async () => {
const created = await postEngine.createPost({ title: '' });
expect(created.title).toBe('');