Files
thoughtsync/frontend/src/router/index.ts
T
bvandeusen bc22f8e249
CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Failing after 6s
CI & Build / Build & push image (push) Skipped
CI & Build / Python tests (push) Successful in 8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Failing after 31s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Failing after 37s
Desktop (Tauri) / Update manifest (push) Skipped
Android / Kotlin + Rust (APK) (push) Failing after 1m56s
Remove [[wiki-links]], backlinks and the graph
Operator, 2026-08-22 (note 2897): ThoughtSync is an intermediary surface. You
write here because it's easy — a notebook in your pocket — and later you recall
the thing and go finish it somewhere else. Recall is the product; organization
is secondary. A linking system is organization, and it isn't what this is for.

So: `[[wiki-links]]`, backlinks, the `[[` autocomplete, the note_links table,
`/api/notes/link-search`, `/api/notes/<id>/backlinks`, the whole graph blueprint
and GraphView. Rust core loses `extract_links`, `backlinks`, `link_search` and
`create_titled`; the desktop loses the three Tauri commands that exposed them.

This subsumes 982d24c rather than reverting it. That commit bound links to a
note id so a rename would stop rewriting other notes' bodies — real infra, but
infra for a feature that is now gone, and nothing it added survives. Alembic
0023 stays in the chain anyway: it shipped in an image and may already be
applied, and deleting an applied revision strands a database's version pointer.
0024 drops the table and takes the column with it. The history stays honest
about the fact that it existed for a day.

Two things deliberately kept, because they were serving recall and only
incidentally serving links:

- `/api/notes/titles` and the titles store. The command palette lists them so
  you can jump to a note by name. `resolve()` — the name→note lookup that only
  linking needed — is gone.
- `display_title`. Every note still has a name for search results and export
  filenames. What that name is FOR changed; that it exists did not.

`notes/links.py` is now `notes/tags.py`, holding the #tag→label reconciliation
it always also owned. A file called links.py with no links in it would have been
exactly the drift this removal is meant to end.

Also swept out on the way: `_escape_like`, whose only caller was link-search,
and the `graph` icon. Nothing lost that a person typed — note_links was always
derived, and the `[[text]]` is still sitting in every body it was written in.
2026-08-22 12:00:57 -04:00

110 lines
4.1 KiB
TypeScript

import { createRouter, createWebHistory } from "vue-router";
import { useSessionStore } from "../stores/session";
import { useConfigStore } from "../stores/config";
import { isDesktop, logEvent } from "../desktop/bridge";
// One-time boot diagnostic: the first navigation is where config + session resolve,
// so it's the moment that tells us whether the app got past its startup gate.
let bootLogged = false;
const router = createRouter({
history: createWebHistory(),
routes: [
{
// Persistent authed shell (sidebar + top bar + search); children render in it.
path: "/",
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: "reminders", name: "reminders", component: () => import("../views/RemindersView.vue") },
{ path: "timeline", name: "timeline", component: () => import("../views/TimelineView.vue") },
],
},
{
path: "/settings",
name: "settings",
component: () => import("../views/SettingsView.vue"),
meta: { requiresAuth: true, requiresAdmin: true },
},
{
// Desktop only: connect this app to a server. Meaningless in the web build,
// which IS a server's UI — there's nothing for it to link to.
path: "/sync",
name: "sync",
component: () => import("../views/SyncView.vue"),
meta: { requiresAuth: true, requiresDesktop: true },
},
{
// Per-user account: linked devices (native-client sync tokens). Any user.
//
// The mirror of `requiresDesktop` above: this one needs a SERVER. The desktop
// is itself one of the devices this page lists, so offline the list is always
// empty and issuing a token rejects — its server relationship lives at /sync.
// Guarded in the router, not just hidden in the shell, so a typed URL or a
// restored history entry can't land on a dead end either.
path: "/account",
name: "account",
component: () => import("../views/AccountView.vue"),
meta: { requiresAuth: true, requiresServer: true },
},
{
path: "/login",
name: "login",
component: () => import("../views/LoginView.vue"),
meta: { guestOnly: true },
},
{
path: "/register",
name: "register",
component: () => import("../views/RegisterView.vue"),
meta: { guestOnly: true },
},
],
});
router.beforeEach(async (to) => {
const session = useSessionStore();
const config = useConfigStore();
await config.load();
if (!session.loaded) {
await session.fetchMe();
}
if (!bootLogged) {
bootLogged = true;
logEvent(
"info",
`first route: config(site=${config.siteName}) session(user=${session.user?.email ?? "none"}) -> ${String(to.name ?? to.path)}`,
);
}
if (to.meta.requiresAuth && !session.user) {
return { name: "login", query: to.fullPath !== "/" ? { redirect: to.fullPath } : undefined };
}
if (to.meta.requiresAdmin && !session.user?.is_admin) {
return { name: "board" };
}
if (to.meta.requiresDesktop && !isDesktop()) {
return { name: "board" };
}
// Deliberately NOT applied to /login and /register: bouncing those on desktop
// would loop against the requiresAuth guard above the moment a session is
// missing. Nothing on the desktop navigates to them any more (AppShell's sign-out
// is web-only), and a fresh launch always resolves the local user.
if (to.meta.requiresServer && isDesktop()) {
return { name: "board" };
}
if (to.name === "register" && !config.allowRegistration) {
return { name: "login" };
}
if (to.meta.guestOnly && session.user) {
return { name: "board" };
}
return true;
});
export default router;