This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Freeplane uses Gradle as its primary build system. Always use gradle command (not gradlew or maven).
gradle :freeplane:compileJava- Compile freeplane subproject (run before committing)gradle :subproject:compileJava- Compile specific subproject if modifiedgradle :freeplane:test- Run tests for validationgradle format_translation- Always run after changing language resource files
gradle dist- Create distribution packagesgradle mac.dist- Create macOS distributiongradle win.dist- Create Windows distributiongradle linux-packages- Create Linux packages
Freeplane is a Java-based mind mapping application built with OSGi architecture and Swing UI.
- OSGi Framework: Uses Knopflerfish OSGi framework for modular plugin system
- Multi-module Gradle project: 17+ submodules with clear separation of concerns
- Java 8 compatibility: Targets Java 8 for broad compatibility - avoid Java 9+ features
- Plugin system: Extensible through OSGi plugins for features like LaTeX, scripting, SVG support
freeplane- Core application (org.freeplane.core bundle)freeplane_api- Public API for plugin developmentfreeplane_framework- OSGi framework and launcherfreeplane_plugin_*- Feature plugins (script, latex, markdown, etc.)freeplane_mac- macOS-specific functionalityJOrtho_0.4_freeplane- Spell checking component
src/main/java- Production codesrc/test/java- Unit testssrc/editor/resources- Editor-specific resources (images, translations, CSS)src/viewer/resources- Viewer-specific resourcessrc/external/resources- External resources (templates, XSLT transformations)
CRITICAL: From the git repository root (where development happens), the structure is:
. (git repository root - where Claude Code starts)
├── freeplane/ ← Main Gradle subproject
│ ├── src/editor/resources/translations/ ← Translation files location
│ ├── src/viewer/resources/
│ └── src/main/java/
├── freeplane_api/ ← API subproject
├── freeplane_framework/ ← Framework subproject
├── JOrtho_0.4_freeplane/ ← Spell checking component
└── other subprojects...
Key Paths from Git Root:
- Translation files:
freeplane/src/editor/resources/translations/Resources_*.properties - Main source:
freeplane/src/main/java/ - Build output:
BIN/(global build directory)
Path Usage:
- All development paths are relative to git repository root
- The
freeplane/subdirectory contains the main application subproject - Absolute paths vary between installations and should not be used in committed files
BIN/- Global build output directory containing the complete application- Plugin JARs are copied to
BIN/plugins/{plugin.id}/lib/ - Core application JARs go to
BIN/core/org.freeplane.core/lib/
- No comments or Javadoc in final code - use self-documenting naming
- Incremental refactoring - small testable steps with commits after each step
- Extract only what's used - avoid speculative generality
- Single responsibility - each extracted class should have clear purpose
- Remove unused imports - Clean up imports after coding changes to keep code tidy
CORE PRINCIPLE: Tests follow features, not vice versa. Every feature development session MUST follow this pattern:
- Extract Business Logic First - Pull complex logic out of UI event handlers into testable classes
- Test Extracted Logic Immediately - Write focused unit tests for the extracted business logic
- Implement Feature Using Tested Components - Build the feature using tested, reliable building blocks
- Minimal UI Integration - Keep UI code as thin wiring that delegates to tested logic
CLAUDE'S RESPONSIBILITY:
- ALWAYS suggest extraction opportunities when seeing complex logic in UI code
- ALWAYS propose testing approach before implementing any non-trivial business logic
- PROACTIVELY identify when feature work could benefit from extract-test-integrate pattern
- INTERRUPT implementation if business logic is being mixed directly into UI event handlers
- REMIND about testing utilities and patterns available for current work
DEVELOPER RESPONSIBILITY:
- FOLLOW the extract-test-integrate pattern for all feature development
- EXPECT Claude to urge this approach and take the suggestions seriously
- RESIST the temptation to modify complex UI code directly without extraction
SUCCESS METRICS:
- Feature development feels faster due to immediate feedback loops
- Fewer debugging sessions needed (logic verified through tests)
- More confidence when making changes to existing functionality
- Gradual accumulation of testable, reusable business logic components
VIOLATION CONSEQUENCES: Technical debt accumulates, debugging becomes harder, feature development slows down, regression risk increases.
- Plugin bundles require
Bundle-ActivatorandRequire-Bundle: org.freeplane.core - Bundle dependencies are externalized via
Bundle-ClassPath - Core module exports packages for plugin consumption
- Translation files location:
freeplane/src/editor/resources/translations/Resources_*.properties(relative to git root) - Gradle tasks:
gradle check_translationandgradle format_translation - Language support: 25+ languages including RTL languages (Arabic, Hebrew)
- CRITICAL: Properties files use ISO-8859-1 encoding with Unicode escapes
- Non-ASCII characters: Must be Unicode-escaped (e.g.,
\u041Efor Cyrillic О) - Examples:
- Russian:
OptionPanel.enabled=\u0412\u043A\u043B\u044E\u0447\u0435\u043D\u043E - Chinese:
OptionPanel.immediate=\u7ACB\u5373 - Arabic:
OptionPanel.disabled=\u0645\u0639\u0637\u0644
- Russian:
- Tool requirement: Use tools that properly handle Unicode escaping for non-Latin scripts
- Verification: Check existing translations in target language for proper escape patterns
- Properties files MUST use ISO-8859-1 encoding with Unicode escapes for non-ASCII characters
- All non-ASCII characters must be Unicode-escaped: Use
\uXXXXformat (e.g.,\u041Efor Cyrillic О) - Arabic text example:
مفعلbecomes\u0645\u0641\u0639\u0644 - Accented characters example:
ActivébecomesActiv\u00E9
- ALWAYS use
native2asciitool for converting UTF-8 text to proper Unicode escapes - NEVER use generic text editors or automated translation tools directly on .properties files
- Required workflow: UTF-8 temp file →
native2ascii input.txt output.properties→ merge into target file - Validation step: After changes, verify with
file *.properties(must show "ASCII text", not binary) - Corruption prevention: Prevents double UTF-8 encoding, broken escapes, and null byte injection
- Always run:
gradle format_translationafter any translation file modifications
Why Critical: Automated tools without proper Java properties support cause systematic Unicode corruption (null bytes, double encoding, broken escapes) that affects all translation files and breaks Weblate integration.
For any automated translation work (Claude Code, translation tools), ALWAYS validate after editing .properties files:
# 1. Check file integrity (must be ASCII text)
file freeplane/src/editor/resources/translations/Resources_*.properties | grep -v "ASCII text"
# 2. Check for broken Unicode escapes
cd freeplane/src/editor/resources/translations/
grep -l 'u[0-9][0-9][0-9][0-9]' *.properties
# 3. Validate git diff before commit
git diff | head -20 # Verify only expected changes, no deletionsValidation Rules for Automated Tools:
- STOP immediately if any file shows as binary/HTML instead of ASCII text
- STOP immediately if broken Unicode patterns found (u0159 instead of \u0159)
- STOP immediately if git diff shows unexpected content deletions
- Always run
gradle format_translationafter any translation changes
- OptionPanel prefix: UI preference keys use
OptionPanel.{key}={value}format - Separator titles: Use
OptionPanel.separator.{name}={title}for section headers - Choice values: Combo box options use same key as preference choice value
- Alphabetical sorting: Properties are sorted alphabetically ignoring case (intentional)
- Always run format_translation: After any translation file changes to maintain sorting
- JUnit 4.13.2 with Hamcrest and Mockito
- AssertJ for fluent assertions
- Use
gradle testorgradle :module:testfor specific modules - Test logging can be enabled with
-PTestLoggingFull
- Extend from OSGi bundle structure
- Use
freeplane_apifor public interfaces - Follow naming convention:
org.freeplane.plugin.{name} - Bundle activator required for OSGi lifecycle management
- NodeSelector - handles node selection timing and behavior
- NodeFolder - handles node folding timing and behavior (independent from selection)
- DefaultNodeMouseMotionListener - coordinates mouse events between NodeSelector and NodeFolder
- Events are routed based on mouse regions (folding vs selection areas)
- Always get Filter from MapView: Use
map.getFilter()from the MapView context - After map.select(): Can use
controller.getSelection().getFilter()- they're equivalent - Follow existing patterns: Most code operates on selected node/map view with consistent filter usage
- Method signatures matter:
setFolded(node, boolean, filter)vsunfoldAndScroll(node, filter)vstoggleFoldedAndScroll(node)(handles filter internally)
- ApplicationResourceController.isPropertySetByUser(): Checks if property exists in user's props (reliable way to detect user customization)
- Static migration blocks: Use for one-time property updates on startup
- Self-documenting method names: Replace explanatory comments with well-named methods
- Default properties: Add to
freeplane.propertiesfor system defaults
- Separate timing from behavior: Shared timing (
immediate/delayed) used by both selection and folding - Regional event routing:
isInFoldingControl()vsisInside()determines which handler processes the event - Legacy compatibility: Support both old
selection_methodand new granular configuration
- Freeplane is complex software without comprehensive tests
- Follow existing patterns rather than reinventing
- Check method signatures carefully (compilation catches parameter mismatches)
- Use existing controller methods rather than lower-level operations
- Reduce visibility: Use package-private classes for internal UI components, only expose what clients need
- Static organization: Static blocks first, constants grouped after, inner classes positioned strategically
- Inner class optimization: Static when no outer instance access needed, non-static when accessing outer fields
- Migration pattern: Clean separation of initialization logic from business logic using static blocks with helper methods at bottom
- Field organization: Instance fields grouped logically after constants, methods ordered by visibility
- Freeplane uses proprietary logging utilities (not standard Java logging)
- Standard Java logging configuration in
logging.propertiesonly affects Swing and other Java log records - For Freeplane-specific logging, use the proprietary logging utilities directly
- Use
LogUtils.warn()and similar methods for Freeplane logging (as seen in existing codebase)
- Multi-platform support (Windows, macOS, Linux)
- Portable application support via PortableApps format
- Windows installer via Inno Setup
- macOS DMG with codesigning support
- Linux packages for Debian-based systems