This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
VNote is a Qt-based, cross-platform (Windows/Linux/macOS) note-taking application focused on Markdown. It uses C++14, Qt 6, and CMake 3.20+. The project is undergoing a major architectural migration from singletons to dependency injection via ServiceLocator.
# Setup (first time after clone)
bash scripts/init.sh # Linux/macOS
scripts\init.cmd # Windows
# Configure + build (Release)
mkdir build && cd build
cmake .. -GNinja # Windows with MSVC: run from VS Developer Command Prompt
cmake --build . --config Release
# Debug build
cmake .. -DCMAKE_BUILD_TYPE=Debug
cmake --build . --config Debug
# Clean rebuild
rm -rf build && mkdir build && cd build && cmake .. && cmake --build .Tests use Qt Test framework. Always call vxcore_set_test_mode(1) before vxcore_context_create() in tests to prevent corrupting real user data.
# Build all tests
cmake --build build --config Release
# Run all tests via CTest
ctest --test-dir build --output-on-failure
# Run a single test (pattern match)
ctest --test-dir build -R test_error
# Run test executable directly (Windows: Qt DLLs must be in PATH)
./build/tests/core/test_error.exeUse the add_qt_test() helper in tests/<module>/CMakeLists.txt:
add_qt_test(test_myclass
SOURCES test_myclass.cpp ${CMAKE_SOURCE_DIR}/src/module/myclass.cpp
LINKS core_services vxcore # Optional extra link libraries
GUILESS # Optional: headless (QCoreApplication)
)Test files use QTEST_GUILESS_MAIN(tests::TestClassName) and must end with #include "test_filename.moc".
The codebase has two coexisting architectures:
- Legacy: Singleton-based (
VNoteX::getInst(),ConfigMgr::getInst()) — do NOT use for new code - New (files with
2suffix): Dependency injection viaServiceLocatorpassed through constructors
New files coexist with legacy via the 2 suffix convention (e.g., MainWindow2, ConfigMgr2, Buffer2).
Controllers (src/controllers/) — business logic, QObject (not QWidget), testable
↕ signals/slots
Models (src/models/) — QAbstractItemModel subclasses, no UI logic
Views (src/views/) — QTreeView/delegates, display only, emit signals
↕
ServiceLocator — DI container (non-owning pointers, NOT a singleton)
↕
Services (src/core/services/) — wrap vxcore C API with Qt-friendly interface
↕
vxcore (libs/vxcore/) — C library: notebook/config/search backend
MVC rules: Models must not contain UI logic. Views must not modify data directly. Controllers must not inherit QWidget. All layers receive ServiceLocator& via constructor.
| Type | Role |
|---|---|
ServiceLocator |
DI container; stores non-owning void* pointers keyed by type_index |
NodeIdentifier |
Lightweight value type: notebookId (GUID) + relativePath |
Buffer2 |
Lightweight copyable handle (like QModelIndex), returned by BufferService::openBuffer() |
XXXCoreService |
Low-level services wrapping vxcore C API (hold VxCoreContextHandle) |
BufferService |
Hook-aware wrapper over BufferCoreService; fires vnote.file.* hooks |
HookManager |
WordPress-style hook system: actions (cancellable events) + filters (data transforms) |
Services are stack-allocated in main() within a scoped block, registered as non-owning pointers in ServiceLocator, and destroyed before vxcore_context_destroy(). Order matters.
Plugins use HookManager::addAction() / addFilter() with priority ordering. Hook names are constants in src/core/hooknames.h (e.g., vnote.notebook.before_open, vnote.file.before_save). Actions can cancel operations via HookContext::cancel(). When adding hooks to existing code, use the WRAP pattern: fire before-hook → original signal → after-hook.
Three submodules in libs/:
- vtextedit — Rich text/Markdown editor widget
- QHotkey — Cross-platform global hotkey support
- vxcore — C library backend (built as static lib, tests/CLI disabled)
CRITICAL — push submodules before pushing vnote. The parent repo pins each submodule to a commit SHA, but CI clones submodules from their own remotes. If you change submodule code, you MUST push the submodule's branch to its remote before pushing the vnote commit that bumps the pointer. Otherwise every CI job fails at "Init Submodules" with fatal: remote error: upload-pack: not our ref <sha>, which breaks all checks before any build runs.
# 1) Push the submodule first
cd libs/vxcore && git push origin HEAD:main && cd ../..
# 2) Then push vnote. Use a guard to catch stranded submodule commits:
git config push.recurseSubmodules check # one-time: aborts parent push if a submodule commit is unpushed
git push --recurse-submodules=on-demand # or push both together
# Verify the pinned SHA exists upstream:
git submodule foreach 'git status -sb' # "[ahead N]" means an UNPUSHED submodule commitIf CI already shows "not our ref", fix it by pushing the missing submodule commit (do not roll back the parent pointer when newer parent commits depend on the new submodule API).
Enforced by .clang-format via pre-commit hook. Key conventions:
- C++14, 2-space indent, 100-char line limit
- Naming:
CamelCaseclasses,camelCasemethods,p_params,m_members,c_constants - Pointer alignment: right (
int *ptr) - Include order: own header → Qt → local/project →
using namespace vnotex;in.cpponly - Header guards:
#ifndef CLASSNAME_H/#define CLASSNAME_H - Namespace: single
vnotexnamespace; neverusing namespacein headers libs/directory is excluded from formatting (third-party code)
clang-format -i src/core/myfile.cpp| Directory | Contents |
|---|---|
src/core/services/ |
Service layer (core_services static library, links vxcore) |
src/core/ |
Core types, legacy code, config, hook system |
src/controllers/ |
MVC controllers |
src/models/ |
MVC models |
src/views/ |
MVC views and delegates |
src/widgets/ |
UI widgets (receive ServiceLocator&), including dialogs/ |
src/gui/services/ |
GUI-aware services (ThemeService, ViewWindowFactory) |
src/gui/utils/ |
GUI utility helpers |
tests/ |
Qt Test suites; helpers/ has TempDirFixture and common includes |
libs/ |
Git submodules (vtextedit, QHotkey, vxcore) |
When migrating legacy code to new architecture:
- Create new file with
2suffix - Replace
ConfigMgr::getInst()→m_services.get<ConfigCoreService>() - Replace
VNoteX::getInst().getNotebookMgr()→m_services.get<NotebookCoreService>() - Add
ServiceLocator &p_servicesconstructor parameter, store asm_services - Add to appropriate
CMakeLists.txt
GitHub Actions workflows in .github/workflows/: ci-win.yml, ci-linux.yml, ci-macos.yml. Trigger on push/PR to master. Windows builds use Ninja + MSVC 2022 with Qt 6.10.3.