walph
CLAUDE.md9 starsChanged 4 days agoMITView on GitHub
Detailed patterns live in the skills (.claude/skills/). Layers: main-ipc, main-processes, renderer-store, renderer-components, renderer-styles, renderer-editor, renderer-terminal, app-testing, i18n. Domains: workspaces, commands-settings, files-git-search, lsp, debugger, claude-panel, containers, packaging-release. Load the relevant skills before writing code in those areas; this file only covers the global rules. Language: the whole project is in English (code, comments, test names, logs, docs, commits, PRs, .claude/); always respond to the user in Portuguese (pt-BR). The app UI is translated into pt-BR (reference), English and Spanish: no UI…
- Installs packages
In this repository17 other files it gives its agents.
- app-testingSkill
- claude-panelSkill
- commands-settingsSkill
- containersSkill
- debuggerSkill
- files-git-searchSkill
- i18nSkill
- lspSkill
- main-ipcSkill
- main-processesSkill
- packaging-releaseSkill
- renderer-componentsSkill
- renderer-editorSkill
- renderer-storeSkill
- renderer-stylesSkill
- renderer-terminalSkill
- workspacesSkill
The file
We split this CLAUDE.md into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 136
window.prompt()doesn't exist in Electron: use inline inputs or native dialogs through main.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Detailed patterns live in the skills (
.claude/skills/). Layers:main-ipc,main-processes,renderer-store,renderer-components,renderer-styles,renderer-editor,renderer-terminal,app-testing,i18n. Domains:workspaces,commands-settings,files-git-search,lsp,debugger,claude-panel,containers,packaging-release. Load the relevant skills before writing code in those areas; this file only covers the global rules.Language: the whole project is in English (code, comments, test names, logs, docs, commits, PRs,
.claude/); always respond to the user in Portuguese (pt-BR). The app UI is translated into pt-BR (reference), English and Spanish: no UI text is written in code, it always goes through the catalogs insrc/shared/i18n/(i18nskill).
Overview
Walph is a personal IDE built from scratch: Electron 45 + electron-vite 6 + React 19 + TypeScript 7, cross-platform (macOS, Windows, Linux).
- src/shared/:
types/(index.tscomposesWalphApi; one file per domain with its types and its<Domain>Apiinterface) is the contract between main and renderer, andipcSpec.ts(runtimeinvoke/send/event+ channel per method;ipc.tsderives the typed channel maps andIPC_CHANNELSfrom it). Every capability starts here.i18n/(locale,t(), one message catalog per domain ini18n/messages/, each with pt-BR, en and es).commands.ts(CommandId,defaultKeybindings,isCommandAvailable),settings.ts(schema, defaults,mergeSettings: default ← user ← workspace),pathCompare.ts(the single path comparison of the repo). - src/main/:
index.ts(lifecycle),window/(BaseWindow + one WebContentsView per workspace),menu.ts(native menu →app:command),ipc/<domain>.ts(one file per domain:fs,workspace,session,recent,search,files,git,terminal,settings,docker,lsp,debug,claude,update,cli…). Anipc/<domain>.tsis wiring only (channels, validation, delegation); services, state and pure logic live in<domain>/(git/ops/,settings/service.ts,recent/store.ts,terminal/create.ts,window/closeController.ts,search/parseRipgrep.ts…). Nothing outsideipc/**,index.tsandmenu.tsimportsipc/*(lint-enforced, no exceptions) and anipc/<domain>.tsimports no siblingipc/*.tsbut./typed: what two domains share lives in the domain (<domain>/registry.ts), the event pushsendinwindow/send.tsand the argument validators invalidate/args.ts. - src/preload/:
index.tsexposeswindow.apiviacontextBridge, built bybuildApi(ipcSpec, ipcRenderer)(buildApi.ts) from the spec inshared/ipcSpec.ts, plus the few members that are not channels (platform,isPackaged,homeDir,locale,files.pathForFile). Forwards only; zero logic; never edited per capability. - src/renderer/:
store.ts(zustand base; imports no action module;Statecomposes the per-domain interfaces oftypes/, whose initial values live instate/) plus the action modules beside it (<domain>Actions.ts,diskSync.ts,workspaceLifecycle.ts…),commands/(registry.ts+builtin.ts: menu and palette callexecuteCommand(id)),components/(editor/CodeMirror 6,sidebar/,quickInput/,claude/, terminal panel with xterm.js),lib/(pure utilities),styles.css+styles/*.css(tokens instyles/tokens.css). - e2e/: Playwright specs launching the built app with a temp repo as argv and an isolated
--user-data-dir.
Development Commands
pnpm install # installs deps, downloads the Electron binary, rebuilds node-pty
pnpm dev # app in dev mode (renderer HMR; main/preload changes restart the app)
pnpm dev <dir> # same, opening a folder (argv → `workspace:initial`)
pnpm typecheck # tsc 7 for node, web, e2e and the node/web test projects (tsconfig.{node,web}.test.json)
pnpm lint # eslint --max-warnings 0 + prettier --check
pnpm format # prettier --write + eslint --fix
pnpm test # vitest run (project `node`: main/preload/shared; project `renderer`: jsdom)
pnpm test:e2e # electron-vite build + Playwright (_electron.launch on out/main/index.js)
pnpm knip # unused files, exports and dependencies (knip.json)
pnpm check # typecheck + lint + knip + docs:check + test — must be green with zero warnings
pnpm docs:check # paths cited in every doc (CLAUDE.md, README, docs/, skills, agents, commands) still exist (used by `/audit claude-setup`)
pnpm build # production build into out/
pnpm dist # installer for the current OS into dist/Key Conventions
Files
| Kind | Pattern | Example |
| Main IPC domain | src/main/ipc/<domain>.ts exporting register<Domain>Handlers() | ipc/git.ts |
| Main pure logic | src/main/<domain>/<camelCase>.ts + .test.ts | git/parseStatus.ts |
| React component | PascalCase.tsx, one main component per file | GitView.tsx |
| Renderer utility | src/renderer/src/lib/<camelCase>.ts + .test.ts | lib/path.ts |
| E2E test | e2e/<feature>.spec.ts | e2e/smoke.spec.ts |
Code
- Strict flags in production code (
tsconfig.node.json/tsconfig.web.json):exactOptionalPropertyTypesandnoUncheckedIndexedAccess; tests only get the first (*.test.jsonprojects). Writex?: T | undefinedonly whereundefinedis a real value; otherwise omit the key (omitUndefinedfrom@shared/omitUndefined, or a conditional spread). Indexed access gets a real guard (?? default,.at(),charAt), not!.prefer-nullish-coalescingkeeps a||only witheslint-disable-next-line … -- <why>when''/0must count as missing. Data from IPC, disk or servers is typedunknownat the boundary, sono-unnecessary-conditionnever tempts anyone to delete its checks. - Prettier style: no semicolons, single quotes, 120 columns, 2-space indent.
- New action =
CommandId+defaultKeys/defaultWhenentry indefaultKeybindings(shared) → menu item if it belongs to a menu (main) →registerCommandincommands/builtin.ts. No keydown handlers for app shortcuts — native accelerators fire them. import typefor type-only imports;@shared/*alias for the contract; relative imports inside each process.- Short code comments in English explaining why, not what.
- IPC channels named
domain:action; errors thrown in main with translated messages (t()), shown in the renderer withattempt(fn, { format, fallback, quiet })(renderer/src/attempt.ts:notify(errorMessage(err))+ fallback;errorMessageis inlib/errors.ts). - Every user-visible string (JSX, attributes, toasts, errors, dialogs, menus, quick picks, settings metadata) comes
from
src/shared/i18n/messages/<domain>.tswith the three languages:useT()in components,t()elsewhere, resolved at call time (never in a module constant). Numbers and dates viai18n/format.ts. Details:i18nskill. - Commits follow
.claude/git/commit.md; PRs use.claude/git/pr.md. - Enforced by
pnpm lint(eslint.config.mjs): layer boundaries (sharedimports no Node/Electron/main/renderer/preload;rendererimports no Node/Electron/main/preload;store.tsimports no action module;preloadimports onlyelectron,./buildApiand@shared, runtime only@shared/ipcSpec), no barewriteFile/appendFilein main outsidefs/atomicWrite.ts, no import cycles, theipc/*layering above (no-restricted-imports), nouseStore.getState/setStateoutsidestore.tsand components, no literal UI text in.tsx,no-console(onlyerror/warn) and the type-aware promise/anyrules. Unused exports failpnpm knip: export only what another module imports.
Main patterns (details in main-ipc / main-processes)
- New capability = contract (
shared/types/<domain>.ts) → spec entry (shared/ipcSpec.ts:invoke/send/event+ channel;IpcInvoke/IpcSend/IpcEvents/IPC_CHANNELSinshared/ipc.tsand the preload bridge are derived) → handler (main/ipc, viahandle/onfromipc/typed.ts,sendfromwindow/send.ts) → usage (store action). - External processes with
execFile/spawn+ argument arrays;--before user input; owned and killed with their view.
Renderer patterns (details in the renderer-* skills)
- Single zustand store (
store.ts); actions are exported functions in the modules beside it (<domain>Actions.ts), never re-exported fromstore.ts(import cycle); startup lives inbootstrap.ts(bootstrap(), called once bymain.tsx: locale, built-in commands,startStoreReactions, appearance, auto save, focus contexts,startIpcListeners, then settings sync and workspace restore; App.tsx keeps only DOM concerns); each module exposes its ownstart…Reactions, composed withcreateReactionScope(lib/reactions.ts); store subscriptions useuseStore.subscribe(selector, listener, { equalityFn })(subscribeWithSelector), never a bare(s, prev)listener; modules at the root ofsrc/renderer/srcnever import.tsxcomponents (lint); narrow selectors. - One CodeMirror
EditorViewper editor group (maxMAX_EDITOR_GROUPS= 8, laid out by the split tree inshared/editorLayout.ts) and one canonicalEditorStateper tab; xterm output only throughlib/terminalBus.ts. - Visual language = the Walph design system (reference copy in
docs/design-system/:components/<Name>/README.md,components/bundle.css; tokens named as in itstokens.json, with a Dark and a Light value each, kept in sync withthemes/by a test:bg-000…400,ink*,accent*; light/neon only for AI, focus and the primary action). Colors via CSS tokens only; icons: design-systemIcon→FileIconfor files/folders → codicons for the rest; fonts Geist + JetBrains Mono bundled; UI copy translated (pt-BR, en, es) through thei18ncatalogs.
Security Baseline
contextIsolation: true,sandbox: true,nodeIntegration: false— never change them.- No
shell: true, no command strings; names from the UI validated (assertValidName); deletes go to the trash. - Explorer mutations (
fs:createFile/createDir/rename/trash/move) only inside the view folder (resolveInsideRoot/resolveEntryInsideRootinfs/paths.ts);readFile/exists/readDirstay free;fs:writeFilewrites only inside the view folder or a file main read for that view (fs/writeAccess.ts). - External links open in the browser (
setWindowOpenHandler); no remote content in the window; CSP inindex.html. GIT_TERMINAL_PROMPT=0for every git call (never hangs on credentials).
Known Pitfalls
ELECTRON_RUN_AS_NODE=1inherited from VS Code / Claude Code makesrequire('electron')plain Node (appis undefined). Run the app withenv -u ELECTRON_RUN_AS_NODE.- pnpm only (
nodeLinker: hoisted,allowBuildsinpnpm-workspace.yaml); never commit apackage-lock.json. Install quirks, ESM-only deps and native binaries (asarUnpack):packaging-releaseskill. - TypeScript 7 has no JS API and
typescript-eslintneedstypescript <6.1:typescriptis aliased to@typescript/typescript6(linter/editor tooling only) and TS 7 is installed astypescript7. Both register atscbin, sopnpm exec tscis TS 6: always typecheck withpnpm typecheck. window.prompt()doesn't exist in Electron: use inline inputs or native dialogs through main.- No
StrictModeon purpose: the double mount breaks CodeMirror/xterm and loses the terminal's initial output. - react-hooks v7 rules reject synchronous
setStatein effects: derive the value (e.g. "which query this result answers") or key the state by the entity it belongs to, instead of resetting it in the effect. - Ownership is per workspace view, not per window: every watcher, pty, LSP server, Claude conversation, docker
process, setting layer and session is keyed by the view
webContentsand dies with it. Never useBrowserWindow.fromWebContents/getAllWindows(usewindowOf(wc)); process cleanup lives inwill-quit, neverbefore-quit. - Every disk write goes through
fs/atomicWrite.ts(writeFileAtomic/writeFileAtomicSync/writeJsonAtomicSync); never write a user/state file with a barewriteFile. - Tests run with hidden windows (
WALPH_HIDDEN_WINDOWS=1): the OS focus never changes (spyBaseWindow.prototype.focus), and native menu accelerators don't fire from synthetic keys (sendapp:commandwithe2e/helpers.tssendCommand). Details in theapp-testingskill.
Domain guides
Each domain's rules and traps live in its skill; load it before touching that area. New pitfalls go to the domain skill, never here (a new domain gets a skill and a row in this table).
| Domain | Skill | Never forget |
| Windows, workspaces, sessions, close/quit guard, hibernation, welcome screen | workspaces | the folder lives in main and is unique across all workspaces; gate work on viewActive, never visibilitychange |
Commands, keybindings, when clauses, menus, settings and the Settings modal | commands-settings | effective keybindings and settings are owned by main; keys that execute things are user-only |
| Atomic writes, watcher echoes, git paths, network git, clone, search replace, encodings | files-git-search | repo root via --show-cdup, never --show-toplevel; GIT_TERMINAL_PROMPT=0 |
| Language servers (main + editor extensions) | lsp | server output is untrusted; one applier for every WorkspaceEdit |
| Debugger (DAP client, launch.json, Run and Debug view, breakpoints) | debugger | adapter output is untrusted; every DAP request has a deadline; debug.adapters is user-only |
Claude panel (SDK, permissions, questions, sessions, prompt, /mcp, status) | claude-panel | always pass permissionMode; never send a dormant: id to api.claude.* |
| Containers screen (docker CLI) | containers | every docker process is a process group owned by a view |
Packaging, auto update, walph launcher, release | packaging-release | single instance per userData; native binaries in asarUnpack |
UI text, languages, workbench.language, number/date formatting | i18n | pt-BR, en and es in the same catalog entry; t() at call time, never at module load |
| Editor groups, Markdown preview, themes, terminal links | renderer-editor, renderer-styles, renderer-terminal | one EditorView per group, one canonical state per tab |
Claude Code Workflow
| Need | Command | Agents |
| Feature or fix (main, renderer or both) | /task | developer of each layer touched → reviewers → app-tester |
| Tests | /create-tests | test-creator |
| Review the diff | /review-changes [base branch] | reviewers of each layer touched |
| Audit the whole repo | /audit [all|architecture|ipc|store|claude-setup] (writes AUDIT.html) | Explore + reviewers |
| Ship | /commit-and-push · /create-pr · /release [patch|minor|major] (decides the bump from the commits when empty; check + dist/ + tag, push only after confirmation) | — |
| UI/UX design or audit | — | uiux-expert |
Repository: private GitHub filipe-campos/walph.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

