Analysis Date: November 2025
Project: TaskPaper (Shared Source)
License: Proprietary with shared source for license owners
Main Branch: main
TaskPaper is a sophisticated macOS outlining application with a unique hybrid architecture combining JavaScript for business logic with Swift/AppKit for UI presentation. Developed from 2007-2018 with maintenance continuing for macOS updates, the codebase represents a mature, well-polished application built on pre-modern macOS technologies.
Key Architectural Characteristics:
- Hybrid JavaScript/Swift Architecture: JavaScript (via JavaScriptCore) handles the model layer while Swift/AppKit manages UI
- Pure AppKit Implementation: No SwiftUI or Combine usage; built on NSTextView and related text system classes
- Custom Text Engine: Deeply customized NSTextStorage/NSLayoutManager for outline rendering
- LESS-based Styling: Dynamic stylesheet system using LESS CSS preprocessor
- Multi-target Build: Supports Direct, App Store, and Setapp distribution channels
Technology Snapshot:
- Swift 5.0
- macOS 11.0+ deployment target
- AppKit (no SwiftUI)
- JavaScriptCore bridge
- Carthage dependency management
TaskPaper/
├── BirchOutline/ # Model layer (JavaScript + Swift wrapper)
│ ├── birch-outline.js/ # JavaScript outline model, query language, undo
│ └── BirchOutline.swift/ # Swift/JavaScriptCore bridge
├── BirchEditor/ # View model & view layer
│ ├── birch-editor.js/ # JavaScript editor state, selection, styling
│ └── BirchEditor.swift/ # AppKit-based editor UI (~10,900 LOC)
├── TaskPaper/ # TaskPaper-specific customizations (~300 LOC)
│ ├── TaskPaperAppDelegate.swift
│ ├── TaskPaperDocument.swift
│ ├── base-stylesheet.less # Default styling
│ └── Resources/
├── TaskPaperTests/ # Test suite
├── Carthage/ # External dependencies
└── TaskPaper.xcodeproj # Main Xcode project
File References:
- Main project:
TaskPaper.xcodeproj/project.pbxproj - Build configuration: Lines showing
MACOSX_DEPLOYMENT_TARGET = 11.0/11.5,SWIFT_VERSION = 5.0
The architecture follows a clear layered dependency hierarchy:
TaskPaper (Application Layer)
↓
BirchEditor.swift (View + ViewModel Layer)
↓
BirchOutline.swift (Model Bridge)
↓
birch-editor.js ← birch-outline.js (JavaScript Core)
Key Design Intent (from README.md):
"TaskPaper specific customization to BirchEditor.swift. The intention was that there might be other apps that build off BirchEditor.swift."
This demonstrates a framework-oriented design where BirchEditor.swift is intended as reusable infrastructure.
Dependencies (Cartfile):
github "sparkle-project/Sparkle" ~> 1.2
github "PaddleHQ/Mac-Framework-V4"
Build Targets:
- TaskPaper-Direct: Standalone with Paddle licensing and Sparkle updates
- TaskPaper-AppStore: Mac App Store distribution
- TaskPaper-Setapp: Setapp subscription platform
JavaScript Build Process (from README.md):
- Requires Node.js v11.15.0 (legacy requirement)
- Uses webpack for bundling
npm linkconnects birch-outline and birch-editor during development- Compiled JavaScript bundled into app at build time
TaskPaper employs a unique hybrid architecture where:
JavaScript Layer (Model + ViewModel):
- birch-outline.js: Core outline data structure, attributed strings, query language, undo management
- birch-editor.js: Editor state, selection tracking, visible line calculations, style computations
Swift Layer (View + Controller):
- BirchOutline.swift: JavaScriptCore bridge providing Swift-friendly API
- BirchEditor.swift: AppKit UI implementation with custom text system
- TaskPaper: Application-specific behavior
Bridge Mechanism:
// BirchOutline/BirchOutline.swift/Common/Sources/BirchOutline.swift
open class BirchOutline {
static var _sharedContext: BirchScriptContext!
public static var sharedContext: BirchScriptContext {
// Singleton JSContext for JavaScript execution
}
}Document-Based Architecture:
// TaskPaper/TaskPaperDocument.swift
class TaskPaperDocument: OutlineDocument {
override var outlineRuntimeType: String {
return "com.taskpaper.text"
}
}State Synchronization: The architecture maintains bidirectional sync between JavaScript model and NSTextStorage:
// BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorTextStorage.swift:70-85
override open func replaceCharacters(in range: NSRange, with str: String) {
let updatingFromJS = isUpdatingFromJS
if !updatingFromJS {
// Update JavaScript model
_ = outlineEditor?.jsOutlineEditor.invokeMethod(
"replaceRangeWithString",
withArguments: [range.location, range.length, str, true]
)
}
}This creates a carefully coordinated two-way data flow to prevent infinite update loops.
TaskPaper uses a deeply customized NSTextView text system:
Core Components:
-
OutlineEditorTextStorage (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorTextStorage.swift)
- Custom NSTextStorage subclass
- Syncs with JavaScript item buffer
- Manages outline-specific attributes
-
OutlineEditorLayoutManager (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorLayoutManager.swift)
- Custom layout for outline items
- Handle (disclosure triangle) positioning
- Guide line rendering
-
OutlineEditorTextContainer (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorTextContainer.swift)
- Custom text container for outline-specific layout
-
OutlineEditorView (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorView.swift)
- Custom NSTextView subclass
- Handles outline-specific editing behaviors
Text System Setup (OutlineEditorViewController.swift:42-70):
open var outlineEditor: OutlineEditorType? {
didSet {
if let textStorage = outlineEditor?.textStorage {
let textContainer = OutlineEditorTextContainer()
let layoutManager = OutlineEditorLayoutManager(outlineEditor: outlineEditor!)
textContainer.replaceLayoutManager(layoutManager)
layoutManager.replaceTextStorage(textStorage)
outlineEditorView.replaceTextContainer(textContainer)
}
}
}LESS-Based Dynamic Styling:
TaskPaper uses LESS CSS for theme definitions, compiled at runtime in JavaScript:
Base Stylesheet (TaskPaper/base-stylesheet.less):
@appearance: $APPEARANCE; // dark or light
@user-font-size: $USER_FONT_SIZE;
@tint-color: $CONTROL_ACCENT_COLOR;
editor {
color: @text-color;
font-size: @user-font-size;
background-color: @background-color;
}Runtime Compilation (BirchEditor/BirchEditor.swift/BirchEditor/StyleSheet.swift:65-75):
- Processes LESS with runtime variable substitution
- Supports light/dark appearance modes (macOS 10.14+)
- User-customizable via .less files in Application Support
Computed Styles:
Compiled styles are converted to native Cocoa attributes via ComputedStyle class.
No SwiftUI Usage: Grep analysis confirms zero SwiftUI imports or property wrappers (@State, @ObservedObject, etc.)
UI Framework Breakdown:
- Primary: AppKit (NSViewController, NSTextView, NSSplitView)
- Storyboards: 13 .storyboard/.xib files for interface layout
- Custom Views: Extensive NSView/NSControl subclasses
Main View Controllers:
-
OutlineEditorWindowController (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorWindowController.swift)
- Root window controller
- Manages document window lifecycle
-
OutlineEditorSplitViewController (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorSplitViewController.swift)
- Three-pane split view (sidebar, editor, searchbar)
-
OutlineEditorViewController (BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorViewController.swift)
- Main editor view controller
- ~100 lines core implementation
- Manages OutlineEditorView (NSTextView subclass)
-
OutlineSidebarViewController (BirchEditor/BirchEditor.swift/BirchEditor/OutlineSidebarViewController.swift)
- Sidebar for searches and tags
- NSOutlineView-based
-
SearchBarViewController (BirchEditor/BirchEditor.swift/BirchEditor/SearchBarViewController.swift)
- Search/filter UI
Grep Results: 9 NSViewController subclasses identified in BirchEditor layer
Palette Windows:
- ChoicePaletteViewController: Command palette-style picker
- DatePickerViewController: Date tag insertion UI
- PaletteWindow: Custom NSPanel subclass for floating palettes
Custom Views:
- OutlineEditorView: Custom NSTextView with outline-specific behavior
- OutlineSidebarView: Outline view for searches/tags
- SearchBarSearchField: Custom NSSearchField
Method Swizzling (10 files use Objective-C runtime manipulation):
// BirchEditor/BirchEditor.swift/BirchEditor/JGMethodSwizzler.m
// Used to patch NSTextView, NSWindow, NSTextStorage behaviorsPerformance Hacks:
NSTextView-AccessibilityPerformanceHacks.m: Disables expensive accessibility featuresNSTextStorage-Performance.m: Optimizes text storage operationsNSMutableAttributedString-Performance.m: Attribute manipulation optimizations
This is a red flag for modernization: Method swizzling is fragile and may break with macOS updates.
Format: Plain text (.taskpaper extension)
Example (TaskPaper/Welcome.txt):
Welcome:
- TaskPaper knows about projects, tasks, notes, and tags.
To Create Items:
- To create a task, type a dash followed by a space.
- To create a project, type a line ending with a colon.
- To create a tag, type '@' followed by the tag's name.
Grammar:
- Projects: Lines ending with
: - Tasks: Lines starting with
- - Tags:
@tagnameor@tagname(value) - Notes: Plain text lines
- Indentation: Tab-based hierarchy
JavaScript Parser (BirchOutline/birch-outline.js/src/item-path-parser.js):
- PEG.js-based parser for query language
- Item path expressions for filtering/searching
Document Loading (BirchEditor/BirchEditor.swift/BirchEditor/OutlineDocument.swift:30-45):
override open func read(from data: Data, ofType typeName: String) throws {
// Deserialize plain text into outline model
if let string = String(data: data, encoding: .utf8) {
outline.reloadSerialization(string)
}
}Autosave: Uses NSDocument's autosavesInPlace (enabled)
Undo handled in JavaScript layer:
- birch-outline.js manages undo stack
- Synced to Cocoa undo manager via bridge
- Change tracking for autosave triggers
| Aspect | TaskPaper (Current) | Modern Practice (2025) | Gap |
|---|---|---|---|
| UI Framework | AppKit (NSViewController) | SwiftUI 4+ with AppKit interop | Major |
| Concurrency | GCD, callbacks | Swift Concurrency (async/await, actors) | Significant |
| Reactive | Custom KVO, notifications | Combine, Swift Observation | Significant |
| Language | Swift 5.0 | Swift 6 (strict concurrency) | Moderate |
| Deployment | macOS 11.0 | macOS 13.0+ typical | Moderate |
| Dependencies | Carthage | Swift Package Manager | Minor |
| Text Editing | Custom NSTextView | TextKit 2 (macOS 12+) | Moderate |
1. JavaScript Bridge Architecture
- Current: JavaScriptCore bridge for model layer
- Modern: Pure Swift with Codable, structured concurrency
- Issue: Performance overhead, debugging complexity, two language maintenance burden
2. Method Swizzling
- Current: 10+ files use runtime method replacement
- Modern: Subclassing, delegation, or SwiftUI composition
- Issue: Fragile, breaks with OS updates, hard to debug
3. Callbacks & Delegates
// BirchEditor/BirchEditor.swift/BirchEditor/delay.swift
// Uses GCD-based delayed callbacks
func delay(_ delay: Double, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
closure()
}
}- Modern:
Task.sleep()with async/await
4. No Async/Await
- Limited usage found (only in RemindersStore.swift for EventKit)
- Most async operations use completion handlers
5. No Combine or Swift Observation
- Custom observer pattern implementations
- Manual KVO setup
6. Old Build System
- Carthage (deprecated by many projects)
- Legacy Node.js v11.15.0 requirement
- Modern: Swift Package Manager, modern Node LTS
Available but Unused:
- TextKit 2 (macOS 12+): More efficient text layout
- Swift Concurrency: async/await, actors, @MainActor
- Swift 6: Data-race safety, strict concurrency checking
- NSTextLayoutManager: Modern replacement for NSLayoutManager
- Observation framework: Swift 5.9+ property observation
- Swift Package Manager: Modern dependency management
Size:
- BirchEditor.swift: ~10,900 lines of Swift
- TaskPaper layer: ~300 lines of Swift
- JavaScript: Substantial (birch-outline.js + birch-editor.js)
Code Organization: ★★★★☆ (4/5)
- Clear separation of concerns (Model/ViewModel/View)
- Well-structured file organization
- Logical grouping of related functionality
Strengths:
- Modular Architecture: Clean separation between BirchOutline, BirchEditor, TaskPaper
- Extensive Documentation: README explains build process and architecture
- Consistent Naming: Swift naming conventions followed
- Extension Pattern: Features logically separated (e.g., OutlineEditorViewController-Actions.swift)
Weaknesses:
- Method Swizzling: Makes behavior unpredictable and fragile
- JavaScript Bridge: Adds complexity, debugging difficulty
- Legacy Dependencies: Old Node.js, Carthage instead of SPM
- Limited Comments: Code documentation sparse in implementation files
- Tight Coupling: Text system components heavily interdependent
Maintainability Score: ★★★☆☆ (3/5)
- Well-organized but complex architecture
- Fragile AppKit customizations
- Two-language maintenance burden
Test Coverage: ★★☆☆☆ (2/5)
Existing Tests:
- TaskPaperTests/TaskPaperTests.swift (minimal)
- BirchEditorTests/ (basic outline editor tests)
- BirchOutlineTests/ (JavaScript model tests)
Testability Issues:
- Heavy use of singletons (
BirchOutline.sharedContext) - Tight coupling to AppKit (hard to unit test)
- JavaScript bridge complicates mocking
- Limited dependency injection
- No apparent protocol-oriented design for testing
Improvement Needed: Add comprehensive unit tests, integration tests, and UI tests
Optimizations Present:
- Performance-specific Objective-C categories for NSTextStorage, NSTextView
- Custom accessibility hacks to avoid expensive operations
- Debouncer class for throttling updates
- Incremental text storage updates
Potential Issues:
- JavaScript bridge overhead for every model operation
- Two-way sync between JavaScript and NSTextStorage could cause performance bottlenecks
- Method swizzling may have unexpected performance impacts
Human Interface Guidelines Compliance: ★★★★☆ (4/5)
Strengths:
- Standard Document Model: Uses NSDocument correctly
- Native Controls: Uses standard AppKit controls
- Keyboard Shortcuts: Extensive keyboard support
- Dark Mode: Supports macOS dark mode (10.14+)
- Accessibility: Maintains accessibility (though with performance hacks)
Areas for Improvement:
- Window Tabbing: Uses swizzling for tab bar customization (NSWindowTabbedBase.m)
- Touch Bar: No evidence of Touch Bar support
- Continuity: No apparent Handoff or iCloud sync features
- Localization: Limited localization (es.lproj present but minimal)
1. Migrate to Swift Package Manager
- Why: Carthage is deprecated, SPM is Apple's standard
- Impact: Easier dependency management, better Xcode integration
- Risk: Low (straightforward migration)
- Files: Cartfile → Package.swift
- Effort: 1-2 days
2. Update to Swift 6 (Non-Strict Mode)
- Why: Access to newer language features, preparation for concurrency safety
- Impact: Better type safety, modern syntax
- Risk: Low (backward compatible)
- Files: Project build settings
- Effort: 2-3 days + testing
3. Adopt Async/Await for Asynchronous Operations
- Why: Clearer async code, better error handling
- Impact: More readable, less callback hell
- Risk: Medium (requires iOS 15+/macOS 12+)
- Files: delay.swift, RemindersStore.swift, networking code
- Effort: 1-2 weeks
4. Modernize Node.js Build Process
- Why: Node.js v11.15.0 is ancient and unsupported
- Impact: Security, modern tooling
- Risk: Low (update package.json)
- Files: BirchOutline/birch-outline.js, BirchEditor/birch-editor.js
- Effort: 2-3 days
5. Eliminate Method Swizzling
- Why: Fragile, breaks with OS updates
- Impact: More stable, future-proof
- Risk: High (requires architectural changes)
- Files: JGMethodSwizzler.m, NSWindowTabbedBase.m, NSTextView-*.m
- Approach: Replace with subclassing, composition, or accept default behaviors
- Effort: 3-4 weeks
6. Introduce Protocol-Oriented Design for Testability
- Why: Enable unit testing, reduce coupling
- Impact: Better test coverage, more maintainable
- Risk: Medium (refactoring required)
- Approach: Extract protocols from key classes
- Effort: 2-3 weeks
7. Migrate to TextKit 2
- Why: Better performance, modern text handling
- Impact: Faster rendering, less custom code
- Risk: High (requires macOS 12+, significant rewrite)
- Files: OutlineEditorTextStorage.swift, OutlineEditorLayoutManager.swift
- Effort: 4-6 weeks
8. Gradual SwiftUI Adoption
- Why: Modern UI framework, declarative syntax
- Impact: Easier UI development, better platform integration
- Risk: Very High (major architectural change)
- Approach: Start with preferences, palettes; keep editor in AppKit initially
- Files: PreferencesWindowController.swift, ChoicePaletteViewController.swift
- Effort: 8-12 weeks (incremental)
9. Replace JavaScript Bridge with Pure Swift
- Why: Eliminate two-language complexity, better performance
- Impact: Simplified architecture, easier debugging
- Risk: Very High (complete rewrite of model layer)
- Approach: Rewrite birch-outline.js logic in Swift
- Files: Entire BirchOutline layer
- Effort: 12-16 weeks (major project)
10. Adopt Combine for Reactive Bindings
- Why: Modern reactive programming, SwiftUI integration
- Impact: Cleaner data flow, less manual observation
- Risk: Medium (requires iOS 13+/macOS 10.15+)
- Files: Observer patterns, KVO usage throughout
- Effort: 4-6 weeks
Phase 1 (1-2 months): Foundation
- Migrate to SPM
- Update to Swift 6 (compatibility mode)
- Modernize Node.js toolchain
- Add comprehensive test suite
Phase 2 (2-3 months): Async & Safety
- Adopt async/await throughout
- Introduce protocol-oriented design
- Eliminate critical method swizzling
Phase 3 (3-6 months): UI Modernization
- Migrate preferences to SwiftUI
- Adopt TextKit 2 for text rendering
- Add Touch Bar support
Phase 4 (6-12 months): Deep Architecture
- Consider JavaScript bridge alternatives
- Evaluate full SwiftUI editor (long-term)
- Implement Combine where beneficial
Based on the shared-source license and contribution guidelines from README.md:
"Do submit pull requests if you would like your changes potentially included in the official TaskPaper release."
PR #1: Migrate to Swift Package Manager
- Description: Replace Carthage with SPM for dependency management
- Files: Add Package.swift, remove Cartfile, update project settings
- Benefits: Modern dependency management, better Xcode integration
- Risk: Low
PR #2: Update Node.js Requirement
- Description: Modernize JavaScript build to use Node.js LTS (v20+)
- Files: package.json in birch-outline.js and birch-editor.js, README.md
- Benefits: Security updates, modern tooling
- Risk: Low
PR #3: Add Swift Concurrency to RemindersStore
- Description: Fully convert RemindersStore.swift to async/await
- Files: BirchEditor/BirchEditor.swift/BirchEditor/RemindersStore.swift
- Benefits: Cleaner async code, better error handling
- Risk: Low (isolated component)
PR #4: Comprehensive Test Suite
- Description: Add unit tests for core outline operations
- Files: New test files in TaskPaperTests/
- Benefits: Better reliability, regression prevention
- Risk: Very Low
PR #5: Extract Protocols for Core Types
- Description: Introduce protocols for OutlineEditor, StyleSheet, OutlineDocument
- Files: New protocol files, refactor concrete types
- Benefits: Better testability, reduced coupling
- Risk: Medium
PR #6: Replace delay() with Task.sleep()
- Description: Use Swift concurrency for delayed operations
- Files: delay.swift, call sites throughout codebase
- Benefits: Modern concurrency patterns
- Risk: Medium (requires async context)
PR #7: Localization Infrastructure
- Description: Improve localization support, add more languages
- Files: .strings files, localization infrastructure
- Benefits: Broader user base
- Risk: Medium
PR #8: Eliminate NSTextView Swizzling
- Description: Remove method swizzling from NSTextView customizations
- Files: NSTextView-AccessibilityPerformanceHacks.m, JGMethodSwizzler.m
- Benefits: More stable, future-proof
- Risk: High (may change behavior)
- Note: Discuss approach with maintainer first
PR #9: TextKit 2 Migration (Proof of Concept)
- Description: Experimental TextKit 2 implementation
- Files: New NSTextLayoutManager-based classes
- Benefits: Modern text system, better performance
- Risk: Very High
- Note: Would require macOS 12+ minimum version bump
Per README.md restrictions:
- ✅ DO modify for your own use
- ❌ DO NOT change or disable licensing code (check before submitting PRs)
- ❌ DO NOT redistribute binaries without permission
- ✅ DO submit pull requests for potential inclusion
Before Submitting:
- Contact jesse@hogbaysoftware.com for non-standard contributions
- Ensure no licensing code is affected
- Test thoroughly across supported macOS versions
- Include tests for new functionality
- Follow existing code style and conventions
- Clean Separation of Concerns: Model/ViewModel/View clearly delineated
- Reusable Framework Design: BirchEditor.swift designed for multiple apps
- Sophisticated Text System: Custom NSTextView implementation handles complex outline rendering
- Dynamic Styling: LESS-based theming enables user customization
- Well-Polished: Mature codebase with attention to detail
- Two-Language Complexity: JavaScript + Swift maintenance burden
- Legacy Technologies: Swift 5.0, old Node.js, Carthage, method swizzling
- Limited Test Coverage: Insufficient automated testing
- AppKit Lock-in: No modern SwiftUI adoption
- Modernization Risk: Deep customizations make updates risky
For Individual Contributors:
- Start with low-risk PRs (SPM migration, tests, async/await adoption)
- Focus on improving testability and modernizing build infrastructure
- Respect licensing code boundaries
For Long-Term Modernization:
- Incrementally reduce JavaScript bridge dependency
- Gradually introduce SwiftUI for new UI components
- Plan TextKit 2 migration as multi-phase project
- Eliminate method swizzling through careful refactoring
For Current Maintenance:
- Keep pace with macOS releases (already doing well)
- Update dependencies regularly
- Improve test coverage before major refactoring
- Document architectural decisions
TaskPaper represents a well-architected mature application built on pre-modern macOS technologies. The hybrid JavaScript/Swift architecture was forward-thinking for its time but now presents maintenance challenges. The codebase would benefit significantly from incremental modernization, starting with dependency management, build tooling, and test coverage, before tackling deeper architectural changes.
Overall Code Quality: ★★★★☆ (4/5) Modernization Priority: ★★★★☆ (High) Contribution Friendliness: ★★★☆☆ (Moderate - complex architecture but clear guidelines)
BirchOutline/BirchOutline.swift/Common/Sources/BirchOutline.swift- JavaScript bridge entry pointBirchEditor/BirchEditor.swift/BirchEditor/BirchEditor.swift- Main editor frameworkTaskPaper/TaskPaperAppDelegate.swift- Application entry pointTaskPaper/TaskPaperDocument.swift- Document model
BirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorViewController.swift- Main editor view controllerBirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorView.swift- Custom NSTextViewBirchEditor/BirchEditor.swift/BirchEditor/OutlineEditorTextStorage.swift- Text storage sync
TaskPaper/base-stylesheet.less- Base theme definitionBirchEditor/BirchEditor.swift/BirchEditor/StyleSheet.swift- Style compilation
TaskPaper.xcodeproj/project.pbxproj- Xcode project configurationCartfile- Dependency declarationsBirchOutline/birch-outline.js/package.json- JavaScript model packageBirchEditor/birch-editor.js/package.json- JavaScript editor package
BirchEditor/BirchEditor.swift/BirchEditor/NSTextView-AccessibilityPerformanceHacks.mBirchEditor/BirchEditor.swift/BirchEditor/NSTextStorage-Performance.mBirchEditor/BirchEditor.swift/BirchEditor/JGMethodSwizzler.m
Document Version: 1.0
Author: Automated Architecture Analysis
Last Updated: November 2025