Skip to content
This repository was archived by the owner on Mar 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 132 additions & 134 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,150 +1,148 @@
# Copilot Instructions for Notes App
# NoteX Copilot Instructions

## Project Architecture
## Project Overview
NoteX is a local-first, markdown-based note-taking app built with Tauri v2 (Rust backend + Vue 3 frontend). Notes are stored in SQLite and support cloud sync via custom database paths (OneDrive, Google Drive, etc.).

This is a **Tauri v2 + Vue 3 + Vite** desktop application using a monorepo structure managed by Bun workspaces.
## Architecture

### Repository Structure
- **Root**: Monorepo managed with Bun workspaces (`bun.lock`, `package.json`)
- **`desktop/`**: The Tauri application workspace
- Frontend: Vue 3 SFCs with `<script setup>` syntax
- Build tool: Vite with Tailwind CSS v4
- Backend: Rust using Tauri framework v2
### Monorepo Structure
- **`desktop/`**: Tauri application (main product)
- `src/`: Vue 3 frontend (Composition API with `<script setup>`)
- `src-tauri/`: Rust backend with Tauri commands
- **`apps/website/`**: Nuxt 3 marketing site (SSG mode)
- **Root**: Bun workspace configuration

### Key Architecture Points
- The Rust library is named `notes_lib` (not just `notes`) to avoid Windows binary name conflicts
- Tauri backend is in `desktop/src-tauri/src/lib.rs` with commands registered via `tauri::generate_handler![]`
- Frontend runs on `localhost:1420` (strict port) during development
- Vite HMR uses port `1421` when using `TAURI_DEV_HOST`
### Frontend-Backend Communication
- Frontend: Uses `@tauri-apps/api` to invoke Rust commands via `invoke()`
- Backend: Rust functions annotated with `#[tauri::command]` and registered in `invoke_handler![]`
- Database: SQLite accessed via `@tauri-apps/plugin-sql` (frontend) and managed by Rust (backend)

### Key Data Flow
1. **Database Path**: Rust backend (`get_database_path()`) determines SQLite location (custom or default)
2. **Frontend Init**: Vue component loads DB path via `invoke('get_database_path_cmd')`, then connects using `Database.load()`
3. **Notes Storage**: All CRUD operations use SQLite, no in-memory state persistence

## Critical Patterns

### Database Management
- **Dual Environment Support**: `TAURI_ENV_USE_TEST_DB=true` switches to `notes_test.db` (dev) vs `notes.db` (prod)
- **Custom Paths**: Users can set custom DB directory (e.g., iCloud folder) via settings panel
- **Path Resolution**: See [lib.rs](desktop/src-tauri/src/lib.rs) `get_database_path()` for logic
- Frontend receives env via Vite config: `import.meta.env.VITE_USE_TEST_DB`

### Markdown Rendering
- Uses `marked` (GFM) + `shiki` for syntax highlighting
- **Dual Theme Support**: Generates both light/dark highlighted code in [markdown.ts](desktop/src/utils/markdown.ts)
- **Custom Renderer**: Links open in new tabs, code blocks have copy buttons
- **Tag Extraction**: Hashtags like `#work` extracted via `TAG_REGEX` in [regex.ts](desktop/src/utils/regex.ts)

### Tag System
- Pattern: `(?<=\s|^)#([a-zA-Z][\w+-]*)/g` - matches hashtags at word boundaries
- Tags extracted from note content in real-time (no separate DB table)
- Used for filtering in [TagsPanel.vue](desktop/src/components/TagsPanel.vue) and [App.vue](desktop/src/App.vue)

### State Management
- **No Vuex/Pinia**: Uses Vue 3 Composition API with reactive refs in root [App.vue](desktop/src/App.vue)
- **Infinite Scroll**: `displayedNotesCount` increases by `NOTES_PER_PAGE` (20) on scroll
- **Fuzzy Search**: Uses `Fuse.js` for note content search

## Development Workflows

### Running the Application
### Running the App
```bash
# From root - uses workspace filter
bun run desktop
# Development (with test database)
bun run dev # Runs: cross-env TAURI_ENV_USE_TEST_DB=true bun run --filter desktop tauri dev

# Production mode (uses notes.db)
bun run prod # Runs: bun run --filter desktop tauri dev

# From desktop/ directory
bun run tauri dev
# Build for release
bun run build # Compiles Rust + bundles Vue
```

### Build Commands
### Code Quality
```bash
# Development (frontend only)
cd desktop && bun run dev
bun run lint # oxlint on desktop/src/**/*.{ts,js,vue}
bun run format # Prettier
bun run format:check # Verify formatting
```

# Production build (creates bundle)
cd desktop && bun run build
cd desktop && bun run tauri build
### Key Points
- **Bun workspace**: Root package.json defines workspaces, use `--filter desktop` for app commands
- **Vite HMR**: Runs on port 1420 (see [vite.config.js](desktop/vite.config.js))
- **Tauri dev**: Watches Rust changes, rebuilds on save

## Component Conventions

### Vue Components
- Use `<script setup lang="ts">` exclusively
- Props: `defineProps<Props>()` with TypeScript interfaces
- Emits: `defineEmits<{eventName: [arg: type]}>()` pattern
- Styling: Scoped `<style scoped>` with CSS variables from [theme.css](desktop/src/styles/theme.css)

### Example Pattern (from [NoteItem.vue](desktop/src/components/NoteItem.vue)):
```typescript
interface Props {
note: Note;
}
const props = defineProps<Props>();
const emit = defineEmits<{
delete: [id: number];
edit: [id: number, content: string];
}>();
```

### Project Setup
- Uses **Bun** as the package manager (not npm/yarn)
- Rust toolchain required for Tauri backend
- VSCode extensions recommended: `Vue.volar`, `tauri-apps.tauri-vscode`, `rust-lang.rust-analyzer`

## Technology Stack Conventions

### Vue 3 (Frontend)
- Use `<script setup lang="ts">` syntax (Composition API)
- Global styles imported in `App.vue`: `import './styles/theme.css'` and `import './styles/global.css'`
- Theme system uses `desktop/src/styles/theme.css` with `@theme` directive
- **Prefer Vue scoped styles over Tailwind classes** for component-specific styling
- Use `<style scoped>` blocks in Vue components for better maintainability and encapsulation
- Leverage CSS custom properties from theme.css (e.g., `var(--color-text-primary)`, `var(--color-surface)`)
- Only use Tailwind for prototyping or when rapid iteration is needed
- Semantic class names in scoped styles improve readability and maintenance
- **Dynamic Classes**: Always use Vue's `:class` binding for conditional/dynamic classes
- Use array syntax with objects for conditional classes: `:class="['base-class', { 'conditional-class': condition }]"`
- Never build class strings manually with computed properties using array.push() or string concatenation
- Example: `:class="[{ 'text-lg': size === 'large', 'text-sm': size === 'small' }]"` instead of computed string building
- **Tailwind Custom Properties**: Use direct Tailwind utility classes based on theme tokens
- Custom properties defined in `@theme` in theme.css automatically become Tailwind utilities
- Example: `--color-text-primary` becomes `text-text-primary` utility class
- Example: `--color-surface` becomes `bg-surface` utility class
- **Theme System**: Two-tier token architecture for maintainability
- **Base Tokens** (named after color/value): Raw values like `--color-gray-100`, `--color-gray-200`, etc.
- Grayscale: `gray-100` (lightest) through `gray-900` (darkest)
- Typography: `font-family-base`, `font-family-mono`
- **Semantic Tokens** (named after purpose): Context-specific tokens that reference base tokens
- Backgrounds: `background`, `background-overlay`
- Surfaces: `surface`, `surface-hover`, `surface-active`
- Borders: `border`, `border-hover`, `border-active`
- Text: `text-primary`, `text-secondary`
- **Always use semantic tokens** in components (e.g., `text-text-primary`, `bg-surface`, `border-border`)
- Semantic tokens automatically adapt to light/dark mode via `body.dark` overrides
- **Light & Dark Mode**: Full theme support with toggle button (`ThemeToggle.vue`)
- Light mode: Uses lighter grayscale values (`gray-100`, `gray-200`, `gray-700`)
- Dark mode: Uses darker grayscale values (`gray-800`, `gray-900`, `gray-200`)
- Theme persists via localStorage and respects system preference on first load
- Dark mode class added to `<body>` element: `body.dark`
- **Styling Approach**:
- **Prefer Vue scoped styles** (`<style scoped>`) for component-specific styling
- Use semantic CSS class names that describe purpose (e.g., `.action-button`, `.info-card`, `.panel-header`)
- Reference theme CSS custom properties directly in scoped styles (e.g., `var(--color-surface)`, `var(--color-text-primary)`)
- Reserve Tailwind utility classes for prototyping or quick iterations only
- Benefits: Better maintainability, clearer component boundaries, easier refactoring, improved readability
- **Design principles**: Clean minimal aesthetics with semantic color system, subtle shadows, smooth transitions (0.3s), high contrast for readability

#### Component Architecture
- All reusable components are located in `desktop/src/components/`
- Components use TypeScript with proper interface definitions for props and emits
- Follow Vue 3 Composition API patterns with `<script setup lang="ts">`
- Component naming: PascalCase for files (e.g., `CalendarView.vue`, `NoteItem.vue`)
- Props are defined using `defineProps<PropsInterface>()` with TypeScript interfaces
- Events are defined using `defineEmits<EmitsInterface>()` with typed event signatures
- Components should be self-contained and focused on a single responsibility
- Prefer props and events over complex state management for component communication
- Example components in the project:
- `CalendarView.vue` - Calendar with date selection and note counts
- `TagsPanel.vue` - Tag filtering interface
- `NoteCreator.vue` - Note creation form
- `NoteItem.vue` - Individual note display with edit/delete
- `EmptyState.vue` - Empty state messaging
- `SearchBar.vue` - Search input with clear functionality
- `ThemeToggle.vue` - Light/dark mode toggle button

### Tauri (Backend)
- Commands defined with `#[tauri::command]` attribute in `lib.rs`
- Register new commands in `invoke_handler` macro: `tauri::generate_handler![greet, new_command]`
- Plugin system: Currently uses `tauri-plugin-opener` for external links
- Window configuration in `tauri.conf.json` (800x600 default window)

### Rust Specifics
- Library crate types: `["staticlib", "cdylib", "rlib"]`
- Dependencies: `serde`, `serde_json` for JSON serialization
- The `main.rs` is minimal - just calls `notes_lib::run()`

## Integration Patterns
### Tauri Commands
- Rust: `#[tauri::command]` + register in `invoke_handler![]` in [lib.rs](desktop/src-tauri/src/lib.rs#L380)
- Frontend: `await invoke<ReturnType>('command_name', { arg: value })`
- Error handling: Return `Result<T, String>` in Rust

### Frontend-Backend Communication
- Use `invoke()` from `@tauri-apps/api` to call Rust commands
- Example pattern from existing code:
```rust
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
```
- Access from Vue: Import `invoke` and call async: `await invoke('greet', { name: 'World' })`

### File Locations
- Tauri config: `desktop/src-tauri/tauri.conf.json`
- Capabilities/permissions: `desktop/src-tauri/capabilities/default.json`
- App icons: `desktop/src-tauri/icons/`
- Build artifacts: `desktop/dist` (frontend), `desktop/src-tauri/target` (Rust - gitignored)

## Project-Specific Notes

- **Identifier**: `com.crazypanda.notes` (used for app bundle)
- **License**: GPL-3.0
- Early stage project (README says "TODO", minimal implementation)
- Uses `better-sqlite3` types (devDependency) suggesting planned database integration
- Has Electron in devDependencies (legacy/alternate approach?)

## Development Tips

- Vite config prevents screen clearing to avoid obscuring Rust errors
- HMR watches exclude `src-tauri/**` to prevent unnecessary reloads
- Build scripts configured in `tauri.conf.json`: `beforeDevCommand`, `beforeBuildCommand`
- Target directory structure in Rust follows Windows conventions (`.dll`, `.pdb`, etc.)
## Styling

### Theme System
- CSS variables defined in [theme.css](desktop/src/styles/theme.css)
- Dark mode: `[data-theme='dark']` attribute on `<html>`
- Theme toggle: Persists to `localStorage` via [ThemeToggle.vue](desktop/src/components/ThemeToggle.vue)

### CSS Patterns
- Base reset: [reset.css](desktop/src/styles/reset.css)
- Global styles: [global.css](desktop/src/styles/global.css)
- Markdown styles: [markdown.css](desktop/src/styles/markdown.css) (code blocks, lists, etc.)

## Testing & Debugging

### Test Database
- Always use `bun run dev` (sets `TAURI_ENV_USE_TEST_DB=true`)
- Backend checks `env::var("TAURI_ENV_USE_TEST_DB")` to switch DB files
- Frontend reads `import.meta.env.VITE_USE_TEST_DB` (forwarded by Vite)

### Common Issues
- **DB path not found**: Check `get_database_path()` logs in terminal
- **Tauri command not found**: Verify registration in `invoke_handler![]`
- **HMR not working**: Ensure Vite server on port 1420, check firewall

## Build Configuration

### Tauri Config ([tauri.conf.json](desktop/src-tauri/tauri.conf.json))
- Product name: "NoteX"
- Bundle identifier: "com.notex"
- Dev command: `bun run dev` (Vite server)
- Build command: `bun run build` (produces `dist/`)
- Window: 1366×768 default, min 800×600

### Version Management
- Single source: root [package.json](package.json) version field
- Tauri uses: `"version": "../package.json"` to inherit version

## External Dependencies

### Frontend
- `@tauri-apps/plugin-sql`: SQLite integration
- `@tauri-apps/plugin-dialog`: Native dialogs (file picker)
- `marked`: GFM markdown parser
- `shiki`: Code syntax highlighting
- `fuse.js`: Fuzzy search

### Backend
- `tauri-plugin-sql`: SQLite plugin for Rust
- `serde_json`: JSON serialization for config files
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
### 📝 **Markdown Support**
- Write your notes in plain text files, take your data anywhere
- Supports GitHub Flavored Markdown (GFM)
- Copy and paste images directly into notes
- Images stored locally alongside your notes

### 📅 **Smart Organization**
- Organize notes with hashtags (`#work`, `#personal`, etc.)
Expand Down
8 changes: 8 additions & 0 deletions desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
tauri = { version = "2", features = ["protocol-asset"] }
tauri-plugin-opener = "2"
tauri-plugin-dialog = "2.0"
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
base64 = "0.22"

Loading