ci: dev version includes UTC time (dev-YYYYMMDD-HHmm)
Build & Push Docker Image / build (push) Successful in 2m36s

Phase 1: browse power-up
- faceted filters: tags + features (array containment) and min rating
  on listTools; new ?tags=?features=?minRating= URL params with chips
- global Cmd+K command palette (cmdk) with tool search + navigation
- mini usefulness/usability bars on grid cards, wide cards and table rows
This commit is contained in:
opencode
2026-08-02 11:51:25 +02:00
parent 461eca9a0a
commit 0c27982098
14 changed files with 504 additions and 29 deletions
+14 -1
View File
@@ -52,7 +52,10 @@ router.get("/tools", async (req, res): Promise<void> => {
res.status(400).json({ error: parsed.error.message });
return;
}
const { category, search, sort } = parsed.data;
const { category, search, sort, tags, features, minRating } = parsed.data;
const tagList = (tags ?? "").split(",").map((t) => t.trim()).filter(Boolean);
const featureList = (features ?? "").split(",").map((f) => f.trim()).filter(Boolean);
let query = db.select().from(toolsTable).where(isNull(toolsTable.deletedAt)).$dynamic();
if (category) {
@@ -62,6 +65,12 @@ router.get("/tools", async (req, res): Promise<void> => {
const escaped = search.replace(/[%_\\]/g, (m) => `\\${m}`);
query = query.where(sql`${toolsTable.name} ilike ${`%${escaped}%`} escape '\\'`);
}
if (tagList.length > 0) {
query = query.where(sql`${toolsTable.tags} @> ARRAY[${sql.join(tagList.map((t) => sql`${t}`), sql`, `)}]::text[]`);
}
if (featureList.length > 0) {
query = query.where(sql`${toolsTable.features} @> ARRAY[${sql.join(featureList.map((f) => sql`${f}`), sql`, `)}]::text[]`);
}
const tools = await query.orderBy(desc(toolsTable.createdAt));
@@ -95,6 +104,10 @@ router.get("/tools", async (req, res): Promise<void> => {
result = result.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
}
if (minRating != null) {
result = result.filter((t) => (t.avgCombined ?? 0) >= minRating);
}
res.json(result);
});