electron-study-record
CLAUDE.md0 starsChanged 4 months agoMITView on GitHub
Notes: - npm run start passes --dev to Electron; main.js uses this switch to auto-open DevTools. - There is no test runner or linter configured in this repo. - Node 20.12.1 is the version the author developed against. Three-process Electron app following the standard main / preload / renderer split, plus a SQLite3 layer in the main process. When adding a feature that needs persistence or OS access: 1. Add an ipcMain.handle('study:foo', ...) in main.js. 2. Add a corresponding method…
- Installs packages
In this repository2 other files it gives its agents.
The file
We split this CLAUDE.md into the rules it gives an agent
5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 37
When adding a feature that needs persistence or OS access:
- Very specific: line 38
Add an ipcMain.handle('study:foo', ...) in main.js.
- Very specific: line 39
Add a corresponding method to the contextBridge.exposeInMainWorld('studyRecord', {...}) block in preload.js.
- Very specific: line 40
Call window.studyRecord.foo(...) from renderer.js.
- Very specific: line 60
Start/end shortcuts are handled by the renderer (window.studyRecord.onGlobalShortcut) which programmatically clicks the corresponding …
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Install dependencies
npm i
# Run in dev (nodemon auto-restarts Electron on changes to *.js/*.html/*.css)
npm run start
# Rebuild the sqlite3 native module against the current Electron version
# (run this if you see NODE_MODULE_VERSION / native binding errors after npm install)
npm run rebuild-native
# Package for Windows (NSIS x64). Runs rebuild-native first.
npm run distNotes:
npm run startpasses--devto Electron;main.jsuses this switch to auto-open DevTools.- There is no test runner or linter configured in this repo.
- Node 20.12.1 is the version the author developed against.
Architecture
Three-process Electron app following the standard main / preload / renderer split, plus a SQLite3 layer in the main process.
Process layout
main.js(main process): window lifecycle, tray, global shortcuts, SQLite3 connection, and ALLipcMain.handle(...)handlers (thestudy:*andapp:notifychannels).preload.js: exposes a singlewindow.studyRecordobject viacontextBridge, mapping each renderer method to its IPC channel. All renderer ↔ DB communication must go through this bridge — no directrequirein the renderer.renderer.js+index.html+styles.css: UI only. Useswindow.studyRecord.*for everything that touches disk or system features.
When adding a feature that needs persistence or OS access:
- Add an
ipcMain.handle('study:foo', ...)inmain.js. - Add a corresponding method to the
contextBridge.exposeInMainWorld('studyRecord', {...})block inpreload.js. - Call
window.studyRecord.foo(...)fromrenderer.js.
Window behavior (intentional, easy to break)
The main window is frame: false, titleBarStyle: 'hidden', alwaysOnTop: true, resizable: false, with the menu bar fully removed. The .titlebar element in index.html is the drag region — preserve it when editing the DOM. The window also intercepts close and hides to a tray icon (createTray) instead of quitting; only safeQuit (called from the tray menu or Ctrl+Alt+Q) actually exits, and it first sends global-shortcut: 'end' to the renderer so any in-progress session is closed in the DB before app.quit() fires ~500ms later.
Global shortcuts
Registered in registerGlobalShortcuts(win):
| Shortcut | Action |
| Ctrl+Alt+S | Start session (sends global-shortcut: 'start') |
| Ctrl+Alt+E | End session (sends global-shortcut: 'end') |
| Ctrl+Alt+1 | Show window |
| Ctrl+Alt+2 | Close window (hides to tray, per close handler) |
| Ctrl+Alt+Q | safeQuit — flush end-session then quit |
| Ctrl+Alt+M | Toggle mini mode (sends global-shortcut: 'toggleMiniMode') |
| Ctrl+Alt+T | Toggle theme (sends global-shortcut: 'toggleTheme') |
Start/end shortcuts are handled by the renderer (window.studyRecord.onGlobalShortcut) which programmatically clicks the corresponding button so UI state stays consistent.
Database
- SQLite3 via the
sqlite3native module. Two tables created ininitDatabase():study_sessions(id, start_time, end_time, duration, created_at)— one row per study session;duration(seconds) is computed instudy:end-session.user_config(key, value)— KV store. Used keys:stat_time_range(JSON{start,end}),notified_over_2h_state(JSON{date,count}for daily-resetting 2h notification).
getDatabasePath()returnsouter/study_time.dbin dev andresources/app.asar.unpacked/outer/study_time.dbwhen packaged. Theouter/folder is listed underbuild.asarUnpackinpackage.jsonprecisely so the DB remains writable from the installed app — do not movestudy_time.dbout ofouter/without updating both that config andgetDatabasePath().sql/*.sqlfiles are reference schemas only; they are not executed at runtime (theCREATE TABLE IF NOT EXISTScalls inmain.jsare authoritative).
Time handling (subtle)
Timestamps stored in study_sessions.start_time / end_time are written by the renderer using toChinaTimeString() — "YYYY-MM-DD HH:mm:ss" strings representing China time (UTC+8) with no timezone suffix. parseChinaTime() (renderer) and parseDbTime() (main) handle this format plus legacy shapes (T separator with +08:00 / Z / no suffix, pre-v5 data). When adding code that reads or writes these columns, use these helpers rather than new Date(...) — bare new Date() interprets "YYYY-MM-DD HH:mm:ss" as UTC and will shift timestamps by 8 hours.
The "today" window is configurable: stat_time_range defaults to 05:00-05:00 (a full day starting at 5am, which spans midnight). inStatRange() in renderer.js is the canonical implementation of "does this timestamp belong to the current statistical day?" — reuse it instead of doing date math inline.
Daily quote
Quotes live in the study_quotes table (added by a later migration). loadWordsFromDB() reads enabled rows at startup into the in-memory words array; study:get-word returns a random entry. The legacy outer/word.txt file has been removed — quote CRUD goes through the quote:* IPC handlers and reflects on next app start (or call loadWordsFromDB() again to refresh).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

