Frontend Stack
The desktop application is built with modern web technologies wrapped in Tauri.
Technology Overview
React + Tauri
Tauri provides a secure, lightweight desktop runtime using Rust. The frontend is standard React.
Minimal Tauri + React Example
// main.tsx - Entry point
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// App.tsx - Basic routing
import { Routes, Route } from "react-router";
function App() {
return (
<Routes>
<Route path="/" element={<h1>Home</h1>} />
<Route path="/about" element={<h1>About</h1>} />
</Routes>
);
}
export default App;
WatermelonDB
WatermelonDB is an offline-first database for React. It uses SQLite under the hood.
Why WatermelonDB?
- Offline-first: Works without network
- Lazy loading: Only loads what's needed
- Reactive: Automatic UI updates on data changes
- Sync-ready: Built-in sync protocol support
Standalone Example: Schema + Model
// schema.ts - Define table structure
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
version: 1,
tables: [
tableSchema({
name: 'customers',
columns: [
{ name: 'contact_name', type: 'string' },
{ name: 'company_name', type: 'string' },
{ name: 'notes', type: 'string' },
{ name: 'created_at', type: 'number' },
{ name: 'updated_at', type: 'number' },
{ name: 'deleted', type: 'number' },
],
}),
],
});
// Customer.ts - Model class
import { Model } from '@nozbe/watermelondb';
import { field, date, readonly } from '@nozbe/watermelondb/decorators';
export default class Customer extends Model {
static table = 'customers';
@field('contact_name') contactName!: string;
@field('company_name') companyName!: string;
@field('notes') notes!: string;
@readonly @date('created_at') createdAt!: Date;
@date('updated_at') updatedAt!: Date;
}
// db.ts - Initialize database
import { Database } from '@nozbe/watermelondb';
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs';
import schema from './schema';
import Customer from './Customer';
const adapter = new LokiJSAdapter({
schema,
useWebWorker: false,
useIncrementalIndexedDB: true,
});
export const database = new Database({
adapter,
modelClasses: [Customer],
});
CRUD Operations
// Create
await database.write(async () => {
await database.get('customers').create((customer) => {
customer.contactName = 'John Doe';
customer.companyName = 'ACME Inc';
});
});
// Read (reactive)
const customers = database.get('customers').query().observe();
// Update
await database.write(async () => {
await customer.update((c) => {
c.contactName = 'Jane Doe';
});
});
// Delete (marks as deleted)
await database.write(async () => {
await customer.markAsDeleted();
});
Lexical.js
Lexical is a modern rich text editor framework from Meta.
Why Lexical?
- Framework agnostic: Works with any framework
- Plugin-based: Compose features as needed
- Extensible: Custom nodes and commands
- Performance: Optimized for large documents
Minimal Lexical Example
import { LexicalComposer } from '@lexical/react/LexicalComposer';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
function SimpleEditor() {
const initialConfig = {
namespace: 'MyEditor',
onError: (error: Error) => console.error(error),
};
return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<ContentEditable style={{ minHeight: 200, padding: 16 }} />}
placeholder={<div>Start typing...</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<HistoryPlugin />
<OnChangePlugin
onChange={(editorState) => {
// Save the state as JSON
console.log(JSON.stringify(editorState));
}}
/>
</LexicalComposer>
);
}
Saving/Loading Editor State
// Save state to string
const saveContent = (editorState: EditorState): string => {
return JSON.stringify(editorState);
};
// Load state from string
const loadContent = (editor: LexicalEditor, content: string) => {
const editorState = editor.parseEditorState(content);
editor.setEditorState(editorState);
};
Material UI
Material UI provides pre-built React components.
Example: Form with MUI
import { TextField, Button, Stack, Paper } from '@mui/material';
import { useState } from 'react';
function CustomerForm() {
const [form, setForm] = useState({ name: '', company: '' });
return (
<Paper sx={{ p: 3 }}>
<Stack spacing={2}>
<TextField
label="Contact Name"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<TextField
label="Company"
value={form.company}
onChange={(e) => setForm({ ...form, company: e.target.value })}
/>
<Button variant="contained" onClick={() => console.log(form)}>
Save
</Button>
</Stack>
</Paper>
);
}