This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
bun run start:dev- Start development with hot reload (macOS/Linux)bun run start:dev-win- Start development with hot reload (Windows)bun run build- Production build (Vite)bun run build:dev- Development build (Vite)bun run build:ext- Extension build (Vite)bun run build:pixi- Build PixiJS worker bundle (Vite)bun run typecheck- Run TypeScript type checkingbun run lint- Run ESLint
bun run precommit- Run pre-commit checks (lint-staged)- Always run
bun run typecheckandbun run lintafter making changes
bun run dist:mac- Build macOS distributionbun run dist:win- Build Windows distributionbun run dist:linux- Build Linux distribution
To test a build on macOS without code signing:
ELECTRON_SKIP_NOTARIZE=1 bun run dist:macThe build output is in dist/mac-arm64 (or dist/mac for x64). You can run the app directly from terminal:
./dist/mac-arm64/Anytype.app/Contents/MacOS/AnytypeDependencies included in the packaged app are whitelisted. The bun run build:deps script (using esbuild) auto-detects required dependencies, but if some are missing at runtime, explicitly add them to package.deps.json.
Before development, you need the anytype-heart middleware:
- Run
./update.sh <platform> <arch>to fetch middleware - Start anytypeHelper binary in background
- Use
SERVER_PORTenv var to specify gRPC port
Anytype is an Electron-based desktop application with TypeScript/React frontend communicating with a Go-based middleware (anytype-heart) via gRPC.
Key Components:
- Electron Main Process (
electron.js) - Window management, IPC, system integration - React Frontend (
src/ts/) - UI components and business logic - gRPC Middleware - Backend logic (separate anytype-heart repository)
- Block-based Editor - Document editing with composable blocks
Entry Points:
entry.tsx- Application entry pointapp.tsx- Main React application component
Core Libraries (lib/):
api/- gRPC communication (dispatcher, commands, mapper)keyboard.ts- Keyboard shortcuts and input handlingstorage.ts- Local storage managementrenderer.ts- Electron IPC communicationutil/- Utility functions (common, data, router, etc.)
State Management (store/):
- MobX-based stores for different domains:
common.ts- Global application stateauth.ts- Authentication stateblock.ts- Document block statedetail.ts- Object detail statemenu.ts,popup.ts- UI state
Component Structure (component/):
block/- Document block components (text, dataview, media, etc.)page/- Page-level components (auth, main, settings)menu/- Context menus and dropdownspopup/- Modal dialogssidebar/- Left/right sidebarsutil/- Reusable UI utilities
Block-Based Documents:
- Documents are composed of blocks (text, images, databases, etc.)
- Each block type has corresponding model, content, and component
- Block operations handled via gRPC commands
MobX State Management:
- Reactive state with MobX stores
- Components observe store changes automatically
- Stores organized by domain (auth, blocks, UI, etc.)
gRPC Communication:
- Frontend communicates with middleware via gRPC
- Commands in
lib/api/command.ts - Response mapping in
lib/api/mapper.ts - Real-time updates via gRPC streaming
Electron Integration:
- Main process handles system integration
- Renderer process handles UI
- IPC communication for file operations, updates, etc.
The graph view uses a Web Worker with PixiJS WebGL rendering for performance:
Files:
src/ts/component/graph/provider.tsx- React component, D3 zoom/drag, image loadingdist/workers/graph.js- Web Worker with D3 force simulation + PixiJS WebGL renderingdist/workers/lib/pixi.min.js- Bundled PixiJS for worker (built fromvite.worker.config.ts)
Architecture:
- OffscreenCanvas transferred to worker for off-main-thread rendering
- PixiJS 8 with WebWorkerAdapter for GPU-accelerated WebGL rendering
- D3.js force simulation for physics (center, charge, link, collision, cluster forces)
- Communication via postMessage between provider and worker
Rendering Structure (PixiJS):
- Stage → edgesGraphics (PIXI.Graphics for all edges)
- Stage → nodesContainer (PIXI.Container with Sprites for nodes)
- Stage → labelsContainer (PIXI.Container with Text for labels)
- Stage → selectBoxGraphics (PIXI.Graphics for drag selection)
GraphProvider API:
- Props:
id,rootId,data: { nodes, edges },storageKey,load - Ref methods:
init(),resize(),addNewNode(),forceUpdate() - Window events:
updateGraphSettings.{id},updateGraphRoot.{id},updateGraphData.{id}
Usage locations:
src/ts/component/page/main/graph.tsx- Global graph pagesrc/ts/component/block/dataview/view/graph.tsx- Dataview graphsrc/ts/component/widget/view/graph/index.tsx- Widget graph
Worker message protocol:
- Provider → Worker:
init,updateSettings,image,onZoom,onClick,onSelect,onMouseMove,onDragStart/Move/End,setRootId,resize,updateTheme - Worker → Provider:
onClick,onSelect,onMouseMove,onContextMenu,onTransform,setRootId
Building the PixiJS worker bundle:
bun run build:pixi- Identify the relevant component in
src/ts/component/ - Check corresponding interfaces in
src/ts/interface/ - Look for related stores in
src/ts/store/ - Update models in
src/ts/model/if needed - Add gRPC commands in
src/ts/lib/api/if backend changes needed
- Components: UI components in
src/ts/component/ - Styles: SCSS files in
src/scss/(organized to match components) - Assets: Images and icons in
src/img/ - Configuration: Electron config in
electron/ - Build: Vite configuration in
vite.config.ts(app),vite.extension.config.ts(extension),vite.web.config.ts(web),vite.worker.config.ts(PixiJS worker)
- Uses Vite for bundling (esbuild dev, Rollup production) with bun as package manager
- TypeScript with React 18
- MobX for state management
- Custom block-based editor system
- gRPC for backend communication
- Electron for desktop app packaging
- CSS supports native nesting - use nested selectors instead of flat/inline selectors
- Do not use
cursor: pointerin CSS - the app does not use custom cursors - Do not change any style or design properties (colors, spacing, sizes, etc.) unless explicitly asked. Design decisions are intentional — never "fix" or "improve" visual values on your own
- Never change colors on your own. Colors (CSS variables, hardcoded values, theme overrides) are only changed through design tasks with explicit design specs. Even if a color looks wrong or inconsistent, do not fix it unless a design task specifically asks for it
- For CSS and UI styling changes, match exact pixel values, border-radius, padding, and colors from the user's specifications on the first attempt. Do not guess or approximate visual values
- The project uses tabs for indentation, not spaces. All TypeScript, TSX, and SCSS files use tab characters.
- Write
else ifwith a linebreak beforeif:if (condition) { // ... } else if (anotherCondition) { // ... }
- Wrap logical parts of compound conditions in parentheses for readability:
// Good const isValid = (x > 0) && (y > 0) && (x < maxWidth); if ((a > b) && (c < d)) { ... } // Bad const isValid = x > 0 && y > 0 && x < maxWidth; if (a > b && c < d) { ... }
- Collect CSS class lists into a separate
cnvariable before the return statement:// Good const cn = [ 'commentPost', (isEditing ? 'isEditing' : '') ]; return <div className={cn.join(' ')} />; // Bad — inline class list arrays hurt readability return <div className={[ 'commentPost', (isEditing ? 'isEditing' : '') ].join(' ')} />;
- Never combine a selector's own properties and its nested children in the same braces. Instead, write two separate blocks: a one-liner for the selector's own properties, then a second block with the same selector containing only nested children. Leaf selectors (no nested children) can be one-liners.
// Good .mediaState { display: flex; gap: 12px 0px; align-items: center; } .mediaState { .icon.ghost { width: 48px; height: 48px; } .name { text-align: center; } } // Bad — mixing own properties and children in one block .mediaState { display: flex; gap: 12px 0px; align-items: center; .icon.ghost { width: 48px; height: 48px; } .name { text-align: center; } }
- All new components should be added to Storybook automatically
- Component variations should be implemented as separate props, not as className strings. For example, use
withBackgroundas a boolean prop instead of passing'withBackground'via className — this makes components work properly with Storybook controls
- All UI text should use
translate()function for i18n - Translation keys are defined in
src/json/text.json(source of truth). Files indist/lib/json/lang/are generated — do not edit them directly - Block operations should go through the command system
- Use existing utility functions in
lib/util/before creating new ones - Follow existing component patterns in
component/directory - Store updates should trigger UI re-renders automatically via MobX
- Never use raw
document.getElementById()orelement.querySelector()— useU.Domhelpers instead:U.Dom.get(id)— wrapsdocument.getElementById(id)U.Dom.select(selector, root?)— wrapsroot.querySelector(selector)with error handlingU.Dom.selectAll(selector, root?)— wrapsroot.querySelectorAll(selector)U.Dom.addClass(el, cn),U.Dom.removeClass(el, cn),U.Dom.hasClass(el, cn)— class manipulation
- jQuery (
$) has been removed from the project. Never use$()or import jQuery
Detailed documentation is available in docs/ for deeper context on each module:
docs/src/ts/README.md- TypeScript source overview, entry points, import aliases, key patterns
docs/src/ts/component/README.md- All 18 component subdirectories overviewdocs/src/ts/component/block/README.md- Block system: 19 block types (text, media, dataview, table, chat, embed, etc.)docs/src/ts/component/page/README.md- Page routing: auth flow, main pages, settings hierarchydocs/src/ts/component/menu/README.md- Menu system: ~50 context menu types with positioning and keyboard navdocs/src/ts/component/popup/README.md- Popup system: ~27 modal dialog typesdocs/src/ts/component/editor/README.md- Block-based document editor (page.tsx ~2600 lines)docs/src/ts/component/graph/README.md- Graph visualization: D3 + PixiJS WebGL via Web Workerdocs/src/ts/component/sidebar/README.md- Left/right sidebars with tree navigation and object viewsdocs/src/ts/component/widget/README.md- Dashboard widgets: list, gallery, board, calendar, graph viewsdocs/src/ts/component/cell/README.md- Data cells for dataview grid/board renderingdocs/src/ts/component/drag/README.md- Drag-and-drop system for blocks and listsdocs/src/ts/component/form/README.md- Form controls: inputs, selects, phrases, pinsdocs/src/ts/component/header/README.md- Page headers by context (editor, set, settings, auth)docs/src/ts/component/footer/README.md- Page footers (auth, main editor)docs/src/ts/component/list/README.md- Object list components with virtual scrollingdocs/src/ts/component/notification/README.md- Toast notification systemdocs/src/ts/component/preview/README.md- Preview cards and tooltipsdocs/src/ts/component/selection/README.md- Block and text selection handlingdocs/src/ts/component/util/README.md- ~48 reusable utility componentsdocs/src/ts/component/comment/README.md- Comment system: threaded discussions with Lexical editor, rich content parts, reactions
docs/src/ts/lib/README.md- Core libraries overview (api, util, services, keyboard, storage)docs/src/ts/lib/api/README.md- gRPC communication: dispatcher, 100+ commands, protobuf mappingdocs/src/ts/lib/util/README.md- ~20 utility modules (common, data, menu, object, router, string, etc.)docs/src/ts/lib/constant/README.md- Application constants and static configurationdocs/src/ts/lib/service/README.md- Singleton services (sidebar, analytics, focus, translation)
docs/src/ts/store/README.md- MobX stores: 13 domain stores (auth, block, common, detail, record, menu, popup, chat, etc.)docs/src/ts/model/README.md- Data models: Block, Content classes, View, Filter, Sortdocs/src/ts/interface/README.md- TypeScript interfaces and enums for all domain types
docs/src/ts/hook/README.md- Custom React hooksdocs/src/ts/docs/README.md- In-app documentation and help contentdocs/src/ts/workers/README.md- Web Workers (graph PixiJS worker)docs/electron/README.md- Electron main process: window management, IPC, updates, menusdocs/src/scss/README.md- SCSS stylesheets organized to mirror component structuredocs/src/img/README.md- Images, icons (SVG), and static assetsdocs/src/json/README.md- JSON data: translations, constants, colors, keyboard shortcuts
docs/REVIEW-v0.54.11-to-HEAD.md- Review of changes since v0.54.11 stable release
Run in browser without Electron: bun run start:web (starts anytypeHelper + Vite dev server). Use ANYTYPE_USE_SIDE_SERVER=http://... to skip helper start. See docs/src/ts/lib/web/README.md for details.
Use the LINEAR_API_KEY environment variable to fetch issue details from Linear.
Fetch issue by ID:
curl -s -X POST "https://api.linear.app/graphql" \
--header "Content-Type: application/json" \
--header "Authorization: $(printenv LINEAR_API_KEY)" \
--data '{"query":"query{issue(id:\"JS-1234\"){title description state{name}priority labels{nodes{name}}comments{nodes{body createdAt}}}}"}' | jq .Important: Use $(printenv LINEAR_API_KEY) instead of $LINEAR_API_KEY directly in curl commands to avoid shell expansion issues.
When starting work on an issue:
- Move the issue to "In development" state before beginning implementation.
After pushing a fix for an issue:
- Comment on the issue with a brief description of the fix (what was changed and why).
- Move the issue to the appropriate state based on its labels:
- If the issue has a "Design" label → move to "Design review"
- Otherwise → move to "Waiting for testing"
Comment on an issue:
curl -s -X POST "https://api.linear.app/graphql" \
--header "Content-Type: application/json" \
--header "Authorization: $(printenv LINEAR_API_KEY)" \
--data '{"query":"mutation{commentCreate(input:{issueId:\"<ISSUE_UUID>\",body:\"<comment text>\"}){success}}"}' | jq .Move issue to "Waiting for testing":
# First, find the state ID (one-time per project):
curl -s -X POST "https://api.linear.app/graphql" \
--header "Content-Type: application/json" \
--header "Authorization: $(printenv LINEAR_API_KEY)" \
--data '{"query":"query{workflowStates(filter:{name:{eq:\"Waiting for testing\"}}){nodes{id name}}}"}' | jq .
# Then update the issue:
curl -s -X POST "https://api.linear.app/graphql" \
--header "Content-Type: application/json" \
--header "Authorization: $(printenv LINEAR_API_KEY)" \
--data '{"query":"mutation{issueUpdate(id:\"<ISSUE_UUID>\",input:{stateId:\"<STATE_UUID>\"}){success}}"}' | jq .Use the Figma MCP tools to fetch design context and screenshots from Figma files.
Available tools:
mcp__figma__get_design_context- Get UI code/design context for a Figma node (preferred)mcp__figma__get_screenshot- Get a screenshot of a Figma nodemcp__figma__get_metadata- Get metadata/structure of a Figma node
Extract parameters from Figma URLs:
- URL format:
https://www.figma.com/design/:fileKey/:fileName?node-id=:nodeId fileKeyis the ID after/design/nodeIdis in thenode-idquery parameter (convert-to:for the API)- Example:
https://www.figma.com/design/uWka9aJ7IOdvHch60rIRlb/MyFile?node-id=12769-19003→fileKey:uWka9aJ7IOdvHch60rIRlb,nodeId:12769:19003
Important - Icons and Images:
- All icons and images must be stored locally in
src/img/- do NOT use remote Figma asset URLs - When implementing designs from Figma, recreate icons as SVG files in the appropriate
src/img/icon/subdirectory - Follow existing icon patterns (e.g.,
src/img/icon/add/for editor control button icons) - Icons use semantic naming (e.g.,
arrow.svg,swiper.svg); hover color is handled via CSS, not separate SVG files
After completing any task that adds, removes, or significantly modifies files in a component/abstraction folder, run the /update-docs skill to update the corresponding README.md in docs/. Documentation is kept lean and delta-driven — only sections affected by the change are updated. Skip for trivial changes (typo fixes, minor logic tweaks).
After completing any task that edits SCSS files (src/scss/), SVG/image files (src/img/), or adds new UI components, run the /dark-mode-check skill to audit for dark mode issues. This catches:
- Hardcoded colors that should use CSS variables (
--color-text-*,--color-bg-*,--color-shape-*, etc.) - Missing dark icon variants in
src/img/theme/dark/ - Inline
html.themeDarkoverrides that belong insrc/scss/theme/dark/ - Dynamic icon paths missing
S.Common.getThemePath() - Never duplicate unchanged values from light theme into dark theme — only override CSS vars when the value actually differs
After completing any task that modifies user-facing behavior — especially in the editor (component/editor/, component/block/), menus (component/menu/), popups (component/popup/), sidebar (component/sidebar/), or widgets (component/widget/) — run the /qa-engineer skill to generate E2E test coverage.
The QA Engineer skill:
- Analyzes the git diff to identify user-facing changes
- Checks existing test coverage in
../anytype-desktop-suite/ - Creates a test plan in
../anytype-desktop-suite/specs/ - Generates Playwright test files in
../anytype-desktop-suite/tests/ - Creates page objects if needed in
../anytype-desktop-suite/src/pages/
Skip for changes that have no user-facing impact (type refactors, internal utilities, CSS-only tweaks, build config).
Test suite repo: ../anytype-desktop-suite — Playwright E2E tests with Page Object Model, translation-aware selectors, and gRPC server lifecycle management. See its CLAUDE.md for test architecture details.