Voltar aos insights

Engenharia

Três truques de browser que agora são dívida técnica

Uma funcionalidade torna-se Baseline Widely Available depois de funcionar em todos os principais motores de browser durante 30 meses. O código existente não deixa de funcionar nessa data. Desaparece um dos motivos para manter um truque. Se o seu suporte de browsers segue Baseline, o código escrito à volta da funcionalidade em falta pode agora ser dívida técnica a eliminar.

Desde outubro de 2026, pode reavaliar três truques em bases de código legadas: um helper manual de cancelamento de pedidos, um contentor flex usado apenas para centrar um estado vazio e um contador de carateres. Estas são as datas dos dados de web-features v3.44.0:

FuncionalidadeWidely Available desdePrimeiras versões: Chrome / Edge, Firefox, Safari
AbortSignal.any()19 de setembro de 2026116, 124, 17.4
align-content em layout de bloco16 de outubro de 2026123, 125, 17.4
Intl.Segmenter16 de outubro de 202687, 125, 14.1
AbortSignal.timeout()18 de outubro de 2026124, 100, 16

Para cada caso, mostramos o que escrevíamos antes, o que podemos escrever agora, o que muda e o que fazer se ainda suporta browsers mais antigos. Os exemplos de pedidos e de contagem de carateres usam implementações cobertas por testes no repositório. O exemplo de alinhamento mede o layout no seu browser. A última secção mostra como descobrir a próxima transição sem ler notas de lançamento.

1. Prazos de pedidos: AbortSignal.timeout() e AbortSignal.any()

Um helper manual de cancelamento cria um controlador, inicia um temporizador, propaga o sinal de cancelamento de quem o chamou e faz a limpeza no fim.

O exemplo carrega um utilizador, as suas funções e a atividade recente como um único grupo. Todos os pedidos ainda pendentes têm de ser cancelados ao sair da rota, ao premir Stop ou ao atingir o prazo. Iniciar de novo cancela o carregamento anterior, e um erro num pedido interrompe os restantes. Os dois ficheiros de código implementam esse contrato. O diagrama interativo executa a versão nativa.

Live example

Cancel three requests with one signal

This example runs in your browser and needs JavaScript. The code it runs is below.

before.js
// Before: forward the router's signal, manage the deadline and clean up both.
let current;

export function cancelProfile() {
  current?.abort();
}

export async function loadProfile(navigationSignal, { timeout = 2000 } = {}) {
  current?.abort(); // a new load cancels the previous one
  const load = new AbortController();
  current = load;
  const signal = load.signal;

  // The router owns this signal. Its abort event may have happened before this call.
  if (navigationSignal.aborted) throw navigationSignal.reason;
  const onNavigationAbort = () => load.abort(navigationSignal.reason);
  navigationSignal.addEventListener('abort', onNavigationAbort, { once: true });

  const timer = setTimeout(
    () => load.abort(new DOMException('The deadline expired.', 'TimeoutError')),
    timeout,
  );

  const get = (path) =>
    fetch(path, { signal })
      .then((response) => {
        if (!response.ok) throw new Error(`${path} answered ${response.status}`);
        return response.json();
      })
      .catch((error) => {
        load.abort(error); // all or nothing: stop the other requests
        throw error;
      });

  try {
    const [user, roles, activity] = await Promise.all([
      get('/demo/user'),
      get('/demo/roles'),
      get('/demo/activity'),
    ]);
    return { user, roles, activity };
  } finally {
    clearTimeout(timer);
    navigationSignal.removeEventListener('abort', onNavigationAbort);
  }
}
now.js
// Now: the platform combines the router's signal, Stop and the deadline.
let current;

export function cancelProfile() {
  current?.abort();
}

export async function loadProfile(navigationSignal, { timeout = 2000 } = {}) {
  current?.abort(); // a new load cancels the previous one
  const load = new AbortController();
  current = load;
  const signal = AbortSignal.any([
    navigationSignal,
    load.signal,
    AbortSignal.timeout(timeout),
  ]);
  signal.throwIfAborted(); // do not start work if navigation already cancelled it

  const get = (path) =>
    fetch(path, { signal })
      .then((response) => {
        if (!response.ok) throw new Error(`${path} answered ${response.status}`);
        return response.json();
      })
      .catch((error) => {
        load.abort(error); // all or nothing: stop the other requests
        throw error;
      });

  const [user, roles, activity] = await Promise.all([
    get('/demo/user'),
    get('/demo/roles'),
    get('/demo/activity'),
  ]);
  return { user, roles, activity };
}

O que muda

As duas implementações já tratam a navegação cancelada, os prazos e os motivos de cancelamento concorrentes. Nesta refatoração de código, o ganho é simples: menos código da aplicação para manter.

Em before.js, a aplicação verifica se a navegação já foi cancelada, propaga os eventos de cancelamento posteriores e cria um temporizador. Depois remove o listener e limpa o temporizador em finally. Em now.js, AbortSignal.any() combina os sinais recebidos e throwIfAborted() verifica o seu estado antes de o trabalho começar. AbortSignal.timeout() fornece o prazo sem um temporizador gerido pela aplicação.

A versão manual cria um TimeoutError para o prazo, enquanto a versão nativa o recebe da API. Stop produz AbortError nas duas. A proposta original discute por que motivo as aplicações precisam de distinguir um timeout de um cancelamento intencional. As duas versões também leem os corpos das respostas antes de concluir o carregamento e cancelam explicitamente os pedidos restantes quando ocorre um erro. Promise.all() não cancela os seus membros.

AbortSignal.timeout() conta tempo ativo e pode ficar em pausa enquanto a página está na cache de navegação (back-forward cache), por isso o prazo pode não corresponder ao tempo decorrido no relógio. Cancelar um pedido também não interrompe o trabalho no servidor. Se a operação altera dados, “deixámos de esperar” não significa “falhou”, e repetir automaticamente pode não ser seguro.

Para encontrar esta dívida, procure padrões em vez de nomes de funções: setTimeout e .abort( na mesma função, Promise.race com um temporizador (a pior variante, porque o pedido continua a correr depois de Promise.race terminar), um helper escrito à mão para combinar sinais ou um wrapper de cliente HTTP que trata a opção timeout separadamente de signal. Antes de substituir qualquer um deles, registe o que mais o helper faz, como telemetria, novas tentativas, etiquetas de erro ou um adaptador de biblioteca, porque essas tarefas precisam de ficar a cargo de outro código.

Um sinal de timeout começa a contar quando é criado. As duas versões do exemplo criam um prazo novo para cada carregamento. Se acrescentar novas tentativas, crie outro prazo dentro de cada tentativa em vez de reutilizar o sinal anterior.

Solução de recurso para browsers mais antigos

A versão nativa precisa das duas funcionalidades, por isso a versão mínima real é a mais recente das duas: Chrome e Edge 124, Firefox 124, Safari 17.4. Se suporta versões anteriores, detete os dois métodos e forneça uma solução de recurso para cada um que esteja em falta. Por exemplo, este helper fornece uma solução de recurso para AbortSignal.any():

js
export function anySignal(signals) {
  if (typeof AbortSignal.any === "function") {
    return AbortSignal.any(signals);
  }

  const list = [...signals];

  if (list.some((signal) => !(signal instanceof AbortSignal))) {
    throw new TypeError("Expected AbortSignal");
  }

  const controller = new AbortController();

  for (const signal of list) {
    if (signal.aborted) {
      controller.abort(signal.reason);
      return controller.signal;
    }
  }

  for (const signal of list) {
    signal.addEventListener("abort", () => controller.abort(signal.reason), {
      once: true,
      signal: controller.signal,
    });
  }

  return controller.signal;
}

Este helper verifica se algum dos sinais já foi cancelado e preserva o motivo do cancelamento. Cobre apenas AbortSignal.any(). Se também precisar de uma solução de recurso para AbortSignal.timeout(), lembre-se de que um temporizador comum não reproduz o comportamento de tempo ativo do prazo nativo.

Os listeners só são removidos quando o sinal combinado é cancelado. Se um pedido terminar normalmente, o listener no sinal de quem o chamou mantém-se até um dos sinais ser cancelado, por exemplo quando o prazo expira mais tarde. O documento explicativo de AbortSignal.any() descreve a dificuldade de libertar corretamente estas referências no código da aplicação. Para um sinal de longa duração partilhado por muitos pedidos, como um sinal de rota ou de sessão, use antes a versão manual com limpeza explícita. Quando a sua política de suporte deixar de incluir estas versões dos browsers, apague o helper e chame diretamente os métodos nativos.

2. Conteúdo centrado: align-content em layout de bloco

Um estado vazio tem um título, uma frase e um botão. Para centrar o grupo na vertical num painel de altura fixa, a resposta habitual era Flexbox.

Com align-content: center, o elemento continua a ser um bloco normal. align-content funciona em layouts flex e grid há anos. O que se tornou Widely Available foi o seu efeito nos contentores de bloco. O exemplo compara esse bloco com o contentor flex e mostra as duas folhas de estilo.

Live example

Keep centred content reachable when it overflows

This example runs in your browser and needs JavaScript. The code it runs is below.

before.css
.empty-state {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-block-size: 14rem;
  text-align: center;
}
now.css
.empty-state {
  align-content: center;
  min-block-size: 14rem;
  text-align: center;
}

O que muda

Remover display: flex mantém os elementos filhos no fluxo normal. Verificámos o comportamento do layout em Chromium 152. As medidas abaixo são do exemplo numa janela com 1440 px de largura, com Short text selecionado e Panel height definido para 224 px, salvo indicação em contrário. Os indicadores atualizam-se quando altera estas opções ou a largura da janela.

  • O título e o parágrafo têm, cada um, 0.75rem de margem vertical. Estas margens colapsam num intervalo de 12 px na versão de bloco. A versão flex tem um intervalo de 24 px porque as margens entre itens flex não colapsam. Se acrescentou gap ou uma classe modificadora para compensar, reveja essa regra ao remover o contentor flex.
  • Com align-items: center, o título flex mede 203 px de largura. Na versão de bloco ocupa toda a largura disponível, 312 px nesta configuração. O parágrafo também ocupa a largura do painel, e text-align centra o botão como qualquer outro conteúdo inline. Traduções mais longas podem usar toda a largura de texto do painel.
  • Mude para Long text e reduza Panel height para 80 px. Com a mesma largura de janela, o título flex começa 65 px acima do limite superior, onde a deslocação não o consegue alcançar. A versão de bloco começa nos 0 px e permite deslocar o conteúdo normalmente. A parte cortada depende do conteúdo e do tamanho do painel, por isso observe o indicador top ao ajustar estes valores. A especificação faz o alinhamento de bloco voltar ao início quando o conteúdo transborda. Flex e grid precisam de safe center para fazer o mesmo.

Qualquer valor diferente de normal também faz o contentor de bloco estabelecer um contexto de formatação independente. As margens do primeiro e do último elemento filho deixam de colapsar através dos limites do painel, e os elementos flutuantes dentro dele ficam contidos. Num estado vazio, é normalmente o que se pretende.

As declarações existentes também podem começar a funcionar. Até agora, align-content num bloco normal não fazia nada, por isso pode existir nas folhas de estilo por acidente: copiado de uma regra flex, esquecido depois de uma refatorização ou definido num reset. Essas declarações passam a ter efeito, e o anúncio da equipa do Chrome recomenda procurá-las. Procure align-content em seletores que não são contentores flex nem grid e remova as declarações que ninguém pretendia aplicar.

Um <img>, <video> ou <canvas> inline assenta na linha de base do texto, com um pequeno espaço por baixo para as partes descendentes das letras. O alinhamento de bloco centra a linha inteira, incluindo esse espaço, pelo que a imagem fica ligeiramente acima do centro. Na verificação em Chromium 152, uma imagem de 80 px num painel de 200 px com uma fonte de 32 px ficou a 54 px do topo e a 66 px do fundo. Com vertical-align: top na imagem, ficou a 60 px do topo e a 60 px do fundo. A opção Image do exemplo interativo permite comparar esse efeito com outros tamanhos de painel. Temani Afif descreve o mesmo efeito.

Nada disto substitui o que Flexbox faz para além da centragem. Se o cartão empurra uma ação para o fundo, distribui espaço entre itens ou muda de direção em ecrãs estreitos, mantenha o contentor flex e verifique de que é responsável antes de o remover.

Solução de recurso para browsers mais antigos

Não é possível detetar esta funcionalidade com uma query. Os browsers anteriores a Chrome 123, Firefox 125 e Safari 17.4 já compreendem align-content por causa de Flexbox, por isso @supports (align-content: center) também é verdadeiro nessas versões. A declaração apenas não tem efeito num bloco.

Por isso, pode fazer uma de duas coisas:

  1. Não fazer nada. Num browser mais antigo, o conteúdo fica alinhado ao topo, continua a ser legível e o botão continua a funcionar. Para a maioria dos estados vazios e painéis provisórios, é uma solução de recurso aceitável.

  2. Se o posicionamento central for mesmo importante em browsers mais antigos, use Grid, mantenha o alinhamento seguro e os elementos filhos à largura total:

    css
    .empty-state {
      display: grid;
      align-content: start; /* used where `safe` is not supported */
      align-content: safe center;
      min-block-size: 14rem;
      text-align: center;
    }
    
    .empty-state > button {
      justify-self: center;
    }

    Sem um align-content explícito, as linhas da grid esticam para preencher o painel e os três elementos filhos distribuem-se do topo ao fundo. justify-items: center encolheria o título e o parágrafo como align-items: center fazia na versão flex, por isso só o botão é centrado individualmente. A palavra-chave safe evita o problema de transbordo descrito acima, mas é mais recente do que a própria grid (MDN: posição em caso de transbordo). Onde não é suportada, o browser ignora essa declaração e mantém start, o que volta a alinhar o conteúdo ao topo. Os elementos filhos da grid são itens de grid, por isso as suas margens não colapsam e o espaçamento é gerido com gap.

3. Contagem de carateres: Intl.Segmenter

Um campo diz “Até 80 carateres” e um contador mostra quantos faltam. Um contador baseado em text.length mede unidades de código UTF-16, tal como o atributo nativo maxlength. Os exemplos manuais também contam pontos de código com [...text] e combinam sequências de emoji com uma expressão regular.

Intl.Segmenter com granularity: "grapheme" divide o texto nas unidades que uma pessoa vê, seleciona e apaga. O exemplo compara as quatro abordagens, incluindo o truncamento. As implementações aparecem por baixo.

Live example

Count characters without cutting emoji apart

This example runs in your browser and needs JavaScript. The code it runs is below.

before.js
export const countLength = (text) => text.length;
export const countCodePoints = (text) => [...text].length;

export function truncateOld(text, max) {
  return text.length > max ? `${text.slice(0, max)}…` : text;
}

// The usual home-made fix: glue back what [...text] splits apart. A flag is two regional
// indicators; an emoji may carry a skin tone and a variation selector and be joined to the
// next one with ZWJ; a letter may be followed by combining marks. Every sequence Unicode adds
// needs another branch here.
const CHARACTER =
  /\p{RI}{2}|\p{Extended_Pictographic}\p{EMod}?\uFE0F?(?:\u200D\p{Extended_Pictographic}\p{EMod}?\uFE0F?)*|\P{M}\p{M}*/gsu;

export const countWithRegex = (text) => text.match(CHARACTER)?.length ?? 0;

export function truncateWithRegex(text, max) {
  const characters = text.match(CHARACTER) ?? [];
  return characters.length > max ? `${characters.slice(0, max).join('')}…` : text;
}
now.js
const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });

export function countCharacters(text) {
  let count = 0;
  for (const _ of graphemes.segment(text)) count++;
  return count;
}

export function truncate(text, max) {
  let end = 0;
  let count = 0;
  for (const { index, segment } of graphemes.segment(text)) {
    if (count === max) return `${text.slice(0, end)}…`;
    end = index + segment.length;
    count++;
  }
  return text;
}

O que muda

Até as contagens mais simples divergem:

TextolengthPontos de códigoGrafemas
Lisboa666
Café (é como um único caráter)444
Café (e + acento combinante)554
👍🏽421
👨‍👩‍👧851
🇵🇹421

As duas formas de escrever “Café” parecem idênticas, mas um contador de unidades de código conta-as de forma diferente para o limite, e um emoji de família custa oito. A expressão regular acerta nas linhas desta tabela, mas o exemplo interativo também inclui a bandeira da Escócia e hangul decomposto. A expressão regular divide a bandeira em sete partes em vez de um grafema, e o exemplo de hangul em seis em vez de dois.

O truncamento torna o problema mais fácil de ver. truncateOld("Equipa 🇵🇹 Porto", 8) corta a bandeira a meio e deixa um surrogate isolado, que aparece como um símbolo inválido e nem sequer é texto bem formado. truncate() devolve Equipa 🇵🇹….

Também deixa de manter código. Não há uma dependência para dividir grafemas nem uma expressão regular de emoji feita à mão para atualizar sempre que o Unicode acrescenta uma sequência, porque as regras vêm com o browser.

Antes de disponibilizar o novo contador, verifique estes pontos:

  • Quando o contador passa a mostrar grafemas, deixa de coincidir com o limite de unidades de código de maxlength. O campo pode deixar de aceitar texto enquanto o contador ainda diz que há espaço. Remova maxlength e valide no seu código, ou mantenha deliberadamente unidades de código em todo o lado.
  • Se o backend conta bytes ou unidades de código, pode rejeitar um valor que o contador aceita. O Node.js também tem Intl.Segmenter, por isso a mesma função pode correr dos dois lados. Os limites de armazenamento medidos em bytes continuam a ser uma restrição separada.
  • A segmentação segue os dados Unicode de cada runtime, e as regras dos grafemas mudam entre versões do Unicode, como explica Nikita Prokopov. Uma sequência de emoji muito recente pode contar como mais de um grafema num browser ou servidor mais antigo, por isso deixe alguma folga no limite em vez de depender de uma concordância exata no limiar.
  • As duas formas de escrever “Café” continuam a ter valores de string diferentes, embora agora contem o mesmo. ===, a pesquisa e as verificações de unicidade podem tratá-las de forma diferente. Se isso for relevante, chame text.normalize("NFC") nos pontos em que compara e decida separadamente se deve guardar a forma normalizada.
  • Os métodos de introdução de texto compõem o texto através de estados intermédios. Mostre que o limite foi ultrapassado e deixe a pessoa corrigi-lo, em vez de cortar o valor a cada tecla.

Solução de recurso para browsers mais antigos

Intl.Segmenter só chegou ao Firefox na versão 125, por isso esta solução de recurso é especialmente relevante se suporta versões ESR mais antigas do Firefox ou browsers incorporados. A alternativa depende da margem de erro que pode aceitar:

js
function makeCounter() {
  if (typeof Intl.Segmenter === "function") return countCharacters;
  return (text) => [...text].length; // code points: close, but see the table above
}

Contar pontos de código funciona para texto simples e conta a mais nas sequências de emoji e marcas combinantes. Num campo de descrição, isso costuma ser aceitável: a pessoa pode ficar com um pouco menos de espaço do que o prometido, e o servidor, com um motor atual, continua a aplicar a regra correta. Se a contagem tiver de ser exata em todo o lado, carregue um polyfill de segmentação de grafemas apenas quando Intl.Segmenter não existir, para que os browsers atuais não paguem esse custo.

4. Gerir dívida técnica com um alvo Baseline no Browserslist

Provavelmente não tinha estas datas no calendário. O Browserslist, a configuração de browsers-alvo que Babel, Autoprefixer e muitas ferramentas de lint já leem, compreende queries Baseline (guia do web.dev):

text
# .browserslistrc
baseline widely available

Esta query avança sozinha à medida que as funcionalidades passam a marca dos 30 meses. Executámo-la para três datas com browserslist 4.29.4 e ficámos com a versão mais baixa de cada browser principal:

Data da queryChrome / EdgeFirefoxSafari
1 de outubro de 202612312417.4
17 de outubro de 202612312517.4
19 de outubro de 202612412517.4

O Firefox 125 entrou a 16 de outubro com Intl.Segmenter e align-content em layout de bloco, e o Chrome 124 a 18 de outubro com AbortSignal.timeout(). O mapeamento está no pacote baseline-browser-mapping, de que o Browserslist depende, por isso o alvo só avança quando atualiza as dependências.

Se preferir alterar o alvo de forma deliberada, fixe uma data:

text
baseline widely available on 2026-10-19

Avançar a data passa então a ser um pull request, e a revisão de código é um bom momento para perguntar que truques o novo alvo torna desnecessários. Os três acima são os que a passagem de 1 de outubro para 19 de outubro permitiu remover.

Verifique o alvo real da sua ferramenta de build

Muitas ferramentas de build leem Browserslist, incluindo webpack, Next.js, Rsbuild e Parcel. Outras têm uma definição própria para o alvo. Verifique a versão e a configuração em vigor antes de assumir que uma política Baseline corresponde ao alvo real do build. Estes são os valores predefinidos documentados em outubro de 2026:

Ferramenta de buildBaseline por predefiniçãoOnde configurar
Vite 8Widely Available a 1 de janeiro de 2026build.target
Vite 7Widely Available a 1 de maio de 2025build.target
Rsbuild 2Widely Available a 1 de maio de 2025Browserslist; queries Baseline suportadas a partir de 2.1.11
Angular 20–22Widely Available; a data depende da versão principalPolítica de suporte do Angular e configuração Browserslist do CLI
webpack 5Sem um alvo Baseline predefinido fixoBrowserslist para o runtime; Babel ou SWC para o código da aplicação
Parcel 2Sem um alvo Baseline predefinidoBrowserslist
Next.jsUm conjunto fixo de browsers modernosBrowserslist
esbuildNão; o valor predefinido é esnexttarget

O Vite chama 'baseline-widely-available' ao valor predefinido de build.target, mas a data é fixa para cada versão principal. Não avança com o calendário como a query do Browserslist. O alvo predefinido de janeiro de 2026 do Vite 8 ainda inclui browsers sem as funcionalidades que passaram a Widely Available em outubro. Confirme as versões de browser abrangidas antes de remover os truques acima.

No Vite ou no esbuild, use as versões de browser resultantes para definir build.target ou target. Um ficheiro .browserslistrc, por si só, não configura essas opções. No webpack, mantenha alinhados o alvo do runtime e os alvos do Babel ou do SWC. O Angular CLI também lê Browserslist, mas a política de suporte do framework continua a definir o limite. A transformação de sintaxe também não fornece Web APIs em falta, como AbortSignal.any() ou Intl.Segmenter. Os polyfills continuam a ser uma decisão separada.

Registe o alvo para as pessoas e para as ferramentas de programação com IA

Uma política de suporte que só existe na cabeça de alguém não pode ser seguida por um novo colega, nem por uma ferramenta de programação com IA. Sem um alvo definido, a ferramenta não tem como saber se pode usar AbortSignal.any() ou se deve escrever um helper manual. Mantenha o alvo no repositório, onde ambos o possam ler, e indique-o no ficheiro de instruções para agentes (AGENTS.md, CLAUDE.md ou semelhante). No Vite ou no esbuild, indique a configuração de build. Numa ferramenta que lê Browserslist, a instrução pode ser:

md
## Browser support

Targets are in `.browserslistrc`: Baseline Widely Available, pinned to a date.
Use platform features inside that target directly, without polyfills or fallbacks.
When the date changes, list workarounds the new target makes unnecessary.

As ferramentas de lint transformam a política numa verificação para ambos. O eslint-plugin-compat lê a configuração do Browserslist e assinala APIs JavaScript que os seus alvos não suportam, e o plugin CSS do ESLint tem uma regra use-baseline para CSS.

Antes, agora e solução de recurso

AntesAgoraSe suporta browsers mais antigos
Prazo de pedidosAbortController + setTimeout + limpeza manual de listenersAbortSignal.timeout() + AbortSignal.any()Deteção das duas APIs; solução de recurso para any() acima
Conteúdo centradoContentor flex só para centraralign-content: center no blocoAceitar o alinhamento ao topo, ou usar grid com safe center
Contagem de caratereslength ou pontos de códigoGrafemas com Intl.SegmenterPontos de código, ou um polyfill carregado a pedido

Se quiser uma segunda opinião sobre que truques do seu código vale a pena retirar e quais ainda fazem trabalho útil, isso faz parte do que uma Auditoria de Risco Frontend analisa.

Definições de privacidade

Escolha se permite a analítica e a gravação de sessões do PostHog. Ambas permanecem desativadas até à sua autorização. Pode utilizar o site e o formulário de contacto sem as permitir.

A sua escolha guardada: Não selecionada

Serviços e utilização de dados

PostHog Cloud EU — analítica e gravação de sessões facultativas

As seguintes funções só são carregadas após a sua autorização:

  • Analítica de visitas e interações. As visualizações de páginas, as ações selecionadas e os resultados de envio do formulário ajudam-nos a compreender como o site é utilizado e onde os visitantes encontram dificuldades.
  • Monitorização de erros e desempenho. Os erros do navegador e as medições de desempenho das páginas ajudam-nos a identificar problemas técnicos e a verificar as correções.
  • Gravação de sessões. A navegação, os cliques, a deslocação na página e os movimentos do ponteiro ajudam-nos a investigar problemas da interface. Todo o texto da página é mascarado e o formulário de contacto completo é excluído antes do envio dos dados de gravação. Esta função não grava o seu ambiente de trabalho, outras aplicações, a câmara ou o microfone.

Um identificador aleatório guardado no navegador associa visitas repetidas, eventos e gravações a um perfil pseudonimizado. Estes dados não são totalmente anónimos. Não enviamos o seu nome, endereço de email ou conteúdo do formulário para o PostHog.

A Cloudflare estima o país a partir da ligação de rede. Enviamos o código do país para o PostHog; o endereço IP original, a cidade e a localização precisa não são guardados nos eventos de analítica.

Não vendemos estes dados nem os partilhamos para fins de marketing próprio de terceiros.

Cloudflare — funcionamento e segurança do site

A Cloudflare aloja e disponibiliza o site e protege-o contra utilizações abusivas. O Turnstile verifica sinais do navegador e da ligação para ajudar a impedir envios automatizados do formulário. Não enviamos a sua mensagem para o widget do Turnstile.

As medições operacionais ajudam-nos a acompanhar pedidos técnicos, erros e o processamento do formulário. Baseamo-nos nos nossos interesses legítimos em manter o site disponível, prevenir utilizações abusivas e diagnosticar falhas técnicas. Limitamos este tratamento ao que é necessário para essas finalidades.

Estas funções operam independentemente da sua escolha de analítica. Recusar a analítica não as desativa.

A Cloudflare também processa e entrega as mensagens do formulário de contacto. A autorização para utilizar o seu pedido de contacto para lhe responder é solicitada separadamente no formulário.

Preferências guardadas no navegador

Guardamos neste navegador o tema de cor que escolher e a sua decisão sobre a analítica (permitir ou recusar), para os aplicar nas visitas seguintes. Estas preferências não são utilizadas para acompanhar visitas.

Estas preferências utilizam armazenamento local do navegador (localStorage) e funcionam independentemente da autorização de analítica.

A sua escolha de analítica é guardada durante 180 dias. Este é o prazo da escolha guardada, não o prazo de conservação dos dados de analítica.

Alterar a escolha e pedir a eliminação

Pode alterar a sua escolha a qualquer momento em Política de Privacidade e definições de privacidade, no rodapé.

Não permitir interrompe a analítica e a gravação futuras. Não elimina os dados já recolhidos.

Para pedir a eliminação da analítica associada a este navegador, utilize Pedir eliminação dos dados de analítica na página de definições de privacidade. O pedido abrange o perfil, os eventos e as gravações de sessões associados. A eliminação é processada de forma assíncrona.

Consulte a Política de Privacidade para conhecer os prazos de conservação e obter mais informações sobre os prestadores e o tratamento dos dados.

Ler Política de Privacidade ↗

Fechar esta janela com ×, Escape ou um clique fora guarda uma recusa e interrompe a analítica e a gravação.