From bc22f8e2495fe2cfb786d58983c379c08f5c0b53 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 22 Aug 2026 12:00:57 -0400 Subject: [PATCH] Remove [[wiki-links]], backlinks and the graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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//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. --- alembic/versions/0024_drop_note_links.py | 54 +++ core/src/local/derive.rs | 61 +-- core/src/local/models.rs | 8 +- core/src/local/schema.rs | 3 +- core/src/local/store.rs | 79 ---- desktop/src-tauri/src/commands/local.rs | 18 - desktop/src-tauri/src/lib.rs | 3 - docs/sync.md | 8 +- frontend/src/adapters/local.ts | 5 +- frontend/src/adapters/repo.ts | 7 - frontend/src/adapters/rest.ts | 5 - frontend/src/components/AppShell.vue | 15 +- frontend/src/components/CommandPalette.vue | 1 - frontend/src/components/Icon.vue | 1 - frontend/src/components/MarkdownInline.vue | 58 +-- frontend/src/components/MarkdownText.vue | 19 +- frontend/src/components/NoteCard.vue | 2 +- frontend/src/components/NoteEditor.vue | 208 +--------- frontend/src/composables/useNoteEditor.ts | 4 +- frontend/src/notes/markdown.ts | 23 +- frontend/src/router/index.ts | 1 - frontend/src/stores/notes.ts | 30 +- frontend/src/stores/titles.ts | 14 +- frontend/src/views/GraphView.vue | 420 --------------------- src/thoughtsync/app.py | 2 - src/thoughtsync/graph.py | 117 ------ src/thoughtsync/models/all.py | 1 - src/thoughtsync/models/note.py | 6 +- src/thoughtsync/models/note_link.py | 47 --- src/thoughtsync/notes/__init__.py | 114 +----- src/thoughtsync/notes/helpers.py | 5 - src/thoughtsync/notes/import_export.py | 3 +- src/thoughtsync/notes/links.py | 146 ------- src/thoughtsync/notes/serialize.py | 70 +--- src/thoughtsync/notes/tags.py | 75 ++++ src/thoughtsync/retention.py | 2 - src/thoughtsync/sync.py | 11 +- tests/test_notes.py | 55 +-- 38 files changed, 216 insertions(+), 1485 deletions(-) create mode 100644 alembic/versions/0024_drop_note_links.py delete mode 100644 frontend/src/views/GraphView.vue delete mode 100644 src/thoughtsync/graph.py delete mode 100644 src/thoughtsync/models/note_link.py delete mode 100644 src/thoughtsync/notes/links.py create mode 100644 src/thoughtsync/notes/tags.py diff --git a/alembic/versions/0024_drop_note_links.py b/alembic/versions/0024_drop_note_links.py new file mode 100644 index 0000000..ab831ff --- /dev/null +++ b/alembic/versions/0024_drop_note_links.py @@ -0,0 +1,54 @@ +"""drop note_links — [[wiki-links]] are removed (note 2897) + +Revision ID: 0024 +Revises: 0023 +Create Date: 2026-08-22 + +ThoughtSync is an intermediary surface for capture and recall; a linking system is +organization, which is not what it is for. Backlinks, the graph and the name index +went with it. + +0023 (which added `note_links.target_id`) is deliberately left in the chain rather +than deleted. It shipped in an image and may already be applied, and removing an +applied revision would strand a database's alembic_version pointer. So the column is +dropped here along with the table it lived on, and the history stays honest about the +fact that it existed for a day. + +No down-migration data concern: note_links was always DERIVED from note bodies. The +`[[text]]` is still sitting in every body it was written in; nothing a person typed is +lost by this. +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision = "0024" +down_revision = "0023" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.drop_table("note_links") + + +def downgrade() -> None: + op.create_table( + "note_links", + sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True), + sa.Column( + "source_id", + postgresql.UUID(as_uuid=True), + sa.ForeignKey("notes.id", ondelete="CASCADE"), + nullable=False, + ), + sa.Column( + "target_id", + postgresql.UUID(as_uuid=True), + sa.ForeignKey("notes.id", ondelete="SET NULL"), + nullable=True, + ), + sa.Column("target_norm", sa.Text(), nullable=False), + ) + op.create_index("ix_note_links_target", "note_links", ["target_norm"]) + op.create_index("ix_note_links_target_id", "note_links", ["target_id"]) diff --git a/core/src/local/derive.rs b/core/src/local/derive.rs index 376674f..2588df3 100644 --- a/core/src/local/derive.rs +++ b/core/src/local/derive.rs @@ -1,38 +1,14 @@ -//! Deriving `[[wiki-links]]` and `#tags` from a note's body — the local mirror of -//! what the server computes on save. Pure string scanning (no regex dependency), -//! kept in lockstep with the frontend's inline rules (see frontend notes/markdown.ts): +//! Deriving `#tags` from a note's body — the local mirror of what the server computes +//! on save. Pure string scanning (no regex dependency), kept in lockstep with the +//! frontend's inline rules (see frontend notes/markdown.ts): //! -//! - `[[link]]`: `[[` … `]]` with no brackets inside, inner text trimmed. Used to -//! compute backlinks at query time (links are derived, never stored/synced). //! - `#tag`: `#` at a word boundary followed by tag characters (letter first). //! On save these become labels attached with `via_tag = true`. //! -//! Both dedupe case-insensitively, preserving first-seen order. - -/// Extract the trimmed inner text of every `[[wiki-link]]` in `body`. -pub fn extract_links(body: &str) -> Vec { - let bytes = body.as_bytes(); - let mut out: Vec = Vec::new(); - let mut i = 0; - while i + 1 < bytes.len() { - if bytes[i] == b'[' && bytes[i + 1] == b'[' { - if let Some(rel) = body[i + 2..].find("]]") { - let inner = &body[i + 2..i + 2 + rel]; - // Mirror the frontend's `[^[\]]+`: no stray brackets inside. - if !inner.contains('[') && !inner.contains(']') { - let t = inner.trim(); - if !t.is_empty() { - push_unique(&mut out, t); - } - } - i += 2 + rel + 2; - continue; - } - } - i += 1; - } - out -} +//! Dedupes case-insensitively, preserving first-seen order. +//! +//! Also derived `[[wiki-links]]` until they were removed (note 2897) — this is a +//! capture-and-recall surface, and a linking system is organization. /// Extract every `#tag` name (without the leading `#`) from `body`. pub fn extract_tags(body: &str) -> Vec { @@ -73,28 +49,6 @@ fn push_unique(out: &mut Vec, candidate: &str) { mod tests { use super::*; - #[test] - fn links_basic_and_trim() { - assert_eq!( - extract_links("see [[ Alpha ]] and [[Beta]]"), - vec!["Alpha", "Beta"] - ); - } - - #[test] - fn links_dedupe_case_insensitive_first_seen() { - assert_eq!(extract_links("[[Note]] then [[note]] again"), vec!["Note"]); - } - - #[test] - fn links_ignore_malformed_and_nested_brackets() { - assert_eq!( - extract_links("[[a[b]] [[]] [ [x] ] plain"), - Vec::::new() - ); - assert_eq!(extract_links("[[ok]] [[a]b]]"), vec!["ok"]); - } - #[test] fn tags_basic() { assert_eq!( @@ -116,7 +70,6 @@ mod tests { #[test] fn empty_body() { - assert!(extract_links("").is_empty()); assert!(extract_tags("").is_empty()); } } diff --git a/core/src/local/models.rs b/core/src/local/models.rs index 94ac2fd..fa22918 100644 --- a/core/src/local/models.rs +++ b/core/src/local/models.rs @@ -10,7 +10,7 @@ pub struct Note { pub id: String, pub title: Option, /// title if set, else the note's first body line — always present, so body-only - /// notes are still nameable and `[[link]]`-able. Derived, never stored. + /// notes still have something to be called. Derived, never stored. pub display_title: String, pub body: String, pub color: String, @@ -94,12 +94,6 @@ pub struct TitleEntry { pub title: String, } -#[derive(Serialize)] -pub struct Backlink { - pub id: String, - pub title: String, -} - #[derive(Serialize)] pub struct SavedFilter { pub id: String, diff --git a/core/src/local/schema.rs b/core/src/local/schema.rs index 7e2e9f3..5927324 100644 --- a/core/src/local/schema.rs +++ b/core/src/local/schema.rs @@ -1,7 +1,8 @@ //! Local SQLite schema + migrations. The schema mirrors the note/label model so an //! offline note can later sync 1:1 with the server. Each syncable row carries local //! `sync_revision` + `dirty` bookkeeping (consumed by the sync engine in M10.7); -//! `[[links]]` are NOT stored (derived at query time), matching docs/sync.md. +//! `#tags` are NOT stored as such (derived at query time into labels), matching +//! docs/sync.md. //! //! Migrations are gated on `PRAGMA user_version`; bump it and add a block per change. diff --git a/core/src/local/store.rs b/core/src/local/store.rs index 80fa92a..d3fa9a5 100644 --- a/core/src/local/store.rs +++ b/core/src/local/store.rs @@ -350,61 +350,6 @@ pub fn search(conn: &Connection, q: &str) -> rusqlite::Result> { ids.iter().map(|id| load_note(conn, id)).collect() } -pub fn backlinks(conn: &Connection, id: &str) -> rusqlite::Result> { - let target: String = { - let (t, b): (Option, String) = - conn.query_row("SELECT title, body FROM notes WHERE id = ?1", [id], |r| { - Ok((r.get(0)?, r.get(1)?)) - })?; - display_title(t.as_deref(), &b) - }; - if target.is_empty() { - return Ok(Vec::new()); - } - let mut stmt = - conn.prepare("SELECT id, title, body FROM notes WHERE trashed = 0 AND id != ?1")?; - let rows = stmt.query_map([id], |r| { - let nid: String = r.get(0)?; - let t: Option = r.get(1)?; - let b: String = r.get(2)?; - Ok((nid, t, b)) - })?; - let mut out = Vec::new(); - for row in rows { - let (nid, t, b) = row?; - if derive::extract_links(&b) - .iter() - .any(|l| l.eq_ignore_ascii_case(&target)) - { - out.push(Backlink { - id: nid, - title: display_title(t.as_deref(), &b), - }); - } - } - Ok(out) -} - -pub fn link_search(conn: &Connection, q: &str) -> rusqlite::Result> { - let ql = q.trim().to_lowercase(); - let mut stmt = conn.prepare("SELECT id, title, body FROM notes WHERE trashed = 0")?; - let rows = stmt.query_map([], |r| { - let id: String = r.get(0)?; - let t: Option = r.get(1)?; - let b: String = r.get(2)?; - Ok((id, t, b)) - })?; - let mut out = Vec::new(); - for row in rows { - let (id, t, b) = row?; - let dt = display_title(t.as_deref(), &b); - if ql.is_empty() || dt.to_lowercase().contains(&ql) { - out.push(TitleEntry { id, title: dt }); - } - } - Ok(out) -} - // ---- notes: write ----------------------------------------------------------- pub fn create_note(conn: &Connection, input: &NoteCreateInput) -> rusqlite::Result { @@ -434,30 +379,6 @@ pub fn create_note(conn: &Connection, input: &NoteCreateInput) -> rusqlite::Resu load_note(conn, &id) } -pub fn create_titled(conn: &Connection, title: &str) -> rusqlite::Result { - let input = NoteCreateInput { - title: title.to_string(), - body: String::new(), - color: "default".to_string(), - kind: None, - items: None, - }; - create_note(conn, &input) -} - -fn snapshot_revision(conn: &Connection, id: &str) -> rusqlite::Result<()> { - let (title, body): (Option, String) = - conn.query_row("SELECT title, body FROM notes WHERE id = ?1", [id], |r| { - Ok((r.get(0)?, r.get(1)?)) - })?; - conn.execute( - "INSERT INTO note_revisions (id, note_id, title, body, created_at) VALUES (?1, ?2, ?3, ?4, ?5)", - params![new_id(), id, title, body, now()], - )?; - Ok(()) -} - -/// PATCH semantics: apply exactly the fields present in `changes`. pub fn update_note(conn: &Connection, id: &str, changes: &Value) -> rusqlite::Result { let obj = changes .as_object() diff --git a/desktop/src-tauri/src/commands/local.rs b/desktop/src-tauri/src/commands/local.rs index f294545..36f108a 100644 --- a/desktop/src-tauri/src/commands/local.rs +++ b/desktop/src-tauri/src/commands/local.rs @@ -67,12 +67,6 @@ pub fn notes_create(input: NoteCreateInput, db: State<'_, Db>) -> Result) -> Result { - let conn = db.0.lock().map_err(|e| e.to_string())?; - store::create_titled(&conn, &title).map_err(|e| e.to_string()) -} - #[tauri::command] pub fn notes_update(id: String, changes: Value, db: State<'_, Db>) -> Result { let conn = db.0.lock().map_err(|e| e.to_string())?; @@ -202,18 +196,6 @@ pub fn notes_search(q: String, db: State<'_, Db>) -> Result, String> { store::search(&conn, &q).map_err(|e| e.to_string()) } -#[tauri::command] -pub fn notes_backlinks(id: String, db: State<'_, Db>) -> Result, String> { - let conn = db.0.lock().map_err(|e| e.to_string())?; - store::backlinks(&conn, &id).map_err(|e| e.to_string()) -} - -#[tauri::command] -pub fn notes_link_search(q: String, db: State<'_, Db>) -> Result, String> { - let conn = db.0.lock().map_err(|e| e.to_string())?; - store::link_search(&conn, &q).map_err(|e| e.to_string()) -} - #[tauri::command] pub fn labels_list(db: State<'_, Db>) -> Result, String> { let conn = db.0.lock().map_err(|e| e.to_string())?; diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 77c075b..0f7ca81 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -102,7 +102,6 @@ pub fn run() { commands::local::notes_list, commands::local::notes_get, commands::local::notes_create, - commands::local::notes_create_titled, commands::local::notes_update, commands::local::notes_complete_reminder, commands::local::notes_snooze_reminder, @@ -121,8 +120,6 @@ pub fn run() { commands::local::notes_reminders, commands::local::notes_titles, commands::local::notes_search, - commands::local::notes_backlinks, - commands::local::notes_link_search, commands::local::labels_list, commands::local::labels_create, commands::local::labels_rename, diff --git a/docs/sync.md b/docs/sync.md index 9787f07..709c81f 100644 --- a/docs/sync.md +++ b/docs/sync.md @@ -122,9 +122,9 @@ as `?since=`. `since=0` (or absent) is a **full initial sync**. so pulling the note re-syncs the whole thing. - **Label** — the label *catalog* (name + color) syncs as its own entity so a rename/recolor/delete propagates independently of notes. -- **Derived, NOT synced:** `[[wiki-links]]` and `#tags` are parsed from the note - body. Clients recompute them locally; the server recomputes them on push. They - never travel over the wire. +- **Derived, NOT synced:** `#tags` are parsed from the note body. Clients recompute + them locally; the server recomputes them on push. They never travel over the wire. + (`[[wiki-links]]` were derived the same way until they were removed — note 2897.) - **Attachment blobs** sync by id over the existing upload/download routes (see Attachments below); only their metadata rides the delta feed. @@ -201,7 +201,7 @@ Body: `{ "changes": [ ... ] }` (max 1000 per batch). Each change: - **Whole-note semantics.** A note upsert carries the client's *full* current state (not a partial patch) — the server overwrites all scalar fields, replaces items, and sets manual label memberships from `label_ids` (tag-sourced labels - are re-derived from the body). `[[links]]`/`#tags` are recomputed server-side. + are re-derived from the body). `#tags` are recomputed server-side. - **`op: "delete"`** purges (tombstones) the row. Trashing is just an upsert with `trashed: true`. - **Labels:** `{entity: "label", op: "upsert"|"delete", id, edited_at, name, diff --git a/frontend/src/adapters/local.ts b/frontend/src/adapters/local.ts index 4c20230..ed414af 100644 --- a/frontend/src/adapters/local.ts +++ b/frontend/src/adapters/local.ts @@ -16,7 +16,7 @@ import type { Device } from "../stores/devices"; import type { TitleEntry } from "../stores/titles"; import type { User } from "../stores/session"; import type { PublicConfig } from "../stores/config"; -import type { Backlink, DeviceToken, ImportResult, Repo } from "./repo"; +import type { DeviceToken, ImportResult, Repo } from "./repo"; const NEEDS_SERVER = "That's not available offline — connect a server to use it."; @@ -51,7 +51,6 @@ export const local: Repo = { list: (query) => invoke("notes_list", { query }), get: (id) => invoke("notes_get", { id }), create: (input) => invoke("notes_create", { input }), - createTitled: (title) => invoke("notes_create_titled", { title }), update: (id, changes) => invoke("notes_update", { id, changes }), completeReminder: (id) => invoke("notes_complete_reminder", { id }), snoozeReminder: (id, minutes) => invoke("notes_snooze_reminder", { id, minutes }), @@ -73,8 +72,6 @@ export const local: Repo = { reminders: () => invoke("notes_reminders"), titles: () => invoke("notes_titles"), search: (q) => invoke("notes_search", { q }), - backlinks: (id) => invoke("notes_backlinks", { id }), - linkSearch: (q) => invoke("notes_link_search", { q }), }, savedFilters: { diff --git a/frontend/src/adapters/repo.ts b/frontend/src/adapters/repo.ts index 9ea20ee..0b319e0 100644 --- a/frontend/src/adapters/repo.ts +++ b/frontend/src/adapters/repo.ts @@ -49,10 +49,6 @@ export interface ChecklistItemChanges { checked?: boolean; } -export interface Backlink { - id: string; - title: string; -} export interface ImportResult { source: string; @@ -97,7 +93,6 @@ export interface NotesRepo { list(query: NoteListQuery): Promise; get(id: string): Promise; create(input: NoteCreateInput): Promise; - createTitled(title: string): Promise; update(id: string, changes: NoteChanges): Promise; completeReminder(id: string): Promise; snoozeReminder(id: string, minutes: number): Promise; @@ -119,8 +114,6 @@ export interface NotesRepo { reminders(): Promise; titles(): Promise; search(q: string): Promise; - backlinks(id: string): Promise; - linkSearch(q: string): Promise; } export interface SavedFiltersRepo { diff --git a/frontend/src/adapters/rest.ts b/frontend/src/adapters/rest.ts index d550ca8..9040431 100644 --- a/frontend/src/adapters/rest.ts +++ b/frontend/src/adapters/rest.ts @@ -13,7 +13,6 @@ import type { TitleEntry } from "../stores/titles"; import type { User } from "../stores/session"; import type { PublicConfig } from "../stores/config"; import type { - Backlink, DeviceToken, ImportResult, NoteChanges, @@ -80,7 +79,6 @@ export const rest: Repo = { list: async (query) => (await api.get<{ notes: Note[] }>(`/api/notes?${notesQuery(query)}`)).notes, get: (id) => api.get(`/api/notes/${id}`), create: (input: NoteCreateInput) => api.post("/api/notes", input), - createTitled: (title) => api.post("/api/notes", { title, body: "" }), update: (id, changes: NoteChanges) => api.patch(`/api/notes/${id}`, changes), completeReminder: (id) => api.post(`/api/notes/${id}/reminder/complete`), snoozeReminder: (id, minutes) => api.post(`/api/notes/${id}/reminder/snooze`, { minutes }), @@ -103,9 +101,6 @@ export const rest: Repo = { reminders: async () => (await api.get<{ notes: Note[] }>("/api/notes/reminders")).notes, titles: async () => (await api.get<{ titles: TitleEntry[] }>("/api/notes/titles")).titles, search: async (q) => (await api.get<{ notes: Note[] }>(`/api/notes/search?q=${encodeURIComponent(q)}`)).notes, - backlinks: async (id) => (await api.get<{ backlinks: Backlink[] }>(`/api/notes/${id}/backlinks`)).backlinks, - linkSearch: async (q) => - (await api.get<{ results: TitleEntry[] }>(`/api/notes/link-search?q=${encodeURIComponent(q)}`)).results, }, savedFilters: { diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index e8af7bb..0932fee 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -49,7 +49,6 @@ const shortcuts = [ { label: "New note (or just start typing)", keys: ["Enter", "c"] }, { label: "Search", keys: ["/"] }, { label: "Go to Board", keys: ["g", "b"] }, - { label: "Go to Graph", keys: ["g", "g"] }, { label: "Go to Reminders", keys: ["g", "r"] }, { label: "Go to Timeline", keys: ["g", "t"] }, { label: "Browse cards", keys: ["↑", "↓", "←", "→"] }, @@ -121,11 +120,6 @@ function onKeydown(e: KeyboardEvent) { void router.push("/"); return; } - if (e.key === "g") { - e.preventDefault(); - void router.push("/graph"); - return; - } if (e.key === "r") { e.preventDefault(); void router.push("/reminders"); @@ -207,7 +201,7 @@ watch( * * Keyed off the route name rather than each view declaring its own title, so the * label sits in one place and can't go missing (the board and search never had one) - * or drift in styling (timeline, reminders and graph each had their own h1). + * or drift in styling (timeline and reminders each had their own h1). * * A label lens is named by the label itself — "Groceries" is what the user came * looking for; "Label" would tell them nothing they didn't already know. @@ -224,8 +218,6 @@ const lensName = computed(() => { return "Timeline"; case "reminders": return "Reminders"; - case "graph": - return "Graph"; case "label": // The store may not have loaded yet on a deep link; fall back rather than // flashing an empty slot. @@ -403,9 +395,6 @@ async function signOut() { Notes - - Graph -
Labels @@ -522,7 +511,7 @@ async function signOut() { BoardView, so keying on the route would remount it — blanking the board and refetching, which is precisely the page-change feeling this is meant to remove. Unkeyed, Vue only transitions when the component TYPE changes - (board ↔ search ↔ timeline ↔ graph), and moving between the board's own + (board ↔ search ↔ timeline), and moving between the board's own lenses stays an in-place reflow that NoteGrid animates. -->
diff --git a/frontend/src/components/CommandPalette.vue b/frontend/src/components/CommandPalette.vue index 707301f..5eddbdf 100644 --- a/frontend/src/components/CommandPalette.vue +++ b/frontend/src/components/CommandPalette.vue @@ -42,7 +42,6 @@ const commands = computed(() => { const list: Row[] = [ { id: "cmd:new", label: "New note", hint: "Action", run: compose }, { id: "cmd:board", label: "Go to Board", hint: "Navigate", run: () => go("/") }, - { id: "cmd:graph", label: "Go to Graph", hint: "Navigate", run: () => go("/graph") }, { id: "cmd:reminders", label: "Go to Reminders", hint: "Navigate", run: () => go("/reminders") }, { id: "cmd:timeline", label: "Go to Timeline", hint: "Navigate", run: () => go("/timeline") }, { id: "cmd:archive", label: "Go to Archive", hint: "Navigate", run: () => go("/archive") }, diff --git a/frontend/src/components/Icon.vue b/frontend/src/components/Icon.vue index 4a55a1d..478ec00 100644 --- a/frontend/src/components/Icon.vue +++ b/frontend/src/components/Icon.vue @@ -16,7 +16,6 @@ const paths: Record = { check: '', checkbox: '', image: '', - graph: '', bell: '', grip: '', calendar: '', diff --git a/frontend/src/components/MarkdownInline.vue b/frontend/src/components/MarkdownInline.vue index 1df9f21..5001909 100644 --- a/frontend/src/components/MarkdownInline.vue +++ b/frontend/src/components/MarkdownInline.vue @@ -1,65 +1,17 @@