Skip to content

Latest commit

 

History

History
459 lines (339 loc) · 16.3 KB

File metadata and controls

459 lines (339 loc) · 16.3 KB

Agent Guide: Freelens Development

Overview

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 application
    • src/main/ - Main Electron process code
    • src/renderer/ - Renderer process (UI) code
    • src/common/ - Shared code between processes
  • packages/core/ - Core functionality
    • src/features/ - Feature modules organized by domain
    • src/renderer/ - Renderer-specific utilities
    • src/extensions/ - Extension system
  • packages/ - Monorepo packages (utilities, components, etc.)
  • scripts/ - Build and development scripts

Security

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

Build System

Commands

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 tests

Clean Build

This project uses Turbo for caching build artifacts.

When facing caching issues:

rm -rf .turbo packages/core/dist freelens/dist
pnpm build

Dependency Injection System

This 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.

Registration Hierarchy

The system has three levels of registration files:

  1. 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
  2. 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
  3. Root registration files - Entry points per process

    • register-injectables-main.ts - Main process
    • register-injectables-renderer.ts - Renderer process
    • Called during DI container initialization

Directory Patterns

Shared Aggregators

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/).

Process-Specific Paths

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)

When to Re-run Generation

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.

Common Development Tasks

Adding a New Feature

  1. Create feature directory under packages/core/src/features/
  2. Organize by concern: common/, main/, renderer/
  3. Create injectable files with .injectable.ts suffix
  4. Run pnpm build:di to generate registration files
  5. Write tests alongside implementation

Debugging the Application

Main Process:

  • Logs in terminal where pnpm start was 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 above
  • Tried to inject non-registered injectable - Check registration files were generated
  • Permission errors on macOS - Expected during development

Working with Webpack

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

Troubleshooting Patterns

Changes Not Appearing

  1. Check if file is in ignored directory (dist/, node_modules/)
  2. Clear webpack cache: rm -rf packages/core/dist freelens/dist
  3. Full rebuild: pnpm build
  4. Restart application: pnpm start

Build Failures

  1. Check for TypeScript errors: pnpm type-check
  2. Check for linting errors: pnpm lint
  3. Verify dependencies: pnpm install
  4. Check Node.js version matches .nvmrc

Runtime Errors

  1. Check dev console (renderer) or terminal (main)
  2. Look for stack traces with file:line numbers
  3. Verify all dependencies are registered (DI system)
  4. Check for circular dependencies

Architecture Decisions

Electron Multi-Process

  • Main process - Node.js environment, system access
  • Renderer process - Chromium browser, UI
  • IPC - Communication between processes

Feature Organization

Features are self-contained modules with:

  • Domain logic
  • UI components
  • State management
  • Injectable definitions

Monorepo Structure

Uses pnpm workspaces for:

  • Shared code reuse
  • Faster builds
  • Type safety across packages

Best Practices

  1. Always regenerate DI files after adding/moving injectables
  2. Full rebuild when in doubt about cached state
  3. Check both processes when debugging (main + renderer)
  4. Use semantic search to find examples in codebase
  5. Follow existing patterns - grep for similar implementations
  6. Test changes before committing
  7. Run validation after file changes (especially before commit): run trunk check (or pnpm trunk check if trunk is not installed locally)
  8. For main project TypeScript and HTML files: run biome check directly (or pnpm biome check if biome is not installed locally)
  9. For other file types: use trunk check (or pnpm trunk check if trunk is not installed locally)
  10. 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.

GitHub Actions (Claude Code Action) Rules

When operating via the claude.yaml workflow (i.e., invoked from a PR comment, issue, or review), follow these rules:

Code Review

When reviewing code and proposing fixes:

  1. Show the diff first — present every proposed change as a unified diff block using the diff language 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.

  2. 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.

  3. 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 suggestion block 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 -u blocks in a regular comment instead, with the proposed commit subject shown first

Making Changes to a PR

When asked to implement a change on a PR:

  1. Propose the commit subject (as above)
  2. Describe what will change and why
  3. After confirmation, apply the changes with commits on the PR branch
  4. 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.

Modifying GitHub Actions Workflows

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:

  1. 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/.
  2. 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.
  3. 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.

Branch Naming Conventions

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.

PR Title Conventions

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 prefix Claude: (followed by a space) in the title.

    Examples:

    • Claude: Add rule for PR title conventions in AGENTS.md
    • Claude: Update claude.yaml workflow permissions
  • All other PRs — do not use any prefix (no fix:, feat:, chore:, etc.). Use plain, descriptive titles.

Pushing Changes from Fork PRs

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:

  1. Create a new branch on freelensapp/freelens with the prefix claude/ followed by the original branch name. Push to the upstream remote (not origin, which points to the fork):

    git checkout -b claude/<original-branch-name>
    git push --force-with-lease upstream claude/<original-branch-name>
  2. 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").

  3. 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
  4. Close the original PR.

Closing PRs

Claude may only close a PR when ALL of the following are true:

  1. The PR was created by Claude from a claude/ branch, OR the PR is the original fork PR that Claude's claude/ branch supersedes (see "Pushing Changes from Fork PRs" above).
  2. 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.

Model Information in Comments

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.

Development Environment

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 builds
  • git (all subcommands) — for viewing changes, creating branches, committing, and pushing
  • gh (all subcommands) — for managing pull requests
  • npx, node — for running Node.js tools and scripts inline
  • yq, jq — for YAML and JSON processing
  • grep, rg (ripgrep), find, xargs — for searching and iterating
  • sed, awk, cut, tr — for text transformation
  • sort, uniq — for list processing
  • cat, head, tail, wc — for viewing and measuring files
  • ls, tree — for listing directory contents
  • mkdir, touch, cp, mv, rm — for file and directory operations
  • tee, echo — for pipeline debugging and scripting

Before committing any changes, apply the same validation rules as human developers:

  • Run pnpm biome check --write to auto-format TypeScript/JavaScript and HTML files (or pnpm biome check to check without writing).
  • Run pnpm trunk check to validate all other file types (or trunk check if the trunk CLI is installed globally).
  • Run pnpm build:di if 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:updatesnapshot to regenerate snapshots, review the diff, then commit the updated .snap files.

Getting Help

  • Check existing features for patterns
  • Search codebase for similar implementations
  • Review PR history for related changes
  • Consult DEVELOPMENT.md for setup instructions