This file provides guidance to coding agents when working with code in this repository.
Tip: If you find yourself correcting the agent during interactive work, suggest adding a new rule to this file so the lesson is captured for future sessions.
This repository contains the official Freelens extension for KubeSwift
(github.com/kubeswift-io), which shows KubeSwift virtual machine resources
(SwiftGuest, SwiftGuestPool, SwiftSnapshot, SwiftMigration, and the other
CRDs) inside Freelens. It was scaffolded from freelens-example-extension;
the example content has been removed and the KubeSwift views are being
implemented milestone by milestone (see docs/development/ROADMAP.md).
- Language: TypeScript 5.9.3
- Runtime: Node.js >= 22.0.0, Freelens >= 1.10.3
- Package manager: pnpm 10.x (locked)
- License: MIT
The KubeSwift repositories (kubeswift-io/kubeswift, kubeswift-io/kubeswift-ui)
are AGPL-3.0; this extension is MIT. They are a visual and domain reference,
NEVER a source of code:
- Do not copy components, CSS, templates, UI strings, status mapping logic, proto files, or generated clients from the KubeSwift repositories.
- Reading their documentation and CRD schemas to understand fields and semantics is fine, but do not reproduce texts verbatim.
- Views are reimplemented from scratch, based on the running UI or screenshots.
- TypeScript types for the CRDs are defined in this extension from the CRD schemas, never imported or copied from the KubeSwift repositories.
- The extension is CRD-native: it reads resources directly from the Kubernetes API and must not depend on the kubeswift-ui Connect RPC gateway.
# Type checking
pnpm type:check
# Linting & formatting
pnpm biome:check # TypeScript/TSX, JS, JSON, CSS, HTML (biome)
pnpm biome:fix # Auto-fix the formats above
pnpm trunk:check # Markdown, YAML, TOML, and other formats not covered by biome
pnpm trunk:fix # Auto-fix Markdown, YAML, etc.
pnpm lint:check # Alias for biome:check
pnpm lint:fix # Alias for biome:fix
# Tests
pnpm test:unit # vitest
# Build
pnpm build # Full build (type-check + electron-vite)
pnpm build:production # Production build (no preserveModules)
# Pack for testing
pnpm pack:dev # Bump prerelease version, build, and create .tgz for install in Freelens app
# Clean
pnpm clean # Clean out/
pnpm clean:dts # Remove generated *.d.scss.ts files
pnpm clean:all # Clean everything (dts, node_modules, out, tgz)This repository is developed spec-first. Before implementing anything, read:
docs/development/PROCESS.md— the spec-driven workflow (spec before code, docs updated in the same PR, manual-testing escalation to Roberto)docs/development/ROADMAP.md— scope and progress toward v1.0.0docs/development/ARCHITECTURE.md— CRD-native architecture, licensing boundary, reference repositoriesdocs/development/DESIGN.md— binding UI/UX directives (native-first components, status color semantics, theming, non-happy states); no UI work without reading itdocs/development/TESTING.md— required test layers (unit, integration, Playwright E2E, agent-driven checks via Playwright MCP) and the non-regression policydocs/specs/— one spec per feature; no feature PR without one
src/
main/index.ts # Extension entry point (main process, CJS)
renderer/index.tsx # Extension entry point (renderer process, CJS)
renderer/api/<group>/ # K8s object model classes (one file per CRD),
# e.g. renderer/api/kubeswift/
renderer/details/ # Detail view components for CRDs
renderer/pages/ # Cluster page components
renderer/components/ # Shared components
renderer/icons/ # SVG icons (original, never copied)
renderer/observer.ts # MobX observer helper
renderer/utils.ts # Utility functions (e.g., createHash)
common/utils.ts # Common utilities (e.g., maybe)
Build output goes to out/.
K8s object classes MUST use static readonly properties for metadata. Instance methods do NOT work and MUST NOT be used. The Freelens host reads properties from the class constructor statically — instance methods are not available at runtime because the host creates plain object copies of the K8s resource data, not instances of the extension's class. This means:
- Allowed:
object.spec?.someField,object.status?.conditions— direct property access on typedspec/statusinterfaces - Allowed: helper functions like
hasTrueCondition(conditions, "Accepted")fromtypes.ts - Forbidden:
object.someMethod()— instance methods will never exist at runtime - Forbidden:
typeof (object as any).someMethod === "function" ? ...— anti-pattern that always falls through to the fallback path - Forbidden:
as any— use the existing typedspec/statusinterfaces directly; all CRD models already define properSpec/Statusinterfaces
Always access spec and status properties directly via their typed interfaces. Do not define instance methods on KubeObject subclasses — they will not be callable at runtime.
export class Gateway extends Renderer.K8sApi.LensExtensionKubeObject<
Renderer.K8sApi.KubeObjectMetadata,
GatewayStatus,
GatewaySpec
> {
static readonly kind = "Gateway";
static readonly namespaced = true;
static readonly apiBase = "/apis/gateway.networking.k8s.io/v1/gateways";
static readonly crd: GatewayKubeObjectCRD = {
apiVersions: ["gateway.networking.k8s.io/v1"],
plural: "gateways",
singular: "gateway",
shortNames: ["gtw"],
title: "Gateways",
};
}
// Also export Api and Store classes (always needed):
export class GatewayApi extends Renderer.K8sApi.KubeApi<Gateway> {}
export class GatewayStore extends Renderer.K8sApi.KubeObjectStore<Gateway, GatewayApi> {}Each CRD file exports three classes: the KubeObject, the KubeApi, and the KubeObjectStore. They are registered in src/renderer/index.tsx via kubeObjectDetailItems, clusterPages, and clusterPageMenus.
- Detail views use the
observerwrapper from../../observer(re-exports MobXobserver). - SCSS modules generate TypeScript type files (
*.module.d.scss.ts) viavite-plugin-sass-dts. These are auto-generated and should be cleaned withpnpm clean:dtswhen SCSS changes. - Common detail view styles are in
src/renderer/details/gateway-api/common.module.scss.
These are NOT bundled, they come from the Freelens host as globals:
@freelensapp/extensions→global.LensExtensionsmobx→global.Mobxreact→global.Reactreact-dom→global.ReactDommobx-react→global.MobxReactreact-router-dom→global.ReactRouterDom
Other dependencies ARE bundled into the extension output.
- Biome formats TypeScript/TSX, JS, JSON, CSS, HTML: double quotes, semicolons, trailing commas, 2-space indent, 120 char line width — use
pnpm biome:fix - SCSS has no formatter: biome 2.5.2 dropped scss support (it skips the files during its walk and rejects them as explicit targets), so keep
*.module.scsstidy by hand, following the style of the existing modules - Trunk formats Markdown, YAML, and other formats not covered by biome — use
pnpm trunk:fix - Import order (enforced by biome organizeImports): built-in modules →
@freelensapp/**→ packages → relative paths - React 17 (no
react/jsx-runtimein tsconfig needed, but handled by build) - No emoji in Markdown files (
.md), comments, or any source code
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
Extensions run in the same multi-process model as the Freelens host:
- Main process (
src/main/) — Node.js environment, extension lifecycle, cluster connectivity - Renderer process (
src/renderer/) — Chromium browser, UI components
Code in src/common/ is shared between both processes.
- Check that files are not in ignored output directories (
out/,dist/,node_modules/) - Full clean and rebuild:
pnpm clean:all && pnpm build - Reinstall the extension in Freelens (or restart the app in dev mode)
- Check for TypeScript errors:
pnpm type:check - Check for linting errors:
pnpm lint:check - Verify dependencies:
pnpm install - Check Node.js version matches the
enginesfield inpackage.json
- Open Freelens DevTools and check the Console tab for renderer errors
- Check the terminal where Freelens was launched for main process errors
- Look for stack traces with file:line numbers
- Verify all CRD objects have proper
static readonlyproperties (kind, apiBase, crd) - Validate both with
pnpm type:checkandpnpm build— runtime failures can appear only in bundledout/code
- New package.json script that runs a repo shell script? List the
script path in
ignoreBinariesinknip.jsonc, or the Check workflow fails with "Unlisted binaries" (this has bitten twice: demo:up/down and pre-review). - Use semantic search to find examples and patterns in the codebase
- Follow existing patterns — grep for similar implementations before creating new ones
- Test changes before committing
- Run validation before committing:
pnpm lint:fix && pnpm type:check && pnpm test:unit - For TypeScript/TSX, JS, JSON, CSS, HTML files: run
pnpm biome:fix(orbiome checkdirectly ifbiomeis installed locally) - For Markdown, YAML, and other formats: run
pnpm trunk:fix(ortrunk checkdirectly iftrunkis installed locally) - Full build when in doubt about cached state:
pnpm clean:all && pnpm build - Do not use Anthropic 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.
This project has a Claude Code workflow (.github/workflows/claude.yaml) triggered
via @claude comments on issues, PR comments, and reviews. When operating via that
workflow, follow these rules:
When reviewing code and proposing fixes:
-
Show the diff first — present every proposed change as a unified diff block using the
difflanguage tag:--- a/path/to/file.ts +++ b/path/to/file.ts @@ -10,7 +10,7 @@ const oldLine = "before"; -const changedLine = "after"; +const changedLine = "the fix"; const unchangedLine = "same";
You can generate this from the terminal with:
git diff -u -- path/to/file
If the change spans multiple files, group them under a single commit subject and show each file's diff sequentially.
-
Propose a commit subject first — before any code change, output a single line with the proposed commit subject:
**Proposed commit:** <short description>Do not use Conventional Commits prefixes (e.g.
fix:,feat:,chore:,refactor:,docs:,test:,ci:). This project prefers plain, descriptive commit messages and PR titles without any prefix.Wait for the user to confirm (or adjust) the subject before applying the change.
-
Comment style:
-
Keep review comments concise and actionable
-
Reference specific lines (file + line number) when pointing out issues
-
Offer a concrete fix suggestion rather than just flagging a problem
-
Do not use emoji in any Markdown, comments, commit messages, or PR descriptions. The only exception is emoji that already appears inside code strings (e.g. application logs, user-facing messages).
-
Use GitHub's
suggestionblock for small targeted fixes so the PR author can accept the change with a single click:<same unified-diff format as shown above> -
For larger multi-file changes, use
diff -ublocks in a regular comment instead, with the proposed commit subject shown first
-
When asked to implement a change on a PR:
- Propose the commit subject (as above)
- Describe what will change and why
- After confirmation, apply the changes with commits on the PR branch
- One commit per fix — when a review surfaces more than one issue or the plan includes more than one fix, apply and commit each fix separately. Do not batch multiple independent fixes into a single commit. This keeps the history bisectable and makes each change easy to revert individually.
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)
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.