This document helps AI agents contribute to TimeLens safely and consistently.
TimeLens is a local-first screen-time tracker and desktop widget platform built with:
- Frontend: React + TypeScript + Vite + Tailwind CSS
- Desktop host: Tauri v2 + Rust
- Database: SQLite (local only, no cloud)
- Extensions: Browser extension (Edge/Chrome), VS Code extension
The project root contains the web frontend. The Tauri backend lives in src-tauri/.
Always run these after non-trivial changes:
npm run typecheck # TypeScript check
npm run lint # ESLint (expect 8 pre-existing warnings)
npm run test # Frontend unit testsFor Rust/backend changes (run from src-tauri/):
cargo test
cargo checkFor a full release build:
npm run tauri:buildsrc/pages/— Full-page views (Dashboard, Settings, Focus Mode, Widget Center, etc.)src/widgets/— First-party widget UIs (Todo, Note, Pet, Focus Coach, etc.)src/components/— Shared reusable componentssrc/hooks/— Shared React hookssrc/stores/— Zustand state storessrc/services/tauriApi.ts— All Tauri command wrapperssrc/services/llmApi.ts— LLM streaming and screen-time context buildersrc/types/index.ts— Shared TypeScript typessrc/types/llm.ts— LLM provider, config, conversation typessrc/i18n/locales/— Translation JSON files (en,zh-CN,zh-TW,ja,ko,de,fr,es)src/styles/globals.css— Tailwind entry + custom glassmorphism utilities
commands/— Tauri command handlersdb/— SQLite schema, migrations, and query helpersdb/llm_conversations.rs— Persisted AI conversation storagellm/— Local LLM config (TOML) and provider modelmodels/— Shared Rust data modelsmonitor/— Active window / screen-time monitoringwidget_registry.rs— Widget manifest loading and normalization
Widgets are loaded as separate Tauri webview windows. Official widgets live in src/widgets/. Third-party widgets can be imported from local directories.
Each widget receives a widgetId prop. Use it to namespace localStorage keys (e.g. ${widgetId}-notes).
- Use functional components and hooks.
- Prefer
clsxfor conditional class names. - Keep UI text in
i18nJSON files; never hardcode user-facing strings. - Add new i18n keys to
enandzh-CNfirst; use English stubs for other languages unless you can translate accurately. - Use the existing
glass-card,ui-field,ui-checkbox,btn-primaryutilities instead of inventing new styles.
- Tauri commands return
Result<T, String>for user-facing errors. - Database access goes through
DbState(aMutex<Connection>). - New tables need a migration in
src-tauri/src/db/migrations.rs. - When changing Rust models, update any hand-constructed instances in tests and commands.
Inside a widget, window.dispatchEvent only reaches the same window. To notify other widget windows or the main app, use Tauri events:
import { emit } from "@tauri-apps/api/event";
import { listen } from "@tauri-apps/api/event";Use the useWidgetErrorReporter hook to automatically record unhandled errors to the per-widget error log:
import { useWidgetErrorReporter } from "@/hooks/useWidgetErrorReporter";
export default function MyWidget({ widgetId }: Props) {
useWidgetErrorReporter(widgetId);
// ...
}When adding user-facing text:
- Add key to
src/i18n/locales/en/<namespace>.json - Add key to
src/i18n/locales/zh-CN/<namespace>.json - Add English stub to
src/i18n/locales/{es,de,fr,ko,ja,zh-TW}/<namespace>.json
Namespaces include: common, dashboard, widgets, settings, limits, categories, goals, focus, browserUsage, llm.
When bumping the app version, update all of these:
package.jsonpackage-lock.json(top-level + root package entries)src-tauri/Cargo.tomlsrc-tauri/tauri.conf.jsonsrc-tauri/Cargo.lock(runcargo update -p timelensfromsrc-tauri/)src-tauri/windows/Package.appxmanifestCHANGELOG.md
The src/version.ts file re-exports package.json version, so it does not need manual editing.
- Date parsing: Backend stores local datetimes as
YYYY-MM-DDTHH:MM:SS. Parsing withnew Date()can interpret them as UTC and shift by the local timezone offset. Use a local-component parser when computing durations. - Dropdown z-index:
ExePickerInputand similar popovers may render under later cards. Increasez-indexon both the wrapper and the popup if needed. - Widget window events: Each widget is its own window; use Tauri
emit/listenfor cross-widget communication. - Focus rules: Frontend
FocusRuledoes not includecreated_at; the backend model must keep it optional to avoid deserialization failures. - Cargo lockfile: After editing
Cargo.toml, runcargo update -p timelensinstead of a fullcargo updateto avoid unnecessary dependency churn. - LLM config: Provider settings live in
llm_config.tomlin the app data directory. API keys are stored locally in plain text; never log or expose them. - LLM conversations: Conversations are persisted in SQLite (
llm_conversationstable, migration 013). New tables need a migration insrc-tauri/src/db/migrations.rs. - Analysis context:
buildScreenTimeContextrespectsLlmDataSharingflags andAnalysisRange. When adding new data sources, expose them through both the data-sharing toggle and the context builder.
Add a new section to CHANGELOG.md for every version bump. Follow the existing Keep a Changelog format with Added, Changed, and Fixed subsections.