This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Do not use excessive emojis anywhere.
The app is a thin Electron shell over a PyInstaller-bundled Python CLI. There is no long-running Python service — every operation is a subprocess invocation.
- Electron main (
app/main.js, ~4.3k lines) owns the UI window, tray, deep-link protocol, and orchestrates everything viaipcMain.handle(...). Handlers shell out to the bundled backend throughgetBackendPath()→process.resourcesPath/stenoai/stenoai(ordist/stenoai/stenoaiin dev) usingchild_process.spawn. - Renderer (
app/index.html, ~9.7k lines) is a single-page HTML/JS UI; no framework. All cross-process work goes through IPC. - Python CLI (
simple_recorder.py, ~2.5k lines, ~46 click commands) is the single entry point bundled bystenoai.spec. Sub-modules insrc/:audio_recorder(sounddevice),transcriber(pywhispercpp),summarizer(Ollama HTTP client),ollama_manager(lifecycle of the bundledollama serve),config(JSON-backed user settings + model registry),folders,models. - State across CLI invocations is persisted to
recorder_state.jsonand similar small JSON files — there is no daemon. Long-running recordings are arecordsubprocess kept alive by the Electron main process. - User data lives in
~/Library/Application Support/stenoai/(recordings/,transcripts/,output/), resolved viasrc.config.get_data_dirs(). Repo-rootrecordings//transcripts//output/dirs are dev-only scratch. - Bundled binaries (
bin/): Ollama + ffmpeg, downloaded byscripts/download-ollama.sh. PyInstaller copies them intodist/stenoai/ollama/anddist/stenoai/ffmpeg. Electron then re-bundlesdist/stenoai/as anextraResource. - Deep links: app registers the
stenoai://URL scheme. Handler logic is inapp/main.jsnearSHORTCUT_PROTOCOL. Used by macOS Shortcuts:stenoai://record/start?name=...andstenoai://record/stop.
- Build the bundled backend:
source venv/bin/activate && pyinstaller stenoai.spec --noconfirm - Inspect CLI surface:
dist/stenoai/stenoai --help - Most relevant CLI commands for debugging:
status,setup-check,list_failed,reprocess path/to/summary.json,query transcript.txt,pipeline filename.wav - Lint:
ruff check . - Run all tests:
python -m unittest discover tests - Run a single test:
python -m unittest tests.test_config.ConfigStoragePathTests.test_set_storage_path_handles_permission_errors
- Start app (dev):
cd app && npm start - Build DMG (local, for testing):
cd app && npm run build
For setup from a clean checkout, see CONTRIBUTING.md and README.md.
This app ships as a signed DMG to real users. Before considering any change complete:
- Packaged app test: Dev mode (
npm start) is not sufficient. Always rebuild the DMG (npm run build) and test the installed app from/Applications. - Cold start test: Kill all background processes (
pkill -f ollama) and launch the app fresh. The full pipeline (record, transcribe, summarize) must work with no pre-existing services running. - No shelling out to bundled binaries for operations that have an HTTP/library API. macOS SIP + Electron hardened runtime strips
DYLD_LIBRARY_PATHfrom child processes. Use theollamaPython package (HTTP API) for model operations, notsubprocess.run([ollama_path, ...]). The only acceptable use of the Ollama binary isollama serve(starting the server), which is covered by thecom.apple.security.cs.allow-dyld-environment-variablesentitlement. - No bare
exit()in Python code. PyInstaller bundles don't haveexitas a builtin. Always usesys.exit().
StenoAI logo gradient (used in website logo SVG and app header):
- Indigo:
#6366f1 - Sky blue:
#0ea5e9 - Cyan:
#06b6d4 - CSS:
linear-gradient(135deg, #6366f1, #0ea5e9, #06b6d4)
App UI accent: --accent-primary: #818cf8 (lighter indigo, used for focus states, active tabs, toggles)
- Always create a branch for changes unless explicitly told otherwise
- Never commit directly to
main - Before creating a PR, run a self-review of the full branch diff (
git diff main...HEAD):- Review backend code for security issues, error handling gaps, edge cases, and best practices
- Review frontend code for layout bugs, CSS consistency, accessibility, and polish
- Use the frontend-design skill for UI-related changes
- Categorize findings by severity (critical/medium/low) and fix critical issues before merging
- Do NOT include "Generated with Claude Code" attribution in commit messages
- Do NOT include "Co-Authored-By: Claude noreply@anthropic.com" in commit messages
- Keep commit messages concise and focused on what changed
- Use conventional commit format when appropriate (feat:, fix:, docs:, etc.)
Releases are automated via .github/workflows/build-release.yml. Never create releases manually.
- Bump version in
app/package.json(on the branch, before merging) - After PR is merged to
main, create an annotated tag on main with the release notes in the tag message:git tag -a v0.2.5 -m "Release notes here..." git push origin v0.2.5 - The tag push triggers the workflow which:
- Builds signed + notarized DMGs for both arm64 and x64
- Creates a GitHub Release with the tag message as the "What's New" section
- Uploads both DMGs as release assets
- The tag message becomes the release notes body — write it as markdown with a summary of changes
- Do NOT build DMGs locally for releases, do NOT use
gh release createmanually
The README has a "What's New" table that should be updated every ~2 weeks. When asked to update it (or when shipping a notable feature):
- Check recently merged PRs:
gh pr list --state merged --limit 10 - For each notable PR, add a row to the table with the merge date and a one-sentence summary
- Keep "Coming soon" items for features that are planned but not yet shipped
- Remove entries older than ~2 months to keep the section fresh
- Most recent entries go at the top of the table
When the user says "log session" or similar (e.g., "update session log", "document this session"):
- Update SESSION_LOG.md in the root directory with the current session details
- Include: date/time, summary of work, key decisions, files modified, issues resolved, next steps
- REPLACE or CONDENSE previous session entries to keep the file concise (max 2-3 most recent sessions)
- Keep only relevant context for the next Claude session - remove outdated or completed work details
- Format with clear headers and organized sections