sync: feed pane UX from private repo

- Strip kanban, grid, and saved views from ViewsOverlay (1103→631 lines)
- Add sort order dropdown (Updated/Edges/Created) with localStorage persistence
- Polish filter button, dropdown, and filter chips
- Switch to server-side dimension filtering with AND logic (dimensionsMatch=all)
- Add description preview and edge count to node cards
- Add 'created' sort option and dimensionsMatch param to types, service, API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2026-02-15 13:19:42 +11:00
co-authored by Claude Opus 4.6
parent 86f43c9975
commit 2940e142c8
4 changed files with 251 additions and 695 deletions
+16 -2
View File
@@ -25,12 +25,26 @@ export async function GET(request: NextRequest) {
filters.dimensions = dimensionsParam.split(',').map(dim => dim.trim()).filter(Boolean); filters.dimensions = dimensionsParam.split(',').map(dim => dim.trim()).filter(Boolean);
} }
// Handle sortBy parameter // Handle dimensionsMatch parameter (any|all)
const dimensionsMatchParam = searchParams.get('dimensionsMatch');
if (dimensionsMatchParam === 'all') {
filters.dimensionsMatch = 'all';
}
// Handle sortBy parameter (sortBy=edges|updated|created)
const sortByParam = searchParams.get('sortBy'); const sortByParam = searchParams.get('sortBy');
if (sortByParam === 'edges' || sortByParam === 'updated') { if (sortByParam === 'edges' || sortByParam === 'updated' || sortByParam === 'created') {
filters.sortBy = sortByParam; filters.sortBy = sortByParam;
} }
// Also support sort=created_at|updated_at with order=asc|desc (used by feed)
const sortParam = searchParams.get('sort');
if (sortParam === 'created_at') {
filters.sortBy = 'created';
} else if (sortParam === 'updated_at') {
filters.sortBy = 'updated';
}
const nodes = await nodeService.getNodes(filters); const nodes = await nodeService.getNodes(filters);
return NextResponse.json({ return NextResponse.json({
File diff suppressed because it is too large Load Diff
+20 -7
View File
@@ -10,7 +10,7 @@ export class NodeService {
// PostgreSQL path removed in SQLite-only consolidation // PostgreSQL path removed in SQLite-only consolidation
private async getNodesSQLite(filters: NodeFilters = {}): Promise<Node[]> { private async getNodesSQLite(filters: NodeFilters = {}): Promise<Node[]> {
const { dimensions, search, limit = 100, offset = 0, sortBy } = filters; const { dimensions, search, limit = 100, offset = 0, sortBy, dimensionsMatch = 'any' } = filters;
const sqlite = getSQLiteClient(); const sqlite = getSQLiteClient();
// Use nodes_v view for array-like dimensions behavior (exclude embedding BLOB for performance) // Use nodes_v view for array-like dimensions behavior (exclude embedding BLOB for performance)
@@ -28,12 +28,23 @@ export class NodeService {
// Filter by dimensions (SQLite JOIN with node_dimensions) // Filter by dimensions (SQLite JOIN with node_dimensions)
if (dimensions && dimensions.length > 0) { if (dimensions && dimensions.length > 0) {
query += ` AND EXISTS ( if (dimensionsMatch === 'all' && dimensions.length > 1) {
SELECT 1 FROM node_dimensions nd // AND logic: node must have ALL specified dimensions
WHERE nd.node_id = n.id query += ` AND (
AND nd.dimension IN (${dimensions.map(() => '?').join(',')}) SELECT COUNT(DISTINCT nd.dimension) FROM node_dimensions nd
)`; WHERE nd.node_id = n.id
params.push(...dimensions); AND nd.dimension IN (${dimensions.map(() => '?').join(',')})
) = ?`;
params.push(...dimensions, dimensions.length);
} else {
// OR logic: node must have at least one of the specified dimensions
query += ` AND EXISTS (
SELECT 1 FROM node_dimensions nd
WHERE nd.node_id = n.id
AND nd.dimension IN (${dimensions.map(() => '?').join(',')})
)`;
params.push(...dimensions);
}
} }
// Text search in title, description, and content (SQLite LIKE with COLLATE NOCASE) // Text search in title, description, and content (SQLite LIKE with COLLATE NOCASE)
@@ -62,6 +73,8 @@ export class NodeService {
} else if (sortBy === 'edges') { } else if (sortBy === 'edges') {
// Sort by edge count (most connected first) // Sort by edge count (most connected first)
query += ' ORDER BY edge_count DESC, n.updated_at DESC'; query += ' ORDER BY edge_count DESC, n.updated_at DESC';
} else if (sortBy === 'created') {
query += ' ORDER BY n.created_at DESC';
} else { } else {
query += ' ORDER BY n.updated_at DESC'; query += ' ORDER BY n.updated_at DESC';
} }
+2 -1
View File
@@ -70,7 +70,8 @@ export interface NodeFilters {
search?: string; // Text search in title/content search?: string; // Text search in title/content
limit?: number; limit?: number;
offset?: number; offset?: number;
sortBy?: 'updated' | 'edges'; // Sort by updated_at or edge count sortBy?: 'updated' | 'edges' | 'created'; // Sort by updated_at, edge count, or created_at
dimensionsMatch?: 'any' | 'all'; // 'any' = OR (default), 'all' = AND
} }
export interface ChunkData { export interface ChunkData {