{"owner":"freelensapp","repo":"freelens","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md"],"skills":{"AGENTS.md":"# Agent Guide: Freelens Development\n\n## Overview\n\nThis 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.\n\nFor local development environment setup and extra development tips, see DEVELOPMENT.md.\n\n- **`freelens/`** - Main Electron application\n  - `src/main/` - Main Electron process code\n  - `src/renderer/` - Renderer process (UI) code\n  - `src/common/` - Shared code between processes\n- **`packages/core/`** - Core functionality\n  - `src/features/` - Feature modules organized by domain\n  - `src/renderer/` - Renderer-specific utilities\n  - `src/extensions/` - Extension system\n- **`packages/`** - Monorepo packages (utilities, components, etc.)\n- **`scripts/`** - Build and development scripts\n\n## Security\n\nNever read, display, reference, or include the contents of the following files in any response or context, even if they are open in the editor:\n\n- `.env`\n- `.env.*`\n- `.npmrc`\n- `*.jks`\n- `*.keystore`\n- `*.p12`\n- `*.pfx`\n- `*.pem`\n- `*.key`\n\n## Session and temporary files\n\nFiles created while working on a task — scratch scripts, command output,\nscreenshots, DOM/accessibility snapshots, and AI-agent / MCP-server runtime\nartifacts — must never be written into the tracked working tree, or they leak\ninto git history. Write them to a system temporary directory outside the repo\n(e.g. under `$TMPDIR`, or `mktemp -d`), not to the repo root.\n\nWhen a tool insists on writing inside the repo, keep it out of git:\n\n- point it at a temp path if it accepts one (e.g. pass an absolute\n  `$TMPDIR/...` filename), otherwise\n- git-ignore its default output directory. Already ignored:\n  `.playwright-mcp/` (Playwright MCP), `logs/` (electron-mcp-server).\n\nNever `git add -A` / `git add .` blindly: review `git status` first and stage\nonly the files your change actually touches, never these artifacts.\n\n## Copyright Headers\n\nSource files carry one of two header variants. Which one a file gets depends\non whether it continues code from the original OpenLens fork, not on what its\nneighbours in the same directory look like.\n\n**New files** — anything created from scratch, including rewrites,\ntranslations, and reimplementations of removed or legacy logic — get the\nsingle-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nThis holds even when the new file's logic is inspired by, or replaces, old\nOpenLens code: inspiration is not continuation. `freelens/electron.vite.config.ts`,\nwritten as a translation of the removed webpack config, is a new file.\n\n**Files that continue code from the fork** keep the two-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Copyright (c) OpenLens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nA file continues fork code when its path was present in the fork-import commit\n`0a5798c9` (\"First commit - Open Lens fork from master branch\"):\n\n```sh\ngit ls-tree -r --name-only 0a5798c9 | grep -x <path>\n```\n\nor when `git log --follow -- <path>` traces it back to a path that was — that\nis, git itself detects the file as a rename, move, or copy of fork-era code:\n\n```sh\ngit log --follow --format= --name-only -- <path> | sort -u\n```\n\nNever add the `OpenLens Authors` line to a file that does not already have it\njust because neighbouring files do. Do not touch legal or license text\n(`LICENSE`, `README.md`, `freelens/license-header.txt`,\n`freelens/static/build/license.txt`) or the upstream copyright notices of\nvendored third-party code, which are unrelated to either header variant.\n\nSee [#2352](https://github.com/freelensapp/freelens/issues/2352) for the\ncleanup that established this rule.\n\n## Build System\n\n### Commands\n\n```bash\npnpm build:di           # Generate DI registration files\npnpm build              # Build all packages\npnpm build:app:dir      # Build Electron app directory\npnpm start              # Start development app\npnpm test               # Run tests\n```\n\n### Clean Build\n\nThis project uses Turbo for caching build artifacts.\n\nWhen facing caching issues:\n\n```bash\nrm -rf .turbo packages/core/dist freelens/dist\npnpm build\n```\n\n## Dependency Injection System\n\nThis 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`.\n\n### Registration Hierarchy\n\nThe system has three levels of registration files:\n\n1. **Leaf registration files** - Register individual injectables\n   - Example: `features/preferences/renderer/close-preferences/register-injectables.ts`\n   - Pattern: Import injectable definitions and call `di.register()`\n   - Each call wrapped in try-catch for idempotency\n\n2. **Aggregator registration files** - Register subdirectories\n   - Example: `features/preferences/renderer/register-injectables.ts`\n   - Pattern: Import and call `registerXxxInjectables(di)` from subdirectories\n   - Each call wrapped in try-catch to handle duplicates\n\n3. **Root registration files** - Entry points per process\n   - `register-injectables-main.ts` - Main process\n   - `register-injectables-renderer.ts` - Renderer process\n   - Called during DI container initialization\n\n### Directory Patterns\n\n#### Shared Aggregators\n\nDirectories with `register-injectables.ts` that aggregate subdirectories:\n\n```text\nfeatures/vars/\n├── register-injectables.ts     ← Shared aggregator (calls common/)\n├── common/\n│   └── register-injectables.ts\n└── build-version/\n    ├── main/register-injectables.ts      ← Process-specific (NOT aggregated)\n    └── renderer/register-injectables.ts  ← Process-specific (NOT aggregated)\n```\n\n**Key insight:** Shared aggregators only handle **shared** subdirectories (like `common/`). They do NOT aggregate **process-specific** subdirectories (`main/`, `renderer/`).\n\n#### Process-Specific Paths\n\nPaths containing `/main/` or `/renderer/` are process-specific and must be imported directly:\n\n- ✅ Include: `features/vars/build-version/main/register-injectables`\n- ❌ Exclude: `features/vars/common/register-injectables` (handled by parent aggregator)\n\n### When to Re-run Generation\n\nRun `pnpm build:di` when:\n\n- Adding new injectable files\n- Moving injectable files\n- Renaming injectable files\n- Changing directory structure\n- Modifying generation script\n\nThe build process automatically runs this, but you can run it manually to verify changes.\n\n### Bundled Binary Versions\n\nThe versions of the bundled `freelens-k8s-proxy`, `kubectl` and `helm` live in\nthe `config` block of `freelens/package.json`, and their exact digests are\npinned in `freelens/binaries.lock.json`. The build reads the expected checksum\nfrom that lock rather than from the vendor, so **a version bump without\nregenerating the lock fails the build**:\n\n```sh\npnpm update-binaries-lock\n```\n\nThe generator downloads all eighteen artifacts (three tools, three platforms,\ntwo architectures), checks each against its publisher's signature — GitHub build\nprovenance for freelens-k8s-proxy, PGP for helm, keyless cosign for kubectl —\nand only then writes the lock. `cosign` comes from mise (`mise install`), and\n`GITHUB_TOKEN` should be set unless you want to share 60 unauthenticated API\ncalls per hour with the rest of your IP. Use `--only <tool>` to refresh a single\ntool while iterating.\n\n`.github/workflows/binaries-lock-check.yaml` enforces both that the lock is\ncurrent and that no digest changed while its version stood still.\n\n### Downloaded kubectl Versions\n\nThe bundled kubectl is not the only one the application runs: a cluster whose\nminor version differs gets a version-matched kubectl downloaded at runtime. The\nmap of which patch to fetch per minor lives in\n`packages/kubectl-versions/build/versions.json`, and the digest of every\nartifact that map can produce is pinned in\n`packages/kubectl-versions/build/checksums.json`, keyed by version and then by\n`${platform}/${arch}`.\n\n`Kubectl.downloadKubectl()` hashes what it downloaded and refuses anything that\ndoes not match its pin, and `ensureKubectl()` refuses to download at all when\nthere is no pin, falling back to the bundled binary. **A version added to the\nmap without a pin therefore never gets downloaded**, so the two files are\nregenerated together:\n\n```sh\npnpm --filter @freelensapp/kubectl-versions compute-versions\npnpm update-kubectl-checksums\n```\n\nThe generator reads `dl.k8s.io` only, never a mirror — pinning bytes from a\nmirror would let a compromised mirror bless its own digest. It skips versions\nalready present, which makes a run incremental and an existing pin immutable,\nand it verifies each download against both the published `.sha256` and the\nkeyless cosign signature before recording it. `cosign` comes from mise\n(`mise install`).\n\nBoth files start at 1.22, the oldest line Kubernetes publishes a signature for,\nand coverage is not uniform below that floor's neighbours: v1.22.17 has no\n`windows/arm64` build, so the generator logs an unpublished variant and carries\non rather than failing. `.github/workflows/kubectl-checksums-check.yaml`\nverifies added pins and asserts that no existing digest changed.\n\n## Common Development Tasks\n\n### Adding a New Feature\n\n1. Create feature directory under `packages/core/src/features/`\n2. Organize by concern: `common/`, `main/`, `renderer/`\n3. Create injectable files with `.injectable.ts` suffix\n4. Run `pnpm build:di` to generate registration files\n5. Write tests alongside implementation\n\n### Debugging the Application\n\n**Main Process:**\n- Logs in terminal where `pnpm start` was run\n- Use `console.log()` or proper logger\n\n**Renderer Process:**\n- Open DevTools in the app\n- Check Console tab for errors and logs\n- Use React DevTools for component inspection\n- `pnpm dev` also exposes a Chrome DevTools Protocol endpoint on port 9223\n  (`--remoteDebuggingPort`). Note that each cluster's UI renders in a\n  cross-origin `<clusterId>.renderer.freelens.app` iframe, so inspecting or\n  automating cluster views requires a frame-aware CDP client — see the\n  AI-agent inspection notes in DEVELOPMENT.md.\n\n**Common Errors:**\n- `Tried to register same injectable multiple times` - See DI section above\n- `Tried to inject non-registered injectable` - Check registration files were generated\n- Permission errors on macOS - Expected during development\n\n### Working with the bundler (electron-vite)\n\nThe project bundles with electron-vite (Vite + Rollup); the legacy Webpack\nlayer was removed in #2118.\n\n- `freelens/electron.vite.config.ts` - main/renderer build and dev-server config\n- `pnpm dev` runs `electron-vite dev` with Vite HMR; renderer source changes\n  hot-reload, main-process changes rebuild and relaunch (via `--watch`)\n- Changes to generated files (e.g. DI registration) require a full rebuild\n\n**Cache issues:** Delete the build output and rebuild\n(`rm -rf .turbo packages/core/dist freelens/dist`)\n\n## Troubleshooting Patterns\n\n### Changes Not Appearing\n\n1. Check if file is in ignored directory (`dist/`, `node_modules/`)\n2. Clear the build output: `rm -rf .turbo packages/core/dist freelens/dist`\n3. Full rebuild: `pnpm build`\n4. Restart application: `pnpm start`\n\n### Build Failures\n\n1. Check for TypeScript errors: `pnpm type:check`\n2. Check for linting errors: `pnpm lint`\n3. Verify dependencies: `pnpm install`\n4. Check Node.js version matches `.nvmrc`\n\n### Runtime Errors\n\n1. Check dev console (renderer) or terminal (main)\n2. Look for stack traces with file:line numbers\n3. Verify all dependencies are registered (DI system)\n4. Check for circular dependencies\n\n## Architecture Decisions\n\n### Electron Multi-Process\n\n- **Main process** - Node.js environment, system access\n- **Renderer process** - Chromium browser, UI\n- **IPC** - Communication between processes\n\n### Feature Organization\n\nFeatures are self-contained modules with:\n- Domain logic\n- UI components\n- State management\n- Injectable definitions\n\n### Monorepo Structure\n\nUses pnpm workspaces for:\n- Shared code reuse\n- Faster builds\n- Type safety across packages\n\n## Styling\n\nFreelens v2 carries four styling systems (theme CSS custom properties, global\nplain SCSS, CSS Modules, and Tailwind v4). Which one to use is not a matter of\ntaste — each has a defined role. Before adding or changing any stylesheet or\n`className`, read [`docs/v2-styling.md`](./docs/v2-styling.md). In short:\n\n- **Theme values** (colors, fonts): CSS custom properties from the TS theme\n  system (`var(--…)`) — the single contract every other system reads.\n- **Shared components** (`packages/ui-components`) and anything an extension\n  may restyle: global PascalCase class + plain SCSS + `var(--…)`. No Tailwind\n  (its JIT only scans core TSX), no CSS Modules (the class names are public\n  API).\n- **Core single components / full views**: CSS Modules (`*.module.scss`).\n- **Local layout inside core-only TSX**: Tailwind utilities. The legacy\n  `flexbox.scss` utilities have been removed — do not reintroduce them.\n- **Extensions**: see the styling section of\n  [`docs/v2-extension-migration.md`](./docs/v2-extension-migration.md).\n\n## Best Practices\n\n1. **Always regenerate DI files** after adding/moving injectables\n2. **Full rebuild** when in doubt about cached state\n3. **Check both processes** when debugging (main + renderer)\n4. **Use semantic search** to find examples in codebase\n5. **Follow existing patterns** - grep for similar implementations\n6. **Test changes** before committing\n7. **Run validation after file changes (especially before commit):** run `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n8. **For main project TypeScript and HTML files:** run `biome check` directly (or `pnpm biome check` if `biome` is not installed locally)\n9. **For other file types:** use `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n10. **Do not use Antropic Fable for coding tasks** — Fable may be used only for planning,\n    analysis, and thinking through problems. When writing or editing code,\n    use standard editing tools instead.\n\n## Local Agent: Triggering the GitHub Agent\n\nThese rules apply to an agent running on a developer machine (a local Claude\nCode session), not to the workflow agent. The local agent shares the repository\nwith the CI agent defined in `.github/workflows/claude.yaml`, and every comment\nit writes on GitHub is a potential trigger for it.\n\n### How the trigger works\n\n`claude.yaml` starts a run when the body of a **newly created** comment (issue\ncomment or PR review comment), a **newly opened** issue (body or title), or a\n**submitted** PR review contains the string `@claude`, and the author is an\nOWNER, MEMBER or COLLABORATOR. The check is a plain\n`contains(github.event.comment.body, '@claude')` substring test, so the string\nfires the workflow wherever it appears — including inside a code span, a fenced\nblock, a quoted line, or a URL. Markdown formatting is not an escape.\n\nThe trigger text may also carry `[model:<alias>]` and `[runs-on:<alias>]`\nmarkers, which select the model and runner for that run (see the `parse` job for\nthe accepted aliases). They are only read from the triggering text.\n\n### Rules for the local agent\n\n1. **Write the handle only to start a run.** Ask the user before triggering: a\n   run is a 120-minute CI job on the repository, so it is the user's call, not\n   an implementation detail.\n2. **Escape the handle when merely referring to it.** In issue bodies, PR\n   descriptions, review notes, commit messages and documentation, write\n   `@<!-- -->claude` (displays as the handle, but the raw body does not contain\n   the literal string, so `contains()` does not match) or describe it in prose\n   as \"the Claude handle\". This is what keeps a plan or a bug report that\n   documents the trigger from firing it.\n3. **Editing never triggers.** The workflow subscribes only to `created`,\n   `opened` and `submitted` events — not `edited`. So updating a comment, an\n   issue body or a PR description is always safe, even when the text already\n   contains a real trigger, and conversely editing a comment to add the handle\n   does **not** start a run: a new comment is required.\n4. **One trigger per task.** Do not repeat the handle in follow-up comments\n   while a run is in flight; each occurrence starts another concurrent job.\n5. **Push first.** The workflow checks out the remote ref (the PR head, or the\n   default branch for issues), so anything not pushed is invisible to it.\n\n## GitHub Actions (Claude Code Action) Rules\n\nWhen operating via the `claude.yaml` workflow (i.e., invoked from a PR comment,\nissue, or review), follow these rules:\n\n### Code Review\n\nWhen reviewing code and proposing fixes:\n\n1. **Show the diff first** — present every proposed change as a unified diff\n   block using the `diff` language tag:\n\n   ```diff\n   --- a/path/to/file.ts\n   +++ b/path/to/file.ts\n   @@ -10,7 +10,7 @@\n    const oldLine = \"before\";\n   -const changedLine = \"after\";\n   +const changedLine = \"the fix\";\n    const unchangedLine = \"same\";\n   ```\n\n   You can generate this from the terminal with:\n   ```bash\n   git diff -u -- path/to/file\n   ```\n\n   If the change spans multiple files, group them under a single commit\n   subject and show each file's diff sequentially.\n\n2. **Propose a commit subject first** — before any code change, output a\n   single line with the proposed commit subject:\n\n   ```text\n   **Proposed commit:** <short description>\n   ```\n\n   Do **not** use Conventional Commits prefixes (e.g. `fix:`, `feat:`,\n   `chore:`, `refactor:`, `docs:`, `test:`, `ci:`). This project prefers\n   plain, descriptive commit messages and PR titles without any prefix.\n\n   Wait for the user to confirm (or adjust) the subject before applying the\n   change.\n\n3. **Comment style:**\n   - Keep review comments concise and actionable\n   - Reference specific lines (file + line number) when pointing out issues\n   - Offer a concrete fix suggestion rather than just flagging a problem\n   - Do **not** use emoji in any Markdown, comments, commit messages, or\n     PR descriptions. The only exception is emoji that already appears\n     inside code strings (e.g. application logs, user-facing messages).\n   - Use GitHub's `suggestion` block for small targeted fixes so the PR\n     author can accept the change with a single click:\n\n     ````suggestion\n     <same unified-diff format as shown above>\n     ````\n\n   - For larger multi-file changes, use `diff -u` blocks in a regular\n     comment instead, with the proposed commit subject shown first\n\n### Making Changes to a PR\n\nWhen asked to implement a change on a PR:\n\n1. Propose the commit subject (as above)\n2. Describe what will change and why\n3. After confirmation, apply the changes with commits on the PR branch\n4. **One commit per fix** — when a review surfaces more than one issue or\n   the plan includes more than one fix, apply and commit each fix\n   separately. Do not batch multiple independent fixes into a single\n   commit. This keeps the history bisectable and makes each change easy\n   to revert individually.\n\n### Pushing After Every Commit\n\nThe GitHub Actions job running Claude has a total timeout of 120 minutes.\nWhen the session times out, any commits that exist only in the runner's\nlocal checkout are lost. To make the work resumable in a follow-up session:\n\n1. **Push to the remote branch immediately after every commit.** Do not\n   accumulate multiple local commits before pushing — commit, push, then\n   move on to the next change.\n2. This pairs with the \"one commit per fix\" rule above: each completed fix\n   should land on the remote branch as soon as it is committed, so a\n   timed-out session can be resumed from the last pushed commit instead of\n   starting over.\n\n### Modifying GitHub Actions Workflows\n\nClaude cannot push changes to files under `.github/workflows/` directly,\nbecause the GitHub token used by the action lacks the `workflows` permission.\nAny patch to a workflow file MUST therefore be delivered as a new, complete\nfile under the `github-workflow-fix/` directory instead of editing the file in\nplace:\n\n1. Write the full, final contents of the workflow to\n   `github-workflow-fix/<workflow-file-name>` (e.g.\n   `github-workflow-fix/claude.yaml`). Do **not** edit the original file under\n   `.github/workflows/`.\n2. Make it a **complete** file — the entire workflow as it should look after\n   the change, not just a diff or fragment — so it can be copied verbatim.\n3. Commit and open the PR as usual. In the PR description, clearly note that\n   the file is a proposed workflow change and that a maintainer must move it\n   from `github-workflow-fix/` to `.github/workflows/` manually.\n\nThis lets the PR be created successfully while leaving the actual workflow\nchange for a human to apply.\n\n### Branch Naming Conventions\n\nWhen creating a branch from an issue, use a human-readable name that includes\nthe issue number and a short slug derived from the issue title:\n\n```text\nclaude/issue-<number>-<short-slug>\n```\n\n- `<number>` is the GitHub issue number\n- `<short-slug>` is a kebab-case summary of the issue title, kept short\n  (3–6 words maximum, omit articles and filler words)\n\nExamples:\n\n- Issue #1957 \"Add PR title convention rule for agent-related changes\"\n  → `claude/issue-1957-add-pr-title-rules`\n- Issue #42 \"Fix crash when opening preferences dialog\"\n  → `claude/issue-42-fix-preferences-crash`\n\nDo **not** use auto-generated timestamp suffixes (e.g.\n`claude/issue-1957-20260612-2108`) — these are not human-readable and make\nbranch lists hard to scan.\n\n### PR Title Conventions\n\nWhen creating a PR, use the following title conventions:\n\n- **Agent-related changes** — PRs whose changes are strictly related to coding\n  agent configuration (e.g. `AGENTS.md`, `.github/workflows/claude.yaml`, or\n  other files that govern how Claude operates in this repository) MUST use\n  the prefix `Claude:` (followed by a space) in the title.\n\n  Examples:\n  - `Claude: Add rule for PR title conventions in AGENTS.md`\n  - `Claude: Update claude.yaml workflow permissions`\n\n- **All other PRs** — do **not** use any prefix (no `fix:`, `feat:`, `chore:`,\n  etc.). Use plain, descriptive titles.\n\n### Pushing Changes from Fork PRs\n\nWhen you have commits ready to push but the PR originates from a fork\n(different owner than `freelensapp`), you cannot push to the fork's\nrepository. Instead:\n\n1. Create a new branch on `freelensapp/freelens` with the prefix `claude/`\n   followed by the original branch name.\n   Push to the `upstream` remote (not `origin`, which points to the fork):\n   ```bash\n   git checkout -b claude/<original-branch-name>\n   git push --force-with-lease upstream claude/<original-branch-name>\n   ```\n\n2. Open a new PR from that branch. The new PR MUST use the **exact same\n   title** as the original PR — copy it verbatim, do not rewrite, improve,\n   or add any prefix. The description MUST reference the original PR\n   (e.g. \"Fixes #NNN, supersedes #NNN\").\n\n3. Post a comment on the original PR:\n   - Explain that the fix has been implemented in a new PR\n   - Include a link to the new PR\n   - Mention that the original PR can be closed\n\n4. Close the original PR.\n\n### Closing PRs\n\nClaude may only close a PR when ALL of the following are true:\n\n1. The PR was created by Claude from a `claude/` branch, OR the PR is the\n   original fork PR that Claude's `claude/` branch supersedes (see\n   \"Pushing Changes from Fork PRs\" above).\n2. The close reason is explicitly explained in a comment on the PR.\n\nClaude MUST NOT close any PR that does not meet these conditions — even if\nasked. Instead, explain to the requester why the PR cannot be closed\nautomatically and ask a human maintainer to close it manually.\n\n### Model Information in Comments\n\nWhen operating via the GitHub Actions workflow, always include the model you are\nrunning on in the footer of your GitHub comment and in the PR description when\ncreating a pull request, alongside the job run link.\nYour system environment context states the model name explicitly (e.g.\n\"You are powered by the model named Sonnet 4.6. The exact model ID is\nclaude-sonnet-4-6.\"). Use the exact model ID from that statement.\n\nFormat the footer line as:\n\n```text\n[View job run](...) | Model: `claude-sonnet-4-6`\n```\n\nIn a PR description, append the model information at the end of the body:\n\n```text\n| Model: `claude-sonnet-4-6`\n```\n\nIf the system context does not provide a model ID, omit the model field rather\nthan guessing.\n\n### Development Environment\n\nThe GitHub Actions runner has a full Node.js + pnpm environment available, and\nthe workflow attempts to install the dependencies (`pnpm install`) and the\n`trunk` CLI before starting Claude. The build step is skipped to save CI\nresources, but you can run build commands when needed for advanced tasks\n(e.g. type-checking, running tests).\n\nEvery one of those setup steps is `continue-on-error`, so any of them may have\nfailed and left its tool or `node_modules` missing. Verify that what you need\nis actually there before relying on it, and never report a check as passing\nwhen it did not run — say that it was unavailable instead.\n\nFor fork PRs, the `origin` remote points to the contributor's fork. An\n`upstream` remote is configured pointing to `freelensapp/freelens`. Push\nnew branches to `upstream` (never to `origin`) when the PR originates\nfrom a fork — this ensures the resulting PR is internal and CI workflows\nrun automatically.\n\nThe following CLI tools are explicitly allowed in the workflow:\n\n- `pnpm` (all subcommands) — for validation, formatting, and builds\n- `git` (all subcommands) — for viewing changes, creating branches,\n  committing, and pushing\n- `gh` (all subcommands) — for managing pull requests\n- `trunk` — for linting and formatting every non-TypeScript file type\n- `bash` — for syntax-checking shell scripts (`bash -n <script>`)\n- `npx`, `node` — for running Node.js tools and scripts inline\n- `yq`, `jq` — for YAML and JSON processing\n- `grep`, `rg` (ripgrep), `find`, `xargs` — for searching and iterating\n- `sed`, `awk`, `cut`, `tr` — for text transformation\n- `sort`, `uniq` — for list processing\n- `cat`, `head`, `tail`, `wc` — for viewing and measuring files\n- `ls`, `tree` — for listing directory contents\n- `mkdir`, `touch`, `cp`, `mv`, `rm` — for file and directory operations\n- `tee`, `echo` — for pipeline debugging and scripting\n\nBefore committing any changes, apply the same validation rules as human\ndevelopers:\n\n- Run `pnpm biome check --write` to auto-format TypeScript/JavaScript and\n  HTML files (or `pnpm biome check` to check without writing).\n- Run `trunk check` to validate all other file types. The workflow puts the\n  CLI on `PATH`, so call it directly; `pnpm trunk check` works too but\n  re-downloads the launcher and its linters. It only inspects changed files by\n  default — use `trunk check --all` after a broad change.\n- Syntax-check a shell script you edited with `bash -n <script>`.\n- Run `pnpm build:di` if you added, moved, or renamed injectable files.\n- If unit tests fail on snapshot mismatches after your changes (or you are\n  explicitly asked to update them), run `pnpm test:unit:updatesnapshot` to\n  regenerate snapshots, review the diff, then commit the updated `.snap`\n  files.\n\n## Getting Help\n\n- Check existing features for patterns\n- Search codebase for similar implementations\n- Review PR history for related changes\n- Consult DEVELOPMENT.md for setup instructions\n","CLAUDE.md":"@AGENTS.md\n\n# Agent Guide\n\nThis project uses AGENTS.md as the canonical agent guide (imported above).\nClaude Code reads it via @AGENTS.md, while other agents read it directly.\n"},"files":{"AGENTS.md":"# Agent Guide: Freelens Development\n\n## Overview\n\nThis 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.\n\nFor local development environment setup and extra development tips, see DEVELOPMENT.md.\n\n- **`freelens/`** - Main Electron application\n  - `src/main/` - Main Electron process code\n  - `src/renderer/` - Renderer process (UI) code\n  - `src/common/` - Shared code between processes\n- **`packages/core/`** - Core functionality\n  - `src/features/` - Feature modules organized by domain\n  - `src/renderer/` - Renderer-specific utilities\n  - `src/extensions/` - Extension system\n- **`packages/`** - Monorepo packages (utilities, components, etc.)\n- **`scripts/`** - Build and development scripts\n\n## Security\n\nNever read, display, reference, or include the contents of the following files in any response or context, even if they are open in the editor:\n\n- `.env`\n- `.env.*`\n- `.npmrc`\n- `*.jks`\n- `*.keystore`\n- `*.p12`\n- `*.pfx`\n- `*.pem`\n- `*.key`\n\n## Session and temporary files\n\nFiles created while working on a task — scratch scripts, command output,\nscreenshots, DOM/accessibility snapshots, and AI-agent / MCP-server runtime\nartifacts — must never be written into the tracked working tree, or they leak\ninto git history. Write them to a system temporary directory outside the repo\n(e.g. under `$TMPDIR`, or `mktemp -d`), not to the repo root.\n\nWhen a tool insists on writing inside the repo, keep it out of git:\n\n- point it at a temp path if it accepts one (e.g. pass an absolute\n  `$TMPDIR/...` filename), otherwise\n- git-ignore its default output directory. Already ignored:\n  `.playwright-mcp/` (Playwright MCP), `logs/` (electron-mcp-server).\n\nNever `git add -A` / `git add .` blindly: review `git status` first and stage\nonly the files your change actually touches, never these artifacts.\n\n## Copyright Headers\n\nSource files carry one of two header variants. Which one a file gets depends\non whether it continues code from the original OpenLens fork, not on what its\nneighbours in the same directory look like.\n\n**New files** — anything created from scratch, including rewrites,\ntranslations, and reimplementations of removed or legacy logic — get the\nsingle-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nThis holds even when the new file's logic is inspired by, or replaces, old\nOpenLens code: inspiration is not continuation. `freelens/electron.vite.config.ts`,\nwritten as a translation of the removed webpack config, is a new file.\n\n**Files that continue code from the fork** keep the two-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Copyright (c) OpenLens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nA file continues fork code when its path was present in the fork-import commit\n`0a5798c9` (\"First commit - Open Lens fork from master branch\"):\n\n```sh\ngit ls-tree -r --name-only 0a5798c9 | grep -x <path>\n```\n\nor when `git log --follow -- <path>` traces it back to a path that was — that\nis, git itself detects the file as a rename, move, or copy of fork-era code:\n\n```sh\ngit log --follow --format= --name-only -- <path> | sort -u\n```\n\nNever add the `OpenLens Authors` line to a file that does not already have it\njust because neighbouring files do. Do not touch legal or license text\n(`LICENSE`, `README.md`, `freelens/license-header.txt`,\n`freelens/static/build/license.txt`) or the upstream copyright notices of\nvendored third-party code, which are unrelated to either header variant.\n\nSee [#2352](https://github.com/freelensapp/freelens/issues/2352) for the\ncleanup that established this rule.\n\n## Build System\n\n### Commands\n\n```bash\npnpm build:di           # Generate DI registration files\npnpm build              # Build all packages\npnpm build:app:dir      # Build Electron app directory\npnpm start              # Start development app\npnpm test               # Run tests\n```\n\n### Clean Build\n\nThis project uses Turbo for caching build artifacts.\n\nWhen facing caching issues:\n\n```bash\nrm -rf .turbo packages/core/dist freelens/dist\npnpm build\n```\n\n## Dependency Injection System\n\nThis 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`.\n\n### Registration Hierarchy\n\nThe system has three levels of registration files:\n\n1. **Leaf registration files** - Register individual injectables\n   - Example: `features/preferences/renderer/close-preferences/register-injectables.ts`\n   - Pattern: Import injectable definitions and call `di.register()`\n   - Each call wrapped in try-catch for idempotency\n\n2. **Aggregator registration files** - Register subdirectories\n   - Example: `features/preferences/renderer/register-injectables.ts`\n   - Pattern: Import and call `registerXxxInjectables(di)` from subdirectories\n   - Each call wrapped in try-catch to handle duplicates\n\n3. **Root registration files** - Entry points per process\n   - `register-injectables-main.ts` - Main process\n   - `register-injectables-renderer.ts` - Renderer process\n   - Called during DI container initialization\n\n### Directory Patterns\n\n#### Shared Aggregators\n\nDirectories with `register-injectables.ts` that aggregate subdirectories:\n\n```text\nfeatures/vars/\n├── register-injectables.ts     ← Shared aggregator (calls common/)\n├── common/\n│   └── register-injectables.ts\n└── build-version/\n    ├── main/register-injectables.ts      ← Process-specific (NOT aggregated)\n    └── renderer/register-injectables.ts  ← Process-specific (NOT aggregated)\n```\n\n**Key insight:** Shared aggregators only handle **shared** subdirectories (like `common/`). They do NOT aggregate **process-specific** subdirectories (`main/`, `renderer/`).\n\n#### Process-Specific Paths\n\nPaths containing `/main/` or `/renderer/` are process-specific and must be imported directly:\n\n- ✅ Include: `features/vars/build-version/main/register-injectables`\n- ❌ Exclude: `features/vars/common/register-injectables` (handled by parent aggregator)\n\n### When to Re-run Generation\n\nRun `pnpm build:di` when:\n\n- Adding new injectable files\n- Moving injectable files\n- Renaming injectable files\n- Changing directory structure\n- Modifying generation script\n\nThe build process automatically runs this, but you can run it manually to verify changes.\n\n### Bundled Binary Versions\n\nThe versions of the bundled `freelens-k8s-proxy`, `kubectl` and `helm` live in\nthe `config` block of `freelens/package.json`, and their exact digests are\npinned in `freelens/binaries.lock.json`. The build reads the expected checksum\nfrom that lock rather than from the vendor, so **a version bump without\nregenerating the lock fails the build**:\n\n```sh\npnpm update-binaries-lock\n```\n\nThe generator downloads all eighteen artifacts (three tools, three platforms,\ntwo architectures), checks each against its publisher's signature — GitHub build\nprovenance for freelens-k8s-proxy, PGP for helm, keyless cosign for kubectl —\nand only then writes the lock. `cosign` comes from mise (`mise install`), and\n`GITHUB_TOKEN` should be set unless you want to share 60 unauthenticated API\ncalls per hour with the rest of your IP. Use `--only <tool>` to refresh a single\ntool while iterating.\n\n`.github/workflows/binaries-lock-check.yaml` enforces both that the lock is\ncurrent and that no digest changed while its version stood still.\n\n### Downloaded kubectl Versions\n\nThe bundled kubectl is not the only one the application runs: a cluster whose\nminor version differs gets a version-matched kubectl downloaded at runtime. The\nmap of which patch to fetch per minor lives in\n`packages/kubectl-versions/build/versions.json`, and the digest of every\nartifact that map can produce is pinned in\n`packages/kubectl-versions/build/checksums.json`, keyed by version and then by\n`${platform}/${arch}`.\n\n`Kubectl.downloadKubectl()` hashes what it downloaded and refuses anything that\ndoes not match its pin, and `ensureKubectl()` refuses to download at all when\nthere is no pin, falling back to the bundled binary. **A version added to the\nmap without a pin therefore never gets downloaded**, so the two files are\nregenerated together:\n\n```sh\npnpm --filter @freelensapp/kubectl-versions compute-versions\npnpm update-kubectl-checksums\n```\n\nThe generator reads `dl.k8s.io` only, never a mirror — pinning bytes from a\nmirror would let a compromised mirror bless its own digest. It skips versions\nalready present, which makes a run incremental and an existing pin immutable,\nand it verifies each download against both the published `.sha256` and the\nkeyless cosign signature before recording it. `cosign` comes from mise\n(`mise install`).\n\nBoth files start at 1.22, the oldest line Kubernetes publishes a signature for,\nand coverage is not uniform below that floor's neighbours: v1.22.17 has no\n`windows/arm64` build, so the generator logs an unpublished variant and carries\non rather than failing. `.github/workflows/kubectl-checksums-check.yaml`\nverifies added pins and asserts that no existing digest changed.\n\n## Common Development Tasks\n\n### Adding a New Feature\n\n1. Create feature directory under `packages/core/src/features/`\n2. Organize by concern: `common/`, `main/`, `renderer/`\n3. Create injectable files with `.injectable.ts` suffix\n4. Run `pnpm build:di` to generate registration files\n5. Write tests alongside implementation\n\n### Debugging the Application\n\n**Main Process:**\n- Logs in terminal where `pnpm start` was run\n- Use `console.log()` or proper logger\n\n**Renderer Process:**\n- Open DevTools in the app\n- Check Console tab for errors and logs\n- Use React DevTools for component inspection\n- `pnpm dev` also exposes a Chrome DevTools Protocol endpoint on port 9223\n  (`--remoteDebuggingPort`). Note that each cluster's UI renders in a\n  cross-origin `<clusterId>.renderer.freelens.app` iframe, so inspecting or\n  automating cluster views requires a frame-aware CDP client — see the\n  AI-agent inspection notes in DEVELOPMENT.md.\n\n**Common Errors:**\n- `Tried to register same injectable multiple times` - See DI section above\n- `Tried to inject non-registered injectable` - Check registration files were generated\n- Permission errors on macOS - Expected during development\n\n### Working with the bundler (electron-vite)\n\nThe project bundles with electron-vite (Vite + Rollup); the legacy Webpack\nlayer was removed in #2118.\n\n- `freelens/electron.vite.config.ts` - main/renderer build and dev-server config\n- `pnpm dev` runs `electron-vite dev` with Vite HMR; renderer source changes\n  hot-reload, main-process changes rebuild and relaunch (via `--watch`)\n- Changes to generated files (e.g. DI registration) require a full rebuild\n\n**Cache issues:** Delete the build output and rebuild\n(`rm -rf .turbo packages/core/dist freelens/dist`)\n\n## Troubleshooting Patterns\n\n### Changes Not Appearing\n\n1. Check if file is in ignored directory (`dist/`, `node_modules/`)\n2. Clear the build output: `rm -rf .turbo packages/core/dist freelens/dist`\n3. Full rebuild: `pnpm build`\n4. Restart application: `pnpm start`\n\n### Build Failures\n\n1. Check for TypeScript errors: `pnpm type:check`\n2. Check for linting errors: `pnpm lint`\n3. Verify dependencies: `pnpm install`\n4. Check Node.js version matches `.nvmrc`\n\n### Runtime Errors\n\n1. Check dev console (renderer) or terminal (main)\n2. Look for stack traces with file:line numbers\n3. Verify all dependencies are registered (DI system)\n4. Check for circular dependencies\n\n## Architecture Decisions\n\n### Electron Multi-Process\n\n- **Main process** - Node.js environment, system access\n- **Renderer process** - Chromium browser, UI\n- **IPC** - Communication between processes\n\n### Feature Organization\n\nFeatures are self-contained modules with:\n- Domain logic\n- UI components\n- State management\n- Injectable definitions\n\n### Monorepo Structure\n\nUses pnpm workspaces for:\n- Shared code reuse\n- Faster builds\n- Type safety across packages\n\n## Styling\n\nFreelens v2 carries four styling systems (theme CSS custom properties, global\nplain SCSS, CSS Modules, and Tailwind v4). Which one to use is not a matter of\ntaste — each has a defined role. Before adding or changing any stylesheet or\n`className`, read [`docs/v2-styling.md`](./docs/v2-styling.md). In short:\n\n- **Theme values** (colors, fonts): CSS custom properties from the TS theme\n  system (`var(--…)`) — the single contract every other system reads.\n- **Shared components** (`packages/ui-components`) and anything an extension\n  may restyle: global PascalCase class + plain SCSS + `var(--…)`. No Tailwind\n  (its JIT only scans core TSX), no CSS Modules (the class names are public\n  API).\n- **Core single components / full views**: CSS Modules (`*.module.scss`).\n- **Local layout inside core-only TSX**: Tailwind utilities. The legacy\n  `flexbox.scss` utilities have been removed — do not reintroduce them.\n- **Extensions**: see the styling section of\n  [`docs/v2-extension-migration.md`](./docs/v2-extension-migration.md).\n\n## Best Practices\n\n1. **Always regenerate DI files** after adding/moving injectables\n2. **Full rebuild** when in doubt about cached state\n3. **Check both processes** when debugging (main + renderer)\n4. **Use semantic search** to find examples in codebase\n5. **Follow existing patterns** - grep for similar implementations\n6. **Test changes** before committing\n7. **Run validation after file changes (especially before commit):** run `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n8. **For main project TypeScript and HTML files:** run `biome check` directly (or `pnpm biome check` if `biome` is not installed locally)\n9. **For other file types:** use `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n10. **Do not use Antropic Fable for coding tasks** — Fable may be used only for planning,\n    analysis, and thinking through problems. When writing or editing code,\n    use standard editing tools instead.\n\n## Local Agent: Triggering the GitHub Agent\n\nThese rules apply to an agent running on a developer machine (a local Claude\nCode session), not to the workflow agent. The local agent shares the repository\nwith the CI agent defined in `.github/workflows/claude.yaml`, and every comment\nit writes on GitHub is a potential trigger for it.\n\n### How the trigger works\n\n`claude.yaml` starts a run when the body of a **newly created** comment (issue\ncomment or PR review comment), a **newly opened** issue (body or title), or a\n**submitted** PR review contains the string `@claude`, and the author is an\nOWNER, MEMBER or COLLABORATOR. The check is a plain\n`contains(github.event.comment.body, '@claude')` substring test, so the string\nfires the workflow wherever it appears — including inside a code span, a fenced\nblock, a quoted line, or a URL. Markdown formatting is not an escape.\n\nThe trigger text may also carry `[model:<alias>]` and `[runs-on:<alias>]`\nmarkers, which select the model and runner for that run (see the `parse` job for\nthe accepted aliases). They are only read from the triggering text.\n\n### Rules for the local agent\n\n1. **Write the handle only to start a run.** Ask the user before triggering: a\n   run is a 120-minute CI job on the repository, so it is the user's call, not\n   an implementation detail.\n2. **Escape the handle when merely referring to it.** In issue bodies, PR\n   descriptions, review notes, commit messages and documentation, write\n   `@<!-- -->claude` (displays as the handle, but the raw body does not contain\n   the literal string, so `contains()` does not match) or describe it in prose\n   as \"the Claude handle\". This is what keeps a plan or a bug report that\n   documents the trigger from firing it.\n3. **Editing never triggers.** The workflow subscribes only to `created`,\n   `opened` and `submitted` events — not `edited`. So updating a comment, an\n   issue body or a PR description is always safe, even when the text already\n   contains a real trigger, and conversely editing a comment to add the handle\n   does **not** start a run: a new comment is required.\n4. **One trigger per task.** Do not repeat the handle in follow-up comments\n   while a run is in flight; each occurrence starts another concurrent job.\n5. **Push first.** The workflow checks out the remote ref (the PR head, or the\n   default branch for issues), so anything not pushed is invisible to it.\n\n## GitHub Actions (Claude Code Action) Rules\n\nWhen operating via the `claude.yaml` workflow (i.e., invoked from a PR comment,\nissue, or review), follow these rules:\n\n### Code Review\n\nWhen reviewing code and proposing fixes:\n\n1. **Show the diff first** — present every proposed change as a unified diff\n   block using the `diff` language tag:\n\n   ```diff\n   --- a/path/to/file.ts\n   +++ b/path/to/file.ts\n   @@ -10,7 +10,7 @@\n    const oldLine = \"before\";\n   -const changedLine = \"after\";\n   +const changedLine = \"the fix\";\n    const unchangedLine = \"same\";\n   ```\n\n   You can generate this from the terminal with:\n   ```bash\n   git diff -u -- path/to/file\n   ```\n\n   If the change spans multiple files, group them under a single commit\n   subject and show each file's diff sequentially.\n\n2. **Propose a commit subject first** — before any code change, output a\n   single line with the proposed commit subject:\n\n   ```text\n   **Proposed commit:** <short description>\n   ```\n\n   Do **not** use Conventional Commits prefixes (e.g. `fix:`, `feat:`,\n   `chore:`, `refactor:`, `docs:`, `test:`, `ci:`). This project prefers\n   plain, descriptive commit messages and PR titles without any prefix.\n\n   Wait for the user to confirm (or adjust) the subject before applying the\n   change.\n\n3. **Comment style:**\n   - Keep review comments concise and actionable\n   - Reference specific lines (file + line number) when pointing out issues\n   - Offer a concrete fix suggestion rather than just flagging a problem\n   - Do **not** use emoji in any Markdown, comments, commit messages, or\n     PR descriptions. The only exception is emoji that already appears\n     inside code strings (e.g. application logs, user-facing messages).\n   - Use GitHub's `suggestion` block for small targeted fixes so the PR\n     author can accept the change with a single click:\n\n     ````suggestion\n     <same unified-diff format as shown above>\n     ````\n\n   - For larger multi-file changes, use `diff -u` blocks in a regular\n     comment instead, with the proposed commit subject shown first\n\n### Making Changes to a PR\n\nWhen asked to implement a change on a PR:\n\n1. Propose the commit subject (as above)\n2. Describe what will change and why\n3. After confirmation, apply the changes with commits on the PR branch\n4. **One commit per fix** — when a review surfaces more than one issue or\n   the plan includes more than one fix, apply and commit each fix\n   separately. Do not batch multiple independent fixes into a single\n   commit. This keeps the history bisectable and makes each change easy\n   to revert individually.\n\n### Pushing After Every Commit\n\nThe GitHub Actions job running Claude has a total timeout of 120 minutes.\nWhen the session times out, any commits that exist only in the runner's\nlocal checkout are lost. To make the work resumable in a follow-up session:\n\n1. **Push to the remote branch immediately after every commit.** Do not\n   accumulate multiple local commits before pushing — commit, push, then\n   move on to the next change.\n2. This pairs with the \"one commit per fix\" rule above: each completed fix\n   should land on the remote branch as soon as it is committed, so a\n   timed-out session can be resumed from the last pushed commit instead of\n   starting over.\n\n### Modifying GitHub Actions Workflows\n\nClaude cannot push changes to files under `.github/workflows/` directly,\nbecause the GitHub token used by the action lacks the `workflows` permission.\nAny patch to a workflow file MUST therefore be delivered as a new, complete\nfile under the `github-workflow-fix/` directory instead of editing the file in\nplace:\n\n1. Write the full, final contents of the workflow to\n   `github-workflow-fix/<workflow-file-name>` (e.g.\n   `github-workflow-fix/claude.yaml`). Do **not** edit the original file under\n   `.github/workflows/`.\n2. Make it a **complete** file — the entire workflow as it should look after\n   the change, not just a diff or fragment — so it can be copied verbatim.\n3. Commit and open the PR as usual. In the PR description, clearly note that\n   the file is a proposed workflow change and that a maintainer must move it\n   from `github-workflow-fix/` to `.github/workflows/` manually.\n\nThis lets the PR be created successfully while leaving the actual workflow\nchange for a human to apply.\n\n### Branch Naming Conventions\n\nWhen creating a branch from an issue, use a human-readable name that includes\nthe issue number and a short slug derived from the issue title:\n\n```text\nclaude/issue-<number>-<short-slug>\n```\n\n- `<number>` is the GitHub issue number\n- `<short-slug>` is a kebab-case summary of the issue title, kept short\n  (3–6 words maximum, omit articles and filler words)\n\nExamples:\n\n- Issue #1957 \"Add PR title convention rule for agent-related changes\"\n  → `claude/issue-1957-add-pr-title-rules`\n- Issue #42 \"Fix crash when opening preferences dialog\"\n  → `claude/issue-42-fix-preferences-crash`\n\nDo **not** use auto-generated timestamp suffixes (e.g.\n`claude/issue-1957-20260612-2108`) — these are not human-readable and make\nbranch lists hard to scan.\n\n### PR Title Conventions\n\nWhen creating a PR, use the following title conventions:\n\n- **Agent-related changes** — PRs whose changes are strictly related to coding\n  agent configuration (e.g. `AGENTS.md`, `.github/workflows/claude.yaml`, or\n  other files that govern how Claude operates in this repository) MUST use\n  the prefix `Claude:` (followed by a space) in the title.\n\n  Examples:\n  - `Claude: Add rule for PR title conventions in AGENTS.md`\n  - `Claude: Update claude.yaml workflow permissions`\n\n- **All other PRs** — do **not** use any prefix (no `fix:`, `feat:`, `chore:`,\n  etc.). Use plain, descriptive titles.\n\n### Pushing Changes from Fork PRs\n\nWhen you have commits ready to push but the PR originates from a fork\n(different owner than `freelensapp`), you cannot push to the fork's\nrepository. Instead:\n\n1. Create a new branch on `freelensapp/freelens` with the prefix `claude/`\n   followed by the original branch name.\n   Push to the `upstream` remote (not `origin`, which points to the fork):\n   ```bash\n   git checkout -b claude/<original-branch-name>\n   git push --force-with-lease upstream claude/<original-branch-name>\n   ```\n\n2. Open a new PR from that branch. The new PR MUST use the **exact same\n   title** as the original PR — copy it verbatim, do not rewrite, improve,\n   or add any prefix. The description MUST reference the original PR\n   (e.g. \"Fixes #NNN, supersedes #NNN\").\n\n3. Post a comment on the original PR:\n   - Explain that the fix has been implemented in a new PR\n   - Include a link to the new PR\n   - Mention that the original PR can be closed\n\n4. Close the original PR.\n\n### Closing PRs\n\nClaude may only close a PR when ALL of the following are true:\n\n1. The PR was created by Claude from a `claude/` branch, OR the PR is the\n   original fork PR that Claude's `claude/` branch supersedes (see\n   \"Pushing Changes from Fork PRs\" above).\n2. The close reason is explicitly explained in a comment on the PR.\n\nClaude MUST NOT close any PR that does not meet these conditions — even if\nasked. Instead, explain to the requester why the PR cannot be closed\nautomatically and ask a human maintainer to close it manually.\n\n### Model Information in Comments\n\nWhen operating via the GitHub Actions workflow, always include the model you are\nrunning on in the footer of your GitHub comment and in the PR description when\ncreating a pull request, alongside the job run link.\nYour system environment context states the model name explicitly (e.g.\n\"You are powered by the model named Sonnet 4.6. The exact model ID is\nclaude-sonnet-4-6.\"). Use the exact model ID from that statement.\n\nFormat the footer line as:\n\n```text\n[View job run](...) | Model: `claude-sonnet-4-6`\n```\n\nIn a PR description, append the model information at the end of the body:\n\n```text\n| Model: `claude-sonnet-4-6`\n```\n\nIf the system context does not provide a model ID, omit the model field rather\nthan guessing.\n\n### Development Environment\n\nThe GitHub Actions runner has a full Node.js + pnpm environment available, and\nthe workflow attempts to install the dependencies (`pnpm install`) and the\n`trunk` CLI before starting Claude. The build step is skipped to save CI\nresources, but you can run build commands when needed for advanced tasks\n(e.g. type-checking, running tests).\n\nEvery one of those setup steps is `continue-on-error`, so any of them may have\nfailed and left its tool or `node_modules` missing. Verify that what you need\nis actually there before relying on it, and never report a check as passing\nwhen it did not run — say that it was unavailable instead.\n\nFor fork PRs, the `origin` remote points to the contributor's fork. An\n`upstream` remote is configured pointing to `freelensapp/freelens`. Push\nnew branches to `upstream` (never to `origin`) when the PR originates\nfrom a fork — this ensures the resulting PR is internal and CI workflows\nrun automatically.\n\nThe following CLI tools are explicitly allowed in the workflow:\n\n- `pnpm` (all subcommands) — for validation, formatting, and builds\n- `git` (all subcommands) — for viewing changes, creating branches,\n  committing, and pushing\n- `gh` (all subcommands) — for managing pull requests\n- `trunk` — for linting and formatting every non-TypeScript file type\n- `bash` — for syntax-checking shell scripts (`bash -n <script>`)\n- `npx`, `node` — for running Node.js tools and scripts inline\n- `yq`, `jq` — for YAML and JSON processing\n- `grep`, `rg` (ripgrep), `find`, `xargs` — for searching and iterating\n- `sed`, `awk`, `cut`, `tr` — for text transformation\n- `sort`, `uniq` — for list processing\n- `cat`, `head`, `tail`, `wc` — for viewing and measuring files\n- `ls`, `tree` — for listing directory contents\n- `mkdir`, `touch`, `cp`, `mv`, `rm` — for file and directory operations\n- `tee`, `echo` — for pipeline debugging and scripting\n\nBefore committing any changes, apply the same validation rules as human\ndevelopers:\n\n- Run `pnpm biome check --write` to auto-format TypeScript/JavaScript and\n  HTML files (or `pnpm biome check` to check without writing).\n- Run `trunk check` to validate all other file types. The workflow puts the\n  CLI on `PATH`, so call it directly; `pnpm trunk check` works too but\n  re-downloads the launcher and its linters. It only inspects changed files by\n  default — use `trunk check --all` after a broad change.\n- Syntax-check a shell script you edited with `bash -n <script>`.\n- Run `pnpm build:di` if you added, moved, or renamed injectable files.\n- If unit tests fail on snapshot mismatches after your changes (or you are\n  explicitly asked to update them), run `pnpm test:unit:updatesnapshot` to\n  regenerate snapshots, review the diff, then commit the updated `.snap`\n  files.\n\n## Getting Help\n\n- Check existing features for patterns\n- Search codebase for similar implementations\n- Review PR history for related changes\n- Consult DEVELOPMENT.md for setup instructions\n","CLAUDE.md":"@AGENTS.md\n\n# Agent Guide\n\nThis project uses AGENTS.md as the canonical agent guide (imported above).\nClaude Code reads it via @AGENTS.md, while other agents read it directly.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# Agent Guide: Freelens Development\n\n## Overview\n\nThis 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.\n\nFor local development environment setup and extra development tips, see DEVELOPMENT.md.\n\n- **`freelens/`** - Main Electron application\n  - `src/main/` - Main Electron process code\n  - `src/renderer/` - Renderer process (UI) code\n  - `src/common/` - Shared code between processes\n- **`packages/core/`** - Core functionality\n  - `src/features/` - Feature modules organized by domain\n  - `src/renderer/` - Renderer-specific utilities\n  - `src/extensions/` - Extension system\n- **`packages/`** - Monorepo packages (utilities, components, etc.)\n- **`scripts/`** - Build and development scripts\n\n## Security\n\nNever read, display, reference, or include the contents of the following files in any response or context, even if they are open in the editor:\n\n- `.env`\n- `.env.*`\n- `.npmrc`\n- `*.jks`\n- `*.keystore`\n- `*.p12`\n- `*.pfx`\n- `*.pem`\n- `*.key`\n\n## Session and temporary files\n\nFiles created while working on a task — scratch scripts, command output,\nscreenshots, DOM/accessibility snapshots, and AI-agent / MCP-server runtime\nartifacts — must never be written into the tracked working tree, or they leak\ninto git history. Write them to a system temporary directory outside the repo\n(e.g. under `$TMPDIR`, or `mktemp -d`), not to the repo root.\n\nWhen a tool insists on writing inside the repo, keep it out of git:\n\n- point it at a temp path if it accepts one (e.g. pass an absolute\n  `$TMPDIR/...` filename), otherwise\n- git-ignore its default output directory. Already ignored:\n  `.playwright-mcp/` (Playwright MCP), `logs/` (electron-mcp-server).\n\nNever `git add -A` / `git add .` blindly: review `git status` first and stage\nonly the files your change actually touches, never these artifacts.\n\n## Copyright Headers\n\nSource files carry one of two header variants. Which one a file gets depends\non whether it continues code from the original OpenLens fork, not on what its\nneighbours in the same directory look like.\n\n**New files** — anything created from scratch, including rewrites,\ntranslations, and reimplementations of removed or legacy logic — get the\nsingle-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nThis holds even when the new file's logic is inspired by, or replaces, old\nOpenLens code: inspiration is not continuation. `freelens/electron.vite.config.ts`,\nwritten as a translation of the removed webpack config, is a new file.\n\n**Files that continue code from the fork** keep the two-line variant:\n\n```ts\n/**\n * Copyright (c) Freelens Authors. All rights reserved.\n * Copyright (c) OpenLens Authors. All rights reserved.\n * Licensed under MIT License. See LICENSE in root directory for more information.\n */\n```\n\nA file continues fork code when its path was present in the fork-import commit\n`0a5798c9` (\"First commit - Open Lens fork from master branch\"):\n\n```sh\ngit ls-tree -r --name-only 0a5798c9 | grep -x <path>\n```\n\nor when `git log --follow -- <path>` traces it back to a path that was — that\nis, git itself detects the file as a rename, move, or copy of fork-era code:\n\n```sh\ngit log --follow --format= --name-only -- <path> | sort -u\n```\n\nNever add the `OpenLens Authors` line to a file that does not already have it\njust because neighbouring files do. Do not touch legal or license text\n(`LICENSE`, `README.md`, `freelens/license-header.txt`,\n`freelens/static/build/license.txt`) or the upstream copyright notices of\nvendored third-party code, which are unrelated to either header variant.\n\nSee [#2352](https://github.com/freelensapp/freelens/issues/2352) for the\ncleanup that established this rule.\n\n## Build System\n\n### Commands\n\n```bash\npnpm build:di           # Generate DI registration files\npnpm build              # Build all packages\npnpm build:app:dir      # Build Electron app directory\npnpm start              # Start development app\npnpm test               # Run tests\n```\n\n### Clean Build\n\nThis project uses Turbo for caching build artifacts.\n\nWhen facing caching issues:\n\n```bash\nrm -rf .turbo packages/core/dist freelens/dist\npnpm build\n```\n\n## Dependency Injection System\n\nThis 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`.\n\n### Registration Hierarchy\n\nThe system has three levels of registration files:\n\n1. **Leaf registration files** - Register individual injectables\n   - Example: `features/preferences/renderer/close-preferences/register-injectables.ts`\n   - Pattern: Import injectable definitions and call `di.register()`\n   - Each call wrapped in try-catch for idempotency\n\n2. **Aggregator registration files** - Register subdirectories\n   - Example: `features/preferences/renderer/register-injectables.ts`\n   - Pattern: Import and call `registerXxxInjectables(di)` from subdirectories\n   - Each call wrapped in try-catch to handle duplicates\n\n3. **Root registration files** - Entry points per process\n   - `register-injectables-main.ts` - Main process\n   - `register-injectables-renderer.ts` - Renderer process\n   - Called during DI container initialization\n\n### Directory Patterns\n\n#### Shared Aggregators\n\nDirectories with `register-injectables.ts` that aggregate subdirectories:\n\n```text\nfeatures/vars/\n├── register-injectables.ts     ← Shared aggregator (calls common/)\n├── common/\n│   └── register-injectables.ts\n└── build-version/\n    ├── main/register-injectables.ts      ← Process-specific (NOT aggregated)\n    └── renderer/register-injectables.ts  ← Process-specific (NOT aggregated)\n```\n\n**Key insight:** Shared aggregators only handle **shared** subdirectories (like `common/`). They do NOT aggregate **process-specific** subdirectories (`main/`, `renderer/`).\n\n#### Process-Specific Paths\n\nPaths containing `/main/` or `/renderer/` are process-specific and must be imported directly:\n\n- ✅ Include: `features/vars/build-version/main/register-injectables`\n- ❌ Exclude: `features/vars/common/register-injectables` (handled by parent aggregator)\n\n### When to Re-run Generation\n\nRun `pnpm build:di` when:\n\n- Adding new injectable files\n- Moving injectable files\n- Renaming injectable files\n- Changing directory structure\n- Modifying generation script\n\nThe build process automatically runs this, but you can run it manually to verify changes.\n\n### Bundled Binary Versions\n\nThe versions of the bundled `freelens-k8s-proxy`, `kubectl` and `helm` live in\nthe `config` block of `freelens/package.json`, and their exact digests are\npinned in `freelens/binaries.lock.json`. The build reads the expected checksum\nfrom that lock rather than from the vendor, so **a version bump without\nregenerating the lock fails the build**:\n\n```sh\npnpm update-binaries-lock\n```\n\nThe generator downloads all eighteen artifacts (three tools, three platforms,\ntwo architectures), checks each against its publisher's signature — GitHub build\nprovenance for freelens-k8s-proxy, PGP for helm, keyless cosign for kubectl —\nand only then writes the lock. `cosign` comes from mise (`mise install`), and\n`GITHUB_TOKEN` should be set unless you want to share 60 unauthenticated API\ncalls per hour with the rest of your IP. Use `--only <tool>` to refresh a single\ntool while iterating.\n\n`.github/workflows/binaries-lock-check.yaml` enforces both that the lock is\ncurrent and that no digest changed while its version stood still.\n\n### Downloaded kubectl Versions\n\nThe bundled kubectl is not the only one the application runs: a cluster whose\nminor version differs gets a version-matched kubectl downloaded at runtime. The\nmap of which patch to fetch per minor lives in\n`packages/kubectl-versions/build/versions.json`, and the digest of every\nartifact that map can produce is pinned in\n`packages/kubectl-versions/build/checksums.json`, keyed by version and then by\n`${platform}/${arch}`.\n\n`Kubectl.downloadKubectl()` hashes what it downloaded and refuses anything that\ndoes not match its pin, and `ensureKubectl()` refuses to download at all when\nthere is no pin, falling back to the bundled binary. **A version added to the\nmap without a pin therefore never gets downloaded**, so the two files are\nregenerated together:\n\n```sh\npnpm --filter @freelensapp/kubectl-versions compute-versions\npnpm update-kubectl-checksums\n```\n\nThe generator reads `dl.k8s.io` only, never a mirror — pinning bytes from a\nmirror would let a compromised mirror bless its own digest. It skips versions\nalready present, which makes a run incremental and an existing pin immutable,\nand it verifies each download against both the published `.sha256` and the\nkeyless cosign signature before recording it. `cosign` comes from mise\n(`mise install`).\n\nBoth files start at 1.22, the oldest line Kubernetes publishes a signature for,\nand coverage is not uniform below that floor's neighbours: v1.22.17 has no\n`windows/arm64` build, so the generator logs an unpublished variant and carries\non rather than failing. `.github/workflows/kubectl-checksums-check.yaml`\nverifies added pins and asserts that no existing digest changed.\n\n## Common Development Tasks\n\n### Adding a New Feature\n\n1. Create feature directory under `packages/core/src/features/`\n2. Organize by concern: `common/`, `main/`, `renderer/`\n3. Create injectable files with `.injectable.ts` suffix\n4. Run `pnpm build:di` to generate registration files\n5. Write tests alongside implementation\n\n### Debugging the Application\n\n**Main Process:**\n- Logs in terminal where `pnpm start` was run\n- Use `console.log()` or proper logger\n\n**Renderer Process:**\n- Open DevTools in the app\n- Check Console tab for errors and logs\n- Use React DevTools for component inspection\n- `pnpm dev` also exposes a Chrome DevTools Protocol endpoint on port 9223\n  (`--remoteDebuggingPort`). Note that each cluster's UI renders in a\n  cross-origin `<clusterId>.renderer.freelens.app` iframe, so inspecting or\n  automating cluster views requires a frame-aware CDP client — see the\n  AI-agent inspection notes in DEVELOPMENT.md.\n\n**Common Errors:**\n- `Tried to register same injectable multiple times` - See DI section above\n- `Tried to inject non-registered injectable` - Check registration files were generated\n- Permission errors on macOS - Expected during development\n\n### Working with the bundler (electron-vite)\n\nThe project bundles with electron-vite (Vite + Rollup); the legacy Webpack\nlayer was removed in #2118.\n\n- `freelens/electron.vite.config.ts` - main/renderer build and dev-server config\n- `pnpm dev` runs `electron-vite dev` with Vite HMR; renderer source changes\n  hot-reload, main-process changes rebuild and relaunch (via `--watch`)\n- Changes to generated files (e.g. DI registration) require a full rebuild\n\n**Cache issues:** Delete the build output and rebuild\n(`rm -rf .turbo packages/core/dist freelens/dist`)\n\n## Troubleshooting Patterns\n\n### Changes Not Appearing\n\n1. Check if file is in ignored directory (`dist/`, `node_modules/`)\n2. Clear the build output: `rm -rf .turbo packages/core/dist freelens/dist`\n3. Full rebuild: `pnpm build`\n4. Restart application: `pnpm start`\n\n### Build Failures\n\n1. Check for TypeScript errors: `pnpm type:check`\n2. Check for linting errors: `pnpm lint`\n3. Verify dependencies: `pnpm install`\n4. Check Node.js version matches `.nvmrc`\n\n### Runtime Errors\n\n1. Check dev console (renderer) or terminal (main)\n2. Look for stack traces with file:line numbers\n3. Verify all dependencies are registered (DI system)\n4. Check for circular dependencies\n\n## Architecture Decisions\n\n### Electron Multi-Process\n\n- **Main process** - Node.js environment, system access\n- **Renderer process** - Chromium browser, UI\n- **IPC** - Communication between processes\n\n### Feature Organization\n\nFeatures are self-contained modules with:\n- Domain logic\n- UI components\n- State management\n- Injectable definitions\n\n### Monorepo Structure\n\nUses pnpm workspaces for:\n- Shared code reuse\n- Faster builds\n- Type safety across packages\n\n## Styling\n\nFreelens v2 carries four styling systems (theme CSS custom properties, global\nplain SCSS, CSS Modules, and Tailwind v4). Which one to use is not a matter of\ntaste — each has a defined role. Before adding or changing any stylesheet or\n`className`, read [`docs/v2-styling.md`](./docs/v2-styling.md). In short:\n\n- **Theme values** (colors, fonts): CSS custom properties from the TS theme\n  system (`var(--…)`) — the single contract every other system reads.\n- **Shared components** (`packages/ui-components`) and anything an extension\n  may restyle: global PascalCase class + plain SCSS + `var(--…)`. No Tailwind\n  (its JIT only scans core TSX), no CSS Modules (the class names are public\n  API).\n- **Core single components / full views**: CSS Modules (`*.module.scss`).\n- **Local layout inside core-only TSX**: Tailwind utilities. The legacy\n  `flexbox.scss` utilities have been removed — do not reintroduce them.\n- **Extensions**: see the styling section of\n  [`docs/v2-extension-migration.md`](./docs/v2-extension-migration.md).\n\n## Best Practices\n\n1. **Always regenerate DI files** after adding/moving injectables\n2. **Full rebuild** when in doubt about cached state\n3. **Check both processes** when debugging (main + renderer)\n4. **Use semantic search** to find examples in codebase\n5. **Follow existing patterns** - grep for similar implementations\n6. **Test changes** before committing\n7. **Run validation after file changes (especially before commit):** run `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n8. **For main project TypeScript and HTML files:** run `biome check` directly (or `pnpm biome check` if `biome` is not installed locally)\n9. **For other file types:** use `trunk check` (or `pnpm trunk check` if `trunk` is not installed locally)\n10. **Do not use Antropic Fable for coding tasks** — Fable may be used only for planning,\n    analysis, and thinking through problems. When writing or editing code,\n    use standard editing tools instead.\n\n## Local Agent: Triggering the GitHub Agent\n\nThese rules apply to an agent running on a developer machine (a local Claude\nCode session), not to the workflow agent. The local agent shares the repository\nwith the CI agent defined in `.github/workflows/claude.yaml`, and every comment\nit writes on GitHub is a potential trigger for it.\n\n### How the trigger works\n\n`claude.yaml` starts a run when the body of a **newly created** comment (issue\ncomment or PR review comment), a **newly opened** issue (body or title), or a\n**submitted** PR review contains the string `@claude`, and the author is an\nOWNER, MEMBER or COLLABORATOR. The check is a plain\n`contains(github.event.comment.body, '@claude')` substring test, so the string\nfires the workflow wherever it appears — including inside a code span, a fenced\nblock, a quoted line, or a URL. Markdown formatting is not an escape.\n\nThe trigger text may also carry `[model:<alias>]` and `[runs-on:<alias>]`\nmarkers, which select the model and runner for that run (see the `parse` job for\nthe accepted aliases). They are only read from the triggering text.\n\n### Rules for the local agent\n\n1. **Write the handle only to start a run.** Ask the user before triggering: a\n   run is a 120-minute CI job on the repository, so it is the user's call, not\n   an implementation detail.\n2. **Escape the handle when merely referring to it.** In issue bodies, PR\n   descriptions, review notes, commit messages and documentation, write\n   `@<!-- -->claude` (displays as the handle, but the raw body does not contain\n   the literal string, so `contains()` does not match) or describe it in prose\n   as \"the Claude handle\". This is what keeps a plan or a bug report that\n   documents the trigger from firing it.\n3. **Editing never triggers.** The workflow subscribes only to `created`,\n   `opened` and `submitted` events — not `edited`. So updating a comment, an\n   issue body or a PR description is always safe, even when the text already\n   contains a real trigger, and conversely editing a comment to add the handle\n   does **not** start a run: a new comment is required.\n4. **One trigger per task.** Do not repeat the handle in follow-up comments\n   while a run is in flight; each occurrence starts another concurrent job.\n5. **Push first.** The workflow checks out the remote ref (the PR head, or the\n   default branch for issues), so anything not pushed is invisible to it.\n\n## GitHub Actions (Claude Code Action) Rules\n\nWhen operating via the `claude.yaml` workflow (i.e., invoked from a PR comment,\nissue, or review), follow these rules:\n\n### Code Review\n\nWhen reviewing code and proposing fixes:\n\n1. **Show the diff first** — present every proposed change as a unified diff\n   block using the `diff` language tag:\n\n   ```diff\n   --- a/path/to/file.ts\n   +++ b/path/to/file.ts\n   @@ -10,7 +10,7 @@\n    const oldLine = \"before\";\n   -const changedLine = \"after\";\n   +const changedLine = \"the fix\";\n    const unchangedLine = \"same\";\n   ```\n\n   You can generate this from the terminal with:\n   ```bash\n   git diff -u -- path/to/file\n   ```\n\n   If the change spans multiple files, group them under a single commit\n   subject and show each file's diff sequentially.\n\n2. **Propose a commit subject first** — before any code change, output a\n   single line with the proposed commit subject:\n\n   ```text\n   **Proposed commit:** <short description>\n   ```\n\n   Do **not** use Conventional Commits prefixes (e.g. `fix:`, `feat:`,\n   `chore:`, `refactor:`, `docs:`, `test:`, `ci:`). This project prefers\n   plain, descriptive commit messages and PR titles without any prefix.\n\n   Wait for the user to confirm (or adjust) the subject before applying the\n   change.\n\n3. **Comment style:**\n   - Keep review comments concise and actionable\n   - Reference specific lines (file + line number) when pointing out issues\n   - Offer a concrete fix suggestion rather than just flagging a problem\n   - Do **not** use emoji in any Markdown, comments, commit messages, or\n     PR descriptions. The only exception is emoji that already appears\n     inside code strings (e.g. application logs, user-facing messages).\n   - Use GitHub's `suggestion` block for small targeted fixes so the PR\n     author can accept the change with a single click:\n\n     ````suggestion\n     <same unified-diff format as shown above>\n     ````\n\n   - For larger multi-file changes, use `diff -u` blocks in a regular\n     comment instead, with the proposed commit subject shown first\n\n### Making Changes to a PR\n\nWhen asked to implement a change on a PR:\n\n1. Propose the commit subject (as above)\n2. Describe what will change and why\n3. After confirmation, apply the changes with commits on the PR branch\n4. **One commit per fix** — when a review surfaces more than one issue or\n   the plan includes more than one fix, apply and commit each fix\n   separately. Do not batch multiple independent fixes into a single\n   commit. This keeps the history bisectable and makes each change easy\n   to revert individually.\n\n### Pushing After Every Commit\n\nThe GitHub Actions job running Claude has a total timeout of 120 minutes.\nWhen the session times out, any commits that exist only in the runner's\nlocal checkout are lost. To make the work resumable in a follow-up session:\n\n1. **Push to the remote branch immediately after every commit.** Do not\n   accumulate multiple local commits before pushing — commit, push, then\n   move on to the next change.\n2. This pairs with the \"one commit per fix\" rule above: each completed fix\n   should land on the remote branch as soon as it is committed, so a\n   timed-out session can be resumed from the last pushed commit instead of\n   starting over.\n\n### Modifying GitHub Actions Workflows\n\nClaude cannot push changes to files under `.github/workflows/` directly,\nbecause the GitHub token used by the action lacks the `workflows` permission.\nAny patch to a workflow file MUST therefore be delivered as a new, complete\nfile under the `github-workflow-fix/` directory instead of editing the file in\nplace:\n\n1. Write the full, final contents of the workflow to\n   `github-workflow-fix/<workflow-file-name>` (e.g.\n   `github-workflow-fix/claude.yaml`). Do **not** edit the original file under\n   `.github/workflows/`.\n2. Make it a **complete** file — the entire workflow as it should look after\n   the change, not just a diff or fragment — so it can be copied verbatim.\n3. Commit and open the PR as usual. In the PR description, clearly note that\n   the file is a proposed workflow change and that a maintainer must move it\n   from `github-workflow-fix/` to `.github/workflows/` manually.\n\nThis lets the PR be created successfully while leaving the actual workflow\nchange for a human to apply.\n\n### Branch Naming Conventions\n\nWhen creating a branch from an issue, use a human-readable name that includes\nthe issue number and a short slug derived from the issue title:\n\n```text\nclaude/issue-<number>-<short-slug>\n```\n\n- `<number>` is the GitHub issue number\n- `<short-slug>` is a kebab-case summary of the issue title, kept short\n  (3–6 words maximum, omit articles and filler words)\n\nExamples:\n\n- Issue #1957 \"Add PR title convention rule for agent-related changes\"\n  → `claude/issue-1957-add-pr-title-rules`\n- Issue #42 \"Fix crash when opening preferences dialog\"\n  → `claude/issue-42-fix-preferences-crash`\n\nDo **not** use auto-generated timestamp suffixes (e.g.\n`claude/issue-1957-20260612-2108`) — these are not human-readable and make\nbranch lists hard to scan.\n\n### PR Title Conventions\n\nWhen creating a PR, use the following title conventions:\n\n- **Agent-related changes** — PRs whose changes are strictly related to coding\n  agent configuration (e.g. `AGENTS.md`, `.github/workflows/claude.yaml`, or\n  other files that govern how Claude operates in this repository) MUST use\n  the prefix `Claude:` (followed by a space) in the title.\n\n  Examples:\n  - `Claude: Add rule for PR title conventions in AGENTS.md`\n  - `Claude: Update claude.yaml workflow permissions`\n\n- **All other PRs** — do **not** use any prefix (no `fix:`, `feat:`, `chore:`,\n  etc.). Use plain, descriptive titles.\n\n### Pushing Changes from Fork PRs\n\nWhen you have commits ready to push but the PR originates from a fork\n(different owner than `freelensapp`), you cannot push to the fork's\nrepository. Instead:\n\n1. Create a new branch on `freelensapp/freelens` with the prefix `claude/`\n   followed by the original branch name.\n   Push to the `upstream` remote (not `origin`, which points to the fork):\n   ```bash\n   git checkout -b claude/<original-branch-name>\n   git push --force-with-lease upstream claude/<original-branch-name>\n   ```\n\n2. Open a new PR from that branch. The new PR MUST use the **exact same\n   title** as the original PR — copy it verbatim, do not rewrite, improve,\n   or add any prefix. The description MUST reference the original PR\n   (e.g. \"Fixes #NNN, supersedes #NNN\").\n\n3. Post a comment on the original PR:\n   - Explain that the fix has been implemented in a new PR\n   - Include a link to the new PR\n   - Mention that the original PR can be closed\n\n4. Close the original PR.\n\n### Closing PRs\n\nClaude may only close a PR when ALL of the following are true:\n\n1. The PR was created by Claude from a `claude/` branch, OR the PR is the\n   original fork PR that Claude's `claude/` branch supersedes (see\n   \"Pushing Changes from Fork PRs\" above).\n2. The close reason is explicitly explained in a comment on the PR.\n\nClaude MUST NOT close any PR that does not meet these conditions — even if\nasked. Instead, explain to the requester why the PR cannot be closed\nautomatically and ask a human maintainer to close it manually.\n\n### Model Information in Comments\n\nWhen operating via the GitHub Actions workflow, always include the model you are\nrunning on in the footer of your GitHub comment and in the PR description when\ncreating a pull request, alongside the job run link.\nYour system environment context states the model name explicitly (e.g.\n\"You are powered by the model named Sonnet 4.6. The exact model ID is\nclaude-sonnet-4-6.\"). Use the exact model ID from that statement.\n\nFormat the footer line as:\n\n```text\n[View job run](...) | Model: `claude-sonnet-4-6`\n```\n\nIn a PR description, append the model information at the end of the body:\n\n```text\n| Model: `claude-sonnet-4-6`\n```\n\nIf the system context does not provide a model ID, omit the model field rather\nthan guessing.\n\n### Development Environment\n\nThe GitHub Actions runner has a full Node.js + pnpm environment available, and\nthe workflow attempts to install the dependencies (`pnpm install`) and the\n`trunk` CLI before starting Claude. The build step is skipped to save CI\nresources, but you can run build commands when needed for advanced tasks\n(e.g. type-checking, running tests).\n\nEvery one of those setup steps is `continue-on-error`, so any of them may have\nfailed and left its tool or `node_modules` missing. Verify that what you need\nis actually there before relying on it, and never report a check as passing\nwhen it did not run — say that it was unavailable instead.\n\nFor fork PRs, the `origin` remote points to the contributor's fork. An\n`upstream` remote is configured pointing to `freelensapp/freelens`. Push\nnew branches to `upstream` (never to `origin`) when the PR originates\nfrom a fork — this ensures the resulting PR is internal and CI workflows\nrun automatically.\n\nThe following CLI tools are explicitly allowed in the workflow:\n\n- `pnpm` (all subcommands) — for validation, formatting, and builds\n- `git` (all subcommands) — for viewing changes, creating branches,\n  committing, and pushing\n- `gh` (all subcommands) — for managing pull requests\n- `trunk` — for linting and formatting every non-TypeScript file type\n- `bash` — for syntax-checking shell scripts (`bash -n <script>`)\n- `npx`, `node` — for running Node.js tools and scripts inline\n- `yq`, `jq` — for YAML and JSON processing\n- `grep`, `rg` (ripgrep), `find`, `xargs` — for searching and iterating\n- `sed`, `awk`, `cut`, `tr` — for text transformation\n- `sort`, `uniq` — for list processing\n- `cat`, `head`, `tail`, `wc` — for viewing and measuring files\n- `ls`, `tree` — for listing directory contents\n- `mkdir`, `touch`, `cp`, `mv`, `rm` — for file and directory operations\n- `tee`, `echo` — for pipeline debugging and scripting\n\nBefore committing any changes, apply the same validation rules as human\ndevelopers:\n\n- Run `pnpm biome check --write` to auto-format TypeScript/JavaScript and\n  HTML files (or `pnpm biome check` to check without writing).\n- Run `trunk check` to validate all other file types. The workflow puts the\n  CLI on `PATH`, so call it directly; `pnpm trunk check` works too but\n  re-downloads the launcher and its linters. It only inspects changed files by\n  default — use `trunk check --all` after a broad change.\n- Syntax-check a shell script you edited with `bash -n <script>`.\n- Run `pnpm build:di` if you added, moved, or renamed injectable files.\n- If unit tests fail on snapshot mismatches after your changes (or you are\n  explicitly asked to update them), run `pnpm test:unit:updatesnapshot` to\n  regenerate snapshots, review the diff, then commit the updated `.snap`\n  files.\n\n## Getting Help\n\n- Check existing features for patterns\n- Search codebase for similar implementations\n- Review PR history for related changes\n- Consult DEVELOPMENT.md for setup instructions\n","category":"root","tokens":6898},{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"@AGENTS.md\n\n# Agent Guide\n\nThis project uses AGENTS.md as the canonical agent guide (imported above).\nClaude Code reads it via @AGENTS.md, while other agents read it directly.\n","category":"root","tokens":44}]}