diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue
index 2a4ab75..1b6c184 100644
--- a/frontend/src/components/AppShell.vue
+++ b/frontend/src/components/AppShell.vue
@@ -428,7 +428,21 @@ async function signOut() {
-
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/NoteGrid.vue b/frontend/src/components/NoteGrid.vue
new file mode 100644
index 0000000..5d3d402
--- /dev/null
+++ b/frontend/src/components/NoteGrid.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
diff --git a/frontend/src/style.css b/frontend/src/style.css
index 46d9060..8330f34 100644
--- a/frontend/src/style.css
+++ b/frontend/src/style.css
@@ -127,6 +127,21 @@ body {
transform: scale(0.94);
}
+/* Lens cross-fade (M7). Only fires between genuinely different surfaces; the
+ * board's own lenses share a component and reflow in place instead. Out is quicker
+ * than in so the gap between them stays short — `mode="out-in"` means the two
+ * durations are additive, and anything slower starts to feel like waiting. */
+.lens-enter-active {
+ transition: opacity 150ms ease-out;
+}
+.lens-leave-active {
+ transition: opacity 100ms ease-in;
+}
+.lens-enter-from,
+.lens-leave-to {
+ opacity: 0;
+}
+
@layer components {
.icon-btn {
@apply rounded-md p-1.5 text-neutral-500 transition hover:bg-black/5 focus:outline-none
diff --git a/frontend/src/views/BoardView.vue b/frontend/src/views/BoardView.vue
index e56982e..d77b992 100644
--- a/frontend/src/views/BoardView.vue
+++ b/frontend/src/views/BoardView.vue
@@ -9,7 +9,7 @@ import { useNoteEditor } from "../composables/useNoteEditor";
import AsyncState from "../components/AsyncState.vue";
import EmptyState from "../components/EmptyState.vue";
import FilterBar from "../components/FilterBar.vue";
-import NoteCard from "../components/NoteCard.vue";
+import NoteGrid from "../components/NoteGrid.vue";
import NoteEditor from "../components/NoteEditor.vue";
import { isDesktop, sync as syncBridge } from "../desktop/bridge";
@@ -76,6 +76,9 @@ const orderedNotes = computed(() =>
isMainBoard.value ? [...pinnedNotes.value, ...otherNotes.value] : notes.items,
);
const focusedIndex = ref(-1);
+/** The focused note's id — what the grids compare against, so neither has to know
+ * its own offset within the combined keyboard-navigation order. */
+const focusedId = computed(() => orderedNotes.value[focusedIndex.value]?.id ?? null);
const inTrash = computed(() => currentView.value === "trash");
function moveFocus(delta: number) {
@@ -300,59 +303,42 @@ async function onDrop(targetId: string) {
-
-
-
+ :notes="notes.items"
+ :active-id="focusedId"
+ reorderable
+ @open="openEditor"
+ @dragstart="onDragStart"
+ @dragend="onDragEnd"
+ @drop="onDrop"
+ />
diff --git a/frontend/src/views/SearchView.vue b/frontend/src/views/SearchView.vue
index 65efebb..1bf7d16 100644
--- a/frontend/src/views/SearchView.vue
+++ b/frontend/src/views/SearchView.vue
@@ -6,7 +6,7 @@ import { useNoteList } from "../composables/useNoteList";
import { useNoteEditor } from "../composables/useNoteEditor";
import AsyncState from "../components/AsyncState.vue";
import EmptyState from "../components/EmptyState.vue";
-import NoteCard from "../components/NoteCard.vue";
+import NoteGrid from "../components/NoteGrid.vue";
import NoteEditor from "../components/NoteEditor.vue";
const route = useRoute();
@@ -38,9 +38,7 @@ watch(query, run, { immediate: true });
-
-
-
+
diff --git a/frontend/src/views/TimelineView.vue b/frontend/src/views/TimelineView.vue
index dda3078..3beccde 100644
--- a/frontend/src/views/TimelineView.vue
+++ b/frontend/src/views/TimelineView.vue
@@ -8,7 +8,7 @@ import { useNoteList } from "../composables/useNoteList";
import { useNoteEditor } from "../composables/useNoteEditor";
import AsyncState from "../components/AsyncState.vue";
import EmptyState from "../components/EmptyState.vue";
-import NoteCard from "../components/NoteCard.vue";
+import NoteGrid from "../components/NoteGrid.vue";
import NoteEditor from "../components/NoteEditor.vue";
// The "find by WHEN" recall lens: your notes grouped by when you captured them,
@@ -149,9 +149,7 @@ onMounted(load);
{{ group.label }}
-
-
-
+