search is a facet on the board, not a place you go
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / Python tests (push) Successful in 16s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Successful in 37s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m3s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m13s
Desktop (Tauri) / Update manifest (push) Successful in 5s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / Python tests (push) Successful in 16s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Successful in 37s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m3s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m13s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Operator (note 2930): tags exist so you can *"filter during a search"*. The server has always been able to do that — `GET /api/notes` composes `?q=` with `?label=` and the rest into one AND-ed query. The frontend never reached it. The header search box navigated to `/search`, and that view called a DIFFERENT endpoint — `GET /api/notes/search?q=`, full text only, no facets at all. So the one screen you landed on when you searched was the one screen where you could not narrow by tag. Tag filtering lived on the board's FilterBar, which is where you weren't searching. Two search boxes, two endpoints, and only the hidden one did what tags are for. Now the header box writes `?q=` into the board's URL beside whatever labels are already there, and stays on the lens you're in — searching while looking at Trash searches Trash. The box READS from the URL rather than holding its own copy, so it stays in step with the Filters panel's Clear and with a saved view opened from the sidebar. Deleted: `SearchView.vue`, its route, `GET /api/notes/search`, `repo.notes.search` and both adapter implementations, and the `notes_search` Tauri command whose only caller was the adapter entry. FilterBar loses its own "Search text…" input — it was the same facet, hidden behind a collapsed panel, duplicating a box that is always on screen. Filters now does what its name says: narrowing. The header does searching. `core::store::search` STAYS. Android calls it through the FFI (`search_notes`) and has its own search surface — which has the same no-tag-filter gap the web just lost, and deserves the same fix on its own terms rather than as a rider here.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useRoute, useRouter, type LocationQueryRaw } from "vue-router";
|
||||
import { useSessionStore } from "../stores/session";
|
||||
import { useConfigStore } from "../stores/config";
|
||||
import { useLabelsStore } from "../stores/labels";
|
||||
@@ -177,21 +177,48 @@ function labelDot(color: string): string {
|
||||
return NOTE_SWATCH_CLASSES[color as NoteColor] ?? NOTE_SWATCH_CLASSES.default;
|
||||
}
|
||||
|
||||
// The board lenses — the routes a search can happen *within*. Searching while looking
|
||||
// at Trash should search Trash, not silently move you.
|
||||
const BOARD_ROUTES = new Set(["board", "archive", "trash", "label"]);
|
||||
|
||||
/**
|
||||
* Search is a FACET, not a destination.
|
||||
*
|
||||
* It used to navigate to a `/search` view backed by a different endpoint with no
|
||||
* facets at all — so the one screen you landed on when you searched was the one
|
||||
* screen where you could not also narrow by tag, which is precisely what tags are
|
||||
* for (note 2930). Now it writes `?q=` into the board's URL, beside any labels
|
||||
* already there, and the same AND-ed query serves both.
|
||||
*
|
||||
* Existing facets are preserved, so "filter by #grocery, then search" and the reverse
|
||||
* both work.
|
||||
*/
|
||||
function onSearch(value: string) {
|
||||
searchText.value = value;
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
const q = searchText.value.trim();
|
||||
if (q) router.push({ name: "search", query: { q } });
|
||||
else if (route.name === "search") router.push("/");
|
||||
const onBoard = BOARD_ROUTES.has(String(route.name));
|
||||
const query: LocationQueryRaw = onBoard ? { ...route.query } : {};
|
||||
if (q) query.q = q;
|
||||
else delete query.q;
|
||||
void router.push({ path: onBoard ? route.path : "/", query });
|
||||
}, 250);
|
||||
}
|
||||
|
||||
// Clear the search box when navigating to a non-search view.
|
||||
// The URL is the filter state (see notes/facets.ts), so the box READS from it rather
|
||||
// than holding its own copy — which is also what keeps it in step with the Filters
|
||||
// panel's Clear button and with a saved view opened from the sidebar.
|
||||
watch(
|
||||
() => route.query.q,
|
||||
(q) => {
|
||||
searchText.value = typeof q === "string" ? q : "";
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => route.name,
|
||||
(name) => {
|
||||
if (name !== "search") searchText.value = "";
|
||||
() => {
|
||||
drawer.value = false;
|
||||
},
|
||||
);
|
||||
@@ -200,7 +227,7 @@ watch(
|
||||
* What to call the lens currently in view.
|
||||
*
|
||||
* Keyed off the route name rather than each view declaring its own title, so the
|
||||
* label sits in one place and can't go missing (the board and search never had one)
|
||||
* label sits in one place and can't go missing (the board never had one)
|
||||
* or drift in styling (timeline and reminders each had their own h1).
|
||||
*
|
||||
* A label lens is named by the label itself — "Groceries" is what the user came
|
||||
@@ -212,8 +239,6 @@ const lensName = computed<string>(() => {
|
||||
return "Archive";
|
||||
case "trash":
|
||||
return "Trash";
|
||||
case "search":
|
||||
return "Search";
|
||||
case "timeline":
|
||||
return "Timeline";
|
||||
case "reminders":
|
||||
@@ -286,7 +311,7 @@ async function signOut() {
|
||||
page you navigated to — so it sits in the bar that never moves, beside
|
||||
the app name, and stays in one place while everything beneath it
|
||||
re-filters. Replaces the per-view <h1>s, which sat in a different spot
|
||||
in each view and were absent entirely on the board and in search. -->
|
||||
in each view and were absent entirely on the board. -->
|
||||
<span aria-live="polite" class="flex min-w-0 shrink items-center gap-2 text-sm text-neutral-400">
|
||||
<!-- The separator only makes sense next to the app name, which is itself
|
||||
hidden on narrow screens. There, the lens name simply takes the space
|
||||
@@ -511,7 +536,7 @@ async function signOut() {
|
||||
BoardView, so keying on the route would remount it — blanking the board
|
||||
and refetching, which is precisely the page-change feeling this is meant
|
||||
to remove. Unkeyed, Vue only transitions when the component TYPE changes
|
||||
(board ↔ search ↔ timeline), and moving between the board's own
|
||||
(board ↔ timeline ↔ reminders), and moving between the board's own
|
||||
lenses stays an in-place reflow that NoteGrid animates. -->
|
||||
<main id="main" tabindex="-1" class="min-w-0 flex-1 focus:outline-none">
|
||||
<RouterView v-slot="{ Component }">
|
||||
|
||||
Reference in New Issue
Block a user