From 515e18f465e47fdb899a604bb9f0032947da344d Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 27 Jun 2026 14:46:17 +0530 Subject: [PATCH] feat: add Bug Report option & About menu in Settings, fix Windows onboarding path linking --- AUDIT_LOG.md | 12 ++++ CHANGELOG.md | 4 ++ src-tauri/src/commands/fs.rs | 27 ++++++--- src/GraphView.tsx | 2 +- src/Settings.tsx | 96 ++++++++++++++++++++++++++++++++ src/hooks/useNoteStorage.ts | 2 +- src/lib/editor/markdownPlugin.ts | 2 +- src/setupTests.ts | 4 ++ 8 files changed, 139 insertions(+), 10 deletions(-) diff --git a/AUDIT_LOG.md b/AUDIT_LOG.md index cf13c4b..06f3916 100644 --- a/AUDIT_LOG.md +++ b/AUDIT_LOG.md @@ -2,6 +2,18 @@ This log tracks all significant changes, updates, and versions in the PaperCache project. +## 2026-06-27 (Settings & Onboarding Fixes) +**Change:** feat: add Bug Report option and About section in Settings; fix Windows onboarding file path generation + +**Details/Why:** +1. Added "Submit a Bug Report" button under Settings > System linking directly to GitHub Issues creation page (`https://github.com/VariableThe/PaperCache/issues/new`). +2. Added dedicated "About" section in Settings featuring app logo (`/icon.png`), dynamic app version number (`getVersion`), check for updates button, Ko-fi support button (`https://ko-fi.com/thevariable`), and user thank you message. +3. Resolved onboarding note generation and `/file` linking failures on Windows. Previously, `walk_dir` generated note IDs with backslashes on Windows (e.g. `onboarding\Editor.md`), causing internal `/file` links using forward slashes in `Welcome.md` to fail lookup and duplicate empty notes. Normalized path generation across Rust (`fs.rs`) and frontend (`useNoteStorage.ts`, `markdownPlugin.ts`, `GraphView.tsx`) to guarantee consistent forward slash IDs across all OS targets. Updated `write_onboarding_file` to regenerate onboarding files on version bumps. + +**Files changed:** `src/Settings.tsx`, `src/setupTests.ts`, `src-tauri/src/commands/fs.rs`, `src/hooks/useNoteStorage.ts`, `src/lib/editor/markdownPlugin.ts`, `src/GraphView.tsx`, `CHANGELOG.md`, `AUDIT_LOG.md`. + +--- + ## 2026-06-27 (Lockfile Sync) **Change:** build(deps): move @emnapi WASM fallbacks to devDependencies for deterministic lockfile resolution across OS targets diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b6e8e..182881f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v0.5.4] - 2026-06-25 +### Added +- **Settings Bug Report & About Menu**: Added a "Submit a Bug Report" button under System settings linking directly to the GitHub issue creation form. Added a dedicated "About" section displaying the app logo, current version number, update checker, Ko-fi support link, and a thank you message. + ### Fixed +- **Windows Onboarding File Linking & Generation**: Fixed a bug on Windows where backslashes in generated note IDs caused internal `/file` links in `Welcome.md` to fail and create duplicate empty notes. Normalized note ID generation across Rust and TypeScript to consistently use forward slashes on all platforms, and ensured onboarding template files regenerate correctly on application updates. - **Window position/size now persists across restarts**: The window-state plugin's `on_window_ready` fires before the macOS display server is ready, causing `available_monitors()` to return empty and the saved position to be silently discarded. Fixed by deferring window-state restoration via a background thread + `run_on_main_thread` 300ms after `setup()` completes, bypassing the plugin's monitor-intersection check with a direct file read. Both the tray "Quit" and Settings "Quit" buttons now explicitly save window state before exit. - **Launch at Startup now registers as a proper Login Item**: Changed `MacosLauncher` from `LaunchAgent` to `AppleScript`, which registers PaperCache in System Settings > General > Login Items instead of creating a hidden `launchd` plist. Users can now see and manage the autostart entry directly from System Settings. - **Window no longer shifts down on restart on macOS**: Removed the +/-28px compensation that was incorrectly added for a macOS frameless window offset which does not exist in `tauri-plugin-window-state` v2.4.1 — the plugin correctly saves `outer_position()` and restores via `set_position()` with no titlebar offset for frameless windows. diff --git a/src-tauri/src/commands/fs.rs b/src-tauri/src/commands/fs.rs index 975357c..75aaade 100644 --- a/src-tauri/src/commands/fs.rs +++ b/src-tauri/src/commands/fs.rs @@ -22,7 +22,15 @@ pub fn get_papercache_dir() -> Result { pub fn get_safe_path(id: &str) -> Result { let base = get_papercache_dir()?; - let target = base.join(id); + let clean_id = id.replace('\\', "/"); + let mut target = base.clone(); + for comp in clean_id.split('/') { + if !comp.is_empty() && comp != "." && comp != ".." { + target.push(comp); + } else if comp == ".." { + return Err("Path traversal detected".to_string()); + } + } let parent = target.parent().ok_or("Invalid path parent")?; if !parent.exists() { @@ -74,7 +82,7 @@ fn walk_dir(dir: &Path, notes: &mut Vec, base_path: &Path) -> Result<(), S .strip_prefix(base_path) .unwrap_or(&path) .to_string_lossy() - .to_string(); + .replace('\\', "/"); notes.push(Note { id, content, mtime }); } } @@ -125,7 +133,7 @@ pub fn read_note(id: String) -> Result { #[tauri::command] pub fn delete_note(id: String) -> Result { - if id.starts_with("commands/") { + if id.replace('\\', "/").starts_with("commands/") { return Err("Cannot delete protected command files".into()); } let path = get_safe_path(&id)?; @@ -197,13 +205,18 @@ pub fn set_dialog_open(state: tauri::State<'_, crate::DialogState>, open: bool) state.is_open.store(open, Ordering::SeqCst); } -fn write_onboarding_file(base: &Path, rel_path: &str, content: &str, _is_new_version: bool) { - let path = base.join(rel_path); +fn write_onboarding_file(base: &Path, rel_path: &str, content: &str, is_new_version: bool) { + let mut path = base.to_path_buf(); + for comp in rel_path.replace('\\', "/").split('/') { + if !comp.is_empty() { + path.push(comp); + } + } if let Some(parent) = path.parent() { let _ = fs::create_dir_all(parent); } - // Only write if file doesn't exist — preserve user edits across version changes - if !path.exists() { + // Only write if file doesn't exist, or if upgrading to a new app version + if !path.exists() || is_new_version { let _ = fs::write(&path, content); } } diff --git a/src/GraphView.tsx b/src/GraphView.tsx index cf6abce..5902855 100644 --- a/src/GraphView.tsx +++ b/src/GraphView.tsx @@ -140,7 +140,7 @@ export default function GraphView({ const re = /\]\(\/file\s+([^)]+)\)/g let match while ((match = re.exec(note.content)) !== null) { - let targetId = match[1] + let targetId = match[1].trim().replace(/\\/g, '/') if (!targetId.endsWith('.md')) targetId += '.md' if (nodeIds.has(targetId)) { links.push({ source: note.id, target: targetId }) diff --git a/src/Settings.tsx b/src/Settings.tsx index 4f8db56..0a698e6 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, Fragment } from 'react' +import { getVersion } from '@tauri-apps/api/app' import { SETTINGS_KEYS } from './lib/settingsKeys' import { useAppStore } from './store/useAppStore' import { useSettingsStore } from './store/useSettingsStore' @@ -58,6 +59,13 @@ export default function Settings({ onClose }: { onClose?: () => void }) { }) }, []) + const [appVersion, setAppVersion] = useState('0.5.3') + useEffect(() => { + getVersion() + .then((ver) => setAppVersion(ver)) + .catch(() => {}) + }, []) + // Appearance State const initialSettings = useSettingsStore.getState() const [fontFamily, setFontFamily] = useState(initialSettings.fontFamily) @@ -250,6 +258,28 @@ export default function Settings({ onClose }: { onClose?: () => void }) { Check for Updates Now +
+ + +
@@ -348,6 +378,72 @@ export default function Settings({ onClose }: { onClose?: () => void }) { setAiColor(e.target.value)} />
+ +
+

About

+
+ PaperCache Logo +
PaperCache
+
+ Version {appVersion} +
+

+ Thank you for using PaperCache! We hope it helps organize your thoughts and boost your + daily productivity. +

+
+ + +
+
+
diff --git a/src/hooks/useNoteStorage.ts b/src/hooks/useNoteStorage.ts index 868976e..9883610 100644 --- a/src/hooks/useNoteStorage.ts +++ b/src/hooks/useNoteStorage.ts @@ -36,7 +36,7 @@ export function useNoteStorage() { useEffect(() => { const handleOpenNote = (e: Event) => { const customEvent = e as CustomEvent - let path = customEvent.detail.path + let path = customEvent.detail.path.replace(/\\/g, '/') if (!path.endsWith('.md')) path += '.md' // We need the latest notes, so use useAppStore.getState() diff --git a/src/lib/editor/markdownPlugin.ts b/src/lib/editor/markdownPlugin.ts index 413976d..a57e074 100644 --- a/src/lib/editor/markdownPlugin.ts +++ b/src/lib/editor/markdownPlugin.ts @@ -87,7 +87,7 @@ export const markdownPlugin = ViewPlugin.fromClass( if (linkPath.startsWith('/file')) { isFile = true - linkPath = linkPath.substring(5).trim() + linkPath = linkPath.substring(5).trim().replace(/\\/g, '/') } else if (linkPath.startsWith('/url')) { linkPath = linkPath.substring(4).trim() } diff --git a/src/setupTests.ts b/src/setupTests.ts index 5a89745..eaa73d6 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -2,6 +2,10 @@ import '@testing-library/jest-dom' import { vi, afterEach } from 'vitest' import type { ElectronAPI } from './types' +vi.mock('@tauri-apps/api/app', () => ({ + getVersion: vi.fn().mockResolvedValue('0.5.3'), +})) + // Mock matchMedia which is not present in jsdom but might be needed by some components if (typeof window !== 'undefined') { Object.defineProperty(window, 'matchMedia', {