diff --git a/frontend/src/views/BoardView.vue b/frontend/src/views/BoardView.vue
index 1d56e9c..cff184b 100644
--- a/frontend/src/views/BoardView.vue
+++ b/frontend/src/views/BoardView.vue
@@ -11,6 +11,7 @@ import EmptyState from "../components/EmptyState.vue";
import FilterBar from "../components/FilterBar.vue";
import NoteCard from "../components/NoteCard.vue";
import NoteEditor from "../components/NoteEditor.vue";
+import { isDesktop, sync as syncBridge } from "../desktop/bridge";
const notes = useNotesStore();
const config = useConfigStore();
@@ -157,12 +158,45 @@ const retentionNotice = computed(() => {
: `Notes here are permanently deleted ${days} days after you trash them. Restore one to keep it.`;
});
+/**
+ * Whether this desktop install has been linked to a server. Only consulted for the
+ * first-run copy below — offering to connect a server to someone who already did
+ * would be noise. Never set on the web build, which IS a server's UI.
+ */
+const linkedToServer = ref(false);
+
+/**
+ * The desktop's start state: a fresh install, nothing written yet.
+ *
+ * There is no login and no onboarding gate — the app is usable the instant it
+ * opens, and interrupting that with a welcome modal would tax the one thing the
+ * product is for (dumping a thought in a second). So the empty board carries the
+ * message instead, and retires itself the moment a first note exists. That also
+ * means no "seen it" flag to persist, migrate, or get out of step with reality.
+ */
+const startState = computed(
+ () =>
+ isDesktop() &&
+ isMainBoard.value &&
+ !filtered.value &&
+ !linkedToServer.value &&
+ notes.items.length === 0,
+);
+
const emptyState = computed(() => {
if (filtered.value) return { title: "No notes match these filters", subtitle: "Try clearing or loosening a facet." };
if (currentView.value === "trash") return { title: "Trash is empty", subtitle: "Notes you delete land here first." };
if (currentView.value === "archived")
return { title: "Nothing archived", subtitle: "Archived notes are tucked away here." };
if (currentLabel.value) return { title: "No notes with this label", subtitle: "Tag a note to see it here." };
+ // Says what "no account" actually means for the notes about to be written here.
+ // A new user otherwise has no way to tell whether this thing is storing their
+ // thoughts locally, silently waiting for a login, or quietly sending them off.
+ if (startState.value)
+ return {
+ title: "Your notes live on this device",
+ subtitle: "ThoughtSync works fully offline — no account, nothing to sign in to.",
+ };
return { title: "No notes yet", subtitle: "Your captured thoughts will show up here." };
});
@@ -178,6 +212,17 @@ async function reload() {
onMounted(() => {
void reload();
window.addEventListener("keydown", onBoardKey);
+ // Best-effort and never awaited: the board must render at full speed whether or
+ // not this answers. A failure just leaves the start-state copy showing, which is
+ // the correct reading of "we don't know of a server".
+ if (isDesktop()) {
+ void syncBridge
+ .status()
+ .then((s) => {
+ linkedToServer.value = s.linked;
+ })
+ .catch(() => {});
+ }
});
onBeforeUnmount(() => {
window.removeEventListener("keydown", onBoardKey);
@@ -234,7 +279,21 @@ async function onDrop(target: Note) {
error-title="Couldn't load your notes"
@retry="reload"
>
-
+
+
+
+
+ Connect a sync server
+
+
+ Optional — only if you want these notes on your other devices.
+