From 659237ccc6ffe73cfa9e70b2f4e8713c0b1eae40 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 16 Aug 2026 11:37:29 -0400 Subject: [PATCH] desktop: the empty board explains where your notes live (task 1999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh desktop install has no login — auth_me returns a synthetic local user so the shared router's guard resolves — but nothing said so. You landed on a bare board with no way to tell whether the app was storing your thoughts on this machine, waiting for a credential, or quietly shipping them somewhere. The start state is the empty board itself, not a welcome modal or an onboarding gate. The product exists to take a thought in under a second; spending that second on a dialog taxes the one thing it is for. It also means there is no "seen it" flag to persist, migrate, or let drift out of step with reality — the message retires itself the moment a first note exists, which is exactly when it stops being true that you have nothing here. Shown only when the app is unlinked: offering to connect a server to someone who already has one is noise. The status read is best-effort and never awaited, so the board renders at full speed regardless; if it fails we keep showing the offline copy, which is the honest reading of "we know of no server". Co-Authored-By: Claude Opus 5 (1M context) --- frontend/src/views/BoardView.vue | 61 +++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) 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" > - + + + +