Craft: Native-First Dokument-Exzellenz

Warum Craft den Apple Design Award 2021 gewann: Native Performance, verschachtelte Seiten, Block-Architektur und Ein-Klick-Web-Publishing. Mit Swift-Implementierungsmustern.

4 Min. Lesezeit 743 Worter
Craft: Native-First Dokument-Exzellenz screenshot

Craft: Native-First-Dokumentenexzellenz

„Wir glauben, dass Werkzeuge für das Denken sich wie eine Erweiterung der eigenen Gedanken anfühlen sollten, nicht wie ein Hindernis."

Craft beweist, dass native Apps Erlebnisse bieten können, die Web-Apps nicht erreichen. Durch tiefe Plattformintegration erreicht es eine Reaktionsfähigkeit und Qualität, die digitales Notizenmachen so natürlich wie Papier erscheinen lässt.


Wichtigste Erkenntnisse

  1. Native schlägt Electron - Reaktionszeiten unter 50ms erfordern plattformspezifische UI, keine Web-Wrapper
  2. Seiten in Seiten - Jeder Block kann zur Seite werden, sodass Struktur aus dem Inhalt entsteht
  3. Mehrere Layouts, dieselben Daten - Listen, Karten, Galerien und Boards bieten die richtige Ansicht für jeden Kontext
  4. Web-Publishing mit einem Klick - Geteilte Seiten funktionieren ohne Empfänger-Accounts oder App-Downloads
  5. Plattformgerecht, nicht identisch - Mac Craft fühlt sich nach Mac an; iOS Craft fühlt sich nach iOS an

Warum Craft wichtig ist

Craft gewann den Apple Design Award 2021 und erhält konstant App Store-Auszeichnungen, weil es sich weigert, native Performance für plattformübergreifende Bequemlichkeit zu opfern.

Wichtigste Errungenschaften: - Echte native Apps auf iOS, macOS, Windows (nicht Electron) - Reaktionszeit unter 50ms bei allen Interaktionen - Offline-first mit nahtloser Synchronisierung - Block-basierte Architektur ohne die Web-App-Verzögerungen - Schöne geteilte Seiten, die ohne Accounts funktionieren


Kernphilosophie des Designs

Native-First, nicht Web-verpackt

Crafts entscheidende Wahl: native Apps für jede Plattform entwickeln, mit geteilter Geschäftslogik aber plattformspezifischer UI.

ELECTRON/WEB APPROACH              CRAFT'S NATIVE APPROACH
───────────────────────────────────────────────────────────────────
Single codebase (JavaScript)       Platform-specific UI (Swift, etc.)
Web rendering engine               Native rendering
Cross-platform "consistency"       Platform-appropriate behavior
~200ms input latency               ~16ms input latency
Generic keyboard shortcuts         Platform-native shortcuts
Web-standard text selection        Native text engine

Zentrale Erkenntnis: Benutzer wollen keine Apps, die überall gleich aussehen – sie wollen Apps, die sich auf ihrer Plattform richtig anfühlen.

macOS via Catalyst (richtig gemacht)

Craft nutzt Mac Catalyst, passt es aber umfassend an, damit es sich wirklich Mac-nativ anfühlt:

// Craft's Catalyst customization approach
#if targetEnvironment(macCatalyst)
extension SceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession) {
        guard let windowScene = scene as? UIWindowScene else { return }

        // Enable native Mac toolbar
        if let titlebar = windowScene.titlebar {
            titlebar.titleVisibility = .hidden
            titlebar.toolbar = createNativeToolbar()
        }

        // Mac-specific window sizing
        windowScene.sizeRestrictions?.minimumSize = CGSize(width: 800, height: 600)

        // Enable window tabs
        UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .forEach { $0.titlebar?.toolbar?.showsBaselineSeparator = false }
    }

    private func createNativeToolbar() -> NSToolbar {
        let toolbar = NSToolbar(identifier: "CraftToolbar")
        toolbar.displayMode = .iconOnly
        toolbar.delegate = self
        return toolbar
    }
}
#endif

Musterbibliothek

1. Block-basierte Inhaltsarchitektur

Jedes Element in Craft ist ein Block. Jeder Block mit Inhalt ist potenziell eine Seite, was unendliche Verschachtelung ohne kognitive Überlastung ermöglicht.

Block-Typen:

TEXT BLOCKS
├── Paragraph (default)
├── Heading 1, 2, 3
├── Quote
├── Callout (info, warning, success)
└── Code (with syntax highlighting)

MEDIA BLOCKS
├── Image (with caption)
├── Video
├── File attachment
├── Drawing (Apple Pencil)
└── Audio recording

STRUCTURAL BLOCKS
├── Toggle (collapsible)
├── Page (nested document)
├── Divider
└── Table

Implementierungskonzept:

protocol CraftBlock: Identifiable {
    var id: UUID { get }
    var content: BlockContent { get set }
    var style: BlockStyle { get set }
    var children: [any CraftBlock] { get set }
    var metadata: BlockMetadata { get }
}

enum BlockContent {
    case text(AttributedString)
    case image(ImageData)
    case page(PageReference)
    case toggle(isExpanded: Bool)
    case code(language: String, content: String)
    case table(TableData)
}

struct BlockMetadata {
    let created: Date
    let modified: Date
    let createdBy: UserReference?
}

// Every block can contain other blocks
class PageBlock: CraftBlock {
    var id = UUID()
    var content: BlockContent
    var style: BlockStyle
    var children: [any CraftBlock] = []
    var metadata: BlockMetadata

    // A page is just a block that can be opened full-screen
    var canOpenAsPage: Bool { true }
}

Zentrale Erkenntnis: Wenn jeder Block eine Seite sein kann, entsteht die Informationsarchitektur aus dem Inhalt, nicht aus vorgegebener Struktur.


2. Verschachtelte Seiten (Seiten in Seiten)

Crafts Alleinstellungsmerkmal: Jeder Block kann zur Seite werden, und Seiten verschachteln sich unendlich.

DOCUMENT STRUCTURE
───────────────────────────────────────────────────────────────────
📄 Project Alpha
├── 📝 Introduction paragraph
├── 📄 Research Notes         ← This is a page (nested document)
│   ├── 📝 User interviews
│   ├── 📄 Interview: Sarah   ← Another nested page
│   │   └── 📝 Key insights
│   └── 📝 Competitive analysis
├── 📝 Timeline overview
└── 📄 Meeting Notes          ← Another page
    └── 📝 Action items

Navigation: Breadcrumb trail shows path
Project Alpha > Research Notes > Interview: Sarah

SwiftUI-Implementierungsmuster:

struct PageView: View {
    @Bindable var page: PageDocument
    @State private var selectedBlock: CraftBlock?

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                ForEach(page.blocks) { block in
                    BlockView(block: block, onTap: { tapped in
                        if tapped.canOpenAsPage {
                            navigateToPage(tapped)
                        } else {
                            selectedBlock = tapped
                        }
                    })
                }
            }
        }
        .navigationTitle(page.title)
        .toolbar {
            // Breadcrumb navigation
            ToolbarItem(placement: .principal) {
                BreadcrumbView(path: page.ancestorPath)
            }
        }
    }
}

struct BreadcrumbView: View {
    let path: [PageReference]

    var body: some View {
        HStack(spacing: 4) {
            ForEach(Array(path.enumerated()), id: \.element.id) { index, page in
                if index > 0 {
                    Image(systemName: "chevron.right")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                Button(page.title) {
                    navigateTo(page)
                }
                .buttonStyle(.plain)
                .foregroundStyle(index == path.count - 1 ? .primary : .secondary)
            }
        }
    }
}

3. Karten-Layout-System

Craft bietet 5 visuelle Stile für Seiten, die Dokumente auf einen Blick überschaubar machen.

Kartenstile:

STIL 1: LISTE (Standard)
┌────────────────────────────────────────┐
│ 📄 Seitentitel                         │
│    Vorschautext erscheint hier...      │
└────────────────────────────────────────┘

STIL 2: KARTE (Mittel)
┌──────────────────┐
│ ┌──────────────┐ │
│ │   [Bild]     │ │
│ └──────────────┘ │
│ Seitentitel      │
│ Vorschautext...  │
└──────────────────┘

STIL 3: KARTE (Groß)
┌────────────────────────────────────────┐
│ ┌────────────────────────────────────┐ │
│ │                                    │ │
│ │          [Titelbild]               │ │
│ │                                    │ │
│ └────────────────────────────────────┘ │
│ Seitentitel                            │
│ Längerer Vorschautext mit mehr Platz...│
└────────────────────────────────────────┘

STIL 4: GALERIE (Raster)
┌────────┐ ┌────────┐ ┌────────┐
│ [Bild] │ │ [Bild] │ │ [Bild] │
│ Titel  │ │ Titel  │ │ Titel  │
└────────┘ └────────┘ └────────┘

STIL 5: BOARD (Kanban-Stil)
│ To Do    │ In Arbeit│ Erledigt │
├──────────┼──────────┼──────────┤
│ Aufgabe 1│ Aufgabe 3│ Aufgabe 5│
│ Aufgabe 2│ Aufgabe 4│          │

Implementierung:

enum PageLayoutStyle: String, CaseIterable {
    case list = "list"
    case cardMedium = "card_medium"
    case cardLarge = "card_large"
    case gallery = "gallery"
    case board = "board"
}

struct PageGridView: View {
    let pages: [PageReference]
    let style: PageLayoutStyle

    var body: some View {
        switch style {
        case .list:
            LazyVStack(spacing: 8) {
                ForEach(pages) { page in
                    ListRowView(page: page)
                }
            }

        case .cardMedium, .cardLarge:
            let columns = style == .cardMedium ? 3 : 2
            LazyVGrid(columns: Array(repeating: .init(.flexible()), count: columns)) {
                ForEach(pages) { page in
                    CardView(page: page, size: style == .cardLarge ? .large : .medium)
                }
            }

        case .gallery:
            LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 4)) {
                ForEach(pages) { page in
                    GalleryThumbnail(page: page)
                }
            }

        case .board:
            BoardView(pages: pages)
        }
    }
}

4. Seitenhintergründe und Theming

Jede Seite kann durch Hintergründe und Akzentfarben ihre eigene visuelle Identität erhalten.

struct PageAppearance {
    // Background options
    enum Background {
        case solid(Color)
        case gradient(Gradient)
        case image(ImageReference)
        case paper(PaperTexture)
    }

    // Paper textures for writing feel
    enum PaperTexture: String {
        case none
        case lined
        case grid
        case dotted
    }

    var background: Background = .solid(.white)
    var accentColor: Color = .blue
    var iconEmoji: String?
    var coverImage: ImageReference?
}

// Applied to page
struct PageContainer: View {
    let page: PageDocument

    var body: some View {
        ZStack {
            // Background layer
            backgroundView(for: page.appearance.background)

            // Content layer
            PageContentView(page: page)
        }
    }

    @ViewBuilder
    private func backgroundView(for background: PageAppearance.Background) -> some View {
        switch background {
        case .solid(let color):
            color.ignoresSafeArea()
        case .gradient(let gradient):
            LinearGradient(gradient: gradient, startPoint: .top, endPoint: .bottom)
                .ignoresSafeArea()
        case .image(let ref):
            AsyncImage(url: ref.url) { image in
                image.resizable().scaledToFill()
            } placeholder: {
                Color.gray.opacity(0.1)
            }
            .ignoresSafeArea()
            .overlay(.ultraThinMaterial)
        case .paper(let texture):
            PaperTextureView(texture: texture)
        }
    }
}

5. Share Pages (Web-Veröffentlichung)

Craft generiert aus Dokumenten ansprechende, responsive Webseiten. Zum Ansehen ist kein Konto erforderlich.

DOKUMENT IN CRAFT                 SHARE PAGE IM WEB
───────────────────────────────────────────────────────────────────
📄 Projektvorschlag               https://www.craft.do/s/abc123
├── 📝 Zusammenfassung      →
├── 📄 Budgetdetails              Sauberes, responsives Layout
├── 📝 Zeitplan                   Typografie erhalten
└── 📄 Team-Biografien            Bilder optimiert
                                  Dark Mode unterstützt
                                  Kein Craft-Konto erforderlich

Wichtige Funktionen: - Veröffentlichung mit einem Klick - Eigene Domains verfügbar - Passwortschutz optional - Aufrufstatistiken - SEO-freundliche Darstellung - Responsive auf allen Geräten


Visuelles Design-System

Farbpalette

extension Color {
    // Craft's signature palette
    static let craftPurple = Color(hex: "#6366F1")  // Primary accent
    static let craftBackground = Color(hex: "#FAFAFA")  // Light mode
    static let craftSurface = Color(hex: "#FFFFFF")

    // Semantic colors
    static let craftSuccess = Color(hex: "#10B981")
    static let craftWarning = Color(hex: "#F59E0B")
    static let craftError = Color(hex: "#EF4444")
    static let craftInfo = Color(hex: "#3B82F6")

    // Text hierarchy
    static let craftTextPrimary = Color(hex: "#111827")
    static let craftTextSecondary = Color(hex: "#6B7280")
    static let craftTextMuted = Color(hex: "#9CA3AF")
}

Typografie

struct CraftTypography {
    // Document typography
    static let title = Font.system(size: 32, weight: .bold, design: .default)
    static let heading1 = Font.system(size: 28, weight: .semibold)
    static let heading2 = Font.system(size: 22, weight: .semibold)
    static let heading3 = Font.system(size: 18, weight: .medium)
    static let body = Font.system(size: 16, weight: .regular)
    static let caption = Font.system(size: 14, weight: .regular)

    // Code blocks
    static let code = Font.system(size: 14, weight: .regular, design: .monospaced)

    // Line heights (generous for readability)
    static let bodyLineSpacing: CGFloat = 8
    static let headingLineSpacing: CGFloat = 4
}

Abstands-System

struct CraftSpacing {
    // Block spacing
    static let blockGap: CGFloat = 4        // Between blocks
    static let sectionGap: CGFloat = 24     // Between sections
    static let pageMargin: CGFloat = 40     // Page edges (desktop)
    static let mobileMargin: CGFloat = 16   // Page edges (mobile)

    // Content width
    static let maxContentWidth: CGFloat = 720  // Optimal reading
    static let wideContentWidth: CGFloat = 960 // Tables, galleries
}

Animations-Philosophie

Flüssige Seitenübergänge

// Navigation between pages uses matched geometry
struct NavigationTransition: View {
    @Namespace private var namespace
    @State private var selectedPage: PageReference?

    var body: some View {
        ZStack {
            // Page list
            PageListView(
                onSelect: { page in
                    withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
                        selectedPage = page
                    }
                },
                namespace: namespace
            )

            // Selected page expands from its card position
            if let page = selectedPage {
                PageDetailView(page: page)
                    .matchedGeometryEffect(id: page.id, in: namespace)
                    .transition(.scale.combined(with: .opacity))
            }
        }
    }
}

Block-Animationen

// Blocks animate when reordering
struct AnimatedBlockList: View {
    @Binding var blocks: [CraftBlock]

    var body: some View {
        LazyVStack(spacing: CraftSpacing.blockGap) {
            ForEach(blocks) { block in
                BlockView(block: block)
                    .transition(.asymmetric(
                        insertion: .move(edge: .top).combined(with: .opacity),
                        removal: .move(edge: .bottom).combined(with: .opacity)
                    ))
            }
        }
        .animation(.spring(response: 0.3, dampingFraction: 0.8), value: blocks)
    }
}

Erkenntnisse für unsere Arbeit

1. Native Performance ist die Investition wert

Nutzer spüren den Unterschied zwischen 16ms und 200ms Latenz. Native Apps gewinnen beim Gefühl.

2. Seiten innerhalb von Seiten ermöglichen organische Organisation

Lass Struktur aus dem Inhalt entstehen. Zwinge Nutzer nicht, Hierarchien von Anfang an festzulegen.

3. Mehrere visuelle Layouts für denselben Inhalt

Karten, Listen, Galerien—dieselben Daten, unterschiedliche Ansichten für verschiedene Kontexte.

4. Teilen ohne Reibungsverluste

One-Click Web-Publishing beseitigt das „Wie teile ich das?"-Problem vollständig.

5. Plattformgerecht, nicht plattformidentisch

Craft auf dem Mac fühlt sich an wie eine Mac-App. Craft auf iOS fühlt sich an wie eine iOS-App. Das ist das Ziel.


Häufig gestellte Fragen

Wie unterscheidet sich Craft von Notion?

Craft ist nativ (speziell für Apple-Plattformen entwickelt), während Notion web-basiert ist. Das bedeutet, Craft erreicht Reaktionszeiten unter 50ms, funktioniert vollständig offline und integriert sich tief in iOS/macOS-Funktionen wie Apple Pencil, Shortcuts und systemweite Suche. Notion bietet mehr Datenbankfunktionen; Craft priorisiert das Schreiberlebnis.

Kann ich Craft offline nutzen?

Ja. Craft speichert alle Dokumente lokal und synchronisiert über iCloud, wenn eine Verbindung besteht. Du kannst Dokumente erstellen, bearbeiten und organisieren, ohne Internet. Änderungen synchronisieren sich automatisch, wenn du dich wieder verbindest.

Was passiert, wenn ich eine Craft-Seite teile?

Craft generiert eine responsive Webseite unter einer craft.do-URL. Empfänger können sie in jedem Browser ansehen, ohne ein Konto zu erstellen oder die App zu installieren. Die Seite bewahrt die Typografie, Bilder und verschachtelte Seitenstruktur deines Dokuments.

Unterstützt Craft Markdown?

Craft verwendet sein eigenes Block-Format, exportiert aber nach Markdown. Du kannst Inhalte als Markdown kopieren oder ganze Dokumente exportieren. Einige Markdown-Shortcuts funktionieren beim Bearbeiten (wie # für Überschriften), aber Craft betont visuelles Bearbeiten gegenüber Plain-Text-Markup.

Wie funktionieren verschachtelte Seiten in Craft?

Jeder Textblock kann zur Seite werden, indem man das Seiten-Icon drückt oder /page tippt. Verschachtelte Seiten erscheinen inline im übergeordneten Dokument und können im Vollbildmodus geöffnet werden. Die Breadcrumb-Navigation zeigt deine Position in der Hierarchie. Das schafft natürliche Organisation, ohne von Anfang an Ordnerstrukturen zu erzwingen.