docs(decisions): document validated editor architecture and workspace model
This commit is contained in:
parent
2c923c245a
commit
7d4538d8c1
1 changed files with 383 additions and 0 deletions
383
decisions.md
383
decisions.md
|
|
@ -395,3 +395,386 @@ Negative:
|
||||||
- The editor abstraction must prevent the rest of the app from depending directly on NSTextView or UITextView.
|
- The editor abstraction must prevent the rest of the app from depending directly on NSTextView or UITextView.
|
||||||
- A future custom editor engine may still be required if line replacement or overlay rendering cannot preserve cursor correctness.
|
- A future custom editor engine may still be required if line replacement or overlay rendering cannot preserve cursor correctness.
|
||||||
- Native adapter updates must isolate user-originated selection changes from programmatic text, selection, and attribute changes to avoid SwiftUI/TextKit feedback loops.
|
- Native adapter updates must isolate user-originated selection changes from programmatic text, selection, and attribute changes to avoid SwiftUI/TextKit feedback loops.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# D-014 — Hybrid Editor Architecture Validated
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Date
|
||||||
|
|
||||||
|
2026-06-02
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Sapling's core user experience depends on a hybrid editing model:
|
||||||
|
|
||||||
|
* active content is displayed as Markdown source
|
||||||
|
* inactive content is displayed as rendered output
|
||||||
|
|
||||||
|
This model combines advantages of:
|
||||||
|
|
||||||
|
* plain-text Markdown editors
|
||||||
|
* live preview editors
|
||||||
|
* rendered document editors
|
||||||
|
|
||||||
|
However, early in development there was uncertainty regarding:
|
||||||
|
|
||||||
|
* editor performance
|
||||||
|
* large-document scalability
|
||||||
|
* rendering determinism
|
||||||
|
* active-line tracking
|
||||||
|
* viewport stability
|
||||||
|
* TextKit suitability
|
||||||
|
* code block rendering
|
||||||
|
* interactive rendered elements
|
||||||
|
|
||||||
|
A significant portion of Milestones 1–3 was dedicated to validating the feasibility of this architecture before proceeding with workspace and Git functionality.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Sapling adopts a hybrid rendered/source editing architecture as its primary editing model.
|
||||||
|
|
||||||
|
The architecture is considered validated and will remain the foundation of the application.
|
||||||
|
|
||||||
|
The editor will not be rewritten and no custom text engine is planned at this time.
|
||||||
|
|
||||||
|
## Validated Properties
|
||||||
|
|
||||||
|
The following properties have been demonstrated through implementation, profiling, and real-world testing.
|
||||||
|
|
||||||
|
### Large Document Scalability
|
||||||
|
|
||||||
|
Documents exceeding:
|
||||||
|
|
||||||
|
* 50,000 lines
|
||||||
|
* 5 MB of Markdown content
|
||||||
|
|
||||||
|
remain usable.
|
||||||
|
|
||||||
|
Profiling identified document-wide operations and replaced them with incremental approaches.
|
||||||
|
|
||||||
|
### Incremental Editing
|
||||||
|
|
||||||
|
Sapling maintains:
|
||||||
|
|
||||||
|
* incremental line indexing
|
||||||
|
* incremental invalidation
|
||||||
|
* incremental rendering updates
|
||||||
|
|
||||||
|
Editor interactions scale with the edited region rather than total document size.
|
||||||
|
|
||||||
|
### Rendering Determinism
|
||||||
|
|
||||||
|
Rendering output is derived from document state rather than interaction history.
|
||||||
|
|
||||||
|
Rendered content behaves consistently across:
|
||||||
|
|
||||||
|
* scrolling
|
||||||
|
* focus changes
|
||||||
|
* selection changes
|
||||||
|
* document reloads
|
||||||
|
|
||||||
|
### Editable Regions
|
||||||
|
|
||||||
|
The editor supports:
|
||||||
|
|
||||||
|
* single-line editing
|
||||||
|
* multi-line editing
|
||||||
|
* block editing
|
||||||
|
|
||||||
|
Rendered content transitions into source mode when actively edited.
|
||||||
|
|
||||||
|
### Rendered Elements
|
||||||
|
|
||||||
|
The editor supports first-class rendered elements including:
|
||||||
|
|
||||||
|
* headings
|
||||||
|
* task lists
|
||||||
|
* links
|
||||||
|
* code blocks
|
||||||
|
|
||||||
|
The architecture supports future rendered elements without fundamental redesign.
|
||||||
|
|
||||||
|
### Code Blocks
|
||||||
|
|
||||||
|
Code blocks are treated as semantic block elements rather than styled text.
|
||||||
|
|
||||||
|
They support:
|
||||||
|
|
||||||
|
* rendered containers
|
||||||
|
* syntax highlighting
|
||||||
|
* editable source transitions
|
||||||
|
|
||||||
|
### Viewport Stability
|
||||||
|
|
||||||
|
Rendered/source transitions preserve user context and do not require disruptive viewport repositioning.
|
||||||
|
|
||||||
|
## Technology Choice
|
||||||
|
|
||||||
|
The editor continues to use:
|
||||||
|
|
||||||
|
* NSTextView
|
||||||
|
* NSTextStorage
|
||||||
|
* NSLayoutManager
|
||||||
|
* TextKit
|
||||||
|
|
||||||
|
Investigation determined that observed performance bottlenecks originated primarily from Sapling's own document-wide algorithms rather than AppKit itself.
|
||||||
|
|
||||||
|
After introducing:
|
||||||
|
|
||||||
|
* incremental line indexing
|
||||||
|
* incremental invalidation
|
||||||
|
* region-based rendering
|
||||||
|
|
||||||
|
TextKit remained sufficiently performant for the project's requirements.
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
### Custom Text Engine
|
||||||
|
|
||||||
|
Rejected.
|
||||||
|
|
||||||
|
Reasons:
|
||||||
|
|
||||||
|
* significantly higher complexity
|
||||||
|
* increased maintenance burden
|
||||||
|
* no demonstrated need
|
||||||
|
* current architecture satisfies project requirements
|
||||||
|
|
||||||
|
### Split Editor / Preview Model
|
||||||
|
|
||||||
|
Rejected.
|
||||||
|
|
||||||
|
Reasons:
|
||||||
|
|
||||||
|
* interrupts writing flow
|
||||||
|
* increases cognitive overhead
|
||||||
|
* conflicts with Sapling's editing philosophy
|
||||||
|
|
||||||
|
### Full WYSIWYG Editor
|
||||||
|
|
||||||
|
Rejected.
|
||||||
|
|
||||||
|
Reasons:
|
||||||
|
|
||||||
|
* obscures Markdown source
|
||||||
|
* reduces portability
|
||||||
|
* conflicts with project goals
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
### Positive
|
||||||
|
|
||||||
|
* Markdown remains the source of truth.
|
||||||
|
* Editing remains fast on large documents.
|
||||||
|
* Rendered content improves readability.
|
||||||
|
* The editor architecture is stable enough for future feature development.
|
||||||
|
* Workspace and Git functionality can now be built on top of a proven editor foundation.
|
||||||
|
|
||||||
|
### Negative
|
||||||
|
|
||||||
|
* Hybrid editing introduces presentation complexity.
|
||||||
|
* Rendered/source transitions require ongoing testing.
|
||||||
|
* Some presentation-layer edge cases may continue to require refinement.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
The editor is the core product experience.
|
||||||
|
|
||||||
|
Milestones 1–3 demonstrated that a hybrid rendered/source architecture can provide:
|
||||||
|
|
||||||
|
* Markdown transparency
|
||||||
|
* pleasant reading experience
|
||||||
|
* scalable performance
|
||||||
|
* future extensibility
|
||||||
|
|
||||||
|
without requiring a custom editor implementation.
|
||||||
|
|
||||||
|
Future development should build upon this foundation rather than revisit the editor architecture unless substantial new evidence emerges.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# D-015 — Filesystem Is The Source Of Truth
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Date
|
||||||
|
|
||||||
|
2026-06-02
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Sapling introduces a distinction between:
|
||||||
|
|
||||||
|
* ordinary folders
|
||||||
|
* versioned projects
|
||||||
|
* Git subprojects
|
||||||
|
|
||||||
|
A key architectural decision is determining where workspace state lives.
|
||||||
|
|
||||||
|
Two approaches were considered:
|
||||||
|
|
||||||
|
### Option A — Sapling-Owned Workspace Metadata
|
||||||
|
|
||||||
|
Sapling maintains a manifest or database describing:
|
||||||
|
|
||||||
|
* folders
|
||||||
|
* files
|
||||||
|
* projects
|
||||||
|
* attachments
|
||||||
|
* hierarchy
|
||||||
|
|
||||||
|
The filesystem becomes an implementation detail.
|
||||||
|
|
||||||
|
Advantages:
|
||||||
|
|
||||||
|
* complete control
|
||||||
|
* fast metadata access
|
||||||
|
* custom workspace structures
|
||||||
|
|
||||||
|
Disadvantages:
|
||||||
|
|
||||||
|
* duplicates filesystem state
|
||||||
|
* requires synchronization
|
||||||
|
* external modifications become difficult
|
||||||
|
* introduces risk of workspace corruption
|
||||||
|
* reduces interoperability with other tools
|
||||||
|
|
||||||
|
### Option B — Filesystem-Native Workspace
|
||||||
|
|
||||||
|
The workspace is a normal directory on disk.
|
||||||
|
|
||||||
|
Sapling scans and interprets the filesystem directly.
|
||||||
|
|
||||||
|
Advantages:
|
||||||
|
|
||||||
|
* simple mental model
|
||||||
|
* interoperability with external tools
|
||||||
|
* no synchronization layer
|
||||||
|
* naturally compatible with Git
|
||||||
|
* resilient to external modifications
|
||||||
|
|
||||||
|
Disadvantages:
|
||||||
|
|
||||||
|
* requires filesystem scanning
|
||||||
|
* metadata must be derived rather than stored
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Sapling adopts a filesystem-native architecture.
|
||||||
|
|
||||||
|
The filesystem is the authoritative source of truth.
|
||||||
|
|
||||||
|
Sapling does not maintain an authoritative database or manifest describing workspace contents.
|
||||||
|
|
||||||
|
Workspace contents are derived directly from the filesystem.
|
||||||
|
|
||||||
|
Projects are discovered through Git metadata.
|
||||||
|
|
||||||
|
Subprojects are discovered through Git submodules.
|
||||||
|
|
||||||
|
Files and folders remain ordinary filesystem objects.
|
||||||
|
|
||||||
|
## Workspace Model
|
||||||
|
|
||||||
|
Workspace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Workspace/
|
||||||
|
├── Notes/
|
||||||
|
├── Research/
|
||||||
|
├── Project-A/
|
||||||
|
└── Project-B/
|
||||||
|
```
|
||||||
|
|
||||||
|
Sapling scans the workspace root and builds its tree model from the current filesystem state.
|
||||||
|
|
||||||
|
Changes made through:
|
||||||
|
|
||||||
|
* Finder
|
||||||
|
* Terminal
|
||||||
|
* VS Code
|
||||||
|
* Cursor
|
||||||
|
* Xcode
|
||||||
|
* Photoshop
|
||||||
|
* external scripts
|
||||||
|
|
||||||
|
must appear naturally inside Sapling.
|
||||||
|
|
||||||
|
No import or synchronization step should be required.
|
||||||
|
|
||||||
|
## Project Detection
|
||||||
|
|
||||||
|
A directory containing:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.git/
|
||||||
|
```
|
||||||
|
|
||||||
|
is considered a project.
|
||||||
|
|
||||||
|
Projects receive additional capabilities:
|
||||||
|
|
||||||
|
* Git status
|
||||||
|
* commits
|
||||||
|
* branches
|
||||||
|
* remotes
|
||||||
|
* history
|
||||||
|
* Git LFS
|
||||||
|
* submodules
|
||||||
|
|
||||||
|
Folders without Git metadata remain ordinary folders.
|
||||||
|
|
||||||
|
## Subproject Detection
|
||||||
|
|
||||||
|
Git submodules are treated as first-class Sapling subprojects.
|
||||||
|
|
||||||
|
Subproject discovery is derived from Git configuration rather than Sapling metadata.
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
|
||||||
|
Sapling may persist application state separately.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
* recent workspaces
|
||||||
|
* window state
|
||||||
|
* open tabs
|
||||||
|
* sidebar width
|
||||||
|
* editor preferences
|
||||||
|
* UI configuration
|
||||||
|
|
||||||
|
This data must never become the authoritative representation of workspace contents.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
### Positive
|
||||||
|
|
||||||
|
* Workspace remains human-readable.
|
||||||
|
* Workspace remains tool-agnostic.
|
||||||
|
* Users can manipulate files outside Sapling.
|
||||||
|
* Git integration remains natural.
|
||||||
|
* Cloud synchronization solutions work without special support.
|
||||||
|
* Workspaces remain usable even if Sapling is uninstalled.
|
||||||
|
|
||||||
|
### Negative
|
||||||
|
|
||||||
|
* Workspace state must be derived from filesystem scans.
|
||||||
|
* File watching becomes important.
|
||||||
|
* Some metadata may need caching for performance.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
One of Sapling's core values is ownership.
|
||||||
|
|
||||||
|
Users should own their notes, projects, attachments, and repositories without depending on Sapling-specific storage formats.
|
||||||
|
|
||||||
|
A workspace should remain a normal directory that can be understood and manipulated using standard operating system tools.
|
||||||
|
|
||||||
|
Sapling should adapt to the filesystem rather than requiring the filesystem to adapt to Sapling.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue