Sapling/Sources/SaplingCore/BusinessRules.swift

168 lines
6.6 KiB
Swift
Raw Normal View History

2026-05-29 15:19:33 +02:00
import Foundation
public enum SaplingDomainError: Error, Equatable, Sendable {
case workspaceCannotBeInsideProject
case projectMustBeGitRepository
case subprojectPathCannotBeEmpty
}
public enum SaplingRules {
public static func validateWorkspace(_ workspace: Workspace) throws {
for item in workspace.items {
try validateWorkspaceItem(item)
}
}
public static func validateProject(_ project: Project) throws {
guard project.gitRepository.rootURL == project.repositoryURL else {
throw SaplingDomainError.projectMustBeGitRepository
}
}
public static func validateSubproject(_ subproject: Subproject) throws {
guard !subproject.path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw SaplingDomainError.subprojectPathCannotBeEmpty
}
}
private static func validateWorkspaceItem(_ item: WorkspaceItem) throws {
switch item {
case .folder(let folder):
for child in folder.children {
try validateWorkspaceItem(child)
}
case .project(let project):
try validateProject(project)
case .subproject(let subproject):
try validateSubproject(subproject)
case .file:
break
}
}
}
public enum SaplingSampleData {
public static let rootURL = URL(fileURLWithPath: "/tmp/SaplingSample")
public static var document: MarkdownDocument {
MarkdownDocument(
url: rootURL.appendingPathComponent("Writing/Editor Prototype.md"),
title: "Editor Prototype",
2026-05-29 15:19:33 +02:00
content: """
# Editor Prototype Notes
2026-05-29 15:19:33 +02:00
This document exists to test whether Sapling feels comfortable as a writing surface. It should be long enough to scroll, varied enough to exercise Markdown styling, and plain enough that the editor itself stays out of the way.
2026-05-29 15:19:33 +02:00
The goal for Milestone 1 is not a complete editor. The goal is confidence: text should be easy to enter, easy to revise, and easy to navigate without thinking about the machinery underneath.
## What Good Feels Like
A comfortable editor creates very little friction. The cursor lands where expected. Selection feels native. The page has enough breathing room that long paragraphs are readable, but not so much decoration that the document becomes secondary.
Useful qualities:
- The editor owns most of the window.
- Lines are not stretched across the entire screen.
- The current line is visible without shouting.
- Arrow keys, word navigation, and page navigation behave normally.
- Markdown styling helps scanning but never blocks editing.
## Navigation Checklist
Use this section for quick keyboard testing.
- [ ] Move with arrow keys.
- [ ] Jump by word with Option + Arrow.
- [ ] Extend selection with Shift + Arrow.
- [ ] Select across multiple lines.
- [ ] Use Home, End, Page Up, and Page Down.
- [ ] Type into the active line after moving quickly.
## A Normal Paragraph
Sapling should support the kind of writing that happens in notes, design documents, research logs, and project journals. That means paragraphs should feel calm and readable. The editor should avoid cramped edges, unexpected jumps, and visual noise that pulls attention away from the sentence currently being written.
Inline Markdown should remain understandable: **strong emphasis** should be easy to spot, *light emphasis* should remain subtle, and links like [Sapling](https://example.com/sapling) should be legible even before full rendering exists.
## Code Sample
2026-05-29 15:19:33 +02:00
```swift
struct EditorExperiment {
var activeLine: Int
var preservesSelection: Bool
mutating func move(to line: Int) {
activeLine = line
preservesSelection = true
}
}
2026-05-29 15:19:33 +02:00
```
## Notes While Testing
Add observations here while using the prototype for ten or fifteen minutes. The best findings are usually small: a cursor jump, a line that feels too wide, a shortcut that does not behave like a native editor, or a status indicator that creates more distraction than value.
The prototype is successful when this document feels like something you can keep editing rather than something you are merely testing.
2026-05-29 15:19:33 +02:00
"""
)
}
public static var workspace: Workspace {
let repositoryURL = rootURL.appendingPathComponent("Research")
let branch = GitBranch(name: "main", isCurrent: true, upstreamName: "origin/main")
let remote = GitRemote(
name: "origin",
url: URL(string: "https://example.com/sapling/research.git")!
)
let repository = GitRepository(
name: "Research",
rootURL: repositoryURL,
currentBranch: branch,
remotes: [remote],
statusSummary: .dirty
)
let subproject = Subproject(
name: "Shared Assets",
path: "Assets/Shared",
repositoryURL: repositoryURL.appendingPathComponent("Assets/Shared"),
remoteURL: URL(string: "https://example.com/sapling/shared-assets.git")
)
let project = Project(
name: "Research",
repositoryURL: repositoryURL,
gitRepository: repository,
remotes: [remote],
branches: [
branch,
GitBranch(name: "drafts/editor-prototype")
],
subprojects: [subproject],
usesGitLFS: true
)
return Workspace(
name: "Sapling Sample",
rootURL: rootURL,
items: [
.folder(
WorkspaceFolder(
name: "Inbox",
url: rootURL.appendingPathComponent("Inbox"),
children: [
.file(
WorkspaceFile(
name: "Scratch.md",
url: rootURL.appendingPathComponent("Inbox/Scratch.md"),
kind: .markdown
)
)
]
)
),
.project(project),
.subproject(subproject)
]
)
}
}