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
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.
110 lines
4.1 KiB
TypeScript
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;
|