- Linear é uma ferramenta de produtividade que lida com o gerenciamento de issues usando um banco de dados no navegador e sincronização local-first, fazendo com que atualizações de issues sejam refletidas na UI em poucos milissegundos
- O banco de dados real que a UI lê fica no IndexedDB, e as mudanças são aplicadas primeiro localmente, depois enviadas de forma assíncrona ao servidor, que redistribui os deltas via WebSocket
- O carregamento inicial usa uma estratégia para reduzir a espera de rede com pouco envio de JavaScript e CSS, divisão agressiva de código, modulepreload, pré-cache por service worker e app shell inline
- O mecanismo de sincronização hidrata os dados do IndexedDB em um pool de objetos MobX, armazena mudanças em uma fila de transações e re-renderiza apenas as células necessárias com estado observável no nível de campo
- A sensação de velocidade vem do resultado de um design de sistema que combina entrada centrada no teclado, paleta global de comandos, animações amigáveis à GPU e tempos curtos de transição
Banco de dados dentro do navegador
- Um webapp CRUD tradicional passa por requisição HTTP após o clique do usuário, consulta ao banco de dados no servidor, recebimento da resposta e repaint do navegador, gerando por centenas de milissegundos spinners, skeletons e UI travada
- O Linear coloca no IndexedDB do navegador o banco de dados real que a UI lê, aplica as mudanças primeiro localmente e depois as envia de forma assíncrona ao servidor, que transmite os deltas para outros clientes via WebSocket
- Em webapps rápidos, o maior gargalo é a rede, e a transferência de dados entre cliente e servidor custa centenas de milissegundos
- O fluxo central do Linear é tornar as requisições de rede invisíveis para o usuário e eliminar estados de carregamento sempre que possível
// Linear
issue.title = "Faster app launch";
issue.save();
issue.title = "Faster app launch" atualiza o armazenamento de dados em memória e, no caso do Linear, usa observables do MobX
issue.save(); coloca na fila uma transação que o mecanismo de sincronização processará em lote para enviar ao servidor
- A UI é re-renderizada de forma síncrona com base nas mudanças na memória local, e a sincronização dos dados acontece em segundo plano, então não há necessidade de spinner
- Tuomas disse em uma conferência de 2024 que o primeiro código que escreveu no Linear foi o mecanismo de sincronização, descrevendo isso como uma abordagem incomum para uma startup
- A maioria dos apps não precisa criar um mecanismo de sincronização próprio como o do Linear, e só com atualizações otimistas de TanStack Query e SWR já é possível chegar bem perto dessa sensação de velocidade
- Requisições otimistas trazem grande melhora ao remover spinners desnecessários, atualizar o estado imediatamente, validar em segundo plano e fazer rollback quando necessário
- A responsividade da UI não deve depender da latência de rede, e a velocidade percebida pelo usuário é determinada mais pela rapidez da interface do que pela velocidade de resposta do servidor
-
Um olhar rápido sobre a stack do Linear
- O Linear é construído sobre uma stack simples com React, TypeScript, MobX, Postgres e CDN
- No frontend, usa React e
react-dom, MobX, TypeScript, Rolldown-Vite e plugin-react-oxc, ProseMirror e y-prosemirror, primitives do Radix UI, Emotion e StyleX, Comlink, idb, graphql-request, Sentry e Inter Variable
- No backend, usa Node.js e TypeScript, PostgreSQL no Cloud SQL, Redis no Memorystore, turbopuffer, Kubernetes no GCP e Cloudflare Workers
- O cliente desktop é baseado em Electron, e o mobile foi totalmente reimplementado separadamente com Swift para iOS e Kotlin
- O site de marketing usa Next.js, styled-components e sprite SVG inline
- O Linear mantém renderização no lado do cliente e mostra que, com a arquitetura e o design corretos, CSR também pode parecer instantâneo
- Manter o app inteiro no lado do cliente garante um modelo mental mais simples, reduzindo complexidades como a separação entre servidor e cliente, a possibilidade de acessar
window e a configuração de cabeçalhos de cache
Fazer o primeiro carregamento parecer imediato
- O tempo até começar de fato a trabalhar em uma ferramenta de produtividade é um detalhe importante.
- O carregamento inicial de um app client-side pode ficar lento na sequência de requisição de
index.html, requisições de JavaScript e CSS, processamento de autenticação e requisições de API para exibir o app.
-
Fluxo de bundler do Linear: Parcel, Rollup, Vite, Rolldown
- A sensação de velocidade imediata começa no build time, antes do runtime, e para um carregamento rápido é importante reduzir a quantidade de JavaScript e CSS enviada.
- O Linear reescreveu o pipeline de build na sequência Parcel → Rollup → Vite → Rolldown, e cada transição teve como objetivo reduzir a quantidade de JavaScript e CSS e melhorar a experiência do desenvolvedor.
- Números de melhoria segundo o blog do Linear
- 50% de redução no código enviado
- 30% de redução no tamanho após compressão
- 10~30% de melhoria no page load com cold cache
- 59% de redução no Time-to-first-paint da visualização active-issues no Safari
- 70~80% de redução no uso de memória
- Uma parte significativa das melhorias veio da combinação de decidir focar apenas em navegadores modernos, melhor dead-code elimination e code splitting agressivo.
- Encerrar o suporte legado trouxe grandes benefícios ao eliminar polyfills, transpile para ES5 e fallback
nomodule.
- Mesmo após as otimizações, o Linear ainda envia cerca de 21 MB de JavaScript minificado, mas divide isso agressivamente em centenas de chunks no nível de rota, carregados apenas quando necessário.
- O ponto principal não é a escolha de um bundler específico, mas remover navegadores legados, migrar para ESM nativo e aplicar code splitting agressivo.
- Com o acúmulo dessas etapas, o JavaScript do primeiro carregamento do Linear caiu para aproximadamente a metade, e o tempo de build diminuiu não em um dígito percentual, mas em uma ordem de magnitude.
-
Preload após o carregamento inicial
- Ao dividir o JavaScript em chunks pequenos, surge o problema do carregamento em cascata, em que cada chunk importa outros chunks.
- O Linear configura isso para que, antes da execução do JavaScript, o navegador veja a lista completa e inicie as requisições em paralelo, de modo que, quando o script de entrada chega ao primeiro
import, os chunks relacionados já estejam no cache.
- Ao alinhar o
modulepreload em <head> com o valor de crossorigin do script de entrada, o navegador não trata preload e import como recursos separados e reutiliza o fetch em cache.
- A timeline de cold load muda de uma cascata sequencial para um único lote paralelo, e o trabalho de rede continua existindo, mas é feito de uma vez.
- Esse trabalho é feito em segundo plano quando o usuário chega pela primeira vez à página de login e, alguns segundos depois, o app inteiro fica armazenado em cache para ser servido imediatamente.
-
Service Worker para mais velocidade e recursos offline
- Os chunks no nível de rota de visualizações que o usuário ainda não visitou são armazenados em cache em segundo plano pelo service worker.
- O service worker tem um precache manifest embutido na origem, e cerca de 1.200 assets com hash abrangem chunks de rota, ícones e fontes.
- A estrutura é tal que, poucos segundos após chegar à tela de login, o app inteiro já está em cache.
- Depois disso, a navegação pula completamente a rede, e o service worker responde diretamente do próprio cache sem passar pelo cache HTTP.
- Combinado com o motor de sincronização local-first e com os dados do usuário já armazenados no IndexedDB, o Linear pode ser usado offline.
- Há suporte para ler issues, criar novas issues, editar título e descrição e alterar status.
- Todas as operações entram em fila em um armazenamento local de transações e são descarregadas quando a conexão volta.
modulepreload é o mecanismo que traz em paralelo o que é necessário agora, para que o navegador não fique bloqueado em uma cadeia serial de imports.
- O service worker é o mecanismo que prepara o que será necessário depois.
- As etapas de carregamento rápido do Linear são remover o máximo possível de código, dividir em partes pequenas e fazer precache em segundo plano; o objetivo é tornar as requisições de rede rápidas ou eliminá-las completamente.
-
Composição do bundle vendor
- Cada pacote usado pelo Linear é dividido em um chunk separado e armazenado em cache de forma independente.
- Em um
vendor.js tradicional, atualizar apenas uma dependência já invalida o cache de todo o grafo de dependências.
- A divisão de chunks do Linear cria um cache vendor granular em vez de um único arquivo grande; quando uma dependência específica é atualizada, apenas aquele chunk é invalidado e o restante continua no cache.
-
Carregamento de arquivos de fonte grandes
- Um carregamento incorreto de fonte pode gerar texto temporariamente invisível, layout shift quando a fonte real é trocada e fetch duplicado por incompatibilidade de preload.
- O Linear faz preload da fonte Inter Variable em
<head> e preconnect para static.linear.app.
<link rel="preload"
href="https://static.linear.app/fonts/InterVariable.woff2?v=4.1"
as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preconnect" href="https://static.linear.app" crossorigin>
- A variable font cobre todo o eixo de peso de 100~900 em um único woff2, eliminando requisições por peso.
font-display: swap renderiza imediatamente a fallback stack e substitui por Inter depois que o carregamento termina.
- O
crossorigin="anonymous" na tag de preload é uma configuração essencial para que o navegador reutilize o recurso em cache quando o CSS referenciar a mesma fonte depois.
- Sem
crossorigin, o modo CORS do preload e da referência em CSS difere, fazendo o navegador buscar a fonte novamente.
-
App shell inline
- O Linear coloca inline, dentro de
<head>, CSS suficiente para desenhar o estado de carregamento e assim exibir o app shell sem uma requisição externa de stylesheet.
- O JavaScript inline executa imediatamente as ramificações necessárias para a experiência inicial.
- Detecta o Electron e o user agent do Linear para adicionar a classe
electron.
- Se não houver
localStorage.ApplicationStore, adiciona a classe logged-out.
- Restaura shell tokens como fundo da sidebar, largura da sidebar e dark mode a partir de
localStorage.splashScreenConfig.
- Se o usuário configurou para abrir links no app desktop, ajusta a largura da sidebar para
8px.
- Antes de o primeiro bundle de JavaScript chegar pela rede, a tela de loading já está com tema, tamanho e posição ajustados de acordo com o estado de login.
- A forma mais rápida de fazer o usuário sentir que o app está pronto assim que ele pressiona Enter depois de digitar a URL é enviar o app shell na resposta inicial de
index.html.
-
Renderizar primeiro, autenticar depois
- Um fluxo de autenticação típico segue a ordem fetch do HTML, carregamento do bundle, validação da sessão, fetch do usuário, fetch do workspace e renderização, e o usuário pode levar de 1 a 3 segundos para ver alguma coisa.
- O Linear trata autenticação da mesma forma que trata processamento de mudanças: assume o caminho normal e valida em segundo plano.
- A maioria dos apps CRUD mantém a sessão real em um cookie HttpOnly e adiciona um cookie separado legível por JavaScript ou uma requisição para
/me para que o frontend saiba, durante a inicialização, se o usuário está logado.
- O script inline de boot do Linear não usa um sinal de autenticação paralelo; ele apenas verifica a existência de
localStorage.ApplicationStore.
if (localStorage.getItem("ApplicationStore") === null) {
document.documentElement.classList.add("logged-out");
}
- Se
ApplicationStore existir, isso significa que o usuário já usou o Linear neste navegador e que os dados do workspace já estão no IndexedDB.
- Se o valor não existir, não há dados para renderizar, então o shell muda para o layout logged-out e o fluxo de login continua.
- O token de sessão real fica no cookie, e o bundle não tenta determinar antecipadamente o estado da sessão.
- Se um WebSocket handshake, sync delta ou chamada HTTP receber 401 por sessão expirada, o cliente redireciona para o login.
- O padrão como um todo é confiar nos dados locais para renderizar imediatamente, manter o servidor como fonte da verdade para a correção e reconciliar os dois lados de forma assíncrona.
Motor de sincronização
- A velocidade do Linear começa com a decisão de tratar o servidor não como a source of truth da UI, mas como um alvo de sincronização
- A velocidade não é resultado de um único elemento, mas de três eixos que funcionam em conjunto
-
1. Os dados já estão lá
- Ao iniciar o app, em vez de buscar o workspace do servidor, ele é hidratado do IndexedDB para um pool de objetos MobX em memória
- Todas as queries da UI apontam primeiro para o pool de objetos, e como as issues já estão no dispositivo do usuário, não existe estado de “loading issues”
- À medida que o Linear escalou, ele passou a dividir em chunks os dados do motor de sincronização, com um princípio semelhante ao de bundles JavaScript
- As duas tabelas mais pesadas, Issue e Comment, não são carregadas de uma vez; elas são lazy-hydrated quando necessário
- Essa abordagem é um code splitting no nível dos dados, fazendo com que o custo de inicialização siga a estrutura do workspace, e não o tamanho do workspace
- Um workspace com 10.000 issues inicializa quase tão rápido quanto um workspace com 100 issues
- Ao entrar em um projeto, as issues já estão lá; ao filtrar por assignee, os índices já estão construídos
-
2. As mudanças não esperam a rede
- Quando o estado de uma issue muda, três coisas acontecem quase ao mesmo tempo
- A mudança é refletida na UI por uma atualização do observable do MobX
- A alteração é registrada na fila de transações durável do IndexedDB
- A mudança é adicionada à fila de envio para o servidor
- Nesse momento, a rede ainda não foi usada
- O usuário não espera para ver a própria mudança, e retry, rollback e reload across durability são todos tratados em background
- Se o servidor rejeitar, o observable volta ao estado anterior e ocorre um breve flicker, mas a maioria das alterações inválidas é bloqueada antes da criação da transação
- No fluxo do Linear, a mudança começa localmente, e o servidor é tratado não como uma etapa de autorização, mas de confirmação
-
3. Um delta, uma célula
- Quando o servidor confirma uma mudança do usuário ou de outra pessoa, um pequeno JSON envelope indicando o que mudou retorna ao cliente
- O cliente aplica a mudança escrevendo o valor no observable correspondente do MobX
- Todas as propriedades de modelo no Linear são observables individuais, e todos os componentes que leem aquela propriedade são envolvidos com
observer()
- O MobX consegue saber exatamente de quais campos cada componente depende
- Uma mudança em um campo de uma issue faz re-render apenas dos componentes que leem esse campo, sem re-renderizar a lista pai nem toda a sidebar
- A atualização de 50 issues não significa re-renderizar a lista inteira, mas 50 células
- Mesmo em um workspace movimentado, com 10 pessoas editando ao mesmo tempo, o custo de receber updates cresce de acordo com o que realmente mudou, não com o total de itens na tela
-
Por que os três funcionam juntos
- Se houver apenas banco de dados local sem escritas otimistas, ainda haverá spinner ao salvar
- Se houver apenas escritas otimistas sem observables granulares, haverá travadas a cada update
- Se houver apenas observables granulares sem banco de dados local, ainda haverá espera no carregamento inicial
- A velocidade do Linear não é uma propriedade de uma única camada, mas do sistema como um todo
- O bundler e o loader shell fazem o first paint parecer rápido, e o motor de sincronização mantém essa sensação de rapidez depois que o uso começa
Design para velocidade
- Velocidade é tanto um problema de engenharia quanto de design
- Se o caminho de ação mais rápido exige o mouse, três menus e um clique, o usuário paga esse custo em etapas independentemente da velocidade do motor interno
- Outro eixo da velocidade do Linear é integrar o teclado como principal ferramenta para navegação e execução de tarefas
- Há shortcuts para todas as tarefas comuns, a command palette abre com uma única tecla, e o menu de clique direito foi construído de forma customizada
-
Toda ação tem um shortcut
- Um único caractere edita a issue em foco, combinações de duas letras são usadas para navegação, e modifiers são usados para ações globais
- Desde os estágios iniciais do Linear, os shortcuts foram um elemento fundamental, e o motor de sincronização foi em parte desenhado para permitir que qualquer ação pudesse ser executada a qualquer momento
- Os shortcuts aparecem por toda a UI, e os mais usados são de um único caractere
- Para não excluir iniciantes, todas as ações também podem ser feitas com o mouse
-
A command palette está sempre a uma tecla de distância
⌘ k abre uma command palette na qual é possível buscar quase todas as ações do Linear
- É possível buscar issues, projetos, labels, mudanças de status, navegação, criação de issue, configurações, alternância de tema e mais
- A command palette busca no pool local de objetos MobX, não no servidor, então é muito rápida
- O app inteiro pode ser acessado a partir de um único pane, e navegação, criação de issue e mudança de status podem ser feitas por busca
- A command palette se adapta ao contexto atual de trabalho e também serve para ensinar as ações principais e os shortcuts de cada view
- Um app rápido precisa tanto de excelente engenharia quanto de excelente design: a velocidade de engenharia torna uma interação individual rápida, e a velocidade de design encurta o caminho até essa interação
- Em uma ferramenta usada o dia todo, a diferença entre um shortcut e um caminho de mouse de 2 segundos se acumula em cada ação
Animação
- Animações ruins podem desperdiçar, na etapa final, os milissegundos economizados com carga inicial, atualizações e otimização de consultas ao banco de dados
- Um elemento como uma animação de
height de 500 ms pode minar o esforço para não fazer o usuário esperar
-
Há apenas algumas propriedades que devem ser animadas
- A mudança de propriedades no navegador tem três camadas de custo, dependendo de onde ocorre no pipeline de renderização
- Propriedades compostas como
transform e opacity delegam o trabalho à GPU e rodam de forma independente da main thread
- Propriedades que disparam paint, como
color, background-color, border-color e fill, pulam o layout, mas provocam redesenho de pixels
- Propriedades que disparam layout, como
width, height, top, left, margin e padding, fazem com que a posição de todos os elementos seguintes seja recalculada e não devem ser alvo de animação
/* Jeito do Linear */
.row:hover {
background-color: var(--color-bg-hover);
transition: background-color 0.12s;
}
.icon-arrow {
transform: translateX(0);
transition: transform 0.15s;
}
- Se você animar
margin-left, o layout de todas as linhas abaixo da linha em hover será recalculado a cada frame durante toda a transição de 200 ms
- Em listas longas de issues, essa diferença separa uma interface fluida de uma com jank
- As propriedades animadas no Linear são, em sua maioria, propriedades compostas como
transform e opacity, com uso ocasional de background-color e border-color
-
É preciso saber quando se conter
- Em ferramentas usadas todos os dias, animações que ficam bonitas em sites de marketing podem atrapalhar o trabalho
- Até um pequeno delay de hover no lugar errado pode ser algo que o usuário percebe
- Muitas animações do Linear funcionam bem porque fazem referência à origem
- O popover de status se expande a partir da pílula de status, e o painel do agente desliza a partir do toggle
- Esse movimento não é um fade decorativo; ele cumpre um papel espacial de mostrar de onde o novo elemento veio
-
Mantenha durações curtas e imediatas
--speed-highlightFadeIn: 0s;
--speed-highlightFadeOut: .15s;
--speed-quickTransition: .1s;
--speed-regularTransition: .25s;
--speed-slowTransition: .35s;
- Muitos design systems definem durações padrão mais longas do que o necessário
- A duração padrão do Material é 200 ms, e a spring do iOS fica mais perto de 350 ms
- Os valores padrão do Linear ficam no lado mais curto em relação às práticas do setor
- O Linear usa timing assimétrico entre entrada e saída
- O destaque de hover, o popover e o painel do agente aparecem imediatamente quando são acionados e fazem fade out por 150 ms ao fechar
- A janela do agente aparece imediatamente e desaparece com fade out, de forma parecida com o macOS
Como o Linear é rápido
- O desempenho do Linear não vem de um único segredo ou de uma tecnologia só, mas do acúmulo de centenas de decisões corretas
- Grande parte da abordagem é simples e resulta de definir cedo e manter uma arquitetura adequada ao usuário, sem depender de Next, TanStack ou frameworks chamativos
- O servidor não atua como source of truth da UI, mas como alvo de sincronização
- O banco de dados está dentro do navegador, e as mudanças são aplicadas primeiro localmente antes de serem reconciliadas em segundo plano
- Na carga inicial, menos código é enviado em mais partes, e o service worker faz o precache do restante enquanto o usuário está na página de login
- A autenticação assume o caminho normal com base no estado local e valida depois
- O mecanismo de sincronização hidrata observables MobX por propriedade a partir do IndexedDB, então a atualização de 50 issues vira a re-renderização de 50 células, e não da lista inteira
- O modelo de entrada é keyboard-first, e todas as tarefas comuns têm atalhos e uma command palette global
- As animações ficam em propriedades amigáveis à GPU, e propriedades que disparam layout não são animadas
- A parte difícil, mais do que a implementação em si, é manter por anos o foco na qualidade dos detalhes enquanto o codebase amadurece, cresce e encontra novas restrições
1 comentários
Comentários do Hacker News
Se você quiser colocar esse tipo de experiência em uma aplicação, vale a pena dar uma olhada no Zero(https://zero.rocicorp.dev/)
Demo ao vivo: https://gigabugs.rocicorp.dev/
Também há uma lista de alternativas aqui: https://zero.rocicorp.dev/docs/when-to-use#alternatives
Se tiver curiosidade sobre o funcionamento interno, a documentação de design do Replicache também vale a leitura: https://doc.replicache.dev/concepts/how-it-works
O Replicache é o antecessor do Zero, e o protocolo central ainda funciona basicamente da mesma forma
Além do claro ganho de desempenho que vem de manter os dados sincronizados no cliente, também me surpreendeu o quanto o código em React fica mais simples. Com um motor de sincronização, a maior parte do estado do cliente desaparece e você consegue pensar a maior parte do código dos componentes de forma síncrona
Provavelmente é a opção mais próxima do que se pode obter sem montar uma equipe dedicada
Eu sempre ouvia dizer que o Linear era rápido, mas depois de usá-lo todos os dias na prática, meu entusiasmo esfriou. A busca é bem lenta, a UI muitas vezes é travada e, embora seja bonita, o “Pulse” parece uma enxurrada de ruído mesmo em pequena escala
É difícil encontrar o que preciso, então acabo colocando tudo nos favoritos. O Trello do começo era disparado a melhor experiência de acompanhamento de projetos
No ano passado, alguém fez engenharia reversa do motor de sincronização do Linear, publicou no GitHub e ainda acrescentou uma ótima explicação
https://github.com/wzhudev/reverse-linear-sync-engine/blob/m...
Esses apps web com sincronização local-first são realmente interessantes e podem ser úteis, mas acho que a premissa está um tanto errada
A premissa é algo como: “No Linear, bastam alguns milissegundos para atualizar uma issue. Um app CRUD tradicional leva cerca de 300ms para a mesma tarefa”, “todo dado que vai e volta entre cliente e servidor paga um custo de centenas de milissegundos”
Não dá para resolver o problema de o tempo de ida e volta entre cliente HTTP e servidor aumentar por causa da velocidade da luz, mas dá para colocar o backend perto do usuário e torná-lo rápido
Por exemplo, é perfeitamente viável operar o backend de um app web com cerca de 10ms de latência de ida e volta para a maioria dos usuários, e fazer o backend também renderizar a resposta em cerca de 10ms. Ou seja, um app CRUD tradicional também pode fazer a mesma tarefa em algo como 30ms, não 300ms
O Linear pode precisar da ajuda do frontend porque, por razões legítimas, leva mais tempo no backend, mas isso não dá para generalizar. Cada pedaço de JavaScript também tem seu próprio custo
us-west-1 está a 60ms, eu-centra-1 a 100ms, e a Ásia a 200ms. E isso é tráfego entre data centers; na internet pública real, até conexões residenciais, a latência é muito pior
O banco de dados precisa estar exatamente em uma única região. Onde quer que você o coloque, a maioria dos usuários no planeta vai estar a mais de 100ms dali
Não importa onde o endpoint esteja, porque, para ler e gravar dados, ele precisa se comunicar com o banco de dados. No momento em que você tenta replicar os dados para perto do usuário, você acaba possuindo um banco de dados de sincronização local-first
Seja feito por você ou comprado pronto, esse banco de dados replicado acaba tendo todos os mesmos problemas da sincronização no lado do cliente, e ainda continua com uma latência de rede considerável. Não dá para escapar da física, então para a maioria dos usuários a escolha acaba sendo um commit de 0,25 segundo ou consistência eventual, isto é, sincronização
Claro, você pode colocar um “backend intermediário” em algo como uma rede global de edge da CDN, mas nesse ponto você acaba pagando o mesmo custo de complexidade desta abordagem de colocar o “backend intermediário” no cliente
No pior caso, o worker em segundo plano pode emitir uma mensagem de falha na atualização e a thread da UI pode recebê-la e exibi-la. O caminho de sucesso continua absurdamente rápido
Escrever um banco de dados de consistência eventual é difícil e, para o caso de uso da Linear, isso até pode ser aceitável, mas é um problema não saber se minha atualização chegou ao servidor, ou seja, à equipe
Em outros projetos dos quais participei no passado, atrasos de sincronização criaram inúmeros problemas, então sempre escolho uma solução síncrona. Só ativo recursos sofisticados quando são realmente necessários e, em vez disso, prefiro otimizar o servidor ao máximo e deixar o usuário arcar com a latência da rede
A empresa usa Linear. Sei que sou minoria, mas a experiência do usuário é realmente difícil. Também é difícil chamar isso de rápido
A página em si carrega tecnicamente até que rápido, mas metade das vezes você vê os números da página mudando sem qualquer indicação visual de que os dados ainda estão carregando
É tão ruim nesse ponto que, na Linear, eu só crio issues com uma descrição de uma frase e depois vou para o GitHub preencher os detalhes. É basicamente só nisso que a Linear é boa e rápida
É uma pena, mas para a empresa sobreviver e subir para o mercado enterprise, na prática não havia outro caminho
Eu não usaria a palavra “rápida”. Quando o carregamento inicial já leva 30 segundos, não importa muito se a atualização de uma issue caiu de 300ms para “alguns” milissegundos
Ainda é melhor que Jira, mas essa barra é bem baixa
Legal. Talvez eu consiga colocar algo parecido no jogo de navegador e no motor que estou desenvolvendo para eliminar completamente o estado de carregamento depois do primeiro load. O meu tem uma estrutura de assets estáticos totalmente client-side, sem servidor
Venho ficando obcecado com a performance desse jogo. Antes do fim de semana passado, eu estava penando para manter 120fps em um M1 MacBook Pro simulando 128 jogadores simultâneos, com pathfinding, lógica estratégica pesada e renderização tudo dentro do viewport, com quedas de frame muito ocasionais e frame time em torno de 4ms
Trabalhei pesado em performance durante o fim de semana e agora consigo simular 2048 jogadores simultâneos com frame time abaixo de 1ms. Isso inclui renderização, toda a lógica e até geração procedural
Também apliquei um throttling de CPU de 11,2x para simular aparelhos móveis de baixo desempenho, e mesmo assim obtive 60fps estáveis com frame time de cerca de 5ms em 256~512 jogadores simultâneos. Agora o principal gargalo é um pequeno problema de lógica e o tempo de inicialização/boot que preciso melhorar em dispositivos fracos, e acho que há algo para aprender com a Linear
Sempre achei a Linear bem lenta, na verdade. Houve semanas em que, depois de deixar a aba aberta por um tempo, ela ficava em 100% de CPU
É interessante. Sinceramente, nunca achei a Linear “rápida”. Como a maioria dos web apps, ela tinha latência, mas comparada ao JIRA, claro, é a velocidade da luz
A própria Linear é excelente e, depois da tortura do JIRA, é realmente refrescante. Se vamos falar de rotas otimistas e “rapidez”, talvez devêssemos começar pelo Gmail
A resposta para a velocidade é prefetch. Basicamente, é baixar o banco de dados do cliente na inicialização e ter uma estratégia de invalidação de cache
Criei o starfx para lidar com o aspecto de sincronização de dados desse paradigma: https://starfx.bower.sh/learn#data-loading-strategy-stale-wh...