Development Guidelines
This guide describes how to make maintainable changes to History Workbench.
UI Terminology Mapping
Section titled “UI Terminology Mapping”The user-facing UI uses CAD-oriented terminology while internal code keeps Git/diff terms for the time being.
| UI term | Internal term |
|---|---|
| Project | Git repository |
| Iteration | Git commit |
| Save Iteration | git commit operation |
| Reviewed Area | Git staging/index |
| Current Files Area | Git working tree |
| Tree Comparison | snapshot/tree/property diff |
| 3D Comparison | visual/BREP diff |
| History Panel | Diff panel window |
Contributor guidance:
- User-facing text must use UI terms and translation-safe literals.
- Internal code may keep Git/diff terms until additional refactoring phase.
- Do not rename internal domain/application/infrastructure classes (
GitService,DiffEngine, etc.).
Required Checks
Section titled “Required Checks”Run all checks before submitting changes:
task checkRun tests (includes unit and integration):
task testProject Layout
Section titled “Project Layout”freecad/history_wb/├── application/ # Use cases and dependency injection container├── domain/ # Core models, services, settings, ports, snapshot and diff logic├── entrypoints/ # FreeCAD workbench and command integration├── infrastructure/ # FreeCAD, git, and persistence adapters├── resources/ # Icons, translations, UI resources└── ui/ # Qt views, presenters, UI state, and signal wiring
tests/├── unit/ # Fast tests using fakes and pure Python behavior├── integration/ # FreeCAD runtime tests├── fakes/ # Test doubles for ports, views, loggers, and repositories└── freecad/ # Test FreeCAD documentsKeep tests close to the source structure they cover. For example, behavior in freecad/history_wb/domain/diff/engine.py belongs under tests/unit/domain/diff/ unless it needs the FreeCAD runtime.
Coding Standards
Section titled “Coding Standards”- Keep changes minimal and focused.
- Prefer readable functions and direct data flow over new abstractions.
- Do not add compatibility layers unless there is a real persisted-data, shipped-API, or external-consumer need.
- Do not add comments that describe old bugs or temporary phases.
- Add comments only when code would otherwise be hard to understand.
- Use ASCII in new text unless the file already uses non-ASCII or the content needs it.
- Keep user-facing English strings extractable with
translate("History", "...")orQT_TRANSLATE_NOOP(...)in-place. - Logs do not require translation.
Every Python file must start with a responsibility comment:
# File responsibility: Explains what this file owns.Every package __init__.py must start with a module responsibility comment:
# Module responsibility: Explains what this package exposes or coordinates.Keep those comments accurate when files change.
Cyclomatic Complexity
Section titled “Cyclomatic Complexity”Keep function complexity at B (5-10) or better. Functions rated C (10-20) or higher must be refactored by extracting helper methods.
Check complexity before submitting changes:
uv run radon cc --min C freecad/history_wb --no-assert -sTarget: No C-rated functions in the codebase.
Dependency Injection
Section titled “Dependency Injection”Create dependencies at composition roots and pass them into classes or actions. Do not create FreeCAD, git, filesystem, or UI dependencies deep inside domain or application logic.
Preferred pattern:
class CreateDiffAction: def __init__(self, diff_engine: DiffEngine) -> None: self._diff_engine = diff_engine
def execute(self, old_snapshot: Snapshot, new_snapshot: Snapshot) -> Result[DiffResult]: diff = self._diff_engine.compute_diff(old_snapshot, new_snapshot) return Result.success(diff)Guidelines:
- Application actions receive services and ports in constructors.
- Domain services receive protocols or value objects, not concrete infrastructure classes.
- UI presenters receive the specific view objects, action objects, and UI state they need.
- Infrastructure adapters wrap external APIs.
ApplicationContainerwires application actions and domain services.compose_and_register_panel()wires views, presenters,DialogView, andUIState.ui/wiring.pybinds public component signals to presenter listener methods.
Classes And Functions
Section titled “Classes And Functions”Use classes when identity, state, interface contracts, or dependency injection make behavior clearer.
Good class use cases:
- Domain models and dataclasses:
Snapshot,TreeNode,Property,DiffResult. - Protocols and interfaces:
GitPort,SettingsRepository,FreeCadPort. - Services with injected dependencies:
DiffEngine,GitService. - Stateful infrastructure adapters:
GitPortAdapter,FreeCADSettingsRepository. - UI presenters and views.
Use functions for stateless, deterministic behavior.
Good function use cases:
- Pure transformations.
- Small comparison helpers.
- Serialization helpers.
- Test data builders.
- Local helpers that do not need injected dependencies.
Do not wrap a simple pure algorithm in a class only to make it look architectural.
Imports And Public APIs
Section titled “Imports And Public APIs”Prefer package-level imports when symbols are exported by __init__.py:
from freecad.history_wb.domain.diff import DiffEnginefrom freecad.history_wb.domain.snapshots import Snapshotfrom freecad.history_wb.domain.tree import PropertyDirect module imports are fine when a symbol is intentionally not re-exported:
from freecad.history_wb.infrastructure.git.git_port_adapter import GitPortAdapterUse __all__ for clear module APIs when a package or module exposes a stable set of public symbols. Internal helpers should use a leading underscore.
Logging
Section titled “Logging”Use Log from freecad.history_wb.utils for project logging:
from freecad.history_wb.utils import Log
Log.info("Repository refreshed")Log.warning("No active document")Log.error("Snapshot could not be loaded")Behavior:
- Before FreeCAD initialization, logging falls back to stdout/stderr.
- After first workbench activation, logs go through
FreeCADLoggerto the FreeCAD console. - Tests can install
FakeLoggerwithset_logger(). - Log messages are developer-facing and do not need translation.
Translations
Section titled “Translations”All user-facing English UI text should be defined at the usage site so Qt extraction can detect literal strings.
Pattern:
- Translate immediate UI strings with literal calls:
translate("History", "..."). - Use
QT_TRANSLATE_NOOPfor deferred strings with explicit context:- Command
GetResources()strings use exact command-name contexts (for example,DiffCommit). - Workbench menu/toolbar strings use
Workbenchcontext.
- Command
- Use Qt-style placeholders such as
%1and%2in templates. - Presenters should pass raw data rather than formatted translated messages.
- Keep translation template at
freecad/history_wb/resources/translations/History.tswith locale files namedHistory_<locale>.ts.
Example:
REPOSITORY_INFO_TEMPLATE = "Repository: %1"Use literals directly in views and entry points for extractor visibility; avoid unextracted variable indirection.
Testing Strategy
Section titled “Testing Strategy”Write tests that protect real behavior and document useful contracts. Avoid tests that only freeze implementation details.
Good tests:
- Verify public behavior and result contracts.
- Verify components are wired correctly through observable outcomes.
- Verify domain algorithms with meaningful examples.
- Verify integration between application actions and services.
- Verify FreeCAD runtime behavior when stubs or fakes are insufficient.
Avoid tests that:
- Check non-existence of fields or classes (negative tests).
- Duplicate existing coverage without adding a new failure mode.
- Assert private implementation details like
_git_port,_git_service, or internal action wiring. - Test fake internals, protocol compliance, or method existence.
- Verify dataclass defaults,
hasattrchecks, repr output, or construction trivia. - Preserve temporary development phases in filenames or test names.
Test Ownership Rules
Section titled “Test Ownership Rules”Each behavior should have one owning layer. Duplicate tests across layers create noise when failures occur.
- Domain tests own pure business behavior and algorithms: models, services, diff logic, snapshot extraction rules, git workflow rules.
- Application tests own orchestration, result contracts, and dependency forwarding only when forwarding is part of the action contract.
- Infrastructure tests own adapter parsing, command construction, subprocess invocation shape, and external error mapping.
- UI tests own observable presenter/view behavior, state updates, and signal wiring. Do not test private Qt styling details unless styling is an explicit product contract.
- Integration tests own behavior that requires real FreeCAD, Qt runtime, real document structure, workbench activation, or real runtime wiring.
Integration tests run with FreeCAD’s Python interpreter and real App runtime. In CLI/headless runs where FreeCADGui is unavailable, integration fixtures provide a GuiLike mock adapter that implements the required port surface (getDocument(), isModified(), getViewProvider()) against real App documents. This keeps integration coverage stable without requiring a full interactive GUI session.
Skipped Tests
Section titled “Skipped Tests”Skipped tests should not remain in the suite long-term. If a unit test needs FreeCAD or Qt runtime, move useful coverage to integration tests and delete the skipped version. Debug-only or permanently skipped files provide no value and should be removed.
Parametrized Consolidation
Section titled “Parametrized Consolidation”Use @pytest.mark.parametrize to keep edge-case coverage concise. When multiple tests differ only in input values or expected outputs, consolidate them into a single parametrized test. This reduces file size, prevents repetitive failures, and makes the test suite easier to scan.
Mocking Guidelines
Section titled “Mocking Guidelines”When patching standard library modules (subprocess, os, pathlib) in unit tests, use unittest.mock.patch context managers instead of pytest.monkeypatch.setattr. Context managers limit the scope of patches and prevent global state from leaking into IDE pytest hooks.
For application-specific modules, monkeypatch is appropriate and convenient.
Unit Tests
Section titled “Unit Tests”Location: tests/unit/
Use unit tests for fast feedback on pure behavior. Unit tests should not require FreeCAD to be running. Follow the ownership rules above: domain algorithms, application orchestration, infrastructure adapters (without real subprocess or git), and UI presenter logic with fakes.
Do not use /tmp or /var/tmp paths in tests. Use stable fake paths such as /home/user/dir/... to prevent FreeCAD Addons Report static analyzer failures for insecure temporary path usage.
Run tests (includes unit and integration):
task testCommon Contributor Tasks
Section titled “Common Contributor Tasks”Add An Application Action
Section titled “Add An Application Action”- Add a focused action class under the appropriate subdirectory in
freecad/history_wb/application/actions/(e.g.git_repo/,diffs/,documents/). - Return an existing result model or add a small result model when needed.
- Inject domain services or ports through the constructor.
- Wire the action in
application/container.py. - Add unit tests under the matching subdirectory in
tests/unit/application/actions/.
Add UI Text
Section titled “Add UI Text”- Add translated UI text at the display site using
translate("History", "..."). - For deferred text, define literal with
QT_TRANSLATE_NOOPin correct context. - Keep placeholders (
%1,%2) in the source literal and replace after translation. - Avoid formatting translated strings in presenters.
Add A FreeCAD Command
Section titled “Add A FreeCAD Command”- Add a command class in
entrypoints/commands.py. - Keep FreeCAD-specific code in the command.
- Delegate behavior to a presenter or application action.
- Register the command in
register_commands(). - Add the command name to the workbench toolbox if it should appear in toolbar/menu.
- Add command tests under
tests/unit/entrypoints/.
Add A Setting Or Preference
Section titled “Add A Setting Or Preference”- Add default values in
domain/config.pyif needed. - Update settings models or codecs under
domain/settings/. - Update
FreeCADSettingsRepositorypersistence behavior. - Update preferences UI in
ui/views/settings_preferences_page.py. - Add tests for model/codec behavior and repository persistence behavior.
Add Snapshot Or Diff Behavior
Section titled “Add Snapshot Or Diff Behavior”- Put pure comparison behavior under
domain/diff/. - Put snapshot model or serialization behavior under
domain/snapshots/orinfrastructure/persistence/depending on whether it is core model behavior or file format behavior. - Use focused unit tests for algorithms.
- Add integration tests if real FreeCAD documents or runtime APIs are required.
FreeCAD Runtime Notes
Section titled “FreeCAD Runtime Notes”FreeCAD type stubs are installed by uv under .venv/lib/python3.11/site-packages/. Check them before assuming FreeCAD signatures.
Use the extracted AppImage runtime for live API checks:
./run_with_freecad.sh python -c "import FreeCAD; print(dir(FreeCAD))"The FreeCADGui module is not generally available through run_with_freecad.sh. GUI behavior often needs manual FreeCAD testing or integration tests that run through FreeCAD’s runtime.