bfloydd_coalesce/refactor-plan.md
2025-11-19 19:24:45 -07:00

12 KiB
Raw Blame History

Clean code refactor plan

Scope

  • Refactor 1: Align orchestrator and slice contracts with implementation.
  • Refactor 2: Factor CoalescePlugin into smaller, testable modules.
  • Refactor 3: Introduce a shared navigation facade used consistently by navigation and backlinks.

This file is implementationoriented and intended for Code mode to execute.

Refactor 1 Orchestrator and slice contracts

Status

  • Completed:
  • Still planned:
    • Add/update orchestrator-level tests to validate initialization order, getSlice(), and event/statistics behavior.

Notes

Refactor 2 Factor CoalescePlugin

Current structure (after refactor)

Target structure

Organize plugin responsibilities into small, focused modules under src/orchestrator (or a future src/plugin folder):

  • PluginBootstrap constructs PluginOrchestrator, initializes slices, and wires settings/logging.
  • PluginDebugCommands defines functions that attach and detach all coalesce* debug helpers on app.
  • PluginEvents encapsulates registration logic for DOM custom events (coalesce-settings-*, coalesce-navigate*), workspace events, and orchestrator events.
  • PluginViewInitializer handles initial view discovery and the duplicateprocessing 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

  1. Extract a plugin bootstrap module Completed

    • PluginBootstrap provides createAndStartOrchestrator(app, plugin, config).
    • CoalescePlugin.onload now uses createAndStartOrchestrator instead of manual new PluginOrchestrator(...) and separate initialize() / start() calls.
  2. Extract debug command wiring Completed

  3. Extract DOM+workspace+orchestrator event registration Completed

    • PluginEvents implements registerPluginEvents(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.
    • CoalescePlugin.registerEventHandlers now delegates to:

      registerPluginEvents(
        this.app,
        this,
        this.orchestrator,
        this.logger,
        this.viewInitializer.updateForFile.bind(this.viewInitializer)
      );
      
  4. Extract view initialization and duplicateprocessing logic Completed

    • PluginViewInitializer now owns:
      • Duplicate suppression logic (previously shouldProcessFile in main.ts).
      • Backlinks UI attachment and settings application logic (previously updateCoalesceUIForFile).
      • Initial and delayed file:opened emission for all markdown views (previously initializeExistingViews).
    • CoalescePlugin creates the view initializer and delegates:
      • this.viewInitializer = createViewInitializer(this.app, this.orchestrator, this.logger);
      • Startup initialization via this.viewInitializer.initializeExistingViews();.
  5. Add tests for the new modules In progress

    • PluginDebugCommands Completed:
      • PluginDebugCommands.test verifies that attachDebugCommands attaches representative coalesce* helpers on a mocked app and that detachDebugCommands removes them.
    • PluginEvents Planned:
      • Verify that firing coalesce-* DOM events invokes the expected orchestrator or slice methods (using spies/mocks for settings, backlinks, viewIntegration, and navigation behavior).
      • Verify that file:opened drives initializeView + attachToDOM + setOptions as expected when slices are present.
    • PluginViewInitializer Planned:
      • Verify that duplicateprocessing suppression works (previous shouldProcessFile semantics preserved).
      • Verify that backlinks UI is attached only when the correct slices and active view are present.

Refactor 3 Shared navigation facade

Current structure

  • Navigation implementation:

    • NavigationSlice constructs:
      • FileOpener
      • LinkHandler
      • NavigationService
      • and exposes a higher-level navigation API (openPath, handleLinkClick, history, etc.).
    • BacklinksSlice previously constructed its own:
      • FileOpener
      • LinkHandler
      • NavigationService
      • to implement handleNavigation() via wiki-link text.
  • Shared navigation facade (new):

    • NavigationFacade now 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 corresponding FileOpener / LinkHandler) per plugin instance, shared by:
  • Validation, file existence checks, and link processing should flow through this shared stack so navigation behavior is consistent everywhere.

Implementation checklist and status

  1. Introduce a NavigationFacade module Completed

    • Created NavigationFacade with:
      • getSharedNavigation(app, baseLogger): lazily creates and stores { navigationService, fileOpener, linkHandler } on first call, reuses thereafter.
      • __resetSharedNavigationForTests(): test helper to reset the shared instance.
  2. Update NavigationSlice to use the shared facade Completed

    • NavigationSlice now imports getSharedNavigation and 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.

  3. Update BacklinksSlice to use the shared facade Completed

    • BacklinksSlice now imports getSharedNavigation from ../navigation/NavigationFacade and 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 call navigationService.openWikiLink(...), but now using the shared instance.

  4. 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 same navigationService instance.
      • Asserts that __resetSharedNavigationForTests() causes a subsequent call to create a new instance.
    • Optionally, assert that NavigationSlice and BacklinksSlice both obtain a NavigationService that is strictly the same object in an integration-style test.

Suggested implementation order (updated)

  1. Refactor 1 (orchestrator/slice contracts) Completed.
  2. Refactor 2 (CoalescePlugin) Completed:
    • Bootstrap, debug commands, event wiring, and view initialization have all been extracted into helper modules, with main.ts acting as a thin lifecycle adapter.
  3. Refactor 3 (shared navigation facade) Partially completed:
    • Shared navigation facade created and wired into NavigationSlice and BacklinksSlice.
    • Remaining: facade-focused tests and potential further centralization of validation once SharedUtilities validation helpers are introduced into the navigation path.
  4. Next steps:
    • Add tests for navigation facade and remaining helper modules.
    • Proceed to centralizing validation (via SharedUtilitiesSlice.getValidationUtils) and standardizing logging (via SharedUtilitiesSlice.getLogger), continuing to update this document as new refactors land.