Things 3: A Arte da Simplicidade Focada

Como o Things 3 alcançou simplicidade através de restrições: hierarquia natural, separação entre Quando vs Prazo, design orientado ao teclado e uso restrito e significativo de cores. Com padrões de implementação em SwiftUI.

4 min de leitura 1043 palavras
Things 3: A Arte da Simplicidade Focada screenshot

Things 3: A Arte da Simplicidade Focada

“Acreditamos que o simples é difícil.” — Cultured Code

Things 3 é frequentemente chamado de o gerenciador de tarefas mais bonito já criado. Essa beleza emerge do foco implacável no que importa e da eliminação de todo o resto.


Por Que o Things 3 Importa

Things 3 demonstra que restrições criam clareza. Enquanto concorrentes acumulam recursos (tarefas recorrentes com 47 opções, projetos com dependências e gráficos de Gantt), Things pergunta: o que uma pessoa realmente precisa para fazer as coisas?

Principais conquistas: - Alcançou simplicidade visual sem sacrificar profundidade - Tornou datas significativas (Hoje, Noite, Algum Dia) - Criou uma hierarquia que espelha como humanos pensam sobre trabalho - Provou que priorizar teclado pode ser bonito - Estabeleceu o padrão para design de apps nativos macOS/iOS


Principais Lições

  1. Separe “quando vou fazer isso?” de “quando vence?” - O modelo de datas duplas do Things (Quando + Prazo) elimina a ambiguidade que assola a maioria dos apps de tarefas; intenção de agenda e prazos rígidos servem propósitos diferentes
  2. Algum Dia é um recurso, não um estado de falha - Dar às ideias um lar sem pressão previne ansiedade de caixa de entrada; tarefas em Algum Dia não poluem o Hoje nem provocam culpa
  3. Áreas nunca completam, Projetos sim - Essa distinção espelha a cognição humana: “Trabalho” é um domínio de vida contínuo, “Lançar v2.0” tem uma linha de chegada; misturar isso cria confusão
  4. Reserve cor para significado - A interface do Things é 95% neutra (preto, branco, cinza); cor aparece apenas para informação semântica (amarelo=Hoje, vermelho=Prazo, índigo=Noite)
  5. Conclusão deve ser recompensadora - A animação do checkbox (preencher → marca de verificação → som) leva 500ms mas entrega dopamina; completar tarefas se torna intrinsecamente motivador

Princípios Fundamentais de Design

1. A Hierarquia Natural

Things organiza o trabalho da forma como humanos pensam sobre ele: do amplo (áreas da vida) ao específico (tarefas individuais).

HIERARQUIA DO THINGS:

Área (Trabalho, Pessoal, Saúde)   ← Categorias amplas de vida
  │
  └── Projeto (Lançar App)        ← Meta finita com tarefas
        │
        └── Título (Design)       ← Agrupamento opcional
              │
              └── Tarefa          ← Item único acionável
                    │
                    └── Checklist ← Sub-etapas dentro da tarefa

Exemplo:
┌─────────────────────────────────────────────────────────────┐
│ [A] TRABALHO (Área)                                         │
│   ├── [P] Lançar Versão 2.0 (Projeto)                       │
│   │     ├── [H] Design                                      │
│   │     │     ├── [ ] Criar novos ícones                    │
│   │     │     └── [ ] Atualizar paleta de cores             │
│   │     └── [H] Desenvolvimento                             │
│   │           ├── [ ] Implementar fluxo de auth             │
│   │           └── [ ] Adicionar analytics                   │
│   └── [P] Redesign do Site (Projeto)                        │
│         └── [ ] Escrever copy para landing page             │
└─────────────────────────────────────────────────────────────┘

O Insight Principal: Áreas nunca completam (são categorias de vida). Projetos completam (entregar até sexta). A distinção elimina paralisia de planejamento.

Modelo de Dados:

// Hierarquia do Things expressa em código
struct Area: Identifiable {
    let id: UUID
    var title: String
    var projects: [Project]
    var tasks: [Task]  // Tarefas soltas não em projetos
}

struct Project: Identifiable {
    let id: UUID
    var title: String
    var notes: String?
    var deadline: Date?
    var headings: [Heading]
    var tasks: [Task]
    var isComplete: Bool
}

struct Heading: Identifiable {
    let id: UUID
    var title: String
    var tasks: [Task]
}

struct Task: Identifiable {
    let id: UUID
    var title: String
    var notes: String?
    var when: TaskSchedule?
    var deadline: Date?
    var tags: [Tag]
    var checklist: [ChecklistItem]
    var isComplete: Bool
}

enum TaskSchedule {
    case today
    case evening
    case specificDate(Date)
    case someday
}

2. Quando, Não Apenas Vencimento

Things separa “quando vou fazer isso?” (Hoje, Noite, Algum Dia) de “quando isso precisa estar pronto?” (Prazo). A maioria dos apps confunde essas coisas.

MODELO DE DATA TRADICIONAL:
┌──────────────────────────────────────────────────────────────┐
│ Tarefa: Escrever relatório                                   │
│ Vencimento: 15 de março                                      │
│                                                              │
│ Problema: 15 de março é quando vou fazer ou quando vence?    │
│          Se é vencimento, quando devo começar?               │
└──────────────────────────────────────────────────────────────┘

MODELO DE DATA DO THINGS:
┌──────────────────────────────────────────────────────────────┐
│ Tarefa: Escrever relatório                                   │
│ Quando: Hoje (vou trabalhar nisso hoje)                      │
│ Prazo: 15 de março (precisa estar completo até lá)           │
│                                                              │
│ Clareza: Duas perguntas separadas, duas respostas separadas  │
└──────────────────────────────────────────────────────────────┘

LISTAS INTELIGENTES (filtragem automática):

┌───────────────────┬─────────────────────────────────────────┐
│ [I] CAIXA ENTRADA │ Capturas não categorizadas              │
├───────────────────┼─────────────────────────────────────────┤
│ [*] HOJE          │ when == .today OR deadline == today     │
├───────────────────┼─────────────────────────────────────────┤
│ [E] ESTA NOITE    │ when == .evening                        │
├───────────────────┼─────────────────────────────────────────┤
│ [D] EM BREVE      │ when == .specificDate (futuro)          │
├───────────────────┼─────────────────────────────────────────┤
│ [A] A QUALQUER HR │ when == nil AND deadline == nil         │
├───────────────────┼─────────────────────────────────────────┤
│ [S] ALGUM DIA     │ when == .someday                        │
├───────────────────┼─────────────────────────────────────────┤
│ [L] REGISTRO      │ isComplete == true                      │
└───────────────────┴─────────────────────────────────────────┘

O Insight Principal: “Algum Dia” não é um estado de falha—é uma válvula de escape de pressão. Ideias têm um lugar sem poluir o Hoje.

Implementação SwiftUI:

struct WhenPicker: View {
    @Binding var schedule: TaskSchedule?
    @Binding var deadline: Date?
    @State private var showDatePicker = false

    var body: some View {
        VStack(spacing: 12) {
            // Opções rápidas
            HStack(spacing: 8) {
                WhenButton(
                    icon: "star.fill",
                    label: "Hoje",
                    color: .yellow,
                    isSelected: schedule == .today
                ) {
                    schedule = .today
                }

                WhenButton(
                    icon: "moon.fill",
                    label: "Noite",
                    color: .indigo,
                    isSelected: schedule == .evening
                ) {
                    schedule = .evening
                }

                WhenButton(
                    icon: "calendar",
                    label: "Escolher Data",
                    color: .red,
                    isSelected: showDatePicker
                ) {
                    showDatePicker = true
                }

                WhenButton(
                    icon: "archivebox.fill",
                    label: "Algum Dia",
                    color: .brown,
                    isSelected: schedule == .someday
                ) {
                    schedule = .someday
                }
            }

            // Prazo (separado de "quando")
            if let deadline = deadline {
                HStack {
                    Image(systemName: "flag.fill")
                        .foregroundStyle(.red)
                    Text("Prazo: \(deadline, style: .date)")
                    Spacer()
                    Button("Limpar") { self.deadline = nil }
                }
                .font(.caption)
            }
        }
    }
}

struct WhenButton: View {
    let icon: String
    let label: String
    let color: Color
    let isSelected: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            VStack(spacing: 4) {
                Image(systemName: icon)
                    .font(.title2)
                Text(label)
                    .font(.caption2)
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 8)
            .background(isSelected ? color.opacity(0.2) : .clear)
            .cornerRadius(8)
        }
        .buttonStyle(.plain)
        .foregroundStyle(isSelected ? color : .secondary)
    }
}

3. Teclado Primeiro, Amigável ao Mouse

Things pode ser operado inteiramente via teclado com eficiência de memória muscular, mas permanece intuitivo para usuários de mouse.

ATALHOS DE TECLADO (padrões memorizáveis):

NAVEGAÇÃO:
  Cmd+1-6      Ir para listas inteligentes (Caixa de Entrada, Hoje, etc.)
  Cmd+Up/Down  Mover entre seções
  Space        Quick Look (visualizar detalhes da tarefa)

MANIPULAÇÃO DE TAREFAS:
  Cmd+K        Completar tarefa
  Cmd+S        Agendar (seletor de Quando)
  Cmd+Shift+D  Definir prazo
  Cmd+Shift+M  Mover para projeto
  Cmd+Shift+T  Adicionar tags

CRIAÇÃO:
  Space/Return Criar nova tarefa (sensível ao contexto)
  Cmd+N        Nova tarefa na Caixa de Entrada
  Cmd+Opt+N    Novo projeto

Entrada Rápida (tecla de atalho global):
  Ctrl+Space   Abre entrada rápida flutuante de qualquer lugar
  ┌─────────────────────────────────────────────────┐
  │ [ ] |                                           │
  │   Nova tarefa aparece onde você estiver         │
  └─────────────────────────────────────────────────┘

O Insight Principal: Atalhos devem ser descobríveis mas não obrigatórios. A interface sugere teclas sem exigi-las.

Padrão CSS (Dicas de Atalho):

.action-button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
}

.shortcut-hint {
  font-size: 11px;
  font-family: var(--font-mono);
  padding: 2px 6px;
  background: var(--surface-subtle);
  border-radius: 4px;
  color: var(--text-tertiary);
  opacity: 0;
  transition: opacity 0.15s ease;
}

/* Mostrar ao passar o mouse */
.action-button:hover .shortcut-hint {
  opacity: 1;
}

/* Sempre visível quando focado via teclado */
.action-button:focus-visible .shortcut-hint {
  opacity: 1;
}

Entrada Rápida SwiftUI:

struct QuickEntryPanel: View {
    @Binding var isPresented: Bool
    @State private var taskTitle = ""
    @FocusState private var isFocused: Bool

    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 12) {
                // Checkbox (apenas visual)
                Circle()
                    .strokeBorder(.tertiary, lineWidth: 1.5)
                    .frame(width: 20, height: 20)

                // Entrada de tarefa
                TextField("Nova Tarefa", text: $taskTitle)
                    .textFieldStyle(.plain)
                    .font(.body)
                    .focused($isFocused)
                    .onSubmit {
                        createTask()
                    }
            }
            .padding()
            .background(.ultraThinMaterial)
            .cornerRadius(12)
            .shadow(color: .black.opacity(0.2), radius: 20, y: 10)
        }
        .frame(width: 500)
        .onAppear {
            isFocused = true
        }
        .onExitCommand {
            isPresented = false
        }
    }

    private func createTask() {
        guard !taskTitle.isEmpty else { return }
        // Criar tarefa na Caixa de Entrada
        TaskStore.shared.createTask(title: taskTitle)
        taskTitle = ""
    }
}

4. Contenção Visual

Things usa cor com moderação, reservando-a apenas para significado. A maior parte da interface é neutra, permitindo que elementos coloridos se destaquem.

USO DE COR NO THINGS:

Neutro (95% da interface):
├── Fundo: Branco puro / Cinza escuro
├── Texto: Preto / Branco
├── Bordas: Cinza muito sutil
└── Ícones: Monocromático

Cor com Significado (5%):
├── Amarelo [*] = Hoje
├── Vermelho [!] = Prazo / Atrasado
├── Azul [#] = Tags (definidas pelo usuário)
├── Verde [x] = Animação de conclusão
└── Índigo [E] = Noite

EXEMPLO VISUAL:

┌─────────────────────────────────────────────────────────────┐
│ HOJE                                         [*] (amarelo)  │
├─────────────────────────────────────────────────────────────┤
│ ( ) Escrever documentação                                   │
│     Nome do Projeto - [!] Vence amanhã (vermelho)           │
│                                                             │
│ ( ) Revisar pull requests                                   │
│     [#] trabalho (tag azul)                                 │
│                                                             │
│ ( ) Ligar para dentista                                     │
│     [E] Esta Noite (badge índigo)                           │
└─────────────────────────────────────────────────────────────┘

Observe: A maior parte do texto é preta. Cor só aparece onde carrega significado.

O Insight Principal: Quando tudo é colorido, nada se destaca. Reserve cor para informação.

Padrão CSS:

:root {
  /* Paleta neutra (maior parte da UI) */
  --surface-primary: #ffffff;
  --surface-secondary: #f5f5f7;
  --text-primary: #1d1d1f;
  --text-secondary: #86868b;
  --border: rgba(0, 0, 0, 0.08);

  /* Cores semânticas (usadas com moderação) */
  --color-today: #f5c518;
  --color-deadline: #ff3b30;
  --color-evening: #5856d6;
  --color-complete: #34c759;
  --color-tag: #007aff;
}

/* Linha de tarefa - majoritariamente neutra */
.task-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-primary);
  color: var(--text-primary);
}

/* Cor apenas para significado */
.deadline-badge {
  font-size: 12px;
  color: var(--color-deadline);
}

.deadline-badge.overdue {
  font-weight: 600;
  color: var(--color-deadline);
}

.tag {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--color-tag);
  color: white;
}

.evening-badge {
  color: var(--color-evening);
}

5. Conclusão Prazerosa

A animação de conclusão entrega recompensa, não mero feedback. Um som satisfatório de “plink” e uma animação suave de marca de verificação fazem completar tarefas parecer bom.

SEQUÊNCIA DE CONCLUSÃO:

Frame 1:     ○ Título da tarefa
Frame 2-4:   ◔ (círculo preenche no sentido horário)
Frame 5-7:   ● (totalmente preenchido, breve pausa)
Frame 8-10:  ✓ (marca aparece, escala ligeiramente para cima)
Frame 11+:   Linha desliza para fora, lista fecha o espaço

Áudio: Som suave de "plink" no Frame 7-8 (momento de conclusão)
Háptico: Toque leve no iOS na conclusão

Implementação SwiftUI:

struct CompletableCheckbox: View {
    @Binding var isComplete: Bool
    @State private var animationPhase: AnimationPhase = .unchecked

    enum AnimationPhase {
        case unchecked, filling, checked, done
    }

    var body: some View {
        Button {
            completeWithAnimation()
        } label: {
            ZStack {
                // Círculo de fundo
                Circle()
                    .strokeBorder(.tertiary, lineWidth: 1.5)
                    .frame(width: 22, height: 22)

                // Animação de preenchimento
                Circle()
                    .trim(from: 0, to: fillAmount)
                    .stroke(.green, lineWidth: 1.5)
                    .frame(width: 22, height: 22)
                    .rotationEffect(.degrees(-90))

                // Marca de verificação
                Image(systemName: "checkmark")
                    .font(.system(size: 12, weight: .bold))
                    .foregroundStyle(.green)
                    .scaleEffect(checkScale)
                    .opacity(checkOpacity)
            }
        }
        .buttonStyle(.plain)
        .sensoryFeedback(.success, trigger: animationPhase == .checked)
    }

    private var fillAmount: CGFloat {
        switch animationPhase {
        case .unchecked: return 0
        case .filling: return 1
        case .checked, .done: return 1
        }
    }

    private var checkScale: CGFloat {
        animationPhase == .checked ? 1.2 : (animationPhase == .done ? 1.0 : 0)
    }

    private var checkOpacity: CGFloat {
        animationPhase == .unchecked || animationPhase == .filling ? 0 : 1
    }

    private func completeWithAnimation() {
        // Fase 1: Preencher o círculo
        withAnimation(.easeInOut(duration: 0.2)) {
            animationPhase = .filling
        }

        // Fase 2: Mostrar marca de verificação com bounce
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            withAnimation(.spring(response: 0.3, dampingFraction: 0.5)) {
                animationPhase = .checked
            }
        }

        // Fase 3: Estabilizar e marcar como completo
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            withAnimation(.easeOut(duration: 0.15)) {
                animationPhase = .done
            }
            isComplete = true
        }
    }
}

Padrões Transferíveis

Padrão 1: Detalhes Progressivos de Tarefa

Detalhes da tarefa expandem inline em vez de navegar para uma view separada.

struct ExpandableTask: View {
    let task: Task
    @State private var isExpanded = false

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            // Linha sempre visível
            TaskRow(task: task, isExpanded: $isExpanded)

            // Detalhes expansíveis
            if isExpanded {
                TaskDetails(task: task)
                    .padding(.leading, 44) // Alinhar com título
                    .transition(.asymmetric(
                        insertion: .push(from: .top).combined(with: .opacity),
                        removal: .push(from: .bottom).combined(with: .opacity)
                    ))
            }
        }
        .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isExpanded)
    }
}

Padrão 2: Entrada em Linguagem Natural

Things interpreta linguagem natural para definir datas: “amanhã”, “semana que vem”, “15 de junho”.

function parseNaturalDate(input: string): TaskSchedule | null {
  const lower = input.toLowerCase();

  // Atalhos para Hoje
  if (['hoje', 'hj', 'agora'].includes(lower)) {
    return { type: 'today' };
  }

  // Noite
  if (['hoje à noite', 'noite', 'de noite'].includes(lower)) {
    return { type: 'evening' };
  }

  // Amanhã
  if (['amanhã', 'amanha', 'amh'].includes(lower)) {
    const date = new Date();
    date.setDate(date.getDate() + 1);
    return { type: 'date', date };
  }

  // Algum Dia
  if (['algum dia', 'depois', 'eventualmente'].includes(lower)) {
    return { type: 'someday' };
  }

  // Dias relativos
  const inMatch = lower.match(/em (\d+) dias?/);
  if (inMatch) {
    const date = new Date();
    date.setDate(date.getDate() + parseInt(inMatch[1]));
    return { type: 'date', date };
  }

  // Nomes de dias
  const days = ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'];
  const dayIndex = days.findIndex(d => lower.startsWith(d.slice(0, 3)));
  if (dayIndex !== -1) {
    const date = getNextDayOfWeek(dayIndex);
    return { type: 'date', date };
  }

  return null;
}

Padrão 3: Arrastar e Soltar Baseado em Lista

Reordenação é manipulação direta com feedback visual claro.

.task-row {
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.task-row.dragging {
  transform: scale(1.02);
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.15),
    0 0 0 2px var(--color-focus);
  z-index: 100;
}

.task-row.drop-above::before {
  content: '';
  position: absolute;
  top: -2px;
  left: 44px;
  right: 16px;
  height: 4px;
  background: var(--color-focus);
  border-radius: 2px;
}

.task-row.drop-below::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 44px;
  right: 16px;
  height: 4px;
  background: var(--color-focus);
  border-radius: 2px;
}

Lições de Design

  1. Separe preocupações: “Quando vou fazer isso?” vs “Quando vence?” são perguntas diferentes
  2. Contenção com cor: Reserve cor para significado; neutro é o padrão
  3. Conclusão é recompensa: Faça terminar tarefas parecer bom
  4. Teclado acelera, mouse acolhe: Projete para ambos sem comprometer nenhum
  5. Hierarquias naturais: Espelhe como humanos já pensam sobre trabalho (áreas → projetos → tarefas)
  6. Algum Dia é um recurso: Dê às ideias um lar que não crie pressão

Perguntas Frequentes

Qual é a diferença entre “Quando” e “Prazo” no Things 3?

“Quando” responde “quando vou trabalhar nisso?” (Hoje, Esta Noite, Algum Dia, ou uma data específica). “Prazo” responde “quando isso precisa estar completo?” A maioria dos apps de tarefas confunde esses conceitos, mas eles servem propósitos diferentes. Uma tarefa pode vencer na sexta (Prazo), mas você planeja trabalhar nela na quarta (Quando). Essa separação permite planejar seu dia em torno de intenções enquanto ainda rastreia prazos rígidos.

Como funciona a hierarquia do Things 3 (Áreas, Projetos, Tarefas)?

Áreas são categorias amplas de vida que nunca completam (Trabalho, Pessoal, Saúde). Projetos são metas finitas com um estado final claro (Lançar App, Planejar Férias). Tarefas são itens únicos acionáveis. Títulos opcionalmente agrupam tarefas dentro de projetos. Isso espelha como humanos naturalmente pensam sobre trabalho: responsabilidades contínuas contêm metas com prazo determinado, que contêm ações discretas.

Por que o Things 3 usa tão pouca cor?

Things reserva cor exclusivamente para significado semântico. A interface é 95% neutra (preto, branco, cinza) para que quando cor apareça, ela comunique: amarelo significa Hoje, vermelho significa Prazo/Atrasado, índigo significa Noite, azul marca tags. Se tudo fosse colorido, nada se destacaria. A contenção torna as cores significativas impossíveis de ignorar.

O que é “Algum Dia” no Things 3 e por que é valioso?

Algum Dia é um espaço dedicado para tarefas que você quer lembrar mas não quer poluindo suas listas ativas. Não é um estado de falha—é uma válvula de escape de pressão. Ideias para “algum dia” (aprender espanhol, ler aquele livro) têm um lar sem criar culpa diária. Você pode revisar Algum Dia semanalmente e promover tarefas para Hoje quando estiver pronto.

Como funciona a animação de conclusão do Things 3?

Quando você toca no checkbox, o círculo preenche no sentido horário (200ms), então uma marca de verificação aparece com um leve bounce (animação de mola), acompanhada de um som suave de “plink” e feedback háptico no iOS. A linha então desliza para fora (150ms). A sequência total leva cerca de 500ms, mas esse timing deliberado cria uma pequena dose de dopamina que faz completar tarefas parecer recompensador.


Recursos

  • Website: culturedcode.com/things
  • Filosofia de design: Posts do blog da Cultured Code sobre simplicidade
  • Atalhos de teclado: Menu de Ajuda integrado (Cmd+/)
  • Integração GTD: Como Things mapeia para a metodologia Getting Things Done