M2 search: Postgres FTS backend + top search bar + results
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 8s
CI & Build / Build & push image (push) Successful in 31s

- Migration 0005: generated tsvector column (title A + body B) + GIN index on
  notes; GET /api/notes/search?q= (websearch_to_tsquery, ts_rank, ACL-scoped,
  excludes trash), labels merged into results.
- Persistent AppShell layout (parent route + <RouterView> children) so the new
  top search box keeps focus across board/search/label navigation.
- SearchView (debounced live search from the shell → /search?q=, results masonry,
  no-match empty state); BoardView/SearchView render inside the shared shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-19 22:03:19 -04:00
co-authored by Claude Opus 4.8
parent 2046600a95
commit ffc008bf4d
7 changed files with 216 additions and 67 deletions
+9 -22
View File
@@ -6,28 +6,17 @@ const router = createRouter({
history: createWebHistory(),
routes: [
{
// Persistent authed shell (sidebar + top bar + search); children render in it.
path: "/",
name: "board",
component: () => import("../views/BoardView.vue"),
meta: { requiresAuth: true },
},
{
path: "/archive",
name: "archive",
component: () => import("../views/BoardView.vue"),
meta: { requiresAuth: true },
},
{
path: "/trash",
name: "trash",
component: () => import("../views/BoardView.vue"),
meta: { requiresAuth: true },
},
{
path: "/label/:id",
name: "label",
component: () => import("../views/BoardView.vue"),
component: () => import("../components/AppShell.vue"),
meta: { requiresAuth: true },
children: [
{ path: "", name: "board", component: () => import("../views/BoardView.vue") },
{ path: "archive", name: "archive", component: () => import("../views/BoardView.vue") },
{ path: "trash", name: "trash", component: () => import("../views/BoardView.vue") },
{ path: "label/:id", name: "label", component: () => import("../views/BoardView.vue") },
{ path: "search", name: "search", component: () => import("../views/SearchView.vue") },
],
},
{
path: "/settings",
@@ -63,8 +52,6 @@ router.beforeEach(async (to) => {
if (to.meta.requiresAdmin && !session.user?.is_admin) {
return { name: "board" };
}
// Registration closed: keep people out of the register screen (the very first
// account is still creatable because allow_registration defaults to true).
if (to.name === "register" && !config.allowRegistration) {
return { name: "login" };
}