feat: MVP-2 completion - search, quick add, backlinks, guided forms

## Search & Retrieval
- Improved search ranking with scoring (title match, favorites, recency)
- Highlight matches with excerpt extraction
- Fuzzy search with string-similarity
- Unified noteQuery function

## Quick Capture
- Quick Add API (POST /api/notes/quick) with type prefixes
- Quick add parser with tag extraction
- Global Quick Add UI (Ctrl+N shortcut)
- Tag autocomplete in forms

## Note Relations
- Automatic backlinks with sync on create/update/delete
- Backlinks API (GET /api/notes/[id]/backlinks)
- Related notes with scoring and reasons

## Guided Forms
- Type-specific form fields (command, snippet, decision, recipe, procedure, inventory)
- Serialization to/from markdown
- Tag suggestions based on content (GET /api/tags/suggest)

## UX by Type
- Command: Copy button for code blocks
- Snippet: Syntax highlighting with react-syntax-highlighter
- Procedure: Interactive checkboxes

## Quality
- Standardized error handling across all APIs
- Integration tests (28 tests passing)
- Unit tests for search, tags, quick-add

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 13:51:39 -03:00
parent 6694bce736
commit 8b77c7b5df
30 changed files with 6548 additions and 282 deletions
+14 -37
View File
@@ -1,41 +1,18 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { NextRequest } from 'next/server'
import { searchNotes } from '@/lib/search'
import { createErrorResponse, createSuccessResponse } from '@/lib/errors'
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const q = searchParams.get('q') || ''
const type = searchParams.get('type')
const tag = searchParams.get('tag')
const where: Record<string, unknown> = {}
if (q) {
where.OR = [
{ title: { contains: q } },
{ content: { contains: q } },
]
try {
const { searchParams } = new URL(req.url)
const q = searchParams.get('q') || ''
const type = searchParams.get('type') || undefined
const tag = searchParams.get('tag') || undefined
const notes = await searchNotes(q, { type, tag })
return createSuccessResponse(notes)
} catch (error) {
return createErrorResponse(error)
}
if (type) {
where.type = type
}
if (tag) {
where.tags = {
some: {
tag: { name: tag },
},
}
}
const notes = await prisma.note.findMany({
where,
include: { tags: { include: { tag: true } } },
orderBy: [
{ isPinned: 'desc' },
{ updatedAt: 'desc' },
],
})
return NextResponse.json(notes)
}