Craft: Excelencia en Documentos Nativos

Por que Craft gano el Apple Design Award 2021: rendimiento nativo, paginas anidadas, arquitectura de bloques y publicacion con un clic. Codigo Swift incluido.

5 min de lectura 957 palabras
Craft: Excelencia en Documentos Nativos screenshot

“Creemos que las herramientas para pensar deben sentirse como una extensión de tus pensamientos, no como un obstáculo.”

Craft demuestra que las apps nativas pueden ofrecer experiencias que las aplicaciones web no pueden igualar. Construida con una profunda integración de plataforma, logra la capacidad de respuesta y el nivel de pulido que hace que la toma de notas digital se sienta tan natural como el papel.


Puntos Clave

  1. Lo nativo supera a Electron - Un tiempo de respuesta inferior a 50ms requiere UI específica de plataforma, no wrappers web
  2. Páginas dentro de páginas - Cualquier bloque puede convertirse en una página, permitiendo que la estructura emerja del contenido
  3. Múltiples layouts, mismos datos - Listas, tarjetas, galerías y tableros ofrecen al usuario la vista adecuada para cada contexto
  4. Publicación web con un clic - Las páginas compartidas funcionan sin que el destinatario necesite una cuenta o descargar la app
  5. Apropiado para la plataforma, no idéntico - Craft en Mac se siente como Mac; Craft en iOS se siente como iOS

Por Qué Craft Importa

Craft ganó el Apple Design Award 2021 y obtiene reconocimiento constante en la App Store al negarse a comprometer el rendimiento nativo en favor de la conveniencia multiplataforma.

Logros clave: - Apps verdaderamente nativas en iOS, macOS, Windows (no Electron) - Tiempo de respuesta inferior a 50ms en todas las interacciones - Offline-first con sincronización transparente - Arquitectura basada en bloques sin el lag típico de las apps web - Páginas compartidas elegantes que funcionan sin necesidad de cuenta


Filosofía de Diseño Central

Nativo Primero, No Envuelto en Web

La decisión definitoria de Craft: construir apps nativas para cada plataforma, usando lógica de negocio compartida pero UI específica de plataforma.

ENFOQUE ELECTRON/WEB                ENFOQUE NATIVO DE CRAFT
───────────────────────────────────────────────────────────────────
Código único (JavaScript)          UI específica de plataforma (Swift, etc.)
Motor de renderizado web           Renderizado nativo
"Consistencia" multiplataforma     Comportamiento apropiado a la plataforma
~200ms de latencia de entrada      ~16ms de latencia de entrada
Atajos de teclado genéricos        Atajos nativos de plataforma
Selección de texto estándar web    Motor de texto nativo

Idea clave: Los usuarios no quieren apps que se vean igual en todas partes—quieren apps que se sientan correctas en su plataforma.

macOS vía Catalyst (Bien Hecho)

Craft usa Mac Catalyst pero lo personaliza profundamente para que se sienta verdaderamente nativo de Mac:

// 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

Biblioteca de Patrones

1. Arquitectura de Contenido Basada en Bloques

Cada elemento en Craft es un bloque. Cada bloque con contenido es potencialmente una página, creando anidamiento infinito sin sobrecarga cognitiva.

Tipos de bloques:

BLOQUES DE TEXTO
├── Párrafo (predeterminado)
├── Heading 1, 2, 3
├── Cita
├── Callout (info, warning, success)
└── Código (con resaltado de sintaxis)

BLOQUES MULTIMEDIA
├── Imagen (con pie de foto)
├── Video
├── Archivo adjunto
├── Dibujo (Apple Pencil)
└── Grabación de audio

BLOQUES ESTRUCTURALES
├── Toggle (colapsable)
├── Página (documento anidado)
├── Separador
└── Tabla

Concepto de implementación:

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 }
}

Idea clave: Cuando cada bloque puede ser una página, la arquitectura de información emerge del contenido, no de una estructura predeterminada.


2. Páginas Anidadas (Páginas Dentro de Páginas)

La característica distintiva de Craft: cualquier bloque puede convertirse en una página, y las páginas se anidan infinitamente.

ESTRUCTURA DEL DOCUMENTO
───────────────────────────────────────────────────────────────────
📄 Proyecto Alpha
├── 📝 Párrafo de introducción
├── 📄 Notas de Investigación   ← Esto es una página (documento anidado)
│   ├── 📝 Entrevistas con usuarios
│   ├── 📄 Entrevista: Sarah     ← Otra página anidada
│   │   └── 📝 Ideas clave
│   └── 📝 Análisis competitivo
├── 📝 Resumen de cronograma
└── 📄 Notas de Reunión          ← Otra página
    └── 📝 Elementos de acción

Navegación: La ruta de migas de pan muestra el camino
Proyecto Alpha > Notas de Investigación > Entrevista: Sarah

Patrón de implementación en SwiftUI:

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. Sistema de diseño de tarjetas

Craft ofrece 5 estilos visuales para páginas, haciendo que los documentos sean fáciles de escanear de un vistazo.

Estilos de tarjetas:

ESTILO 1: LISTA (Por defecto)
┌────────────────────────────────────────┐
│ 📄 Título de página                    │
│    El texto de vista previa aparece... │
└────────────────────────────────────────┘

ESTILO 2: TARJETA (Mediana)
┌──────────────────┐
│ ┌──────────────┐ │
│ │   [Imagen]   │ │
│ └──────────────┘ │
│ Título de página │
│ Vista previa...  │
└──────────────────┘

ESTILO 3: TARJETA (Grande)
┌────────────────────────────────────────┐
│ ┌────────────────────────────────────┐ │
│ │                                    │ │
│ │         [Imagen de portada]        │ │
│ │                                    │ │
│ └────────────────────────────────────┘ │
│ Título de página                       │
│ Vista previa más larga con más...      │
└────────────────────────────────────────┘

ESTILO 4: GALERÍA (Cuadrícula)
┌────────┐ ┌────────┐ ┌────────┐
│ [Img]  │ │ [Img]  │ │ [Img]  │
│ Título │ │ Título │ │ Título │
└────────┘ └────────┘ └────────┘

ESTILO 5: TABLERO (Estilo Kanban)
│ Por hacer│ En curso │ Hecho    │
├──────────┼──────────┼──────────┤
│ Tarea 1  │ Tarea 3  │ Tarea 5  │
│ Tarea 2  │ Tarea 4  │          │

Implementación:

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. Fondos de página y temas

Cada página puede tener su propia identidad visual a través de fondos y colores de acento.

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

    // Texturas de papel para sensación de escritura
    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?
}

// Aplicado a la página
struct PageContainer: View {
    let page: PageDocument

    var body: some View {
        ZStack {
            // Capa de fondo
            backgroundView(for: page.appearance.background)

            // Capa de contenido
            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. Páginas compartidas (publicación web)

Craft genera páginas web atractivas y responsivas a partir de documentos. No se necesita cuenta para visualizarlas.

DOCUMENTO EN CRAFT                PÁGINA COMPARTIDA EN LA WEB
───────────────────────────────────────────────────────────────────
📄 Propuesta de proyecto           https://www.craft.do/s/abc123
├── 📝 Resumen ejecutivo     →
├── 📄 Detalles del presupuesto   Diseño limpio y responsivo
├── 📝 Cronograma                 Tipografía preservada
└── 📄 Biografías del equipo      Imágenes optimizadas
                                  Modo oscuro compatible
                                  No se necesita cuenta de Craft

Características principales: - Publicación con un solo clic - Dominios personalizados disponibles - Opción de protección con contraseña - Analíticas de visualizaciones - Renderizado optimizado para SEO - Responsivo en todos los dispositivos


Sistema de diseño visual

Paleta de colores

extension Color {
    // Paleta distintiva de Craft
    static let craftPurple = Color(hex: "#6366F1")  // Acento primario
    static let craftBackground = Color(hex: "#FAFAFA")  // Modo claro
    static let craftSurface = Color(hex: "#FFFFFF")

    // Colores semánticos
    static let craftSuccess = Color(hex: "#10B981")
    static let craftWarning = Color(hex: "#F59E0B")
    static let craftError = Color(hex: "#EF4444")
    static let craftInfo = Color(hex: "#3B82F6")

    // Jerarquía de texto
    static let craftTextPrimary = Color(hex: "#111827")
    static let craftTextSecondary = Color(hex: "#6B7280")
    static let craftTextMuted = Color(hex: "#9CA3AF")
}

Tipografía

struct CraftTypography {
    // Tipografía del documento
    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)

    // Bloques de código
    static let code = Font.system(size: 14, weight: .regular, design: .monospaced)

    // Alturas de línea (generosas para legibilidad)
    static let bodyLineSpacing: CGFloat = 8
    static let headingLineSpacing: CGFloat = 4
}

Sistema de espaciado

struct CraftSpacing {
    // Espaciado entre bloques
    static let blockGap: CGFloat = 4        // Entre bloques
    static let sectionGap: CGFloat = 24     // Entre secciones
    static let pageMargin: CGFloat = 40     // Bordes de página (escritorio)
    static let mobileMargin: CGFloat = 16   // Bordes de página (móvil)

    // Ancho de contenido
    static let maxContentWidth: CGFloat = 720  // Lectura óptima
    static let wideContentWidth: CGFloat = 960 // Tablas, galerías
}

Filosofía de animación

Transiciones de página fluidas

// La navegación entre páginas usa geometría coincidente
struct NavigationTransition: View {
    @Namespace private var namespace
    @State private var selectedPage: PageReference?

    var body: some View {
        ZStack {
            // Lista de páginas
            PageListView(
                onSelect: { page in
                    withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
                        selectedPage = page
                    }
                },
                namespace: namespace
            )

            // La página seleccionada se expande desde la posición de su tarjeta
            if let page = selectedPage {
                PageDetailView(page: page)
                    .matchedGeometryEffect(id: page.id, in: namespace)
                    .transition(.scale.combined(with: .opacity))
            }
        }
    }
}

Animaciones de bloques

// Los bloques se animan al reordenarse
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)
    }
}

Lecciones para nuestro trabajo

1. El rendimiento nativo vale la inversión

Los usuarios perciben la diferencia entre 16ms y 200ms de latencia. Las apps nativas ganan en sensación de uso.

2. Las páginas dentro de páginas permiten una organización orgánica

Deja que la estructura emerja del contenido. No obligues a los usuarios a decidir la jerarquía desde el principio.

3. Múltiples diseños visuales para el mismo contenido

Tarjetas, listas, galerías—los mismos datos, diferentes vistas para diferentes contextos.

4. Compartir sin fricción

La publicación web con un solo clic elimina por completo el problema de “¿cómo comparto esto?”.

5. Apropiado para la plataforma, no idéntico entre plataformas

Craft en Mac se siente como una app de Mac. Craft en iOS se siente como una app de iOS. Ese es el objetivo.


Preguntas frecuentes

¿En qué se diferencia Craft de Notion?

Craft es nativo (desarrollado específicamente para plataformas Apple), mientras que Notion está basado en web. Esto significa que Craft logra tiempos de respuesta inferiores a 50ms, funciona completamente sin conexión y se integra profundamente con las funcionalidades de iOS/macOS como Apple Pencil, Shortcuts y la búsqueda del sistema. Notion ofrece más funciones de base de datos; Craft prioriza la experiencia de escritura.

¿Puedo usar Craft sin conexión?

Sí. Craft almacena todos los documentos localmente y se sincroniza a través de iCloud cuando hay conexión. Puedes crear, editar y organizar documentos sin internet. Los cambios se sincronizan automáticamente al reconectarte.

¿Qué sucede cuando comparto una página de Craft?

Craft genera una página web responsiva en una URL de craft.do. Los destinatarios la visualizan en cualquier navegador sin necesidad de crear una cuenta ni instalar la app. La página conserva la tipografía, las imágenes y la estructura de páginas anidadas de tu documento.

¿Craft soporta Markdown?

Craft utiliza su propio formato de bloques pero exporta a Markdown. Puedes copiar contenido como Markdown o exportar documentos completos. Algunos atajos de Markdown funcionan durante la edición (como # para encabezados), pero Craft enfatiza la edición visual sobre el marcado en texto plano.

¿Cómo funcionan las páginas anidadas en Craft?

Cualquier bloque de texto puede convertirse en una página presionando el ícono de página o escribiendo /page. Las páginas anidadas aparecen en línea dentro del documento principal y pueden abrirse en pantalla completa. La navegación por breadcrumbs muestra tu ubicación en la jerarquía. Esto crea una organización natural sin imponer estructuras de carpetas desde el inicio.


Enlaces de referencia