This guide helps AI agents understand the Freelens codebase, common development tasks, troubleshooting patterns, and key architectural decisions. Use this as a reference when working on the project.
For local development environment setup and extra development tips, see DEVELOPMENT.md.
freelens/- Main Electron applicationsrc/main/- Main Electron process codesrc/renderer/- Renderer process (UI) codesrc/common/- Shared code between processes
packages/core/- Core functionalitysrc/features/- Feature modules organized by domainsrc/renderer/- Renderer-specific utilitiessrc/extensions/- Extension system
packages/- Monorepo packages (utilities, components, etc.)scripts/- Build and development scripts
Never read, display, reference, or include the contents of the following files in any response or context, even if they are open in the editor:
.env.env.*.npmrc*.jks*.keystore*.p12*.pfx*.pem*.key
pnpm build:di # Generate DI registration files
pnpm build # Build all packages
pnpm build:app:dir # Build Electron app directory
pnpm start # Start development app
pnpm test # Run testsThis project uses Turbo for caching build artifacts.
When facing caching issues:
rm -rf .turbo packages/core/dist freelens/dist
pnpm buildThis project uses @ogre-tools/injectable for dependency injection with an explicit registration system that replaces the old webpack-based auto-registration. All injectable registrations are generated by pnpm build:di.
The system has three levels of registration files:
-
Leaf registration files - Register individual injectables
- Example:
features/preferences/renderer/close-preferences/register-injectables.ts - Pattern: Import injectable definitions and call
di.register() - Each call wrapped in try-catch for idempotency
- Example:
-
Aggregator registration files - Register subdirectories
- Example:
features/preferences/renderer/register-injectables.ts - Pattern: Import and call
registerXxxInjectables(di)from subdirectories - Each call wrapped in try-catch to handle duplicates
- Example:
-
Root registration files - Entry points per process
register-injectables-main.ts- Main processregister-injectables-renderer.ts- Renderer process- Called during DI container initialization
Directories with register-injectables.ts that aggregate subdirectories:
features/vars/
├── register-injectables.ts ← Shared aggregator (calls common/)
├── common/
│ └── register-injectables.ts
└── build-version/
├── main/register-injectables.ts ← Process-specific (NOT aggregated)
└── renderer/register-injectables.ts ← Process-specific (NOT aggregated)
Key insight: Shared aggregators only handle shared subdirectories (like common/). They do NOT aggregate process-specific subdirectories (main/, renderer/).
Paths containing /main/ or /renderer/ are process-specific and must be imported directly:
- ✅ Include:
features/vars/build-version/main/register-injectables - ❌ Exclude:
features/vars/common/register-injectables(handled by parent aggregator)
Run pnpm build:di when:
- Adding new injectable files
- Moving injectable files
- Renaming injectable files
- Changing directory structure
- Modifying generation script
The build process automatically runs this, but you can run it manually to verify changes.
- Create feature directory under
packages/core/src/features/ - Organize by concern:
common/,main/,renderer/ - Create injectable files with
.injectable.tssuffix - Run
pnpm build:dito generate registration files - Write tests alongside implementation
Main Process:
- Logs in terminal where
pnpm startwas run - Use
console.log()or proper logger
Renderer Process:
- Open DevTools in the app
- Check Console tab for errors and logs
- Use React DevTools for component inspection
Common Errors:
Tried to register same injectable multiple times- See DI section aboveTried to inject non-registered injectable- Check registration files were generated- Permission errors on macOS - Expected during development
The project uses Webpack for bundling:
freelens/webpack/- Webpack configuration- Changes to source files auto-rebuild in dev mode
- Changes to generated files require full rebuild
Cache issues: Delete dist folders and rebuild
- Check if file is in ignored directory (
dist/,node_modules/) - Clear webpack cache:
rm -rf packages/core/dist freelens/dist - Full rebuild:
pnpm build - Restart application:
pnpm start
- Check for TypeScript errors:
pnpm type-check - Check for linting errors:
pnpm lint - Verify dependencies:
pnpm install - Check Node.js version matches
.nvmrc
- Check dev console (renderer) or terminal (main)
- Look for stack traces with file:line numbers
- Verify all dependencies are registered (DI system)
- Check for circular dependencies
- Main process - Node.js environment, system access
- Renderer process - Chromium browser, UI
- IPC - Communication between processes
Features are self-contained modules with:
- Domain logic
- UI components
- State management
- Injectable definitions
Uses pnpm workspaces for:
- Shared code reuse
- Faster builds
- Type safety across packages
- Always regenerate DI files after adding/moving injectables
- Full rebuild when in doubt about cached state
- Check both processes when debugging (main + renderer)
- Use semantic search to find examples in codebase
- Follow existing patterns - grep for similar implementations
- Test changes before committing
- Run validation after file changes (especially before commit): run
trunk check(orpnpm trunk checkiftrunkis not installed locally) - For main project TypeScript and HTML files: run
biome checkdirectly (orpnpm biome checkifbiomeis not installed locally) - For other file types: use
trunk check(orpnpm trunk checkiftrunkis not installed locally) - Do not use Antropic Fable for coding tasks — Fable may be used only for planning, analysis, and thinking through problems. When writing or editing code, use standard editing tools instead.
When operating via the claude.yaml workflow (i.e., invoked from a PR comment,
issue, or review), follow these rules:
When reviewing code and proposing fixes:
-
Show the diff first — present every proposed change as a unified diff block using the
difflanguage tag:--- a/path/to/file.ts +++ b/path/to/file.ts @@ -10,7 +10,7 @@ const oldLine = "before"; -const changedLine = "after"; +const changedLine = "the fix"; const unchangedLine = "same";
You can generate this from the terminal with:
git diff -u -- path/to/file
If the change spans multiple files, group them under a single commit subject and show each file's diff sequentially.
-
Propose a commit subject first — before any code change, output a single line with the proposed commit subject:
**Proposed commit:** <short description>Do not use Conventional Commits prefixes (e.g.
fix:,feat:,chore:,refactor:,docs:,test:,ci:). This project prefers plain, descriptive commit messages and PR titles without any prefix.Wait for the user to confirm (or adjust) the subject before applying the change.
-
Comment style:
-
Keep review comments concise and actionable
-
Reference specific lines (file + line number) when pointing out issues
-
Offer a concrete fix suggestion rather than just flagging a problem
-
Do not use emoji in any Markdown, comments, commit messages, or PR descriptions. The only exception is emoji that already appears inside code strings (e.g. application logs, user-facing messages).
-
Use GitHub's
suggestionblock for small targeted fixes so the PR author can accept the change with a single click:<same unified-diff format as shown above> -
For larger multi-file changes, use
diff -ublocks in a regular comment instead, with the proposed commit subject shown first
-
When asked to implement a change on a PR:
- Propose the commit subject (as above)
- Describe what will change and why
- After confirmation, apply the changes with commits on the PR branch
- One commit per fix — when a review surfaces more than one issue or the plan includes more than one fix, apply and commit each fix separately. Do not batch multiple independent fixes into a single commit. This keeps the history bisectable and makes each change easy to revert individually.
Claude cannot push changes to files under .github/workflows/ directly,
because the GitHub token used by the action lacks the workflows permission.
Any patch to a workflow file MUST therefore be delivered as a new, complete
file under the github-workflow-fix/ directory instead of editing the file in
place:
- Write the full, final contents of the workflow to
github-workflow-fix/<workflow-file-name>(e.g.github-workflow-fix/claude.yaml). Do not edit the original file under.github/workflows/. - Make it a complete file — the entire workflow as it should look after the change, not just a diff or fragment — so it can be copied verbatim.
- Commit and open the PR as usual. In the PR description, clearly note that
the file is a proposed workflow change and that a maintainer must move it
from
github-workflow-fix/to.github/workflows/manually.
This lets the PR be created successfully while leaving the actual workflow change for a human to apply.
When creating a branch from an issue, use a human-readable name that includes the issue number and a short slug derived from the issue title:
claude/issue-<number>-<short-slug>
<number>is the GitHub issue number<short-slug>is a kebab-case summary of the issue title, kept short (3–6 words maximum, omit articles and filler words)
Examples:
- Issue #1957 "Add PR title convention rule for agent-related changes"
→
claude/issue-1957-add-pr-title-rules - Issue #42 "Fix crash when opening preferences dialog"
→
claude/issue-42-fix-preferences-crash
Do not use auto-generated timestamp suffixes (e.g.
claude/issue-1957-20260612-2108) — these are not human-readable and make
branch lists hard to scan.
When creating a PR, use the following title conventions:
-
Agent-related changes — PRs whose changes are strictly related to coding agent configuration (e.g.
AGENTS.md,.github/workflows/claude.yaml, or other files that govern how Claude operates in this repository) MUST use the prefixClaude:(followed by a space) in the title.Examples:
Claude: Add rule for PR title conventions in AGENTS.mdClaude: Update claude.yaml workflow permissions
-
All other PRs — do not use any prefix (no
fix:,feat:,chore:, etc.). Use plain, descriptive titles.
When you have commits ready to push but the PR originates from a fork
(different owner than freelensapp), you cannot push to the fork's
repository. Instead:
-
Create a new branch on
freelensapp/freelenswith the prefixclaude/followed by the original branch name. Push to theupstreamremote (notorigin, which points to the fork):git checkout -b claude/<original-branch-name> git push --force-with-lease upstream claude/<original-branch-name>
-
Open a new PR from that branch. The new PR MUST use the exact same title as the original PR — copy it verbatim, do not rewrite, improve, or add any prefix. The description MUST reference the original PR (e.g. "Fixes #NNN, supersedes #NNN").
-
Post a comment on the original PR:
- Explain that the fix has been implemented in a new PR
- Include a link to the new PR
- Mention that the original PR can be closed
-
Close the original PR.
Claude may only close a PR when ALL of the following are true:
- The PR was created by Claude from a
claude/branch, OR the PR is the original fork PR that Claude'sclaude/branch supersedes (see "Pushing Changes from Fork PRs" above). - The close reason is explicitly explained in a comment on the PR.
Claude MUST NOT close any PR that does not meet these conditions — even if asked. Instead, explain to the requester why the PR cannot be closed automatically and ask a human maintainer to close it manually.
When operating via the GitHub Actions workflow, always include the model you are running on in the footer of your GitHub comment and in the PR description when creating a pull request, alongside the job run link. Your system environment context states the model name explicitly (e.g. "You are powered by the model named Sonnet 4.6. The exact model ID is claude-sonnet-4-6."). Use the exact model ID from that statement.
Format the footer line as:
[View job run](...) | Model: `claude-sonnet-4-6`
In a PR description, append the model information at the end of the body:
| Model: `claude-sonnet-4-6`
If the system context does not provide a model ID, omit the model field rather than guessing.
The GitHub Actions runner has a full Node.js + pnpm environment available.
Dependencies are already installed (pnpm install has been run). The build
step is skipped to save CI resources, but you can run build commands when
needed for advanced tasks (e.g. type-checking, running tests).
For fork PRs, the origin remote points to the contributor's fork. An
upstream remote is configured pointing to freelensapp/freelens. Push
new branches to upstream (never to origin) when the PR originates
from a fork — this ensures the resulting PR is internal and CI workflows
run automatically.
The following CLI tools are explicitly allowed in the workflow:
pnpm(all subcommands) — for validation, formatting, and buildsgit(all subcommands) — for viewing changes, creating branches, committing, and pushinggh(all subcommands) — for managing pull requestsnpx,node— for running Node.js tools and scripts inlineyq,jq— for YAML and JSON processinggrep,rg(ripgrep),find,xargs— for searching and iteratingsed,awk,cut,tr— for text transformationsort,uniq— for list processingcat,head,tail,wc— for viewing and measuring filesls,tree— for listing directory contentsmkdir,touch,cp,mv,rm— for file and directory operationstee,echo— for pipeline debugging and scripting
Before committing any changes, apply the same validation rules as human developers:
- Run
pnpm biome check --writeto auto-format TypeScript/JavaScript and HTML files (orpnpm biome checkto check without writing). - Run
pnpm trunk checkto validate all other file types (ortrunk checkif the trunk CLI is installed globally). - Run
pnpm build:diif you added, moved, or renamed injectable files. - If unit tests fail on snapshot mismatches after your changes (or you are
explicitly asked to update them), run
pnpm test:unit:updatesnapshotto regenerate snapshots, review the diff, then commit the updated.snapfiles.
- Check existing features for patterns
- Search codebase for similar implementations
- Review PR history for related changes
- Consult DEVELOPMENT.md for setup instructions