12 KiB
Clean code refactor plan
Scope
- Refactor 1: Align orchestrator and slice contracts with implementation.
- Refactor 2: Factor
CoalescePlugininto smaller, testable modules. - Refactor 3: Introduce a shared navigation facade used consistently by navigation and backlinks.
This file is implementation‑oriented and intended for Code mode to execute.
Refactor 1 – Orchestrator and slice contracts
Status
- Completed:
- Outdated
IPluginOrchestrator/ISliceFactorycontracts were removed fromslice-interfaces.ts, so that file now describes only slice-level APIs. - Orchestrator contracts live in
src/orchestrator/types.tsand are exported viasrc/orchestrator/index.ts, matching the concretePluginOrchestratorimplementation.
- Outdated
- Still planned:
- Add/update orchestrator-level tests to validate initialization order,
getSlice(), and event/statistics behavior.
- Add/update orchestrator-level tests to validate initialization order,
Notes
PluginOrchestratoris the single source of truth for orchestrator behavior.- Slice interfaces (for example
ISharedUtilitiesSlice,ISettingsSlice,INavigationSlice,IBacklinksSlice,IViewIntegrationSlice) remain the stable contracts between orchestrator and features.
Refactor 2 – Factor CoalescePlugin
Current structure (after refactor)
-
CoalescePluginnow:- Uses
createAndStartOrchestrator()fromPluginBootstrapinonloadinstead of manually constructing and starting the orchestrator. - Delegates debug commands to
PluginDebugCommandsviaattachDebugCommands()anddetachDebugCommands(), with a small localcoalesceUpdateLoggingthat still callsLogger.setGlobalLogging. - Delegates DOM, workspace, and orchestrator event wiring to
registerPluginEvents()inPluginEvents, passing a callback from the view initializer for UI updates. - Delegates view initialization and duplicate suppression to
PluginViewInitializerinstead of owning that logic directly.
- Uses
-
New helpers:
PluginBootstrap–createOrchestratorandcreateAndStartOrchestrator.PluginDebugCommands–attachDebugCommands(app, plugin, orchestrator, logger)anddetachDebugCommands(app).PluginEvents–registerPluginEvents(app, plugin, orchestrator, logger, updateCoalesceUIForFile?).PluginViewInitializer–initializeExistingViews()andupdateForFile(path).
Target structure
Organize plugin responsibilities into small, focused modules under src/orchestrator (or a future src/plugin folder):
PluginBootstrap– constructsPluginOrchestrator, initializes slices, and wires settings/logging.PluginDebugCommands– defines functions that attach and detach allcoalesce*debug helpers onapp.PluginEvents– encapsulates registration logic for DOM custom events (coalesce-settings-*,coalesce-navigate*), workspace events, and orchestrator events.PluginViewInitializer– handles initial view discovery and the duplicate‑processing logic for backlinks UI attachment.
After this refactor, CoalescePlugin is largely a thin adapter from Obsidian lifecycle (onload, onunload) to these helper modules.
Implementation checklist and status
-
Extract a plugin bootstrap module – Completed
PluginBootstrapprovidescreateAndStartOrchestrator(app, plugin, config).CoalescePlugin.onloadnow usescreateAndStartOrchestratorinstead of manualnew PluginOrchestrator(...)and separateinitialize()/start()calls.
-
Extract debug command wiring – Completed
PluginDebugCommandsnow owns allcoalesce*debug helpers.CoalescePlugin.setupDebugMethodscallsattachDebugCommands(this.app, this, this.orchestrator, this.logger)and defines onlycoalesceUpdateLogginglocally to callLogger.setGlobalLogging.CoalescePlugin.onunloadcallsdetachDebugCommands(this.app)instead of manually deleting each debug property.
-
Extract DOM+workspace+orchestrator event registration – Completed
-
PluginEventsimplementsregisterPluginEvents(app, plugin, orchestrator, logger, updateCoalesceUIForFile?), wiring:- DOM events:
coalesce-settings-collapse-changed,coalesce-logging-state-changed,coalesce-navigate,coalesce-navigate-complete. - Workspace events:
file-open,layout-change,active-leaf-change. - Orchestrator events:
file:opened,layout:changed,active-leaf:changed.
- DOM events:
-
CoalescePlugin.registerEventHandlersnow delegates to:registerPluginEvents( this.app, this, this.orchestrator, this.logger, this.viewInitializer.updateForFile.bind(this.viewInitializer) );
-
-
Extract view initialization and duplicate‑processing logic – Completed
PluginViewInitializernow owns:- Duplicate suppression logic (previously
shouldProcessFileinmain.ts). - Backlinks UI attachment and settings application logic (previously
updateCoalesceUIForFile). - Initial and delayed
file:openedemission for all markdown views (previouslyinitializeExistingViews).
- Duplicate suppression logic (previously
CoalescePlugincreates the view initializer and delegates:this.viewInitializer = createViewInitializer(this.app, this.orchestrator, this.logger);- Startup initialization via
this.viewInitializer.initializeExistingViews();.
-
Add tests for the new modules – In progress
PluginDebugCommands– Completed:PluginDebugCommands.testverifies thatattachDebugCommandsattaches representativecoalesce*helpers on a mockedappand thatdetachDebugCommandsremoves them.
PluginEvents– Planned:- Verify that firing
coalesce-*DOM events invokes the expected orchestrator or slice methods (using spies/mocks forsettings,backlinks,viewIntegration, and navigation behavior). - Verify that
file:openeddrivesinitializeView+attachToDOM+setOptionsas expected when slices are present.
- Verify that firing
PluginViewInitializer– Planned:- Verify that duplicate‑processing suppression works (previous
shouldProcessFilesemantics preserved). - Verify that backlinks UI is attached only when the correct slices and active view are present.
- Verify that duplicate‑processing suppression works (previous
Refactor 3 – Shared navigation facade
Current structure
-
Navigation implementation:
NavigationSliceconstructs:FileOpenerLinkHandlerNavigationService- and exposes a higher-level navigation API (
openPath,handleLinkClick, history, etc.).
BacklinksSlicepreviously constructed its own:FileOpenerLinkHandlerNavigationService- to implement
handleNavigation()via wiki-link text.
-
Shared navigation facade (new):
NavigationFacadenow provides a lazily-initialized shared set of navigation components:getSharedNavigation(app, baseLogger)returns{ navigationService, fileOpener, linkHandler }.__resetSharedNavigationForTests()to reset the singleton in tests.
Target design
- There should be exactly one
NavigationService(and correspondingFileOpener/LinkHandler) per plugin instance, shared by:NavigationSliceBacklinksSlice- Any future slice that needs navigation behavior.
- Validation, file existence checks, and link processing should flow through this shared stack so navigation behavior is consistent everywhere.
Implementation checklist and status
-
Introduce a NavigationFacade module – Completed
- Created
NavigationFacadewith:getSharedNavigation(app, baseLogger): lazily creates and stores{ navigationService, fileOpener, linkHandler }on first call, reuses thereafter.__resetSharedNavigationForTests(): test helper to reset the shared instance.
- Created
-
Update NavigationSlice to use the shared facade – Completed
-
NavigationSlicenow importsgetSharedNavigationand uses it in the constructor:const shared = getSharedNavigation(app, this.logger); this.fileOpener = shared.fileOpener; this.linkHandler = shared.linkHandler; this.navigationService = shared.navigationService; -
It no longer constructs its own
FileOpener/LinkHandler/NavigationService; it only configures performance monitoring and history on top.
-
-
Update BacklinksSlice to use the shared facade – Completed
-
BacklinksSlicenow importsgetSharedNavigationfrom../navigation/NavigationFacadeand uses it in the constructor:const sharedNavigation = getSharedNavigation(this.app, this.logger); this.navigationService = sharedNavigation.navigationService; -
It no longer constructs its own navigation stack;
handleNavigation()continues to callnavigationService.openWikiLink(...), but now using the shared instance.
-
-
Add tests for the navigation facade – Planned
- Add a small test module (e.g.
NavigationFacade.test) that:- Asserts that two calls to
getSharedNavigation(app, logger)return the samenavigationServiceinstance. - Asserts that
__resetSharedNavigationForTests()causes a subsequent call to create a new instance.
- Asserts that two calls to
- Optionally, assert that
NavigationSliceandBacklinksSliceboth obtain aNavigationServicethat is strictly the same object in an integration-style test.
- Add a small test module (e.g.
Suggested implementation order (updated)
- Refactor 1 (orchestrator/slice contracts) – Completed.
- Refactor 2 (CoalescePlugin) – Completed:
- Bootstrap, debug commands, event wiring, and view initialization have all been extracted into helper modules, with
main.tsacting as a thin lifecycle adapter.
- Bootstrap, debug commands, event wiring, and view initialization have all been extracted into helper modules, with
- Refactor 3 (shared navigation facade) – Partially completed:
- Shared navigation facade created and wired into
NavigationSliceandBacklinksSlice. - Remaining: facade-focused tests and potential further centralization of validation once SharedUtilities validation helpers are introduced into the navigation path.
- Shared navigation facade created and wired into
- Next steps:
- Add tests for navigation facade and remaining helper modules.
- Proceed to centralizing validation (via
SharedUtilitiesSlice.getValidationUtils) and standardizing logging (viaSharedUtilitiesSlice.getLogger), continuing to update this document as new refactors land.